package com.android.systemui.statusbar;
-import static com.android.systemui.statusbar.notification.NotificationInflater.InflationExceptionHandler;
+import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.widget.ImageView;
import android.widget.RemoteViews;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.statusbar.NotificationGuts.GutsContent;
import com.android.systemui.statusbar.notification.HybridNotificationView;
-import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
}
}
- public void updateNotification(NotificationData.Entry entry) throws InflationException {
+ public void updateNotification(NotificationData.Entry entry) {
mEntry = entry;
mStatusBarNotification = entry.notification;
mNotificationInflater.inflateNotificationViews();
- onNotificationUpdated();
}
- private void onNotificationUpdated() {
+ public void onNotificationUpdated() {
for (NotificationContentView l : mLayouts) {
l.onNotificationUpdated(mEntry);
}
boolean childInGroup = StatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup;
mNotificationParent = childInGroup ? parent : null;
mPrivateLayout.setIsChildInGroup(childInGroup);
- if (mNotificationInflater.setIsChildInGroup(childInGroup)) {
- onNotificationUpdated();
- }
+ mNotificationInflater.setIsChildInGroup(childInGroup);
resetBackgroundAlpha();
updateBackgroundForGroupState();
updateClickAndFocus();
mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler);
}
- public void setInflateExceptionHandler(InflationExceptionHandler inflateExceptionHandler) {
- mNotificationInflater.setInflateExceptionHandler(inflateExceptionHandler);
+ public void setInflationCallback(InflationCallback callback) {
+ mNotificationInflater.setInflationCallback(callback);
}
public void setNeedsRedaction(boolean needsRedaction) {
mNotificationInflater.setRedactAmbient(needsRedaction);
}
+ @VisibleForTesting
+ public NotificationInflater getNotificationInflater() {
+ return mNotificationInflater;
+ }
+
public interface ExpansionLogger {
public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
import android.content.pm.PackageManager;
import android.content.Context;
import android.graphics.drawable.Icon;
+import android.os.AsyncTask;
import android.os.RemoteException;
import android.os.SystemClock;
import android.service.notification.NotificationListenerService;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.view.View;
import android.widget.ImageView;
import android.widget.RemoteViews;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
public List<SnoozeCriterion> snoozeCriteria;
private int mCachedContrastColor = COLOR_INVALID;
private int mCachedContrastColorIsFor = COLOR_INVALID;
+ private ArraySet<AsyncTask> mRunningTasks = new ArraySet();
public Entry(StatusBarNotification n) {
this.key = n.getKey();
mCachedContrastColor = contrasted;
return mCachedContrastColor;
}
+
+ /**
+ * Abort all existing inflation tasks
+ */
+ public void abortInflation() {
+ for (AsyncTask task : mRunningTasks) {
+ task.cancel(true /* mayInterruptIfRunning */);
+ }
+ }
+
+ public void addInflationTask(AsyncTask asyncInflationTask) {
+ mRunningTasks.add(asyncInflationTask);
+ }
}
private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
return mEntries.get(key);
}
- public void add(Entry entry, RankingMap ranking) {
+ public void add(Entry entry) {
synchronized (mEntries) {
mEntries.put(entry.notification.getKey(), entry);
}
mGroupManager.onEntryAdded(entry);
- updateRankingAndSort(ranking);
+ filterAndSort();
}
public Entry remove(String key, RankingMap ranking) {
import android.app.Notification;
import android.content.Context;
+import android.os.AsyncTask;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
import android.widget.RemoteViews;
import com.android.internal.annotations.VisibleForTesting;
@VisibleForTesting
static final int FLAG_REINFLATE_ALL = ~0;
private static final int FLAG_REINFLATE_CONTENT_VIEW = 1<<0;
- private static final int FLAG_REINFLATE_EXPANDED_VIEW = 1<<1;
+ @VisibleForTesting
+ static final int FLAG_REINFLATE_EXPANDED_VIEW = 1<<1;
private static final int FLAG_REINFLATE_HEADS_UP_VIEW = 1<<2;
private static final int FLAG_REINFLATE_PUBLIC_VIEW = 1<<3;
private static final int FLAG_REINFLATE_AMBIENT_VIEW = 1<<4;
private boolean mUsesIncreasedHeadsUpHeight;
private RemoteViews.OnClickHandler mRemoteViewClickHandler;
private boolean mIsChildInGroup;
- private InflationExceptionHandler mInflateExceptionHandler;
+ private InflationCallback mCallback;
private boolean mRedactAmbient;
public NotificationInflater(ExpandableNotificationRow row) {
*
* @return whether the view was re-inflated
*/
- public boolean setIsChildInGroup(boolean childInGroup) {
+ public void setIsChildInGroup(boolean childInGroup) {
if (childInGroup != mIsChildInGroup) {
mIsChildInGroup = childInGroup;
if (mIsLowPriority) {
- try {
- int flags = FLAG_REINFLATE_CONTENT_VIEW | FLAG_REINFLATE_EXPANDED_VIEW;
- inflateNotificationViews(flags);
- } catch (InflationException e) {
- mInflateExceptionHandler.handleInflationException(
- mRow.getStatusBarNotification(), e);
- }
+ int flags = FLAG_REINFLATE_CONTENT_VIEW | FLAG_REINFLATE_EXPANDED_VIEW;
+ inflateNotificationViews(flags);
}
- return true;
- }
- return false;
+ } ;
}
public void setUsesIncreasedHeight(boolean usesIncreasedHeight) {
if (mRow.getEntry() == null) {
return;
}
- try {
- inflateNotificationViews(FLAG_REINFLATE_AMBIENT_VIEW);
- } catch (InflationException e) {
- mInflateExceptionHandler.handleInflationException(
- mRow.getStatusBarNotification(), e);
- }
+ inflateNotificationViews(FLAG_REINFLATE_AMBIENT_VIEW);
}
}
- public void inflateNotificationViews() throws InflationException {
+ /**
+ * Inflate all views of this notification on a background thread. This is asynchronous and will
+ * notify the callback once it's finished.
+ */
+ public void inflateNotificationViews() {
inflateNotificationViews(FLAG_REINFLATE_ALL);
}
/**
- * reinflate all views for the specified flags
+ * Reinflate all views for the specified flags on a background thread. This is asynchronous and
+ * will notify the callback once it's finished.
+ *
* @param reInflateFlags flags which views should be reinflated. Use {@link #FLAG_REINFLATE_ALL}
* to reinflate all of views.
- * @throws InflationException
*/
- private void inflateNotificationViews(int reInflateFlags)
- throws InflationException {
+ @VisibleForTesting
+ void inflateNotificationViews(int reInflateFlags) {
StatusBarNotification sbn = mRow.getEntry().notification;
- try {
- final Notification.Builder recoveredBuilder
- = Notification.Builder.recoverBuilder(mRow.getContext(), sbn.getNotification());
- Context packageContext = sbn.getPackageContext(mRow.getContext());
- inflateNotificationViews(reInflateFlags, recoveredBuilder, packageContext);
-
- } catch (RuntimeException e) {
- final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
- Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
- throw new InflationException("Couldn't inflate contentViews");
- }
+ new AsyncInflationTask(mRow.getContext(), sbn, reInflateFlags).execute();
}
@VisibleForTesting
&& a.getLayoutId() == b.getLayoutId());
}
- public void setInflateExceptionHandler(InflationExceptionHandler inflateExceptionHandler) {
- mInflateExceptionHandler = inflateExceptionHandler;
+ public void setInflationCallback(InflationCallback callback) {
+ mCallback = callback;
}
- public interface InflationExceptionHandler {
+ public interface InflationCallback {
void handleInflationException(StatusBarNotification notification, InflationException e);
+ void onAsyncInflationFinished(NotificationData.Entry entry);
}
public void onDensityOrFontScaleChanged() {
entry.cachedContentView = null;
entry.cachedHeadsUpContentView = null;
entry.cachedPublicContentView = null;
+ inflateNotificationViews();
+ }
+
+ private class AsyncInflationTask extends AsyncTask<Void, Void, Notification.Builder> {
+
+ private final StatusBarNotification mSbn;
+ private final Context mContext;
+ private final int mReInflateFlags;
+ private Context mPackageContext = null;
+ private Exception mError;
+
+ private AsyncInflationTask(Context context, StatusBarNotification notification,
+ int reInflateFlags) {
+ mSbn = notification;
+ mContext = context;
+ mReInflateFlags = reInflateFlags;
+ mRow.getEntry().addInflationTask(this);
+ }
+
+ @Override
+ protected Notification.Builder doInBackground(Void... params) {
+ try {
+ final Notification.Builder recoveredBuilder
+ = Notification.Builder.recoverBuilder(mContext,
+ mSbn.getNotification());
+ mPackageContext = mSbn.getPackageContext(mContext);
+ return recoveredBuilder;
+ } catch (Exception e) {
+ mError = e;
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Notification.Builder builder) {
+ if (mError == null) {
+ finishInflation(mReInflateFlags, builder, mPackageContext);
+ } else {
+ handleError(mError);
+ }
+ }
+ }
+
+ private void finishInflation(int reinflationFlags, Notification.Builder builder,
+ Context context) {
try {
- inflateNotificationViews();
- } catch (InflationException e) {
- mInflateExceptionHandler.handleInflationException(
- mRow.getStatusBarNotification(), e);
+ inflateNotificationViews(reinflationFlags, builder, context);
+ } catch (RuntimeException e){
+ handleError(e);
+ return;
}
+ mRow.onNotificationUpdated();
+ mCallback.onAsyncInflationFinished(mRow.getEntry());
}
+ private void handleError(Exception e) {
+ StatusBarNotification sbn = mRow.getStatusBarNotification();
+ final String ident = sbn.getPackageName() + "/0x"
+ + Integer.toHexString(sbn.getId());
+ Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
+ mCallback.handleInflationException(sbn,
+ new InflationException("Couldn't inflate contentViews" + e));
+ }
}
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.windowStateToString;
-import static com.android.systemui.statusbar.notification.NotificationInflater.InflationExceptionHandler;
+import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
OnHeadsUpChangedListener, VisualStabilityManager.Callback, CommandQueue.Callbacks,
ActivatableNotificationView.OnActivatedListener,
ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
- ExpandableNotificationRow.OnExpandClickListener {
+ ExpandableNotificationRow.OnExpandClickListener, InflationCallback {
public static final boolean MULTIUSER_DEBUG = false;
public static final boolean ENABLE_REMOTE_INPUT =
private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
private NotificationIconAreaController mNotificationIconAreaController;
private ConfigurationListener mConfigurationListener;
- private InflationExceptionHandler mInflationExceptionHandler = this::handleInflationException;
private boolean mReinflateNotificationsOnUserSwitched;
+ private HashMap<String, Entry> mPendingNotifications = new HashMap<>();
private boolean mClearAllEnabled;
private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
return new UserHandle(mCurrentUserId);
}
- public void addNotification(StatusBarNotification notification, RankingMap ranking,
- Entry oldEntry) throws InflationException {
- if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
+ public void addNotification(StatusBarNotification notification, RankingMap ranking)
+ throws InflationException {
+ String key = notification.getKey();
+ if (DEBUG) Log.d(TAG, "addNotification key=" + key);
mNotificationData.updateRanking(ranking);
Entry shadeEntry = createNotificationViews(notification);
boolean isHeadsUped = shouldPeek(shadeEntry);
- if (isHeadsUped) {
- mHeadsUpManager.showNotification(shadeEntry);
- // Mark as seen immediately
- setNotificationShown(notification);
- }
-
if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
- if (shouldSuppressFullScreenIntent(notification.getKey())) {
+ if (shouldSuppressFullScreenIntent(key)) {
if (DEBUG) {
- Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + notification.getKey());
+ Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
}
- } else if (mNotificationData.getImportance(notification.getKey())
+ } else if (mNotificationData.getImportance(key)
< NotificationManager.IMPORTANCE_HIGH) {
if (DEBUG) {
Log.d(TAG, "No Fullscreen intent: not important enough: "
- + notification.getKey());
+ + key);
}
} else {
// Stop screensaver if the notification has a full-screen intent.
Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
try {
EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
- notification.getKey());
+ key);
notification.getNotification().fullScreenIntent.send();
shadeEntry.notifyFullScreenIntentLaunched();
mMetricsLogger.count("note_fullscreen", 1);
}
}
}
- addNotificationViews(shadeEntry, ranking);
+ abortExistingInflation(key);
+ mPendingNotifications.put(key, shadeEntry);
+ }
+
+ private void abortExistingInflation(String key) {
+ if (mPendingNotifications.containsKey(key)) {
+ Entry entry = mPendingNotifications.get(key);
+ entry.abortInflation();
+ mPendingNotifications.remove(key);
+ }
+ Entry addedEntry = mNotificationData.get(key);
+ if (addedEntry != null) {
+ addedEntry.abortInflation();
+ }
+ }
+
+ private void addEntry(Entry shadeEntry) {
+ boolean isHeadsUped = shouldPeek(shadeEntry);
+ if (isHeadsUped) {
+ mHeadsUpManager.showNotification(shadeEntry);
+ // Mark as seen immediately
+ setNotificationShown(shadeEntry.notification);
+ }
+ addNotificationViews(shadeEntry);
// Recalculate the position of the sliding windows and the titles.
setAreThereNotifications();
}
+ @Override
public void handleInflationException(StatusBarNotification notification, InflationException e) {
handleNotificationError(notification, e.getMessage());
}
+ @Override
+ public void onAsyncInflationFinished(Entry entry) {
+ mPendingNotifications.remove(entry.key);
+ if (mNotificationData.get(entry.key) == null) {
+ addEntry(entry);
+ }
+ }
+
private boolean shouldSuppressFullScreenIntent(String key) {
if (isDeviceInVrMode()) {
return true;
public void removeNotification(String key, RankingMap ranking) {
boolean deferRemoval = false;
+ abortExistingInflation(key);
if (mHeadsUpManager.isHeadsUp(key)) {
// A cancel() in repsonse to a remote input shouldn't be delayed, as it makes the
// sending look longer than it takes.
+ " scroll " + mStackScroller.getScrollX()
+ "," + mStackScroller.getScrollY());
}
+ pw.print(" mPendingNotifications=");
+ if (mPendingNotifications.size() == 0) {
+ pw.println("null");
+ } else {
+ for (Entry entry : mPendingNotifications.values()) {
+ pw.println(entry.notification);
+ }
+ }
pw.print(" mInteractingWindows="); pw.println(mInteractingWindows);
pw.print(" mStatusBarWindowState=");
public void run() {
for (StatusBarNotification sbn : notifications) {
try {
- addNotification(sbn, currentRanking, null /* oldEntry */);
+ addNotification(sbn, currentRanking);
} catch (InflationException e) {
handleInflationException(sbn, e);
}
if (isUpdate) {
updateNotification(sbn, rankingMap);
} else {
- addNotification(sbn, rankingMap, null /* oldEntry */);
+ addNotification(sbn, rankingMap);
}
} catch (InflationException e) {
handleInflationException(sbn, e);
}
}
- protected void inflateViews(Entry entry, ViewGroup parent) throws
- InflationException {
+ protected void inflateViews(Entry entry, ViewGroup parent) {
PackageManager pmUser = getPackageManagerForUser(mContext,
entry.notification.getUser().getIdentifier());
row.setRemoteInputController(mRemoteInputController);
row.setOnExpandClickListener(this);
row.setRemoteViewClickHandler(mOnClickHandler);
- row.setInflateExceptionHandler(mInflationExceptionHandler);
+ row.setInflationCallback(this);
// Get the app name.
// Note that Notification.Builder#bindHeaderAppName has similar logic
return entry;
}
- protected void addNotificationViews(Entry entry, RankingMap ranking) {
+ protected void addNotificationViews(Entry entry) {
if (entry == null) {
return;
}
// Add the expanded view and icon.
- mNotificationData.add(entry, ranking);
+ mNotificationData.add(entry);
updateNotifications();
}
if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
final String key = notification.getKey();
+ abortExistingInflation(key);
Entry entry = mNotificationData.get(key);
if (entry == null) {
return;
throw new IllegalStateException("should be called from the main thread.");
}
}
+
+ public static void isNotMainThread() {
+ if (Looper.getMainLooper().isCurrentThread()) {
+ throw new IllegalStateException("should not be called from the main thread.");
+ }
+ }
}
private ExpandHelper.Callback mCallback;
@Before
- @UiThreadTest
- public void setUp() {
+ public void setUp() throws Exception {
Context context = getContext();
mRow = new NotificationTestHelper(context).createRow();
mCallback = mock(ExpandHelper.Callback.class);
- mExpandHelper = new ExpandHelper(context, mCallback, 10, 100);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> mExpandHelper = new ExpandHelper(context, mCallback, 10, 100));
+
}
@Test
private NotificationTestHelper mNotificationTestHelper;
@Before
- @UiThreadTest
- public void setUp() {
+ public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
mNotificationTestHelper = new NotificationTestHelper(mContext);
mGroup = mNotificationTestHelper.createGroup();
package com.android.systemui.statusbar;
import android.app.ActivityManager;
+import android.app.Instrumentation;
import android.app.Notification;
import android.content.Context;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
import android.view.LayoutInflater;
import android.widget.RemoteViews;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.NotificationInflaterTest;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
/**
public class NotificationTestHelper {
private final Context mContext;
+ private final Instrumentation mInstrumentation;
private int mId;
private final NotificationGroupManager mGroupManager = new NotificationGroupManager();
+ private ExpandableNotificationRow mRow;
+ private InflationException mException;
public NotificationTestHelper(Context context) {
mContext = context;
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
}
- public ExpandableNotificationRow createRow() {
+ public ExpandableNotificationRow createRow() throws Exception {
Notification publicVersion = new Notification.Builder(mContext).setSmallIcon(
R.drawable.ic_person)
.setCustomContentView(new RemoteViews(mContext.getPackageName(),
return createRow(notification);
}
- public ExpandableNotificationRow createRow(Notification notification) {
+ public ExpandableNotificationRow createRow(Notification notification) throws Exception {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
mContext.LAYOUT_INFLATER_SERVICE);
- ExpandableNotificationRow row = (ExpandableNotificationRow) inflater.inflate(
- R.layout.status_bar_notification_row,
- null, false);
+ mInstrumentation.runOnMainSync(() -> {
+ mRow = (ExpandableNotificationRow) inflater.inflate(
+ R.layout.status_bar_notification_row,
+ null, false);
+ });
+ ExpandableNotificationRow row = mRow;
row.setGroupManager(mGroupManager);
UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
StatusBarNotification sbn = new StatusBarNotification("com.android.systemui",
2000, notification, mUser, null, System.currentTimeMillis());
NotificationData.Entry entry = new NotificationData.Entry(sbn);
entry.row = row;
- try {
- entry.createIcons(mContext, sbn);
- row.updateNotification(entry);
- } catch (InflationException e) {
- throw new RuntimeException(e.getMessage());
- }
+ entry.createIcons(mContext, sbn);
+ NotificationInflaterTest.runThenWaitForInflation(() -> row.updateNotification(entry),
+ row.getNotificationInflater());
return row;
}
- public ExpandableNotificationRow createGroup() {
+ public ExpandableNotificationRow createGroup() throws Exception {
ExpandableNotificationRow row = createRow();
row.addChildNotification(createRow());
row.addChildNotification(createRow());
--- /dev/null
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import static com.android.systemui.statusbar.notification.NotificationInflater.FLAG_REINFLATE_ALL;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.content.Context;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.widget.RemoteViews;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationTestHelper;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Function;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NotificationInflaterTest {
+
+ private Context mContext;
+ private NotificationInflater mNotificationInflater;
+ private Notification.Builder mBuilder;
+ private ExpandableNotificationRow mRow;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mBuilder = new Notification.Builder(mContext).setSmallIcon(
+ R.drawable.ic_person)
+ .setContentTitle("Title")
+ .setContentText("Text")
+ .setStyle(new Notification.BigTextStyle().bigText("big text"));
+ ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow(
+ mBuilder.build());
+ mRow = spy(row);
+ mNotificationInflater = new NotificationInflater(mRow);
+ mNotificationInflater.setInflationCallback(new NotificationInflater.InflationCallback() {
+ @Override
+ public void handleInflationException(StatusBarNotification notification,
+ InflationException e) {
+ }
+
+ @Override
+ public void onAsyncInflationFinished(NotificationData.Entry entry) {
+ }
+ });
+ }
+
+ @Test
+ public void testIncreasedHeadsUpBeingUsed() {
+ mNotificationInflater.setUsesIncreasedHeadsUpHeight(true);
+ Notification.Builder builder = spy(mBuilder);
+ mNotificationInflater.inflateNotificationViews(FLAG_REINFLATE_ALL, builder, mContext);
+ verify(builder).createHeadsUpContentView(true);
+ }
+
+ @Test
+ public void testIncreasedHeightBeingUsed() {
+ mNotificationInflater.setUsesIncreasedHeight(true);
+ Notification.Builder builder = spy(mBuilder);
+ mNotificationInflater.inflateNotificationViews(FLAG_REINFLATE_ALL, builder, mContext);
+ verify(builder).createContentView(true);
+ }
+
+ @Test
+ public void testInflationCallsUpdated() throws Exception {
+ runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(),
+ mNotificationInflater);
+ verify(mRow).onNotificationUpdated();
+ }
+
+ @Test
+ public void testInflationCallsOnlyRightMethod() throws Exception {
+ mRow.getPrivateLayout().removeAllViews();
+ mRow.getEntry().cachedBigContentView = null;
+ runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(
+ NotificationInflater.FLAG_REINFLATE_EXPANDED_VIEW), mNotificationInflater);
+ Assert.assertTrue(mRow.getPrivateLayout().getChildCount() == 1);
+ Assert.assertTrue(mRow.getPrivateLayout().getChildAt(0)
+ == mRow.getPrivateLayout().getExpandedChild());
+ verify(mRow).onNotificationUpdated();
+ }
+
+ @Test
+ public void testInflationThrowsErrorDoesntCallUpdated() throws Exception {
+ mRow.getPrivateLayout().removeAllViews();
+ mRow.getStatusBarNotification().getNotification().contentView
+ = new RemoteViews(mContext.getPackageName(), R.layout.status_bar);;
+ runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(),
+ true /* expectingException */, mNotificationInflater);
+ Assert.assertTrue(mRow.getPrivateLayout().getChildCount() == 0);
+ verify(mRow, times(0)).onNotificationUpdated();
+ }
+
+ public static void runThenWaitForInflation(Runnable block,
+ NotificationInflater inflater) throws Exception {
+ runThenWaitForInflation(block, false /* expectingException */, inflater);
+ }
+
+ private static void runThenWaitForInflation(Runnable block, boolean expectingException,
+ NotificationInflater inflater) throws Exception {
+ com.android.systemui.util.Assert.isNotMainThread();
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ final ExceptionHolder exceptionHolder = new ExceptionHolder();
+ inflater.setInflationCallback(new NotificationInflater.InflationCallback() {
+ @Override
+ public void handleInflationException(StatusBarNotification notification,
+ InflationException e) {
+ if (!expectingException) {
+ exceptionHolder.setException(e);
+ }
+ countDownLatch.countDown();
+ }
+
+ @Override
+ public void onAsyncInflationFinished(NotificationData.Entry entry) {
+ if (expectingException) {
+ exceptionHolder.setException(new RuntimeException(
+ "Inflation finished even though there should be an error"));
+ }
+ countDownLatch.countDown();
+ }
+ });
+ block.run();
+ countDownLatch.await(5, java.util.concurrent.TimeUnit.SECONDS);
+ if (exceptionHolder.mException != null) {
+ throw exceptionHolder.mException;
+ }
+ }
+
+ private static class ExceptionHolder {
+ private Exception mException;
+
+ public void setException(Exception exception) {
+ mException = exception;
+ }
+ }
+}
+++ /dev/null
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification;
-
-import static com.android.systemui.statusbar.notification.NotificationInflater.FLAG_REINFLATE_ALL;
-
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-
-import android.app.Notification;
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import com.android.systemui.R;
-import com.android.systemui.statusbar.ExpandableNotificationRow;
-import com.android.systemui.statusbar.NotificationTestHelper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class NotificationinflaterTest {
-
- private Context mContext;
- private NotificationInflater mNotificationInflater;
- private Notification.Builder mBuilder;
-
- @Before
- @UiThreadTest
- public void setUp() {
- mContext = InstrumentationRegistry.getTargetContext();
- mBuilder = new Notification.Builder(mContext).setSmallIcon(
- R.drawable.ic_person)
- .setContentTitle("Title")
- .setContentText("Text");
- ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow(
- mBuilder.build());
- mNotificationInflater = new NotificationInflater(row);
- }
-
- @Test
- public void testIncreasedHeadsUpBeingUsed() {
- mNotificationInflater.setUsesIncreasedHeadsUpHeight(true);
- Notification.Builder builder = spy(mBuilder);
- mNotificationInflater.inflateNotificationViews(FLAG_REINFLATE_ALL, builder, mContext);
- verify(builder).createHeadsUpContentView(true);
- }
-
- @Test
- public void testIncreasedHeightBeingUsed() {
- mNotificationInflater.setUsesIncreasedHeight(true);
- Notification.Builder builder = spy(mBuilder);
- mNotificationInflater.inflateNotificationViews(FLAG_REINFLATE_ALL, builder, mContext);
- verify(builder).createContentView(true);
- }
-}
private NotificationTestHelper mNotificationTestHelper;
@Before
- @UiThreadTest
- public void setUp() {
+ public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
mNotificationTestHelper = new NotificationTestHelper(mContext);
mGroup = mNotificationTestHelper.createGroup();