diff options
12 files changed, 550 insertions, 174 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java index 2c384d0f4d80..21a33b0271db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar; +import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_AMBIENT; + import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; @@ -85,6 +87,7 @@ public final class AmbientPulseManager extends AlertingNotificationManager { for (OnAmbientChangedListener listener : mListeners) { listener.onAmbientStateChanged(entry, false); } + entry.row.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_AMBIENT); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index e89e6e89bc07..2db99453e36c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -256,9 +256,9 @@ public class NotificationMediaManager implements Dumpable { private boolean isMediaNotification(NotificationData.Entry entry) { // TODO: confirm that there's a valid media key - return entry.getExpandedContentView() != null && - entry.getExpandedContentView() - .findViewById(com.android.internal.R.id.media_actions) != null; + return entry.row.getExpandedContentView() != null + && entry.row.getExpandedContentView().findViewById( + com.android.internal.R.id.media_actions) != null; } private void clearCurrentMediaNotificationSession() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java index d097c8e706ba..fbf12ed39561 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java @@ -50,7 +50,6 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.view.View; import android.widget.ImageView; -import android.widget.RemoteViews; import androidx.annotation.Nullable; @@ -102,11 +101,6 @@ public class NotificationData { public boolean autoRedacted; // whether the redacted notification was generated by us public int targetSdk; private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET; - public RemoteViews cachedContentView; - public RemoteViews cachedBigContentView; - public RemoteViews cachedHeadsUpContentView; - public RemoteViews cachedPublicContentView; - public RemoteViews cachedAmbientContentView; public CharSequence remoteInputText; public List<SnoozeCriterion> snoozeCriteria; public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL; @@ -178,14 +172,6 @@ public class NotificationData { } } - public View getExpandedContentView() { - return row.getPrivateLayout().getExpandedChild(); - } - - public View getPublicContentView() { - return row.getPublicLayout().getContractedChild(); - } - public void notifyFullScreenIntentLaunched() { setInterruption(); lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index a3e982e77522..28d339aaeab2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -18,6 +18,10 @@ package com.android.systemui.statusbar.notification; import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; import static com.android.systemui.statusbar.NotificationRemoteInputManager .FORCE_REMOTE_INPUT_HISTORY; +import static com.android.systemui.statusbar.notification.row.NotificationInflater + .FLAG_CONTENT_VIEW_AMBIENT; +import static com.android.systemui.statusbar.notification.row.NotificationInflater + .FLAG_CONTENT_VIEW_HEADS_UP; import android.annotation.Nullable; import android.app.Notification; @@ -71,6 +75,7 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationUiAdjustment; import com.android.systemui.statusbar.NotificationUpdateHandler; import com.android.systemui.statusbar.notification.row.NotificationInflater; +import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -440,25 +445,48 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. } private void addEntry(NotificationData.Entry shadeEntry) { - if (shouldHeadsUp(shadeEntry)) { - mHeadsUpManager.showNotification(shadeEntry); - // Mark as seen immediately - setNotificationShown(shadeEntry.notification); - } - if (shouldPulse(shadeEntry)) { - mAmbientPulseManager.showNotification(shadeEntry); - } addNotificationViews(shadeEntry); mCallback.onNotificationAdded(shadeEntry); } + /** + * Adds the entry to the respective alerting manager if the content view was inflated and + * the entry should still alert. + * + * @param entry entry to add + * @param inflatedFlags flags representing content views that were inflated + */ + private void showAlertingView(NotificationData.Entry entry, + @InflationFlag int inflatedFlags) { + if ((inflatedFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { + // Possible for shouldHeadsUp to change between the inflation starting and ending. + // If it does and we no longer need to heads up, we should free the view. + if (shouldHeadsUp(entry)) { + mHeadsUpManager.showNotification(entry); + // Mark as seen immediately + setNotificationShown(entry.notification); + } else { + entry.row.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_HEADS_UP); + } + } + if ((inflatedFlags & FLAG_CONTENT_VIEW_AMBIENT) != 0) { + if (shouldPulse(entry)) { + mAmbientPulseManager.showNotification(entry); + } else { + entry.row.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_AMBIENT); + } + } + } + @Override - public void onAsyncInflationFinished(NotificationData.Entry entry) { + public void onAsyncInflationFinished(NotificationData.Entry entry, + @InflationFlag int inflatedFlags) { mPendingNotifications.remove(entry.key); // If there was an async task started after the removal, we don't want to add it back to // the list, otherwise we might get leaks. boolean isNew = mNotificationData.get(entry.key) == null; if (isNew && !entry.row.isRemoved()) { + showAlertingView(entry, inflatedFlags); addEntry(entry); } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) { mVisualStabilityManager.onLowPriorityUpdated(entry); @@ -636,7 +664,11 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); row.setSmartActions(entry.smartActions); - row.updateNotification(entry); + row.setEntry(entry); + + row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, shouldHeadsUp(entry)); + row.updateInflationFlag(FLAG_CONTENT_VIEW_AMBIENT, shouldPulse(entry)); + row.inflateViews(); } protected void addNotificationViews(NotificationData.Entry entry) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index bce613a33859..23492aa62e37 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -17,12 +17,19 @@ package com.android.systemui.statusbar.notification.row; import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; +import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_AMBIENT; +import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; +import static com.android.systemui.statusbar.notification.row.NotificationInflater + .FLAG_CONTENT_VIEW_AMBIENT; +import static com.android.systemui.statusbar.notification.row.NotificationInflater + .FLAG_CONTENT_VIEW_HEADS_UP; import static com.android.systemui.statusbar.notification.row.NotificationInflater.InflationCallback; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; import android.app.NotificationChannel; @@ -83,6 +90,7 @@ import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.logging.NotificationCounters; import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.phone.NotificationGroupManager; @@ -429,12 +437,59 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - public void updateNotification(NotificationData.Entry entry) { + /** + * Set the entry for the row. + * + * @param entry the entry this row is tied to + */ + public void setEntry(@NonNull NotificationData.Entry entry) { mEntry = entry; mStatusBarNotification = entry.notification; + cacheIsSystemNotification(); + } + + /** + * Inflate views based off the inflation flags set. Inflation happens asynchronously. + */ + public void inflateViews() { mNotificationInflater.inflateNotificationViews(); + } - cacheIsSystemNotification(); + /** + * Marks a content view as freeable, setting it so that future inflations do not reinflate + * and ensuring that the view is freed when it is safe to remove. + * + * @param inflationFlag flag corresponding to the content view to be freed + */ + public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) { + // View should not be reinflated in the future + updateInflationFlag(inflationFlag, false); + Runnable freeViewRunnable = () -> + mNotificationInflater.freeNotificationView(inflationFlag); + switch (inflationFlag) { + case FLAG_CONTENT_VIEW_HEADS_UP: + getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP, + freeViewRunnable); + break; + case FLAG_CONTENT_VIEW_AMBIENT: + getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_AMBIENT, + freeViewRunnable); + getPublicLayout().performWhenContentInactive(VISIBLE_TYPE_AMBIENT, + freeViewRunnable); + break; + default: + break; + } + } + + /** + * Update whether or not a content view should be inflated. + * + * @param flag the flag corresponding to the content view + * @param shouldInflate true if it should be inflated, false if it should not + */ + public void updateInflationFlag(@InflationFlag int flag, boolean shouldInflate) { + mNotificationInflater.updateInflationFlag(flag, shouldInflate); } /** @@ -581,7 +636,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView headsUpHeight = mMaxHeadsUpHeight; } NotificationViewWrapper headsUpWrapper = layout.getVisibleWrapper( - NotificationContentView.VISIBLE_TYPE_HEADSUP); + VISIBLE_TYPE_HEADSUP); if (headsUpWrapper != null) { headsUpHeight = Math.max(headsUpHeight, headsUpWrapper.getMinLayoutHeight()); } @@ -2616,6 +2671,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return shouldShowPublic() ? mPublicLayout : mPrivateLayout; } + public View getExpandedContentView() { + return getPrivateLayout().getExpandedChild(); + } + public void setLegacy(boolean legacy) { for (NotificationContentView l : mLayouts) { l.setLegacy(legacy); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 4ef8dbb19318..78564515a2c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -23,6 +23,7 @@ import android.content.Context; import android.graphics.Rect; import android.os.Build; import android.service.notification.StatusBarNotification; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; @@ -108,6 +109,10 @@ public class NotificationContentView extends FrameLayout { private NotificationGroupManager mGroupManager; private RemoteInputController mRemoteInputController; private Runnable mExpandedVisibleListener; + /** + * List of listeners for when content views become inactive (i.e. not the showing view). + */ + private final ArrayMap<View, Runnable> mOnContentViewInactiveListeners = new ArrayMap<>(); private final ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener = new ViewTreeObserver.OnPreDrawListener() { @@ -517,6 +522,14 @@ public class NotificationContentView extends FrameLayout { removeView(mAmbientChild); } if (child == null) { + mAmbientChild = null; + mAmbientWrapper = null; + if (mVisibleType == VISIBLE_TYPE_AMBIENT) { + mVisibleType = VISIBLE_TYPE_CONTRACTED; + } + if (mTransformationStartVisibleType == VISIBLE_TYPE_AMBIENT) { + mTransformationStartVisibleType = UNDEFINED; + } return; } addView(child); @@ -1163,6 +1176,7 @@ public class NotificationContentView extends FrameLayout { public void onNotificationUpdated(NotificationData.Entry entry) { mStatusBarNotification = entry.notification; + mOnContentViewInactiveListeners.clear(); mBeforeN = entry.targetSdk < Build.VERSION_CODES.N; updateAllSingleLineViews(); if (mContractedChild != null) { @@ -1620,6 +1634,58 @@ public class NotificationContentView extends FrameLayout { fireExpandedVisibleListenerIfVisible(); } + /** + * Set a one-shot listener to run when a given content view becomes inactive. + * + * @param visibleType visible type corresponding to the content view to listen + * @param listener runnable to run once when the content view becomes inactive + */ + public void performWhenContentInactive(int visibleType, Runnable listener) { + View view = getViewForVisibleType(visibleType); + // View is already inactive + if (view == null || isContentViewInactive(visibleType)) { + listener.run(); + return; + } + mOnContentViewInactiveListeners.put(view, listener); + } + + /** + * Whether or not the content view is inactive. This means it should not be visible + * or the showing content as removing it would cause visual jank. + * + * @param visibleType visible type corresponding to the content view to be removed + * @return true if the content view is inactive, false otherwise + */ + public boolean isContentViewInactive(int visibleType) { + View view = getViewForVisibleType(visibleType); + return isContentViewInactive(view); + } + + /** + * Whether or not the content view is inactive. + * + * @param view view to see if its inactive + * @return true if the view is inactive, false o/w + */ + private boolean isContentViewInactive(View view) { + if (view == null) { + return true; + } + return view.getVisibility() != VISIBLE && getViewForVisibleType(mVisibleType) != view; + } + + @Override + protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) { + super.onChildVisibilityChanged(child, oldVisibility, newVisibility); + if (isContentViewInactive(child)) { + Runnable listener = mOnContentViewInactiveListeners.remove(child); + if (listener != null) { + listener.run(); + } + } + } + public void setIsLowPriority(boolean isLowPriority) { mIsLowPriority = isLowPriority; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java index aa4765a349b4..ea1892be1b1f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java @@ -16,12 +16,17 @@ package com.android.systemui.statusbar.notification.row; +import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_AMBIENT; +import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; + +import android.annotation.IntDef; import android.annotation.Nullable; import android.app.Notification; import android.content.Context; import android.os.AsyncTask; import android.os.CancellationSignal; import android.service.notification.StatusBarNotification; +import android.util.ArrayMap; import android.util.Log; import android.view.View; import android.widget.RemoteViews; @@ -35,6 +40,8 @@ import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.util.Assert; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -52,14 +59,64 @@ import java.util.concurrent.atomic.AtomicInteger; public class NotificationInflater { public static final String TAG = "NotificationInflater"; - @VisibleForTesting - static final int FLAG_REINFLATE_ALL = ~0; - private static final int FLAG_REINFLATE_CONTENT_VIEW = 1<<0; - @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; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, + prefix = {"FLAG_CONTENT_VIEW_"}, + value = { + FLAG_CONTENT_VIEW_CONTRACTED, + FLAG_CONTENT_VIEW_EXPANDED, + FLAG_CONTENT_VIEW_HEADS_UP, + FLAG_CONTENT_VIEW_AMBIENT, + FLAG_CONTENT_VIEW_PUBLIC, + FLAG_CONTENT_VIEW_ALL}) + public @interface InflationFlag {} + /** + * The default, contracted view. Seen when the shade is pulled down and in the lock screen + * if there is no worry about content sensitivity. + */ + public static final int FLAG_CONTENT_VIEW_CONTRACTED = 1; + + /** + * The expanded view. Seen when the user expands a notification. + */ + public static final int FLAG_CONTENT_VIEW_EXPANDED = 1 << 1; + + /** + * The heads up view. Seen when a high priority notification peeks in from the top. + */ + public static final int FLAG_CONTENT_VIEW_HEADS_UP = 1 << 2; + + /** + * The ambient view. Seen when a high priority notification is received and the phone + * is dozing. + */ + public static final int FLAG_CONTENT_VIEW_AMBIENT = 1 << 3; + + /** + * The public view. This is a version of the contracted view that hides sensitive + * information and is used on the lock screen if we determine that the notification's + * content should be hidden. + */ + public static final int FLAG_CONTENT_VIEW_PUBLIC = 1 << 4; + + public static final int FLAG_CONTENT_VIEW_ALL = ~0; + + /** + * Content views that must be inflated at all times. + */ + @InflationFlag + private static final int REQUIRED_INFLATION_FLAGS = + FLAG_CONTENT_VIEW_CONTRACTED + | FLAG_CONTENT_VIEW_EXPANDED + | FLAG_CONTENT_VIEW_PUBLIC; + + /** + * The set of content views to inflate. + */ + @InflationFlag + private int mInflationFlags = REQUIRED_INFLATION_FLAGS; + private static final InflationExecutor EXECUTOR = new InflationExecutor(); private final ExpandableNotificationRow mRow; @@ -71,6 +128,7 @@ public class NotificationInflater { private InflationCallback mCallback; private boolean mRedactAmbient; private List<Notification.Action> mSmartActions; + private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>(); public NotificationInflater(ExpandableNotificationRow row) { mRow = row; @@ -89,10 +147,10 @@ public class NotificationInflater { if (childInGroup != mIsChildInGroup) { mIsChildInGroup = childInGroup; if (mIsLowPriority) { - int flags = FLAG_REINFLATE_CONTENT_VIEW | FLAG_REINFLATE_EXPANDED_VIEW; + int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; inflateNotificationViews(flags); } - } ; + } } public void setUsesIncreasedHeight(boolean usesIncreasedHeight) { @@ -117,38 +175,67 @@ public class NotificationInflater { if (mRow.getEntry() == null) { return; } - inflateNotificationViews(FLAG_REINFLATE_AMBIENT_VIEW); + inflateNotificationViews(FLAG_CONTENT_VIEW_AMBIENT); } } /** + * Set whether or not a particular content view is needed and whether or not it should be + * inflated. These flags will be used when we inflate or reinflate. + * + * @param flag the {@link InflationFlag} corresponding to the view that should/should not be + * inflated + * @param shouldInflate true if the view should be inflated, false otherwise + */ + public void updateInflationFlag(@InflationFlag int flag, boolean shouldInflate) { + if (shouldInflate) { + mInflationFlags |= flag; + } else if ((REQUIRED_INFLATION_FLAGS & flag) == 0) { + mInflationFlags &= ~flag; + } + } + + /** + * Add flags for which content views should be inflated in addition to those already set. + * + * @param flags a set of {@link InflationFlag} corresponding to content views that should be + * inflated + */ + public void addInflationFlags(@InflationFlag int flags) { + mInflationFlags |= flags; + } + + /** * 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); + inflateNotificationViews(mInflationFlags); } /** - * Reinflate all views for the specified flags on a background thread. This is asynchronous and - * will notify the callback once it's finished. + * Inflate all views for the specified flags on a background thread. This is asynchronous and + * will notify the callback once it's finished. If the content view is already inflated, this + * will reinflate it. * - * @param reInflateFlags flags which views should be reinflated. Use {@link #FLAG_REINFLATE_ALL} - * to reinflate all of views. + * @param reInflateFlags flags which views should be inflated. Should be a subset of + * {@link NotificationInflater#mInflationFlags} as only those will be + * inflated/reinflated. */ - @VisibleForTesting - void inflateNotificationViews(int reInflateFlags) { + private void inflateNotificationViews(@InflationFlag int reInflateFlags) { if (mRow.isRemoved()) { // We don't want to reinflate anything for removed notifications. Otherwise views might // be readded to the stack, leading to leaks. This may happen with low-priority groups // where the removal of already removed children can lead to a reinflation. return; } + // Only inflate the ones that are set. + reInflateFlags |= mInflationFlags; StatusBarNotification sbn = mRow.getEntry().notification; - AsyncInflationTask task = new AsyncInflationTask(sbn, reInflateFlags, mRow, - mIsLowPriority, - mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient, - mCallback, mRemoteViewClickHandler, mSmartActions); + AsyncInflationTask task = new AsyncInflationTask(sbn, reInflateFlags, mCachedContentViews, + mRow, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight, + mUsesIncreasedHeadsUpHeight, mRedactAmbient, mCallback, mRemoteViewClickHandler, + mSmartActions); if (mCallback != null && mCallback.doInflateSynchronous()) { task.onPostExecute(task.doInBackground()); } else { @@ -157,38 +244,80 @@ public class NotificationInflater { } @VisibleForTesting - InflationProgress inflateNotificationViews(int reInflateFlags, + InflationProgress inflateNotificationViews(@InflationFlag int reInflateFlags, Notification.Builder builder, Context packageContext) { InflationProgress result = createRemoteViews(reInflateFlags, builder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient, packageContext); - apply(result, reInflateFlags, mRow, mRedactAmbient, mRemoteViewClickHandler, null); + apply(result, reInflateFlags, mCachedContentViews, mRow, mRedactAmbient, + mRemoteViewClickHandler, null); return result; } - private static InflationProgress createRemoteViews(int reInflateFlags, + /** + * Frees the content view associated with the inflation flag. Will only succeed if the + * view is safe to remove. + * + * @param inflateFlag the flag corresponding to the content view which should be freed + */ + public void freeNotificationView(@InflationFlag int inflateFlag) { + if ((mInflationFlags & inflateFlag) != 0) { + // The view should still be inflated. + return; + } + switch (inflateFlag) { + case FLAG_CONTENT_VIEW_HEADS_UP: + if (mRow.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) { + mRow.getPrivateLayout().setHeadsUpChild(null); + mCachedContentViews.remove(FLAG_CONTENT_VIEW_HEADS_UP); + } + break; + case FLAG_CONTENT_VIEW_AMBIENT: + boolean privateSafeToRemove = mRow.getPrivateLayout().isContentViewInactive( + VISIBLE_TYPE_AMBIENT); + boolean publicSafeToRemove = mRow.getPublicLayout().isContentViewInactive( + VISIBLE_TYPE_AMBIENT); + if (privateSafeToRemove) { + mRow.getPrivateLayout().setAmbientChild(null); + } + if (publicSafeToRemove) { + mRow.getPublicLayout().setAmbientChild(null); + } + if (privateSafeToRemove && publicSafeToRemove) { + mCachedContentViews.remove(FLAG_CONTENT_VIEW_AMBIENT); + } + break; + case FLAG_CONTENT_VIEW_CONTRACTED: + case FLAG_CONTENT_VIEW_EXPANDED: + case FLAG_CONTENT_VIEW_PUBLIC: + default: + break; + } + } + + private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags, Notification.Builder builder, boolean isLowPriority, boolean isChildInGroup, boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, boolean redactAmbient, Context packageContext) { InflationProgress result = new InflationProgress(); isLowPriority = isLowPriority && !isChildInGroup; - if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) { + if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) { result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight); } - if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) { + if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) { result.newExpandedView = createExpandedView(builder, isLowPriority); } - if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) { + if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight); } - if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) { + if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) { result.newPublicView = builder.makePublicContentView(); } - if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) { + if ((reInflateFlags & FLAG_CONTENT_VIEW_AMBIENT) != 0) { result.newAmbientView = redactAmbient ? builder.makePublicAmbientNotification() : builder.makeAmbientNotification(); } @@ -199,18 +328,20 @@ public class NotificationInflater { return result; } - public static CancellationSignal apply(InflationProgress result, int reInflateFlags, + public static CancellationSignal apply(InflationProgress result, + @InflationFlag int reInflateFlags, ArrayMap<Integer, RemoteViews> cachedContentViews, ExpandableNotificationRow row, boolean redactAmbient, RemoteViews.OnClickHandler remoteViewClickHandler, @Nullable InflationCallback callback) { - NotificationData.Entry entry = row.getEntry(); NotificationContentView privateLayout = row.getPrivateLayout(); NotificationContentView publicLayout = row.getPublicLayout(); final HashMap<Integer, CancellationSignal> runningInflations = new HashMap<>(); - int flag = FLAG_REINFLATE_CONTENT_VIEW; + int flag = FLAG_CONTENT_VIEW_CONTRACTED; if ((reInflateFlags & flag) != 0) { - boolean isNewView = !canReapplyRemoteView(result.newContentView, entry.cachedContentView); + boolean isNewView = + !canReapplyRemoteView(result.newContentView, + cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -222,18 +353,19 @@ public class NotificationInflater { return result.newContentView; } }; - applyRemoteView(result, reInflateFlags, flag, row, redactAmbient, - isNewView, remoteViewClickHandler, callback, entry, privateLayout, + applyRemoteView(result, reInflateFlags, flag, cachedContentViews, row, redactAmbient, + isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getContractedChild(), privateLayout.getVisibleWrapper( NotificationContentView.VISIBLE_TYPE_CONTRACTED), runningInflations, applyCallback); } - flag = FLAG_REINFLATE_EXPANDED_VIEW; + flag = FLAG_CONTENT_VIEW_EXPANDED; if ((reInflateFlags & flag) != 0) { if (result.newExpandedView != null) { - boolean isNewView = !canReapplyRemoteView(result.newExpandedView, - entry.cachedBigContentView); + boolean isNewView = + !canReapplyRemoteView(result.newExpandedView, + cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -245,8 +377,8 @@ public class NotificationInflater { return result.newExpandedView; } }; - applyRemoteView(result, reInflateFlags, flag, row, - redactAmbient, isNewView, remoteViewClickHandler, callback, entry, + applyRemoteView(result, reInflateFlags, flag, cachedContentViews, row, + redactAmbient, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getExpandedChild(), privateLayout.getVisibleWrapper( NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations, @@ -254,11 +386,12 @@ public class NotificationInflater { } } - flag = FLAG_REINFLATE_HEADS_UP_VIEW; + flag = FLAG_CONTENT_VIEW_HEADS_UP; if ((reInflateFlags & flag) != 0) { if (result.newHeadsUpView != null) { - boolean isNewView = !canReapplyRemoteView(result.newHeadsUpView, - entry.cachedHeadsUpContentView); + boolean isNewView = + !canReapplyRemoteView(result.newHeadsUpView, + cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -270,19 +403,20 @@ public class NotificationInflater { return result.newHeadsUpView; } }; - applyRemoteView(result, reInflateFlags, flag, row, - redactAmbient, isNewView, remoteViewClickHandler, callback, entry, + applyRemoteView(result, reInflateFlags, flag, cachedContentViews, row, + redactAmbient, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getHeadsUpChild(), privateLayout.getVisibleWrapper( - NotificationContentView.VISIBLE_TYPE_HEADSUP), runningInflations, + VISIBLE_TYPE_HEADSUP), runningInflations, applyCallback); } } - flag = FLAG_REINFLATE_PUBLIC_VIEW; + flag = FLAG_CONTENT_VIEW_PUBLIC; if ((reInflateFlags & flag) != 0) { - boolean isNewView = !canReapplyRemoteView(result.newPublicView, - entry.cachedPublicContentView); + boolean isNewView = + !canReapplyRemoteView(result.newPublicView, + cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -294,18 +428,19 @@ public class NotificationInflater { return result.newPublicView; } }; - applyRemoteView(result, reInflateFlags, flag, row, - redactAmbient, isNewView, remoteViewClickHandler, callback, entry, + applyRemoteView(result, reInflateFlags, flag, cachedContentViews, row, + redactAmbient, isNewView, remoteViewClickHandler, callback, publicLayout, publicLayout.getContractedChild(), publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED), runningInflations, applyCallback); } - flag = FLAG_REINFLATE_AMBIENT_VIEW; + flag = FLAG_CONTENT_VIEW_AMBIENT; if ((reInflateFlags & flag) != 0) { NotificationContentView newParent = redactAmbient ? publicLayout : privateLayout; - boolean isNewView = !canReapplyAmbient(row, redactAmbient) || - !canReapplyRemoteView(result.newAmbientView, entry.cachedAmbientContentView); + boolean isNewView = (!canReapplyAmbient(row, redactAmbient) + || !canReapplyRemoteView(result.newAmbientView, + cachedContentViews.get(FLAG_CONTENT_VIEW_AMBIENT))); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -317,15 +452,15 @@ public class NotificationInflater { return result.newAmbientView; } }; - applyRemoteView(result, reInflateFlags, flag, row, - redactAmbient, isNewView, remoteViewClickHandler, callback, entry, + applyRemoteView(result, reInflateFlags, flag, cachedContentViews, row, + redactAmbient, isNewView, remoteViewClickHandler, callback, newParent, newParent.getAmbientChild(), newParent.getVisibleWrapper( NotificationContentView.VISIBLE_TYPE_AMBIENT), runningInflations, applyCallback); } // Let's try to finish, maybe nobody is even inflating anything - finishIfDone(result, reInflateFlags, runningInflations, callback, row, + finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, callback, row, redactAmbient); CancellationSignal cancellationSignal = new CancellationSignal(); cancellationSignal.setOnCancelListener( @@ -335,11 +470,11 @@ public class NotificationInflater { @VisibleForTesting static void applyRemoteView(final InflationProgress result, - final int reInflateFlags, int inflationId, - final ExpandableNotificationRow row, - final boolean redactAmbient, boolean isNewView, + final @InflationFlag int reInflateFlags, @InflationFlag int inflationId, + final ArrayMap<Integer, RemoteViews> cachedContentViews, + final ExpandableNotificationRow row, final boolean redactAmbient, boolean isNewView, RemoteViews.OnClickHandler remoteViewClickHandler, - @Nullable final InflationCallback callback, NotificationData.Entry entry, + @Nullable final InflationCallback callback, NotificationContentView parentLayout, View existingView, NotificationViewWrapper existingWrapper, final HashMap<Integer, CancellationSignal> runningInflations, @@ -362,7 +497,7 @@ public class NotificationInflater { existingWrapper.onReinflated(); } } catch (Exception e) { - handleInflationError(runningInflations, e, entry.notification, callback); + handleInflationError(runningInflations, e, row.getStatusBarNotification(), callback); // Add a running inflation to make sure we don't trigger callbacks. // Safe to do because only happens in tests. runningInflations.put(inflationId, new CancellationSignal()); @@ -381,8 +516,8 @@ public class NotificationInflater { existingWrapper.onReinflated(); } runningInflations.remove(inflationId); - finishIfDone(result, reInflateFlags, runningInflations, callback, row, - redactAmbient); + finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, + callback, row, redactAmbient); } @Override @@ -407,7 +542,8 @@ public class NotificationInflater { onViewApplied(newView); } catch (Exception anotherException) { runningInflations.remove(inflationId); - handleInflationError(runningInflations, e, entry.notification, callback); + handleInflationError(runningInflations, e, row.getStatusBarNotification(), + callback); } } }; @@ -430,8 +566,9 @@ public class NotificationInflater { runningInflations.put(inflationId, cancellationSignal); } - private static void handleInflationError(HashMap<Integer, CancellationSignal> runningInflations, - Exception e, StatusBarNotification notification, @Nullable InflationCallback callback) { + private static void handleInflationError( + HashMap<Integer, CancellationSignal> runningInflations, Exception e, + StatusBarNotification notification, @Nullable InflationCallback callback) { Assert.isMainThread(); runningInflations.values().forEach(CancellationSignal::cancel); if (callback != null) { @@ -444,7 +581,8 @@ public class NotificationInflater { * * @return true if the inflation was finished */ - private static boolean finishIfDone(InflationProgress result, int reInflateFlags, + private static boolean finishIfDone(InflationProgress result, + @InflationFlag int reInflateFlags, ArrayMap<Integer, RemoteViews> cachedContentViews, HashMap<Integer, CancellationSignal> runningInflations, @Nullable InflationCallback endListener, ExpandableNotificationRow row, boolean redactAmbient) { @@ -453,40 +591,40 @@ public class NotificationInflater { NotificationContentView privateLayout = row.getPrivateLayout(); NotificationContentView publicLayout = row.getPublicLayout(); if (runningInflations.isEmpty()) { - if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) { + if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) { if (result.inflatedContentView != null) { privateLayout.setContractedChild(result.inflatedContentView); } - entry.cachedContentView = result.newContentView; + cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView); } - if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) { + if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) { if (result.inflatedExpandedView != null) { privateLayout.setExpandedChild(result.inflatedExpandedView); } else if (result.newExpandedView == null) { privateLayout.setExpandedChild(null); } - entry.cachedBigContentView = result.newExpandedView; + cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView); row.setExpandable(result.newExpandedView != null); } - if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) { + if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { if (result.inflatedHeadsUpView != null) { privateLayout.setHeadsUpChild(result.inflatedHeadsUpView); } else if (result.newHeadsUpView == null) { privateLayout.setHeadsUpChild(null); } - entry.cachedHeadsUpContentView = result.newHeadsUpView; + cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView); } - if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) { + if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) { if (result.inflatedPublicView != null) { publicLayout.setContractedChild(result.inflatedPublicView); } - entry.cachedPublicContentView = result.newPublicView; + cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView); } - if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) { + if ((reInflateFlags & FLAG_CONTENT_VIEW_AMBIENT) != 0) { if (result.inflatedAmbientView != null) { NotificationContentView newParent = redactAmbient ? publicLayout : privateLayout; @@ -495,12 +633,12 @@ public class NotificationInflater { newParent.setAmbientChild(result.inflatedAmbientView); otherParent.setAmbientChild(null); } - entry.cachedAmbientContentView = result.newAmbientView; + cachedContentViews.put(FLAG_CONTENT_VIEW_AMBIENT, result.newAmbientView); } entry.headsUpStatusBarText = result.headsUpStatusBarText; entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic; if (endListener != null) { - endListener.onAsyncInflationFinished(row.getEntry()); + endListener.onAsyncInflationFinished(row.getEntry(), reInflateFlags); } return true; } @@ -552,7 +690,15 @@ public class NotificationInflater { public interface InflationCallback { void handleInflationException(StatusBarNotification notification, Exception e); - void onAsyncInflationFinished(NotificationData.Entry entry); + + /** + * Callback for after the content views finish inflating. + * + * @param entry the entry with the content views set + * @param inflatedFlags the flags associated with the content views that were inflated + */ + void onAsyncInflationFinished(NotificationData.Entry entry, + @InflationFlag int inflatedFlags); /** * Used to disable async-ness for tests. Should only be used for tests. @@ -563,18 +709,13 @@ public class NotificationInflater { } public void clearCachesAndReInflate() { - NotificationData.Entry entry = mRow.getEntry(); - entry.cachedAmbientContentView = null; - entry.cachedBigContentView = null; - entry.cachedContentView = null; - entry.cachedHeadsUpContentView = null; - entry.cachedPublicContentView = null; + mCachedContentViews.clear(); inflateNotificationViews(); } private static boolean canReapplyAmbient(ExpandableNotificationRow row, boolean redactAmbient) { NotificationContentView ambientView = redactAmbient ? row.getPublicLayout() - : row.getPrivateLayout(); ; + : row.getPrivateLayout(); return ambientView.getAmbientChild() != null; } @@ -589,7 +730,8 @@ public class NotificationInflater { private final InflationCallback mCallback; private final boolean mUsesIncreasedHeadsUpHeight; private final boolean mRedactAmbient; - private int mReInflateFlags; + private @InflationFlag int mReInflateFlags; + private final ArrayMap<Integer, RemoteViews> mCachedContentViews; private ExpandableNotificationRow mRow; private Exception mError; private RemoteViews.OnClickHandler mRemoteViewClickHandler; @@ -597,15 +739,16 @@ public class NotificationInflater { private List<Notification.Action> mSmartActions; private AsyncInflationTask(StatusBarNotification notification, - int reInflateFlags, ExpandableNotificationRow row, boolean isLowPriority, - boolean isChildInGroup, boolean usesIncreasedHeight, + @InflationFlag int reInflateFlags, + ArrayMap<Integer, RemoteViews> cachedContentViews, ExpandableNotificationRow row, + boolean isLowPriority, boolean isChildInGroup, boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, boolean redactAmbient, - InflationCallback callback, - RemoteViews.OnClickHandler remoteViewClickHandler, + InflationCallback callback, RemoteViews.OnClickHandler remoteViewClickHandler, List<Notification.Action> smartActions) { mRow = row; mSbn = notification; mReInflateFlags = reInflateFlags; + mCachedContentViews = cachedContentViews; mContext = mRow.getContext(); mIsLowPriority = isLowPriority; mIsChildInGroup = isChildInGroup; @@ -622,6 +765,7 @@ public class NotificationInflater { } @VisibleForTesting + @InflationFlag public int getReInflateFlags() { return mReInflateFlags; } @@ -642,10 +786,9 @@ public class NotificationInflater { packageContext); processor.processNotification(notification, recoveredBuilder); } - return createRemoteViews(mReInflateFlags, - recoveredBuilder, mIsLowPriority, mIsChildInGroup, - mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient, - packageContext); + return createRemoteViews(mReInflateFlags, recoveredBuilder, mIsLowPriority, + mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, + mRedactAmbient, packageContext); } catch (Exception e) { mError = e; return null; @@ -655,8 +798,8 @@ public class NotificationInflater { @Override protected void onPostExecute(InflationProgress result) { if (mError == null) { - mCancellationSignal = apply(result, mReInflateFlags, mRow, mRedactAmbient, - mRemoteViewClickHandler, this); + mCancellationSignal = apply(result, mReInflateFlags, mCachedContentViews, mRow, + mRedactAmbient, mRemoteViewClickHandler, this); } else { handleError(mError); } @@ -706,10 +849,11 @@ public class NotificationInflater { } @Override - public void onAsyncInflationFinished(NotificationData.Entry entry) { + public void onAsyncInflationFinished(NotificationData.Entry entry, + @InflationFlag int inflatedFlags) { mRow.getEntry().onInflationTaskFinished(); mRow.onNotificationUpdated(); - mCallback.onAsyncInflationFinished(mRow.getEntry()); + mCallback.onAsyncInflationFinished(mRow.getEntry(), inflatedFlags); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index d477587f8ecb..b4d24d16113e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.policy; +import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_HEADS_UP; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -151,6 +153,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, false); } + entry.row.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_HEADS_UP); } protected void updatePinnedMode() { 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 edf29ac1c4f8..aca1f90b5aa8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -31,9 +31,9 @@ import android.text.TextUtils; import android.view.LayoutInflater; import android.widget.RemoteViews; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import com.android.systemui.statusbar.notification.row.NotificationInflaterTest; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; @@ -67,16 +67,50 @@ public class NotificationTestHelper { mGroupManager.setHeadsUpManager(mHeadsUpManager); } + /** + * Creates a generic row. + * + * @return a generic row with no special properties. + * @throws Exception + */ public ExpandableNotificationRow createRow() throws Exception { return createRow(PKG, UID); } + /** + * Create a row with the package and user id specified. + * + * @param pkg package + * @param uid user id + * @return a row with a notification using the package and user id + * @throws Exception + */ public ExpandableNotificationRow createRow(String pkg, int uid) throws Exception { return createRow(pkg, uid, false /* isGroupSummary */, null /* groupKey */); } + /** + * Creates a row based off the notification given. + * + * @param notification the notification + * @return a row built off the notification + * @throws Exception + */ public ExpandableNotificationRow createRow(Notification notification) throws Exception { - return generateRow(notification, PKG, UID, false /* isGroupRow */); + return generateRow(notification, PKG, UID, 0 /* extraInflationFlags */); + } + + /** + * Create a row with the specified content views inflated in addition to the default. + * + * @param extraInflationFlags the flags corresponding to the additional content views that + * should be inflated + * @return a row with the specified content views inflated in addition to the default + * @throws Exception + */ + public ExpandableNotificationRow createRow(@InflationFlag int extraInflationFlags) + throws Exception { + return generateRow(createNotification(), PKG, UID, extraInflationFlags); } /** @@ -122,34 +156,53 @@ public class NotificationTestHelper { boolean isGroupSummary, @Nullable String groupKey) throws Exception { + Notification notif = createNotification(isGroupSummary, groupKey); + return generateRow(notif, pkg, uid, 0 /* inflationFlags */); + } + + /** + * Creates a generic notification. + * + * @return a notification with no special properties + */ + private Notification createNotification() { + return createNotification(false /* isGroupSummary */, null /* groupKey */); + } + + /** + * Creates a notification with the given parameters. + * + * @param isGroupSummary whether the notification is a group summary + * @param groupKey the group key for the notification group used across notifications + * @return a notification that is in the group specified or standalone if unspecified + */ + private Notification createNotification(boolean isGroupSummary, + @Nullable String groupKey) { Notification publicVersion = new Notification.Builder(mContext).setSmallIcon( R.drawable.ic_person) .setCustomContentView(new RemoteViews(mContext.getPackageName(), R.layout.custom_view_dark)) .build(); - Notification.Builder notificationBuilder = - new Notification.Builder(mContext, "channelId") - .setSmallIcon(R.drawable.ic_person) - .setContentTitle("Title") - .setContentText("Text") - .setPublicVersion(publicVersion); - - // Group notification setup + Notification.Builder notificationBuilder = new Notification.Builder(mContext, "channelId") + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text") + .setPublicVersion(publicVersion) + .setStyle(new Notification.BigTextStyle().bigText("Big Text")); if (isGroupSummary) { notificationBuilder.setGroupSummary(true); } if (!TextUtils.isEmpty(groupKey)) { notificationBuilder.setGroup(groupKey); } - - return generateRow(notificationBuilder.build(), pkg, uid, !TextUtils.isEmpty(groupKey)); + return notificationBuilder.build(); } private ExpandableNotificationRow generateRow( Notification notification, String pkg, int uid, - boolean isGroupRow) + @InflationFlag int extraInflationFlags) throws Exception { LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( mContext.LAYOUT_INFLATER_SERVICE); @@ -179,8 +232,10 @@ public class NotificationTestHelper { entry.channel = new NotificationChannel( notification.getChannelId(), notification.getChannelId(), IMPORTANCE_DEFAULT); entry.channel.setBlockableSystem(true); + row.setEntry(entry); + row.getNotificationInflater().addInflationFlags(extraInflationFlags); NotificationInflaterTest.runThenWaitForInflation( - () -> row.updateNotification(entry), + () -> row.inflateViews(), row.getNotificationInflater()); // This would be done as part of onAsyncInflationFinished, but we skip large amounts of diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index 4e16b7f94283..f01ae7a23533 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -68,6 +68,7 @@ import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.row.NotificationInflater; import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.NotificationGroupManager; @@ -134,8 +135,9 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Override - public void onAsyncInflationFinished(NotificationData.Entry entry) { - super.onAsyncInflationFinished(entry); + public void onAsyncInflationFinished(NotificationData.Entry entry, + @NotificationInflater.InflationFlag int inflatedFlags) { + super.onAsyncInflationFinished(entry, inflatedFlags); mCountDownLatch.countDown(); } @@ -428,7 +430,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { setSmartActions(mEntry.key, new ArrayList<>(Arrays.asList(createAction()))); mEntryManager.updateNotificationRanking(mRankingMap); - verify(mRow).updateNotification(eq(mEntry)); + verify(mRow).setEntry(eq(mEntry)); assertEquals(1, mEntry.smartActions.size()); assertEquals("action", mEntry.smartActions.get(0).title); } @@ -443,7 +445,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { setSmartActions(mEntry.key, null); mEntryManager.updateNotificationRanking(mRankingMap); - verify(mRow, never()).updateNotification(eq(mEntry)); + verify(mRow, never()).setEntry(eq(mEntry)); assertEquals(0, mEntry.smartActions.size()); } @@ -457,7 +459,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { setSmartActions(mEntry.key, new ArrayList<>(Arrays.asList(createAction()))); mEntryManager.updateNotificationRanking(mRankingMap); - verify(mRow, never()).updateNotification(eq(mEntry)); + verify(mRow, never()).setEntry(eq(mEntry)); assertEquals(1, mEntry.smartActions.size()); assertEquals("action", mEntry.smartActions.get(0).title); } @@ -472,7 +474,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { setSmartActions(mEntry.key, new ArrayList<>(Arrays.asList(createAction()))); mEntryManager.updateNotificationRanking(mRankingMap); - verify(mRow, never()).updateNotification(eq(mEntry)); + verify(mRow, never()).setEntry(eq(mEntry)); assertEquals(1, mEntry.smartActions.size()); assertEquals("action", mEntry.smartActions.get(0).title); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 743b307d0666..cfc75261123a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -18,8 +18,13 @@ package com.android.systemui.statusbar.notification.row; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_ALL; +import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_HEADS_UP; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -35,6 +40,7 @@ import android.app.AppOpsManager; import android.app.NotificationChannel; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.util.ArraySet; import android.view.NotificationHeaderView; @@ -134,6 +140,15 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test + public void testFreeContentViewWhenSafe() throws Exception { + ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL); + + row.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_HEADS_UP); + + assertNull(row.getPrivateLayout().getHeadsUpChild()); + } + + @Test public void testAboveShelfChangedListenerCalled() throws Exception { ExpandableNotificationRow row = mNotificationTestHelper.createRow(); AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInflaterTest.java index 81e79d1490b9..150d9337d4a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInflaterTest.java @@ -16,10 +16,13 @@ package com.android.systemui.statusbar.notification.row; +import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_HEADS_UP; +import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_ALL; +import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_EXPANDED; -import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_REINFLATE_ALL; - -import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_REINFLATE_EXPANDED_VIEW; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -34,6 +37,7 @@ import android.service.notification.StatusBarNotification; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import android.util.ArrayMap; import android.view.View; import android.view.ViewGroup; import android.widget.RemoteViews; @@ -82,7 +86,8 @@ public class NotificationInflaterTest extends SysuiTestCase { } @Override - public void onAsyncInflationFinished(NotificationData.Entry entry) { + public void onAsyncInflationFinished(NotificationData.Entry entry, + @NotificationInflater.InflationFlag int inflatedFlags) { } }); } @@ -91,7 +96,7 @@ public class NotificationInflaterTest extends SysuiTestCase { public void testIncreasedHeadsUpBeingUsed() { mNotificationInflater.setUsesIncreasedHeadsUpHeight(true); Notification.Builder builder = spy(mBuilder); - mNotificationInflater.inflateNotificationViews(FLAG_REINFLATE_ALL, builder, mContext); + mNotificationInflater.inflateNotificationViews(FLAG_CONTENT_VIEW_ALL, builder, mContext); verify(builder).createHeadsUpContentView(true); } @@ -99,7 +104,7 @@ public class NotificationInflaterTest extends SysuiTestCase { public void testIncreasedHeightBeingUsed() { mNotificationInflater.setUsesIncreasedHeight(true); Notification.Builder builder = spy(mBuilder); - mNotificationInflater.inflateNotificationViews(FLAG_REINFLATE_ALL, builder, mContext); + mNotificationInflater.inflateNotificationViews(FLAG_CONTENT_VIEW_ALL, builder, mContext); verify(builder).createContentView(true); } @@ -111,14 +116,14 @@ public class NotificationInflaterTest extends SysuiTestCase { } @Test - public void testInflationCallsOnlyRightMethod() throws Exception { - mRow.getPrivateLayout().removeAllViews(); - mRow.getEntry().cachedBigContentView = null; - runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews( - FLAG_REINFLATE_EXPANDED_VIEW), mNotificationInflater); - assertTrue(mRow.getPrivateLayout().getChildCount() == 1); - assertTrue(mRow.getPrivateLayout().getChildAt(0) - == mRow.getPrivateLayout().getExpandedChild()); + public void testInflationOnlyInflatesSetFlags() throws Exception { + mNotificationInflater.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, + true /* shouldInflate */); + runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(), + mNotificationInflater); + + assertNotNull(mRow.getPrivateLayout().getHeadsUpChild()); + assertNull(mRow.getShowingLayout().getAmbientChild()); verify(mRow).onNotificationUpdated(); } @@ -155,8 +160,9 @@ public class NotificationInflaterTest extends SysuiTestCase { new NotificationInflater.InflationProgress(); result.packageContext = mContext; CountDownLatch countDownLatch = new CountDownLatch(1); - NotificationInflater.applyRemoteView(result, FLAG_REINFLATE_EXPANDED_VIEW, 0, mRow, - false /* redactAmbient */, true /* isNewView */, new RemoteViews.OnClickHandler(), + NotificationInflater.applyRemoteView(result, FLAG_CONTENT_VIEW_EXPANDED, 0, + new ArrayMap() /* cachedContentViews */, mRow, false /* redactAmbient */, + true /* isNewView */, new RemoteViews.OnClickHandler(), new NotificationInflater.InflationCallback() { @Override public void handleInflationException(StatusBarNotification notification, @@ -166,10 +172,11 @@ public class NotificationInflaterTest extends SysuiTestCase { } @Override - public void onAsyncInflationFinished(NotificationData.Entry entry) { + public void onAsyncInflationFinished(NotificationData.Entry entry, + @NotificationInflater.InflationFlag int inflatedFlags) { countDownLatch.countDown(); } - }, mRow.getEntry(), mRow.getPrivateLayout(), null, null, new HashMap<>(), + }, mRow.getPrivateLayout(), null, null, new HashMap<>(), new NotificationInflater.ApplyCallback() { @Override public void setResultView(View v) { @@ -186,16 +193,19 @@ public class NotificationInflaterTest extends SysuiTestCase { /* Cancelling requires us to be on the UI thread otherwise we might have a race */ @Test - public void testSupersedesExistingTask() throws Exception { + public void testSupersedesExistingTask() { + mNotificationInflater.addInflationFlags(FLAG_CONTENT_VIEW_ALL); mNotificationInflater.inflateNotificationViews(); + + // Trigger inflation of content and expanded only. mNotificationInflater.setIsLowPriority(true); mNotificationInflater.setIsChildInGroup(true); + InflationTask runningTask = mRow.getEntry().getRunningTask(); NotificationInflater.AsyncInflationTask asyncInflationTask = (NotificationInflater.AsyncInflationTask) runningTask; - Assert.assertSame("Successive inflations don't inherit the previous flags!", - asyncInflationTask.getReInflateFlags(), - NotificationInflater.FLAG_REINFLATE_ALL); + assertEquals("Successive inflations don't inherit the previous flags!", + asyncInflationTask.getReInflateFlags(), FLAG_CONTENT_VIEW_ALL); runningTask.abort(); } @@ -231,7 +241,8 @@ public class NotificationInflaterTest extends SysuiTestCase { } @Override - public void onAsyncInflationFinished(NotificationData.Entry entry) { + public void onAsyncInflationFinished(NotificationData.Entry entry, + @NotificationInflater.InflationFlag int inflatedFlags) { if (expectingException) { exceptionHolder.setException(new RuntimeException( "Inflation finished even though there should be an error")); |