diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index dff09bda88442..16c53b87d321b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -16,7 +16,7 @@ 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; @@ -47,6 +47,7 @@ import android.widget.FrameLayout; 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; @@ -61,7 +62,6 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; 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; @@ -314,14 +314,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - 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); } @@ -482,9 +481,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView 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(); @@ -1111,14 +1108,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView 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); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 90e908b0a4689..b5f7ee637f404 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -24,6 +24,7 @@ import android.content.pm.IPackageManager; 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; @@ -32,6 +33,7 @@ import android.service.notification.NotificationListenerService.RankingMap; 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; @@ -41,6 +43,7 @@ import com.android.internal.annotations.VisibleForTesting; 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; @@ -83,6 +86,7 @@ public class NotificationData { public List snoozeCriteria; private int mCachedContrastColor = COLOR_INVALID; private int mCachedContrastColorIsFor = COLOR_INVALID; + private ArraySet mRunningTasks = new ArraySet(); public Entry(StatusBarNotification n) { this.key = n.getKey(); @@ -210,6 +214,19 @@ public class NotificationData { 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 mEntries = new ArrayMap<>(); @@ -302,12 +319,12 @@ public class NotificationData { 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) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java index 2e34f2483a857..3bad5cc7cdee9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java @@ -18,11 +18,10 @@ package com.android.systemui.statusbar.notification; 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; @@ -39,7 +38,8 @@ public class NotificationInflater { @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; @@ -50,7 +50,7 @@ public class NotificationInflater { private boolean mUsesIncreasedHeadsUpHeight; private RemoteViews.OnClickHandler mRemoteViewClickHandler; private boolean mIsChildInGroup; - private InflationExceptionHandler mInflateExceptionHandler; + private InflationCallback mCallback; private boolean mRedactAmbient; public NotificationInflater(ExpandableNotificationRow row) { @@ -66,21 +66,14 @@ public class NotificationInflater { * * @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) { @@ -101,39 +94,29 @@ public class NotificationInflater { 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 @@ -284,12 +267,13 @@ public class NotificationInflater { && 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() { @@ -299,12 +283,67 @@ public class NotificationInflater { entry.cachedContentView = null; entry.cachedHeadsUpContentView = null; entry.cachedPublicContentView = null; - try { - inflateNotificationViews(); - } catch (InflationException e) { - mInflateExceptionHandler.handleInflationException( - mRow.getStatusBarNotification(), e); + inflateNotificationViews(); + } + + private class AsyncInflationTask extends AsyncTask { + + 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(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)); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 46d64153d04d9..c1859fe43e017 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -22,7 +22,7 @@ import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; 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; @@ -257,7 +257,7 @@ public class StatusBar extends SystemUI implements DemoMode, 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 = @@ -713,8 +713,8 @@ public class StatusBar extends SystemUI implements DemoMode, private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); private NotificationIconAreaController mNotificationIconAreaController; private ConfigurationListener mConfigurationListener; - private InflationExceptionHandler mInflationExceptionHandler = this::handleInflationException; private boolean mReinflateNotificationsOnUserSwitched; + private HashMap mPendingNotifications = new HashMap<>(); private void recycleAllVisibilityObjects(ArraySet array) { final int N = array.size(); @@ -1544,29 +1544,24 @@ public class StatusBar extends SystemUI implements DemoMode, 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. @@ -1578,7 +1573,7 @@ public class StatusBar extends SystemUI implements DemoMode, 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); @@ -1586,15 +1581,47 @@ public class StatusBar extends SystemUI implements DemoMode, } } } - 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; @@ -1614,6 +1641,7 @@ public class StatusBar extends SystemUI implements DemoMode, 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. @@ -3295,6 +3323,14 @@ public class StatusBar extends SystemUI implements DemoMode, + " 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="); @@ -5531,7 +5567,7 @@ public class StatusBar extends SystemUI implements DemoMode, public void run() { for (StatusBarNotification sbn : notifications) { try { - addNotification(sbn, currentRanking, null /* oldEntry */); + addNotification(sbn, currentRanking); } catch (InflationException e) { handleInflationException(sbn, e); } @@ -5574,7 +5610,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (isUpdate) { updateNotification(sbn, rankingMap); } else { - addNotification(sbn, rankingMap, null /* oldEntry */); + addNotification(sbn, rankingMap); } } catch (InflationException e) { handleInflationException(sbn, e); @@ -6132,8 +6168,7 @@ public class StatusBar extends SystemUI implements DemoMode, } } - protected void inflateViews(Entry entry, ViewGroup parent) throws - InflationException { + protected void inflateViews(Entry entry, ViewGroup parent) { PackageManager pmUser = getPackageManagerForUser(mContext, entry.notification.getUser().getIdentifier()); @@ -6154,7 +6189,7 @@ public class StatusBar extends SystemUI implements DemoMode, 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 @@ -6552,12 +6587,12 @@ public class StatusBar extends SystemUI implements DemoMode, 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(); } @@ -6675,6 +6710,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); final String key = notification.getKey(); + abortExistingInflation(key); Entry entry = mNotificationData.get(key); if (entry == null) { return; diff --git a/packages/SystemUI/src/com/android/systemui/util/Assert.java b/packages/SystemUI/src/com/android/systemui/util/Assert.java index af447f3f74ecf..0f7c9a462c0a6 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Assert.java +++ b/packages/SystemUI/src/com/android/systemui/util/Assert.java @@ -28,4 +28,10 @@ public class Assert { 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."); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java index 1b5d4a4cb9cd7..aa8409890336d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java @@ -43,12 +43,13 @@ public class ExpandHelperTest extends SysuiTestCase { 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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java index 3db2440485366..5cd092bc8b53c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java @@ -39,8 +39,7 @@ public class ExpandableNotificationRowTest { private NotificationTestHelper mNotificationTestHelper; @Before - @UiThreadTest - public void setUp() { + public void setUp() throws Exception { mContext = InstrumentationRegistry.getTargetContext(); mNotificationTestHelper = new NotificationTestHelper(mContext); mGroup = mNotificationTestHelper.createGroup(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java index c91b269d6329e..cb238ddbabaa7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -17,15 +17,18 @@ 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; /** @@ -34,14 +37,18 @@ 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(), @@ -56,12 +63,15 @@ public class NotificationTestHelper { 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", @@ -69,16 +79,13 @@ public class NotificationTestHelper { 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()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java new file mode 100644 index 0000000000000..65e056830ea0c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java @@ -0,0 +1,168 @@ +/* + * 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; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationinflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationinflaterTest.java deleted file mode 100644 index 0ec9c106a5964..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationinflaterTest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java index dbe0de42fd0a1..f051f3043224c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java @@ -42,8 +42,7 @@ public class NotificationChildrenContainerTest { private NotificationTestHelper mNotificationTestHelper; @Before - @UiThreadTest - public void setUp() { + public void setUp() throws Exception { mContext = InstrumentationRegistry.getTargetContext(); mNotificationTestHelper = new NotificationTestHelper(mContext); mGroup = mNotificationTestHelper.createGroup();