From caf2293885ddc754522018353cfb55f619a92f24 Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Fri, 10 Sep 2021 19:55:33 +0000 Subject: [PATCH 1/9] New pipeline: remove non-silent section headers Fixes: 204127880 Bug: 199765418 Test: manual Merged-In: I729cca549154c5762c2bb7082111d19b3ec1f9d9 Change-Id: I729cca549154c5762c2bb7082111d19b3ec1f9d9 --- .../collection/coordinator/ConversationCoordinator.kt | 4 +++- .../collection/coordinator/HeadsUpCoordinator.java | 6 +++++- .../collection/coordinator/RankingCoordinator.java | 7 ++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt index f0eb084ea8ef3..efec94f547dc6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt @@ -48,7 +48,9 @@ class ConversationCoordinator @Inject constructor( val sectioner = object : NotifSectioner("People") { override fun isInSection(entry: ListEntry): Boolean = isConversation(entry.representativeEntry!!) - override fun getHeaderNodeController() = peopleHeaderController + override fun getHeaderNodeController() = + // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController + if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null } override fun attach(pipeline: NotifPipeline) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java index 6e98c27fe9a96..1bde312d02712 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java @@ -205,7 +205,11 @@ public class HeadsUpCoordinator implements Coordinator { @Nullable @Override public NodeController getHeaderNodeController() { - return mIncomingHeaderController; + // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController + if (RankingCoordinator.SHOW_ALL_SECTIONS) { + return mIncomingHeaderController; + } + return null; } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java index 6da4d8b709442..d556e97c1bc6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -41,6 +41,7 @@ import javax.inject.Inject; */ @SysUISingleton public class RankingCoordinator implements Coordinator { + public static final boolean SHOW_ALL_SECTIONS = false; private final StatusBarStateController mStatusBarStateController; private final HighPriorityProvider mHighPriorityProvider; private final NodeController mSilentHeaderController; @@ -83,7 +84,11 @@ public class RankingCoordinator implements Coordinator { @Nullable @Override public NodeController getHeaderNodeController() { - return mAlertingHeaderController; + // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mAlertingHeaderController + if (SHOW_ALL_SECTIONS) { + return mAlertingHeaderController; + } + return null; } }; From 53f489d7b0584e3185f23895866f9ee930ba9d6f Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Fri, 10 Sep 2021 20:10:11 +0000 Subject: [PATCH 2/9] Remove dismiss-rtl setters because dismiss is bidirectional Fixes: 204127880 Test: dismiss notifs; change RTL; dismiss more Merged-In: Ia3e0c80a1bfb81f039c6409718d5fbeced455939 Change-Id: Ia3e0c80a1bfb81f039c6409718d5fbeced455939 --- .../plugins/statusbar/NotificationMenuRowPlugin.java | 6 ------ .../notification/row/ExpandableNotificationRow.java | 7 ------- .../notification/row/NotificationMenuRow.java | 9 --------- .../stack/NotificationStackScrollLayout.java | 12 ------------ .../NotificationStackScrollLayoutController.java | 4 ---- 5 files changed, 38 deletions(-) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java index 883f4de1149cc..94fdbae832533 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java @@ -182,12 +182,6 @@ public interface NotificationMenuRowPlugin extends Plugin { */ public boolean canBeDismissed(); - /** - * Informs the menu whether dismiss gestures are left-to-right or right-to-left. - */ - default void setDismissRtl(boolean dismissRtl) { - } - /** * Determines whether the menu should remain open given its current state, or snap closed. * @return true if the menu should remain open, false otherwise. 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 0d8e850946464..e956046abd042 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 @@ -3201,13 +3201,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - /** Sets whether dismiss gestures are right-to-left (instead of left-to-right). */ - public void setDismissRtl(boolean dismissRtl) { - if (mMenuRow != null) { - mMenuRow.setDismissRtl(dismissRtl); - } - } - private static class NotificationViewState extends ExpandableViewState { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index d59318e45e7e4..3a37fb44b33a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -85,7 +85,6 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl private ArrayList mRightMenuItems; private final Map mMenuItemsByView = new ArrayMap<>(); private OnMenuEventListener mMenuListener; - private boolean mDismissRtl; private ValueAnimator mFadeAnimator; private boolean mAnimating; @@ -790,14 +789,6 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl return getParent().canViewBeDismissed(); } - @Override - public void setDismissRtl(boolean dismissRtl) { - mDismissRtl = dismissRtl; - if (mMenuContainer != null) { - createMenuViews(true); - } - } - public static class NotificationMenuItem implements MenuItem { View mMenuView; GutsContent mGutsContent; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 58e44792571c0..1f764255914b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -161,7 +161,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private final Paint mBackgroundPaint = new Paint(); private final boolean mShouldDrawNotificationBackground; private boolean mHighPriorityBeforeSpeedBump; - private boolean mDismissRtl; private float mExpandedHeight; private int mOwnScrollY; @@ -613,16 +612,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable addView(mFgsSectionView, -1); } - void updateDismissRtlSetting(boolean dismissRtl) { - mDismissRtl = dismissRtl; - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - if (child instanceof ExpandableNotificationRow) { - ((ExpandableNotificationRow) child).setDismissRtl(dismissRtl); - } - } - } - /** * Set the overexpansion of the panel to be applied to the view. */ @@ -2915,7 +2904,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateChronometerForChild(child); if (child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; - row.setDismissRtl(mDismissRtl); row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 9923faeb8b5c2..b4d96421e2c29 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -783,9 +783,6 @@ public class NotificationStackScrollLayoutController { mTunerService.addTunable( (key, newValue) -> { switch (key) { - case Settings.Secure.NOTIFICATION_DISMISS_RTL: - mView.updateDismissRtlSetting("1".equals(newValue)); - break; case Settings.Secure.NOTIFICATION_HISTORY_ENABLED: updateFooter(); break; @@ -795,7 +792,6 @@ public class NotificationStackScrollLayoutController { } }, HIGH_PRIORITY, - Settings.Secure.NOTIFICATION_DISMISS_RTL, Settings.Secure.NOTIFICATION_HISTORY_ENABLED); mKeyguardMediaController.setVisibilityChangedListener(visible -> { From 61499496b1cd6fc648dd81f93e2284b8dcf8f917 Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Fri, 10 Sep 2021 20:01:10 +0000 Subject: [PATCH 3/9] New pipeline: Section manager is old pipeline, so don't call it for the new. Fixes: 204127880 Bug: 199767031 Test: manual Merged-In: I01f1eed3edd67095e25ce30c03fab77272688f2b Change-Id: I01f1eed3edd67095e25ce30c03fab77272688f2b --- .../stack/NotificationStackScrollLayoutController.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index b4d96421e2c29..444e5f3c69158 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1300,6 +1300,9 @@ public class NotificationStackScrollLayoutController { } public void updateSectionBoundaries(String reason) { + if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { + return; + } mView.updateSectionBoundaries(reason); } From 5e6673f96a4547ed7b4ea9aab3617f4ce36da22e Mon Sep 17 00:00:00 2001 From: Jay Aliomer Date: Thu, 16 Sep 2021 17:02:18 -0400 Subject: [PATCH 4/9] Enabled x button for clearable silent Notif[s] Fixes: 204127880 Bug: 199902183 Test: RankingCoordinatorTest ShadeListBuilderTest Merged-In: If55b0b7fc35bb6ece83878cf3db0d7abfc2a214a Change-Id: If55b0b7fc35bb6ece83878cf3db0d7abfc2a214a --- .../notification/collection/ListEntry.java | 2 +- .../collection/ShadeListBuilder.java | 26 ++++++++++++-- .../coordinator/RankingCoordinator.java | 25 +++++++++++-- .../listbuilder/pluggable/NotifSectioner.java | 8 +++++ .../render/SectionHeaderController.kt | 12 +++++-- .../stack/NotificationSectionsManager.kt | 2 +- ...tificationStackScrollLayoutController.java | 2 +- .../notification/stack/SectionHeaderView.java | 8 +++-- .../collection/ShadeListBuilderTest.java | 27 ++++++++++++++ .../coordinator/RankingCoordinatorTest.java | 36 +++++++++++++++++-- 10 files changed, 133 insertions(+), 15 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java index fd0476b76a9ae..37eacada19fc2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java @@ -36,7 +36,7 @@ public abstract class ListEntry { private final ListAttachState mPreviousAttachState = ListAttachState.create(); private final ListAttachState mAttachState = ListAttachState.create(); - ListEntry(String key, long creationTime) { + protected ListEntry(String key, long creationTime) { mKey = key; mCreationTime = creationTime; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index e26fa045d297d..122fb1c2f6b89 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -78,6 +78,8 @@ public class ShadeListBuilder implements Dumpable { private final SystemClock mSystemClock; private final ShadeListBuilderLogger mLogger; private final NotificationInteractionTracker mInteractionTracker; + // used exclusivly by ShadeListBuilder#notifySectionEntriesUpdated + private final ArrayList mTempSectionMembers = new ArrayList<>(); private List mNotifList = new ArrayList<>(); private List mNewNotifList = new ArrayList<>(); @@ -356,7 +358,7 @@ public class ShadeListBuilder implements Dumpable { // section by our list of custom comparators dispatchOnBeforeSort(mReadOnlyNotifList); mPipelineState.incrementTo(STATE_SORTING); - sortList(); + sortListAndNotifySections(); // Step 7: Lock in our group structure and log anything that's changed since the last run mPipelineState.incrementTo(STATE_FINALIZING); @@ -382,6 +384,22 @@ public class ShadeListBuilder implements Dumpable { mIterationCount++; } + private void notifySectionEntriesUpdated() { + NotifSection currentSection = null; + mTempSectionMembers.clear(); + for (int i = 0; i < mNotifList.size(); i++) { + ListEntry currentEntry = mNotifList.get(i); + if (currentSection != currentEntry.getSection()) { + if (currentSection != null) { + currentSection.getSectioner().onEntriesUpdated(mTempSectionMembers); + mTempSectionMembers.clear(); + } + currentSection = currentEntry.getSection(); + } + mTempSectionMembers.add(currentEntry); + } + } + /** * Points mNotifList to the list stored in mNewNotifList. * Reuses the (emptied) mNotifList as mNewNotifList. @@ -713,7 +731,7 @@ public class ShadeListBuilder implements Dumpable { } } - private void sortList() { + private void sortListAndNotifySections() { // Assign sections to top-level elements and sort their children for (ListEntry entry : mNotifList) { NotifSection section = applySections(entry); @@ -728,6 +746,9 @@ public class ShadeListBuilder implements Dumpable { // Finally, sort all top-level elements mNotifList.sort(mTopLevelComparator); + + // notify sections since the list is sorted now + notifySectionEntriesUpdated(); } private void freeEmptyGroups() { @@ -937,7 +958,6 @@ public class ShadeListBuilder implements Dumpable { } entry.getAttachState().setSection(finalSection); - return finalSection; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java index d556e97c1bc6b..c62214ca16fae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator; +import android.annotation.NonNull; import android.annotation.Nullable; import com.android.systemui.dagger.SysUISingleton; @@ -27,9 +28,12 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.collection.render.NodeController; +import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import com.android.systemui.statusbar.notification.dagger.AlertingHeader; import com.android.systemui.statusbar.notification.dagger.SilentHeader; +import java.util.List; + import javax.inject.Inject; /** @@ -44,7 +48,8 @@ public class RankingCoordinator implements Coordinator { public static final boolean SHOW_ALL_SECTIONS = false; private final StatusBarStateController mStatusBarStateController; private final HighPriorityProvider mHighPriorityProvider; - private final NodeController mSilentHeaderController; + private final NodeController mSilentNodeController; + private final SectionHeaderController mSilentHeaderController; private final NodeController mAlertingHeaderController; @Inject @@ -52,10 +57,12 @@ public class RankingCoordinator implements Coordinator { StatusBarStateController statusBarStateController, HighPriorityProvider highPriorityProvider, @AlertingHeader NodeController alertingHeaderController, - @SilentHeader NodeController silentHeaderController) { + @SilentHeader SectionHeaderController silentHeaderController, + @SilentHeader NodeController silentNodeController) { mStatusBarStateController = statusBarStateController; mHighPriorityProvider = highPriorityProvider; mAlertingHeaderController = alertingHeaderController; + mSilentNodeController = silentNodeController; mSilentHeaderController = silentHeaderController; } @@ -101,7 +108,19 @@ public class RankingCoordinator implements Coordinator { @Nullable @Override public NodeController getHeaderNodeController() { - return mSilentHeaderController; + return mSilentNodeController; + } + + @Nullable + @Override + public void onEntriesUpdated(@NonNull List entries) { + for (int i = 0; i < entries.size(); i++) { + if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) { + mSilentHeaderController.setClearSectionEnabled(true); + return; + } + } + mSilentHeaderController.setClearSectionEnabled(false); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java index c8982d35c4a01..58afca8043edd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java @@ -23,6 +23,8 @@ import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; import com.android.systemui.statusbar.notification.collection.render.NodeController; import com.android.systemui.statusbar.notification.collection.render.NodeSpec; +import java.util.List; + /** * Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSections}. */ @@ -46,4 +48,10 @@ public abstract class NotifSectioner extends Pluggable { public @Nullable NodeController getHeaderNodeController() { return null; } + + /** + * Notify of children of this section being updated + * @param entries of this section that are borrowed (must clone to store) + */ + public void onEntriesUpdated(List entries) {} } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt index 1311e3e756dc5..8c15647c50389 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt @@ -33,7 +33,8 @@ import javax.inject.Inject interface SectionHeaderController { fun reinflateView(parent: ViewGroup) val headerView: SectionHeaderView? - fun setOnClearAllClickListener(listener: View.OnClickListener) + fun setClearSectionEnabled(enabled: Boolean) + fun setOnClearSectionClickListener(listener: View.OnClickListener) } @SectionHeaderScope @@ -46,6 +47,7 @@ internal class SectionHeaderNodeControllerImpl @Inject constructor( ) : NodeController, SectionHeaderController { private var _view: SectionHeaderView? = null + private var clearAllButtonEnabled = false private var clearAllClickListener: View.OnClickListener? = null private val onHeaderClickListener = View.OnClickListener { activityStarter.startActivity( @@ -76,12 +78,18 @@ internal class SectionHeaderNodeControllerImpl @Inject constructor( parent.addView(inflated, oldPos) } _view = inflated + _view?.setClearSectionButtonEnabled(clearAllButtonEnabled) } override val headerView: SectionHeaderView? get() = _view - override fun setOnClearAllClickListener(listener: View.OnClickListener) { + override fun setClearSectionEnabled(enabled: Boolean) { + clearAllButtonEnabled = enabled + _view?.setClearSectionButtonEnabled(enabled) + } + + override fun setOnClearSectionClickListener(listener: View.OnClickListener) { clearAllClickListener = listener _view?.setOnClearAllClickListener(listener) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt index 45ce20a1f08fd..6ee14b53dd732 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt @@ -350,7 +350,7 @@ class NotificationSectionsManager @Inject internal constructor( silentHeaderView?.run { val hasActiveClearableNotifications = this@NotificationSectionsManager.parent .hasActiveClearableNotifications(NotificationStackScrollLayout.ROWS_GENTLE) - setAreThereDismissableGentleNotifs(hasActiveClearableNotifications) + setClearSectionButtonEnabled(hasActiveClearableNotifications) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 444e5f3c69158..83e6ca0c8998d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -813,7 +813,7 @@ public class NotificationStackScrollLayoutController { mOnAttachStateChangeListener.onViewAttachedToWindow(mView); } mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener); - mSilentHeaderController.setOnClearAllClickListener(v -> clearSilentNotifications()); + mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications()); } private boolean isInVisibleLocation(NotificationEntry entry) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java index 99ec7548fb9dc..baf09c70f9367 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java @@ -85,8 +85,12 @@ public class SectionHeaderView extends StackScrollerDecorView { return true; } - void setAreThereDismissableGentleNotifs(boolean areThereDismissableGentleNotifs) { - mClearAllButton.setVisibility(areThereDismissableGentleNotifs ? View.VISIBLE : View.GONE); + /** + * Show the clear section [X] button + * @param enabled + */ + public void setClearSectionButtonEnabled(boolean enabled) { + mClearAllButton.setVisibility(enabled ? View.VISIBLE : View.GONE); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index 3378003b0d44e..c862f97681096 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -33,6 +33,7 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static java.util.Collections.singletonList; @@ -42,6 +43,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArrayMap; +import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -78,6 +80,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; @SmallTest @@ -607,6 +610,30 @@ public class ShadeListBuilderTest extends SysuiTestCase { assertEquals(promoter2, mEntrySet.get(3).getNotifPromoter()); } + @Test + public void testNotifSectionsChildrenUpdated() { + AtomicBoolean validChildren = new AtomicBoolean(false); + final NotifSectioner pkg1Sectioner = spy(new PackageSectioner(PACKAGE_1) { + @Nullable + @Override + public void onEntriesUpdated(List entries) { + super.onEntriesUpdated(entries); + validChildren.set(entries.size() == 2); + } + }); + mListBuilder.setSectioners(Arrays.asList(pkg1Sectioner)); + + addNotif(0, PACKAGE_4); + addNotif(1, PACKAGE_1); + addNotif(2, PACKAGE_1); + addNotif(3, PACKAGE_3); + + dispatchBuild(); + + verify(pkg1Sectioner, times(1)).onEntriesUpdated(any()); + assertTrue(validChildren.get()); + } + @Test public void testNotifSections() { // GIVEN a filter that removes all PACKAGE_4 notifs and sections that divide diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java index 1031d6befc368..8f241a37c5ba4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java @@ -21,17 +21,22 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICAT import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Notification; +import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; +import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.RankingBuilder; +import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; @@ -39,6 +44,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.collection.render.NodeController; +import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import org.junit.Before; import org.junit.Test; @@ -46,8 +52,11 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.Arrays; + @SmallTest @RunWith(AndroidTestingRunner.class) public class RankingCoordinatorTest extends SysuiTestCase { @@ -56,7 +65,8 @@ public class RankingCoordinatorTest extends SysuiTestCase { @Mock private HighPriorityProvider mHighPriorityProvider; @Mock private NotifPipeline mNotifPipeline; @Mock private NodeController mAlertingHeaderController; - @Mock private NodeController mSilentHeaderController; + @Mock private NodeController mSilentNodeController; + @Mock private SectionHeaderController mSilentHeaderController; @Captor private ArgumentCaptor mNotifFilterCaptor; @@ -72,7 +82,7 @@ public class RankingCoordinatorTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); RankingCoordinator rankingCoordinator = new RankingCoordinator( mStatusBarStateController, mHighPriorityProvider, mAlertingHeaderController, - mSilentHeaderController); + mSilentHeaderController, mSilentNodeController); mEntry = new NotificationEntryBuilder().build(); rankingCoordinator.attach(mNotifPipeline); @@ -84,6 +94,28 @@ public class RankingCoordinatorTest extends SysuiTestCase { mSilentSectioner = rankingCoordinator.getSilentSectioner(); } + @Test + public void testSilentHeaderClearableChildrenUpdate() { + StatusBarNotification sbn = Mockito.mock(StatusBarNotification.class); + Mockito.doReturn("key").when(sbn).getKey(); + Mockito.doReturn(Mockito.mock(Notification.class)).when(sbn).getNotification(); + NotificationEntry entry = new NotificationEntryBuilder().setSbn(sbn).build(); + ListEntry listEntry = new ListEntry("key", 0L) { + @Nullable + @Override + public NotificationEntry getRepresentativeEntry() { + return entry; + } + }; + Mockito.doReturn(true).when(sbn).isClearable(); + mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry)); + verify(mSilentHeaderController).setClearSectionEnabled(eq(true)); + + Mockito.doReturn(false).when(sbn).isClearable(); + mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry)); + verify(mSilentHeaderController).setClearSectionEnabled(eq(false)); + } + @Test public void testUnfilteredState() { // GIVEN no suppressed visual effects + app not suspended From 4ddf9d094c733e9040d9a71f378521525ec07f4c Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Fri, 24 Sep 2021 17:50:55 +0000 Subject: [PATCH 5/9] Populate buckets to ensure correct corner rounds on notifications Fixes: 204127880 Bug: 200956118 Bug: 199388061 Test: manual Test: atest SystemUITests:ShadeListBuilderTest SystemUITests:NotifSpecBuilderTest Merged-In: I36f6f2ac2a503daa01594248a1e6cffcfbd5c348 Change-Id: I36f6f2ac2a503daa01594248a1e6cffcfbd5c348 --- ...NotificationLockscreenUserManagerImpl.java | 4 +-- .../collection/NotificationEntry.java | 2 +- .../collection/ShadeListBuilder.java | 27 ++++++++++++------- .../coordinator/AppOpsCoordinator.java | 4 ++- .../coordinator/ConversationCoordinator.kt | 3 ++- .../coordinator/HeadsUpCoordinator.java | 4 ++- .../coordinator/RankingCoordinator.java | 7 +++-- .../collection/listbuilder/NotifSection.kt | 5 +++- .../listbuilder/pluggable/NotifSectioner.java | 17 ++++++++++-- .../logging/NotificationPanelLogger.java | 12 ++++----- .../stack/NotificationPriorityBucket.kt | 25 +++++++++++++++++ .../stack/NotificationSection.java | 2 +- .../stack/NotificationSectionsManager.kt | 23 ---------------- .../stack/NotificationStackScrollLayout.java | 2 +- ...NotificationLockscreenUserManagerTest.java | 8 +++--- .../collection/ShadeListBuilderTest.java | 2 +- .../collection/render/NodeSpecBuilderTest.kt | 26 +++++++++++++----- .../NotificationSectionsManagerTest.java | 13 +++++---- 18 files changed, 116 insertions(+), 70 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index db7d5c113031b..856052e1a4d96 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -19,8 +19,8 @@ import static android.app.Notification.VISIBILITY_SECRET; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static com.android.systemui.DejankUtils.whitelistIpcs; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import android.app.ActivityManager; import android.app.KeyguardManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 94ee868ceebc3..66d019e778bde 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -31,7 +31,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING; import static java.util.Objects.requireNonNull; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 122fb1c2f6b89..3730524353ec4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -52,6 +52,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; +import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; import com.android.systemui.util.Assert; import com.android.systemui.util.time.SystemClock; @@ -738,7 +739,7 @@ public class ShadeListBuilder implements Dumpable { if (entry instanceof GroupEntry) { GroupEntry parent = (GroupEntry) entry; for (NotificationEntry child : parent.getChildren()) { - child.getAttachState().setSection(section); + setEntrySection(child, section); } parent.sortChildren(sChildComparator); } @@ -957,10 +958,18 @@ public class ShadeListBuilder implements Dumpable { } } - entry.getAttachState().setSection(finalSection); + setEntrySection(entry, finalSection); return finalSection; } + private void setEntrySection(ListEntry entry, NotifSection finalSection) { + entry.getAttachState().setSection(finalSection); + NotificationEntry representativeEntry = entry.getRepresentativeEntry(); + if (representativeEntry != null && finalSection != null) { + representativeEntry.setBucket(finalSection.getBucket()); + } + } + @NonNull private NotifSection findSection(ListEntry entry) { for (int i = 0; i < mNotifSections.size(); i++) { @@ -1039,13 +1048,13 @@ public class ShadeListBuilder implements Dumpable { void onRenderList(@NonNull List entries); } - private static final NotifSectioner DEFAULT_SECTIONER = - new NotifSectioner("UnknownSection") { - @Override - public boolean isInSection(ListEntry entry) { - return true; - } - }; + private static final NotifSectioner DEFAULT_SECTIONER = new NotifSectioner("UnknownSection", + NotificationPriorityBucketKt.BUCKET_UNKNOWN) { + @Override + public boolean isInSection(ListEntry entry) { + return true; + } + }; private static final int MIN_CHILDREN_FOR_GROUP = 2; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java index 3a87f6853bcf5..301b185c73737 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java @@ -30,6 +30,7 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; +import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; import com.android.systemui.util.concurrency.DelayableExecutor; import javax.inject.Inject; @@ -102,7 +103,8 @@ public class AppOpsCoordinator implements Coordinator { /** * Puts foreground service notifications into its own section. */ - private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService") { + private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService", + NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) { @Override public boolean isInSection(ListEntry entry) { NotificationEntry notificationEntry = entry.getRepresentativeEntry(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt index efec94f547dc6..c38583679b4f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt @@ -26,6 +26,7 @@ import com.android.systemui.statusbar.notification.collection.render.NodeControl import com.android.systemui.statusbar.notification.dagger.PeopleHeader import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON +import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE import javax.inject.Inject /** @@ -45,7 +46,7 @@ class ConversationCoordinator @Inject constructor( } } - val sectioner = object : NotifSectioner("People") { + val sectioner = object : NotifSectioner("People", BUCKET_PEOPLE) { override fun isInSection(entry: ListEntry): Boolean = isConversation(entry.representativeEntry!!) override fun getHeaderNodeController() = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java index 1bde312d02712..4d36251579bdf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java @@ -34,6 +34,7 @@ import com.android.systemui.statusbar.notification.collection.render.NodeControl import com.android.systemui.statusbar.notification.dagger.IncomingHeader; import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; +import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; @@ -196,7 +197,8 @@ public class HeadsUpCoordinator implements Coordinator { } }; - private final NotifSectioner mNotifSectioner = new NotifSectioner("HeadsUp") { + private final NotifSectioner mNotifSectioner = new NotifSectioner("HeadsUp", + NotificationPriorityBucketKt.BUCKET_HEADS_UP) { @Override public boolean isInSection(ListEntry entry) { return isCurrentlyShowingHun(entry); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java index c62214ca16fae..1a6a63a9ccb6e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -31,6 +31,7 @@ import com.android.systemui.statusbar.notification.collection.render.NodeControl import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import com.android.systemui.statusbar.notification.dagger.AlertingHeader; import com.android.systemui.statusbar.notification.dagger.SilentHeader; +import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; import java.util.List; @@ -82,7 +83,8 @@ public class RankingCoordinator implements Coordinator { return mSilentNotifSectioner; } - private final NotifSectioner mAlertingNotifSectioner = new NotifSectioner("Alerting") { + private final NotifSectioner mAlertingNotifSectioner = new NotifSectioner("Alerting", + NotificationPriorityBucketKt.BUCKET_ALERTING) { @Override public boolean isInSection(ListEntry entry) { return mHighPriorityProvider.isHighPriority(entry); @@ -99,7 +101,8 @@ public class RankingCoordinator implements Coordinator { } }; - private final NotifSectioner mSilentNotifSectioner = new NotifSectioner("Silent") { + private final NotifSectioner mSilentNotifSectioner = new NotifSectioner("Silent", + NotificationPriorityBucketKt.BUCKET_SILENT) { @Override public boolean isInSection(ListEntry entry) { return !mHighPriorityProvider.isHighPriority(entry); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt index c9fc9929f0d37..6424e37ad3282 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt @@ -18,14 +18,17 @@ package com.android.systemui.statusbar.notification.collection.listbuilder import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.render.NodeController +import com.android.systemui.statusbar.notification.stack.PriorityBucket data class NotifSection( val sectioner: NotifSectioner, val index: Int ) { val label: String - get() = "Section($index, \"${sectioner.name}\")" + get() = "Section($index, $bucket, \"${sectioner.name}\")" val headerController: NodeController? get() = sectioner.headerNodeController + + @PriorityBucket val bucket: Int = sectioner.bucket } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java index 58afca8043edd..ef9ee11ef116a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java @@ -22,15 +22,28 @@ import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; import com.android.systemui.statusbar.notification.collection.render.NodeController; import com.android.systemui.statusbar.notification.collection.render.NodeSpec; +import com.android.systemui.statusbar.notification.stack.PriorityBucket; import java.util.List; /** - * Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSections}. + * Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSectioners}. */ public abstract class NotifSectioner extends Pluggable { - protected NotifSectioner(String name) { + @PriorityBucket + private final int mBucket; + + protected NotifSectioner(String name, @PriorityBucket int bucket) { super(name); + mBucket = bucket; + } + + /** + * @return the "bucket" value to apply to entries in this section + */ + @PriorityBucket + public final int getBucket() { + return mBucket; } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java index c147023edf8d3..9faef1b43bc13 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java @@ -16,12 +16,12 @@ package com.android.systemui.statusbar.notification.logging; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_FOREGROUND_SERVICE; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_HEADS_UP; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import android.annotation.Nullable; import android.service.notification.StatusBarNotification; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt new file mode 100644 index 0000000000000..31f4857e4b045 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt @@ -0,0 +1,25 @@ +package com.android.systemui.statusbar.notification.stack + +import android.annotation.IntDef + +/** + * For now, declare the available notification buckets (sections) here so that other + * presentation code can decide what to do based on an entry's buckets + */ +@Retention(AnnotationRetention.SOURCE) +@IntDef( + prefix = ["BUCKET_"], + value = [ + BUCKET_UNKNOWN, BUCKET_MEDIA_CONTROLS, BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, + BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT + ] +) +annotation class PriorityBucket + +const val BUCKET_UNKNOWN = 0 +const val BUCKET_MEDIA_CONTROLS = 1 +const val BUCKET_HEADS_UP = 2 +const val BUCKET_FOREGROUND_SERVICE = 3 +const val BUCKET_PEOPLE = 4 +const val BUCKET_ALERTING = 5 +const val BUCKET_SILENT = 6 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java index ab39de0f9bc74..bc172ce537f36 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.stack; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt index 6ee14b53dd732..5f157a767c5e4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.stack import android.annotation.ColorInt -import android.annotation.IntDef import android.annotation.LayoutRes import android.util.Log import android.view.LayoutInflater @@ -448,25 +447,3 @@ class NotificationSectionsManager @Inject internal constructor( private const val DEBUG = false } } - -/** - * For now, declare the available notification buckets (sections) here so that other - * presentation code can decide what to do based on an entry's buckets - */ -@Retention(AnnotationRetention.SOURCE) -@IntDef( - prefix = ["BUCKET_"], - value = [ - BUCKET_UNKNOWN, BUCKET_MEDIA_CONTROLS, BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, - BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT - ] -) -annotation class PriorityBucket - -const val BUCKET_UNKNOWN = 0 -const val BUCKET_MEDIA_CONTROLS = 1 -const val BUCKET_HEADS_UP = 2 -const val BUCKET_FOREGROUND_SERVICE = 3 -const val BUCKET_PEOPLE = 4 -const val BUCKET_ALERTING = 5 -const val BUCKET_SILENT = 6 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 1f764255914b0..6aa5f0709accb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.stack; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; import static com.android.systemui.util.Utils.shouldUseSplitNotificationShade; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index ea21aa906ab14..23cca727335e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -20,10 +20,10 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.content.Intent.ACTION_USER_SWITCHED; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index c862f97681096..e9e6718f5a5c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -1660,7 +1660,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { private final String mPackage; PackageSectioner(String pkg) { - super("PackageSection_" + pkg); + super("PackageSection_" + pkg, 0); mPackage = pkg; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt index 2e676bbe65416..ed48452eccc73 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt @@ -26,6 +26,10 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.getAttachState import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING +import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE +import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT +import com.android.systemui.statusbar.notification.stack.PriorityBucket import com.android.systemui.util.mockito.any import org.junit.Before import org.junit.Test @@ -45,11 +49,15 @@ class NodeSpecBuilderTest : SysuiTestCase() { private var headerController1: NodeController = buildFakeController("header1") private var headerController2: NodeController = buildFakeController("header2") - private val section0 = buildSection(0, headerController0) - private val section0NoHeader = buildSection(0, null) - private val section1 = buildSection(1, headerController1) - private val section1NoHeader = buildSection(1, null) - private val section2 = buildSection(2, headerController2) + private val section0Bucket = BUCKET_PEOPLE + private val section1Bucket = BUCKET_ALERTING + private val section2Bucket = BUCKET_SILENT + + private val section0 = buildSection(0, section0Bucket, headerController0) + private val section0NoHeader = buildSection(0, section0Bucket, null) + private val section1 = buildSection(1, section1Bucket, headerController1) + private val section1NoHeader = buildSection(1, section1Bucket, null) + private val section2 = buildSection(2, section2Bucket, headerController2) private val fakeViewBarn = FakeViewBarn() @@ -297,8 +305,12 @@ private fun buildFakeController(name: String): NodeController { return controller } -private fun buildSection(index: Int, nodeController: NodeController?): NotifSection { - return NotifSection(object : NotifSectioner("Section $index") { +private fun buildSection( + index: Int, + @PriorityBucket bucket: Int, + nodeController: NodeController? +): NotifSection { + return NotifSection(object : NotifSectioner("Section $index (bucket=$bucket)", bucket) { override fun isInSection(entry: ListEntry?): Boolean { throw NotImplementedError("This should never be called") diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java index c1d2ea88a1b14..f11f8c476433f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java @@ -18,11 +18,11 @@ package com.android.systemui.statusbar.notification.stack; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_FOREGROUND_SERVICE; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_HEADS_UP; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import static com.google.common.truth.Truth.assertThat; @@ -64,7 +64,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Answers; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -608,7 +607,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { } } - private View mockNotification(int bucket, boolean isGone) { + private View mockNotification(@PriorityBucket int bucket, boolean isGone) { ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS); when(notifRow.getVisibility()).thenReturn(View.VISIBLE); From 6a9caed07f779d8ee825bbff5ea67f42f8e6f34a Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Wed, 6 Oct 2021 19:17:43 +0000 Subject: [PATCH 6/9] Add ListenerSet and use in places which currently copy-on-iterate. Fixes: 204127880 Test: atest ListenerSetTest Merged-In: Ic1320b4f6c424322451f7def11346865bf878f99 Change-Id: Ic1320b4f6c424322451f7def11346865bf878f99 --- .../statusbar/phone/KeyguardBouncer.java | 7 +- .../statusbar/policy/HeadsUpManager.java | 15 +- .../com/android/systemui/util/ListenerSet.kt | 47 ++++++ .../android/systemui/util/ListenerSetTest.kt | 135 ++++++++++++++++++ 4 files changed, 193 insertions(+), 11 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index b9b663c33a452..353868ba969f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -45,6 +45,7 @@ import com.android.systemui.dagger.qualifiers.RootView; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.ListenerSet; import java.io.PrintWriter; import java.util.ArrayList; @@ -84,11 +85,11 @@ public class KeyguardBouncer { private final Runnable mRemoveViewRunnable = this::removeView; private final KeyguardBypassController mKeyguardBypassController; private KeyguardHostViewController mKeyguardViewController; - private final List mResetCallbacks = new ArrayList<>(); + private final ListenerSet mResetCallbacks = new ListenerSet<>(); private final Runnable mResetRunnable = ()-> { if (mKeyguardViewController != null) { mKeyguardViewController.resetSecurityContainer(); - for (KeyguardResetCallback callback : new ArrayList<>(mResetCallbacks)) { + for (KeyguardResetCallback callback : mResetCallbacks) { callback.onKeyguardReset(); } } @@ -602,7 +603,7 @@ public class KeyguardBouncer { } public void addKeyguardResetCallback(KeyguardResetCallback callback) { - mResetCallbacks.add(callback); + mResetCallbacks.addIfAbsent(callback); } public void removeKeyguardResetCallback(KeyguardResetCallback callback) { 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 a8097c4d74b04..e0b0dd36ccd93 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -38,11 +38,10 @@ import com.android.systemui.R; import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; +import com.android.systemui.util.ListenerSet; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashSet; /** * A manager which handles heads up notifications which is a special mode where @@ -52,7 +51,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { private static final String TAG = "HeadsUpManager"; private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms"; - protected final HashSet mListeners = new HashSet<>(); + protected final ListenerSet mListeners = new ListenerSet<>(); protected final Context mContext; @@ -118,7 +117,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { * Adds an OnHeadUpChangedListener to observe events. */ public void addListener(@NonNull OnHeadsUpChangedListener listener) { - mListeners.add(listener); + mListeners.addIfAbsent(listener); } /** @@ -158,7 +157,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(), entry.getSbn().getPackageName(), entry.getSbn().getInstanceId()); } - for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { + for (OnHeadsUpChangedListener listener : mListeners) { if (isPinned) { listener.onHeadsUpPinned(entry); } else { @@ -178,7 +177,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { entry.setHeadsUp(true); setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(entry)); EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */); - for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { + for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, true); } } @@ -189,7 +188,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { entry.setHeadsUp(false); setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */); EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */); - for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { + for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, false); } } @@ -207,7 +206,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { if (mHasPinnedNotification) { MetricsLogger.count(mContext, "note_peek", 1); } - for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { + for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpPinnedModeChanged(hasPinnedNotification); } } diff --git a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt new file mode 100644 index 0000000000000..0f4193e941968 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 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.util + +import java.util.concurrent.CopyOnWriteArrayList + +/** + * A collection of listeners, observers, callbacks, etc. + * + * This container is optimized for infrequent mutation and frequent iteration, with thread safety + * and reentrant-safety guarantees as well. + */ +class ListenerSet : Iterable { + private val listeners: CopyOnWriteArrayList = CopyOnWriteArrayList() + + /** + * A thread-safe, reentrant-safe method to add a listener. + * Does nothing if the listener is already in the set. + */ + fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(element) + + /** + * A thread-safe, reentrant-safe method to remove a listener. + */ + fun remove(element: E): Boolean = listeners.remove(element) + + /** + * Returns an iterator over the listeners currently in the set. Note that to ensure + * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes + * made to the set after the iterator is constructed. + */ + override fun iterator(): Iterator = listeners.iterator() +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt new file mode 100644 index 0000000000000..2662da201460c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2021 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.util + +import android.test.suitebuilder.annotation.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ListenerSetTest : SysuiTestCase() { + + var runnableSet: ListenerSet = ListenerSet() + + @Before + fun setup() { + runnableSet = ListenerSet() + } + + @Test + fun addIfAbsent_doesNotDoubleAdd() { + // setup & preconditions + val runnable1 = Runnable { } + val runnable2 = Runnable { } + assertThat(runnableSet.toList()).isEmpty() + + // Test that an element can be added + assertThat(runnableSet.addIfAbsent(runnable1)).isTrue() + assertThat(runnableSet.toList()).containsExactly(runnable1) + + // Test that a second element can be added + assertThat(runnableSet.addIfAbsent(runnable2)).isTrue() + assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) + + // Test that re-adding the first element does nothing and returns false + assertThat(runnableSet.addIfAbsent(runnable1)).isFalse() + assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) + } + + @Test + fun remove_removesListener() { + // setup and preconditions + val runnable1 = Runnable { } + val runnable2 = Runnable { } + assertThat(runnableSet.toList()).isEmpty() + runnableSet.addIfAbsent(runnable1) + runnableSet.addIfAbsent(runnable2) + assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) + + // Test that removing the first runnable only removes that one runnable + assertThat(runnableSet.remove(runnable1)).isTrue() + assertThat(runnableSet.toList()).containsExactly(runnable2) + + // Test that removing a non-present runnable does not error + assertThat(runnableSet.remove(runnable1)).isFalse() + assertThat(runnableSet.toList()).containsExactly(runnable2) + + // Test that removing the other runnable succeeds + assertThat(runnableSet.remove(runnable2)).isTrue() + assertThat(runnableSet.toList()).isEmpty() + } + + @Test + fun remove_isReentrantSafe() { + // Setup and preconditions + val runnablesCalled = mutableListOf() + // runnable1 is configured to remove itself + val runnable1 = object : Runnable { + override fun run() { + runnableSet.remove(this) + runnablesCalled.add(1) + } + } + val runnable2 = Runnable { + runnablesCalled.add(2) + } + assertThat(runnableSet.toList()).isEmpty() + runnableSet.addIfAbsent(runnable1) + runnableSet.addIfAbsent(runnable2) + assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) + + // Test that both runnables are called and 1 was removed + for (runnable in runnableSet) { + runnable.run() + } + assertThat(runnablesCalled).containsExactly(1, 2) + assertThat(runnableSet.toList()).containsExactly(runnable2) + } + + @Test + fun addIfAbsent_isReentrantSafe() { + // Setup and preconditions + val runnablesCalled = mutableListOf() + val runnable99 = Runnable { + runnablesCalled.add(99) + } + // runnable1 is configured to add runnable99 + val runnable1 = Runnable { + runnableSet.addIfAbsent(runnable99) + runnablesCalled.add(1) + } + val runnable2 = Runnable { + runnablesCalled.add(2) + } + assertThat(runnableSet.toList()).isEmpty() + runnableSet.addIfAbsent(runnable1) + runnableSet.addIfAbsent(runnable2) + assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) + + // Test that both original runnables are called and 99 was added but not called + for (runnable in runnableSet) { + runnable.run() + } + assertThat(runnablesCalled).containsExactly(1, 2) + assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2, runnable99) + } +} \ No newline at end of file From f7ec42ff8d1835c696339be21f8c8faf9d457bd2 Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Tue, 28 Sep 2021 20:33:11 +0000 Subject: [PATCH 7/9] New Pipeline: Implement lifetime extension for guts Fixes: 204127880 Bug: 201413978 Test: GutsCoordinatorTest Merged-In: Icaeb83fc41bf92328e66b3a9a6ecc5ba23e515d9 Change-Id: Icaeb83fc41bf92328e66b3a9a6ecc5ba23e515d9 --- .../collection/NotifCollection.java | 4 +- .../collection/coordinator/GutsCoordinator.kt | 126 ++++++++++++++++++ .../coordinator/GutsCoordinatorLogger.kt | 32 +++++ .../coordinator/HeadsUpCoordinator.java | 11 +- .../coordinator/NotifCoordinators.java | 2 + .../NotifLifetimeExtender.java | 14 +- .../render/NotifGutsViewListener.kt | 30 +++++ .../collection/render/NotifGutsViewManager.kt | 24 ++++ .../dagger/NotificationsModule.java | 9 ++ .../row/NotificationGutsManager.java | 30 ++++- .../collection/NotifCollectionTest.java | 8 +- .../coordinator/GutsCoordinatorTest.kt | 117 ++++++++++++++++ 12 files changed, 386 insertions(+), 21 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 277b4acb32370..dfdc548ea35e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -523,7 +523,9 @@ public class NotifCollection implements Dumpable { } } - private void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry) { + private void onEndLifetimeExtension( + @NonNull NotifLifetimeExtender extender, + @NonNull NotificationEntry entry) { Assert.isMainThread(); if (!mAttached) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt new file mode 100644 index 0000000000000..8948969120ace --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2021 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.collection.coordinator + +import android.util.ArraySet +import com.android.systemui.Dumpable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback +import com.android.systemui.statusbar.notification.row.NotificationGuts +import com.android.systemui.statusbar.notification.row.NotificationGutsManager +import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener +import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager +import java.io.FileDescriptor +import java.io.PrintWriter +import javax.inject.Inject + +private const val TAG = "GutsCoordinator" + +/** + * Coordinates the guts displayed by the [NotificationGutsManager] with the pipeline. + * Specifically, this just adds the lifetime extension necessary to keep guts from disappearing. + */ +@SysUISingleton +class GutsCoordinator @Inject constructor( + private val notifGutsViewManager: NotifGutsViewManager, + private val logger: GutsCoordinatorLogger, + dumpManager: DumpManager +) : Coordinator, Dumpable { + + /** Keys of any Notifications for which we've been told the guts are open */ + private val notifsWithOpenGuts = ArraySet() + + /** Keys of any Notifications we've extended the lifetime for, based on guts */ + private val notifsExtendingLifetime = ArraySet() + + /** Callback for ending lifetime extension */ + private var onEndLifetimeExtensionCallback: OnEndLifetimeExtensionCallback? = null + + init { + dumpManager.registerDumpable(TAG, this) + } + + override fun attach(pipeline: NotifPipeline) { + notifGutsViewManager.setGutsListener(mGutsListener) + pipeline.addNotificationLifetimeExtender(mLifetimeExtender) + } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array) { + pw.println(" notifsWithOpenGuts: ${notifsWithOpenGuts.size}") + for (key in notifsWithOpenGuts) { + pw.println(" * $key") + } + pw.println(" notifsExtendingLifetime: ${notifsExtendingLifetime.size}") + for (key in notifsExtendingLifetime) { + pw.println(" * $key") + } + pw.println(" onEndLifetimeExtensionCallback: $onEndLifetimeExtensionCallback") + } + + private val mLifetimeExtender: NotifLifetimeExtender = object : NotifLifetimeExtender { + override fun getName(): String { + return TAG + } + + override fun setCallback(callback: OnEndLifetimeExtensionCallback) { + onEndLifetimeExtensionCallback = callback + } + + override fun shouldExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { + val isShowingGuts = isCurrentlyShowingGuts(entry) + if (isShowingGuts) { + notifsExtendingLifetime.add(entry.key) + } + return isShowingGuts + } + + override fun cancelLifetimeExtension(entry: NotificationEntry) { + notifsExtendingLifetime.remove(entry.key) + } + } + + private val mGutsListener: NotifGutsViewListener = object : NotifGutsViewListener { + override fun onGutsOpen(entry: NotificationEntry, guts: NotificationGuts) { + logger.logGutsOpened(entry.key, guts) + if (guts.isLeavebehind) { + // leave-behind guts should not extend the lifetime of the notification + closeGutsAndEndLifetimeExtension(entry) + } else { + notifsWithOpenGuts.add(entry.key) + } + } + + override fun onGutsClose(entry: NotificationEntry) { + logger.logGutsClosed(entry.key) + closeGutsAndEndLifetimeExtension(entry) + } + } + + private fun isCurrentlyShowingGuts(entry: ListEntry) = + notifsWithOpenGuts.contains(entry.key) + + private fun closeGutsAndEndLifetimeExtension(entry: NotificationEntry) { + notifsWithOpenGuts.remove(entry.key) + if (notifsExtendingLifetime.remove(entry.key)) { + onEndLifetimeExtensionCallback?.onEndLifetimeExtension(mLifetimeExtender, entry) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt new file mode 100644 index 0000000000000..bac5223c1de7a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt @@ -0,0 +1,32 @@ +package com.android.systemui.statusbar.notification.collection.coordinator + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.statusbar.notification.row.NotificationGuts +import javax.inject.Inject + +private const val TAG = "GutsCoordinator" + +class GutsCoordinatorLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + + fun logGutsOpened(key: String, guts: NotificationGuts) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = key + str2 = guts::class.simpleName + bool1 = guts.isLeavebehind + }, { + "Guts of type $str2 (leave behind: $bool1) opened for class $str1" + }) + } + + fun logGutsClosed(key: String) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = key + }, { + "Guts closed for class $str1" + }) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java index 4d36251579bdf..8c8a8a977d1c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java @@ -19,7 +19,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; import static com.android.systemui.statusbar.notification.interruption.HeadsUpController.alertAgain; -import android.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -164,17 +165,17 @@ public class HeadsUpCoordinator implements Coordinator { private final NotifLifetimeExtender mLifetimeExtender = new NotifLifetimeExtender() { @Override - public String getName() { + public @NonNull String getName() { return TAG; } @Override - public void setCallback(OnEndLifetimeExtensionCallback callback) { + public void setCallback(@NonNull OnEndLifetimeExtensionCallback callback) { mEndLifetimeExtension = callback; } @Override - public boolean shouldExtendLifetime(NotificationEntry entry, int reason) { + public boolean shouldExtendLifetime(@NonNull NotificationEntry entry, int reason) { boolean isShowingHun = isCurrentlyShowingHun(entry); if (isShowingHun) { mNotifExtendingLifetime = entry; @@ -183,7 +184,7 @@ public class HeadsUpCoordinator implements Coordinator { } @Override - public void cancelLifetimeExtension(NotificationEntry entry) { + public void cancelLifetimeExtension(@NonNull NotificationEntry entry) { if (Objects.equals(mNotifExtendingLifetime, entry)) { mNotifExtendingLifetime = null; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java index 25b2019262395..f132703660dd0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java @@ -57,6 +57,7 @@ public class NotifCoordinators implements Dumpable { DeviceProvisionedCoordinator deviceProvisionedCoordinator, BubbleCoordinator bubbleCoordinator, HeadsUpCoordinator headsUpCoordinator, + GutsCoordinator gutsCoordinator, ConversationCoordinator conversationCoordinator, PreparationCoordinator preparationCoordinator, MediaCoordinator mediaCoordinator, @@ -81,6 +82,7 @@ public class NotifCoordinators implements Dumpable { if (featureFlags.isNewNotifPipelineRenderingEnabled()) { mCoordinators.add(headsUpCoordinator); + mCoordinators.add(gutsCoordinator); mCoordinators.add(preparationCoordinator); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java index f8fe0676e0038..2fe3bd63c2e6c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection.notifcollection; +import androidx.annotation.NonNull; + import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -26,14 +28,14 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; */ public interface NotifLifetimeExtender { /** Name to associate with this extender (for the purposes of debugging) */ - String getName(); + @NonNull String getName(); /** * Called on the extender immediately after it has been registered. The extender should hang on * to this callback and execute it whenever it no longer needs to extend the lifetime of a * notification. */ - void setCallback(OnEndLifetimeExtensionCallback callback); + void setCallback(@NonNull OnEndLifetimeExtensionCallback callback); /** * Called by the NotifCollection whenever a notification has been retracted (by the app) or @@ -43,7 +45,7 @@ public interface NotifLifetimeExtender { * called on all lifetime extenders even if earlier ones return true (in other words, multiple * lifetime extenders can be extending a notification at the same time). */ - boolean shouldExtendLifetime(NotificationEntry entry, @CancellationReason int reason); + boolean shouldExtendLifetime(@NonNull NotificationEntry entry, @CancellationReason int reason); /** * Called by the NotifCollection to inform a lifetime extender that its extension of a notif @@ -51,7 +53,7 @@ public interface NotifLifetimeExtender { * lifetime extension). The extender should clean up any references it has to the notif in * question. */ - void cancelLifetimeExtension(NotificationEntry entry); + void cancelLifetimeExtension(@NonNull NotificationEntry entry); /** Callback for notifying the NotifCollection that a lifetime extension has expired.*/ interface OnEndLifetimeExtensionCallback { @@ -59,6 +61,8 @@ public interface NotifLifetimeExtender { * Stop extending the lifetime of `entry` with `extender` and then immediately re-evaluates * whether to continue lifetime extending this notification or to remove it. */ - void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry); + void onEndLifetimeExtension( + @NonNull NotifLifetimeExtender extender, + @NonNull NotificationEntry entry); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt new file mode 100644 index 0000000000000..129f6b1750e69 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 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.collection.render + +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.row.NotificationGuts + +/** + * Interface for listening to guts open and close events. + */ +interface NotifGutsViewListener { + /** A notification's guts are being opened */ + fun onGutsOpen(entry: NotificationEntry, guts: NotificationGuts) + + /** A notification's guts are being closed */ + fun onGutsClose(entry: NotificationEntry) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt new file mode 100644 index 0000000000000..0260f89110f82 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 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.collection.render + +/** A type which provides open and close guts events to a single listener */ +interface NotifGutsViewManager { + /** + * @param listener the object that will listen to open and close guts events + */ + fun setGutsListener(listener: NotifGutsViewListener?) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index 94f5c44d7c78c..dfa1f5f81696a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -58,6 +58,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl; +import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl; import com.android.systemui.statusbar.notification.init.NotificationsControllerStub; @@ -169,6 +170,14 @@ public interface NotificationsModule { dumpManager); } + /** Provides an instance of {@link NotifGutsViewManager} */ + @SysUISingleton + @Provides + static NotifGutsViewManager provideNotifGutsViewManager( + NotificationGutsManager notificationGutsManager) { + return notificationGutsManager; + } + /** Provides an instance of {@link VisualStabilityManager} */ @SysUISingleton @Provides diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 7eec95acc6ec6..8e02d9f635d33 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -65,6 +65,8 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; +import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener; +import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager; import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -83,7 +85,8 @@ import dagger.Lazy; * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and * closing guts, and keeping track of the currently exposed notification guts. */ -public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender { +public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender, + NotifGutsViewManager { private static final String TAG = "NotificationGutsManager"; // Must match constant in Settings. Used to highlight preferences when linking to Settings. @@ -123,7 +126,6 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx private final Optional mBubblesManagerOptional; private Runnable mOpenRunnable; private final INotificationManager mNotificationManager; - private final NotificationEntryManager mNotificationEntryManager; private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager; private final LauncherApps mLauncherApps; private final ShortcutManager mShortcutManager; @@ -131,6 +133,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx private final UiEventLogger mUiEventLogger; private final ShadeController mShadeController; private final AppWidgetManager mAppWidgetManager; + private NotifGutsViewListener mGutsListener; /** * Injected constructor. See {@link NotificationsModule}. @@ -161,7 +164,6 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx mAccessibilityManager = accessibilityManager; mHighPriorityProvider = highPriorityProvider; mNotificationManager = notificationManager; - mNotificationEntryManager = notificationEntryManager; mPeopleSpaceWidgetManager = peopleSpaceWidgetManager; mLauncherApps = launcherApps; mShortcutManager = shortcutManager; @@ -258,10 +260,10 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx @VisibleForTesting protected boolean bindGuts(final ExpandableNotificationRow row, NotificationMenuRowPlugin.MenuItem item) { - StatusBarNotification sbn = row.getEntry().getSbn(); + NotificationEntry entry = row.getEntry(); row.setGutsView(item); - row.setTag(sbn.getPackageName()); + row.setTag(entry.getSbn().getPackageName()); row.getGuts().setClosedListener((NotificationGuts g) -> { row.onGutsClosed(); if (!g.willBeRemoved() && !row.isRemoved()) { @@ -272,7 +274,10 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx mNotificationGutsExposed = null; mGutsMenuItem = null; } - String key = sbn.getKey(); + if (mGutsListener != null) { + mGutsListener.onGutsClose(entry); + } + String key = entry.getKey(); if (key.equals(mKeyToRemoveOnGutsClosed)) { mKeyToRemoveOnGutsClosed = null; if (mNotificationLifetimeFinishedCallback != null) { @@ -650,6 +655,10 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx needsFalsingProtection, row::onGutsOpened); + if (mGutsListener != null) { + mGutsListener.onGutsOpen(row.getEntry(), guts); + } + row.closeRemoteInput(); mListContainer.onHeightChanged(row, true /* needsAnimation */); mGutsMenuItem = menuItem; @@ -695,10 +704,17 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("NotificationGutsManager state:"); - pw.print(" mKeyToRemoveOnGutsClosed: "); + pw.print(" mKeyToRemoveOnGutsClosed (legacy): "); pw.println(mKeyToRemoveOnGutsClosed); } + /** + * @param gutsListener the listener for open and close guts events + */ + public void setGutsListener(NotifGutsViewListener gutsListener) { + mGutsListener = gutsListener; + } + public interface OnSettingsClickListener { public void onSettingsClick(String key); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 39d794dc0bd96..ebeb59177397c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -60,6 +60,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; +import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; @@ -1405,25 +1406,26 @@ public class NotifCollectionTest extends SysuiTestCase { mName = name; } + @NonNull @Override public String getName() { return mName; } @Override - public void setCallback(OnEndLifetimeExtensionCallback callback) { + public void setCallback(@NonNull OnEndLifetimeExtensionCallback callback) { this.callback = callback; } @Override public boolean shouldExtendLifetime( - NotificationEntry entry, + @NonNull NotificationEntry entry, @CancellationReason int reason) { return shouldExtendLifetime; } @Override - public void cancelLifetimeExtension(NotificationEntry entry) { + public void cancelLifetimeExtension(@NonNull NotificationEntry entry) { if (onCancelLifetimeExtension != null) { onCancelLifetimeExtension.run(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt new file mode 100644 index 0000000000000..0cba07033c63c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2021 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.collection.coordinator + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback +import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener +import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager +import com.android.systemui.statusbar.notification.row.NotificationGuts +import com.android.systemui.util.mockito.argumentCaptor +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations.initMocks + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class GutsCoordinatorTest : SysuiTestCase() { + private lateinit var coordinator: GutsCoordinator + private lateinit var notifLifetimeExtender: NotifLifetimeExtender + private lateinit var notifGutsViewListener: NotifGutsViewListener + + private lateinit var entry1: NotificationEntry + private lateinit var entry2: NotificationEntry + + @Mock private lateinit var notifGutsViewManager: NotifGutsViewManager + @Mock private lateinit var pipeline: NotifPipeline + @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var logger: GutsCoordinatorLogger + @Mock private lateinit var lifetimeExtenderCallback: OnEndLifetimeExtensionCallback + + @Before + fun setUp() { + initMocks(this) + coordinator = GutsCoordinator(notifGutsViewManager, logger, dumpManager) + coordinator.attach(pipeline) + notifLifetimeExtender = argumentCaptor().let { + verify(pipeline).addNotificationLifetimeExtender(it.capture()) + it.value!! + } + notifGutsViewListener = argumentCaptor().let { + verify(notifGutsViewManager).setGutsListener(it.capture()) + it.value!! + } + notifLifetimeExtender.setCallback(lifetimeExtenderCallback) + entry1 = NotificationEntryBuilder().setId(1).build() + entry2 = NotificationEntryBuilder().setId(2).build() + } + + @Test + fun testSimpleLifetimeExtension() { + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() + notifGutsViewListener.onGutsClose(entry1) + verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + } + + @Test + fun testDoubleOpenLifetimeExtension() { + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() + notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() + notifGutsViewListener.onGutsClose(entry1) + verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + } + + @Test + fun testTwoEntryLifetimeExtension() { + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse() + notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse() + notifGutsViewListener.onGutsOpen(entry2, mock(NotificationGuts::class.java)) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isTrue() + notifGutsViewListener.onGutsClose(entry1) + verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isTrue() + notifGutsViewListener.onGutsClose(entry2) + verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry2) + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse() + } +} From 4d9465b028030cd687ba3c942f5c235024e6deda Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Sat, 23 Oct 2021 01:41:17 +0000 Subject: [PATCH 8/9] Make FeatureFlagManager Dumpable Also direct developers who find FeatureFlagManager in the src/ tree to the src-debug/ version Fixes: 204127880 Bug: 203904245 Test: adb shell dumpsys activity service com.android.systemui Test: FeatureFlagManagerTest Merged-In: I6a42a697120179048e848f7f432fd753db514b9c Change-Id: I5bec46e60befaa1890b9ff4d4bb5e89b0ebba256 --- .../systemui/flags/FeatureFlagManager.java | 25 ++++++++++- .../systemui/flags/FeatureFlagManager.java | 29 ++++++++++++- .../flags/FeatureFlagManagerTest.java | 41 ++++++++++++++++++- 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java index 5b6845fcdb4fa..3a8ee2997025d 100644 --- a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java +++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java @@ -23,11 +23,18 @@ import android.content.IntentFilter; import android.os.Bundle; import android.util.Log; +import androidx.annotation.NonNull; + +import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dump.DumpManager; import org.json.JSONException; import org.json.JSONObject; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -43,7 +50,7 @@ import javax.inject.Inject; * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command. */ @SysUISingleton -public class FeatureFlagManager implements FlagReader, FlagWriter { +public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable { private static final String TAG = "SysUIFlags"; private static final String SYSPROP_PREFIX = "persist.systemui.flag_"; @@ -58,11 +65,13 @@ public class FeatureFlagManager implements FlagReader, FlagWriter { private final Map mBooleanFlagCache = new HashMap<>(); @Inject - public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper, Context context) { + public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper, Context context, + DumpManager dumpManager) { mSystemPropertiesHelper = systemPropertiesHelper; IntentFilter filter = new IntentFilter(ACTION_SET_FLAG); context.registerReceiver(mReceiver, filter, FLAGS_PERMISSION, null); + dumpManager.registerDumpable(TAG, this); } /** Return a {@link BooleanFlag}'s value. */ @@ -186,4 +195,16 @@ public class FeatureFlagManager implements FlagReader, FlagWriter { } } }; + + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + ArrayList flagStrings = new ArrayList<>(mBooleanFlagCache.size()); + for (Map.Entry entry : mBooleanFlagCache.entrySet()) { + flagStrings.add(" sysui_flag_" + entry.getKey() + ": " + entry.getValue()); + } + flagStrings.sort(String.CASE_INSENSITIVE_ORDER); + for (String flagString : flagStrings) { + pw.println(flagString); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java index 85baed4a221ca..78f0b5f5196d2 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java @@ -16,23 +16,48 @@ package com.android.systemui.flags; +import android.util.SparseBooleanArray; + +import androidx.annotation.NonNull; + +import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dump.DumpManager; + +import java.io.FileDescriptor; +import java.io.PrintWriter; import javax.inject.Inject; /** * Default implementation of the a Flag manager that returns default values for release builds + * + * There's a version of this file in src-debug which allows overriding, and has documentation about + * how to set flags. */ @SysUISingleton -public class FeatureFlagManager implements FlagReader, FlagWriter { +public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable { + SparseBooleanArray mAccessedFlags = new SparseBooleanArray(); @Inject - public FeatureFlagManager() {} + public FeatureFlagManager(DumpManager dumpManager) { + dumpManager.registerDumpable("SysUIFlags", this); + } public boolean isEnabled(String key, boolean defaultValue) { return defaultValue; } public boolean isEnabled(int key, boolean defaultValue) { + mAccessedFlags.append(key, defaultValue); return defaultValue; } public void setEnabled(String key, boolean value) {} public void setEnabled(int key, boolean value) {} + + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + int size = mAccessedFlags.size(); + for (int i = 0; i < size; i++) { + pw.println(" sysui_flag_" + mAccessedFlags.keyAt(i) + + ": " + mAccessedFlags.valueAt(i)); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java index 172dcda5321fd..b3c098c37dbaa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java @@ -18,23 +18,35 @@ package com.android.systemui.flags; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.dump.DumpManager; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; + @SmallTest public class FeatureFlagManagerTest extends SysuiTestCase { FeatureFlagManager mFeatureFlagManager; + @Mock private DumpManager mDumpManager; + @Before public void setup() { MockitoAnnotations.initMocks(this); - mFeatureFlagManager = new FeatureFlagManager(); + mFeatureFlagManager = new FeatureFlagManager(mDumpManager); } @Test @@ -43,4 +55,31 @@ public class FeatureFlagManagerTest extends SysuiTestCase { // Again, nothing changes. assertThat(mFeatureFlagManager.isEnabled(1, false)).isFalse(); } + + @Test + public void testDump() { + // Even if a flag is set before + mFeatureFlagManager.setEnabled(1, true); + + // WHEN the flags have been accessed + assertFalse(mFeatureFlagManager.isEnabled(1, false)); + assertTrue(mFeatureFlagManager.isEnabled(2, true)); + + // Even if a flag is set after + mFeatureFlagManager.setEnabled(2, false); + + // THEN the dump contains the flags and the default values + String dump = dumpToString(); + assertThat(dump).contains(" sysui_flag_1: false\n"); + assertThat(dump).contains(" sysui_flag_2: true\n"); + } + + private String dumpToString() { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + mFeatureFlagManager.dump(mock(FileDescriptor.class), pw, new String[0]); + pw.flush(); + String dump = sw.toString(); + return dump; + } } From ddfd237df03d79ec9d626563c880e7136eb730ce Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Tue, 28 Sep 2021 16:27:26 +0000 Subject: [PATCH 9/9] New pipeline: Fix notification re-inflation on dark mode change. Fixes: 204127880 Bug: 201329618 Bug: 201413798 Test: tap dark mode QS tile Test: atest PreparationCoordinatorTest ShadeEventCoordinatorTest Merged-In: I05931f89596b4145f2c5d31cb32b2d451fc280b6 Change-Id: I05931f89596b4145f2c5d31cb32b2d451fc280b6 --- .../coordinator/GutsCoordinatorLogger.kt | 2 +- .../coordinator/NotifCoordinators.java | 4 + .../coordinator/ShadeEventCoordinator.kt | 78 +++++++++++++ .../ShadeEventCoordinatorLogger.kt | 38 ++++++ .../coordinator/ViewConfigCoordinator.kt | 109 ++++++++++++++++++ ...LegacyNotificationPresenterExtensions.java | 100 ++++++++++++++++ .../render/NotifShadeEventSource.kt | 35 ++++++ .../dagger/NotificationsModule.java | 19 ++- .../row/ExpandableNotificationRow.java | 1 + .../systemui/statusbar/phone/StatusBar.java | 6 + .../phone/StatusBarNotificationPresenter.java | 83 +++++++------ .../phone/dagger/StatusBarPhoneModule.java | 3 + .../coordinator/ShadeEventCoordinatorTest.kt | 106 +++++++++++++++++ .../StatusBarNotificationPresenterTest.java | 7 +- .../statusbar/phone/StatusBarTest.java | 3 + 15 files changed, 548 insertions(+), 46 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt index bac5223c1de7a..e8f352f60da02 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt @@ -15,7 +15,7 @@ class GutsCoordinatorLogger @Inject constructor( fun logGutsOpened(key: String, guts: NotificationGuts) { buffer.log(TAG, LogLevel.DEBUG, { str1 = key - str2 = guts::class.simpleName + str2 = guts.gutsContent::class.simpleName bool1 = guts.isLeavebehind }, { "Guts of type $str2 (leave behind: $bool1) opened for class $str1" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java index f132703660dd0..93059009a56ee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java @@ -61,7 +61,9 @@ public class NotifCoordinators implements Dumpable { ConversationCoordinator conversationCoordinator, PreparationCoordinator preparationCoordinator, MediaCoordinator mediaCoordinator, + ShadeEventCoordinator shadeEventCoordinator, SmartspaceDedupingCoordinator smartspaceDedupingCoordinator, + ViewConfigCoordinator viewConfigCoordinator, VisualStabilityCoordinator visualStabilityCoordinator) { dumpManager.registerDumpable(TAG, this); @@ -74,6 +76,8 @@ public class NotifCoordinators implements Dumpable { mCoordinators.add(bubbleCoordinator); mCoordinators.add(conversationCoordinator); mCoordinators.add(mediaCoordinator); + mCoordinators.add(shadeEventCoordinator); + mCoordinators.add(viewConfigCoordinator); mCoordinators.add(visualStabilityCoordinator); if (featureFlags.isSmartspaceDedupingEnabled()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt new file mode 100644 index 0000000000000..f9648a0b94a3f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021 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.collection.coordinator + +import android.service.notification.NotificationListenerService +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource +import javax.inject.Inject + +/** + * A coordinator which provides callbacks to a view surfaces for various events relevant to the + * shade, such as when the user removes a notification, or when the shade is emptied. + */ +@SysUISingleton +class ShadeEventCoordinator @Inject internal constructor( + private val mLogger: ShadeEventCoordinatorLogger +) : Coordinator, NotifShadeEventSource { + private var mNotifRemovedByUserCallback: Runnable? = null + private var mShadeEmptiedCallback: Runnable? = null + private var mEntryRemoved = false + private var mEntryRemovedByUser = false + + override fun attach(pipeline: NotifPipeline) { + pipeline.addCollectionListener(mNotifCollectionListener) + pipeline.addOnBeforeRenderListListener(this::onBeforeRenderList) + } + + private val mNotifCollectionListener = object : NotifCollectionListener { + override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { + mEntryRemoved = true + mEntryRemovedByUser = + reason == NotificationListenerService.REASON_CLICK || + reason == NotificationListenerService.REASON_CANCEL_ALL || + reason == NotificationListenerService.REASON_CANCEL + } + } + + override fun setNotifRemovedByUserCallback(callback: Runnable) { + check(mNotifRemovedByUserCallback == null) { "mNotifRemovedByUserCallback already set" } + mNotifRemovedByUserCallback = callback + } + + override fun setShadeEmptiedCallback(callback: Runnable) { + check(mShadeEmptiedCallback == null) { "mShadeEmptiedCallback already set" } + mShadeEmptiedCallback = callback + } + + private fun onBeforeRenderList(entries: List) { + if (mEntryRemoved && entries.isEmpty()) { + mLogger.logShadeEmptied() + mShadeEmptiedCallback?.run() + } + if (mEntryRemoved && mEntryRemovedByUser) { + mLogger.logNotifRemovedByUser() + mNotifRemovedByUserCallback?.run() + } + mEntryRemoved = false + mEntryRemovedByUser = false + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt new file mode 100644 index 0000000000000..c687e1bacbc9c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 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.collection.coordinator + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.NotificationLog +import javax.inject.Inject + +private const val TAG = "ShadeEventCoordinator" + +/** Logger for the [ShadeEventCoordinator] */ +class ShadeEventCoordinatorLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + + fun logShadeEmptied() { + buffer.log(TAG, LogLevel.DEBUG, { }, { "Shade emptied" }) + } + + fun logNotifRemovedByUser() { + buffer.log(TAG, LogLevel.DEBUG, { }, { "Notification removed by user" }) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt new file mode 100644 index 0000000000000..df1132b20a4b9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2021 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.collection.coordinator + +import com.android.internal.widget.MessagingGroup +import com.android.internal.widget.MessagingMessage +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener +import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.row.NotificationGutsManager +import com.android.systemui.statusbar.policy.ConfigurationController +import javax.inject.Inject + +/** + * A coordinator which ensures that notifications within the new pipeline are correctly inflated + * for the current uiMode and screen properties; additionally deferring those changes when a user + * change is in progress until that process has completed. + */ +@SysUISingleton +class ViewConfigCoordinator @Inject internal constructor( + configurationController: ConfigurationController, + lockscreenUserManager: NotificationLockscreenUserManagerImpl, + featureFlags: FeatureFlags, + private val mGutsManager: NotificationGutsManager, + private val mKeyguardUpdateMonitor: KeyguardUpdateMonitor +) : Coordinator, UserChangedListener, ConfigurationController.ConfigurationListener { + + private var mReinflateNotificationsOnUserSwitched = false + private var mDispatchUiModeChangeOnUserSwitched = false + private var mPipeline: NotifPipeline? = null + + init { + if (featureFlags.isNewNotifPipelineRenderingEnabled) { + lockscreenUserManager.addUserChangedListener(this) + configurationController.addCallback(this) + } + } + + override fun attach(pipeline: NotifPipeline) { + mPipeline = pipeline + } + + override fun onDensityOrFontScaleChanged() { + MessagingMessage.dropCache() + MessagingGroup.dropCache() + if (!mKeyguardUpdateMonitor.isSwitchingUser) { + updateNotificationsOnDensityOrFontScaleChanged() + } else { + mReinflateNotificationsOnUserSwitched = true + } + } + + override fun onUiModeChanged() { + if (!mKeyguardUpdateMonitor.isSwitchingUser) { + updateNotificationsOnUiModeChanged() + } else { + mDispatchUiModeChangeOnUserSwitched = true + } + } + + override fun onThemeChanged() { + onDensityOrFontScaleChanged() + } + + override fun onUserChanged(userId: Int) { + if (mReinflateNotificationsOnUserSwitched) { + updateNotificationsOnDensityOrFontScaleChanged() + mReinflateNotificationsOnUserSwitched = false + } + if (mDispatchUiModeChangeOnUserSwitched) { + updateNotificationsOnUiModeChanged() + mDispatchUiModeChangeOnUserSwitched = false + } + } + + private fun updateNotificationsOnUiModeChanged() { + mPipeline?.allNotifs?.forEach { entry -> + val row = entry.row + row?.onUiModeChanged() + } + } + + private fun updateNotificationsOnDensityOrFontScaleChanged() { + mPipeline?.allNotifs?.forEach { entry -> + entry.onDensityOrFontScaleChanged() + val exposedGuts = entry.areGutsExposed() + if (exposedGuts) { + mGutsManager.onDensityOrFontScaleChanged(entry) + } + } + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java new file mode 100644 index 0000000000000..4ee08ed4899f7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2010 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.collection.legacy; + +import static com.android.systemui.statusbar.phone.StatusBar.SPEW; + +import android.service.notification.StatusBarNotification; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; + +import org.jetbrains.annotations.NotNull; + +import javax.inject.Inject; + +/** + * This is some logic extracted from the + * {@link com.android.systemui.statusbar.phone.StatusBarNotificationPresenter} + * into a class that implements a new-pipeline interface so that the new pipeline can implement it + * correctly. + * + * Specifically, this is the logic which updates notifications when uiMode and screen properties + * change, and which closes the shade when the last notification disappears. + */ +public class LegacyNotificationPresenterExtensions implements NotifShadeEventSource { + private static final String TAG = "LegacyNotifPresenter"; + private final NotificationEntryManager mEntryManager; + private boolean mEntryListenerAdded; + private Runnable mShadeEmptiedCallback; + private Runnable mNotifRemovedByUserCallback; + + @Inject + public LegacyNotificationPresenterExtensions(NotificationEntryManager entryManager) { + mEntryManager = entryManager; + } + + private void ensureEntryListenerAdded() { + if (mEntryListenerAdded) return; + mEntryListenerAdded = true; + mEntryManager.addNotificationEntryListener(new NotificationEntryListener() { + @Override + public void onEntryRemoved( + @NotNull NotificationEntry entry, + NotificationVisibility visibility, + boolean removedByUser, + int reason) { + StatusBarNotification old = entry.getSbn(); + if (SPEW) { + Log.d(TAG, "removeNotification key=" + entry.getKey() + + " old=" + old + " reason=" + reason); + } + + if (old != null && !mEntryManager.hasActiveNotifications()) { + if (mShadeEmptiedCallback != null) mShadeEmptiedCallback.run(); + } + if (removedByUser) { + if (mNotifRemovedByUserCallback != null) mNotifRemovedByUserCallback.run(); + } + } + }); + } + + @Override + public void setNotifRemovedByUserCallback(@NonNull Runnable callback) { + if (mNotifRemovedByUserCallback != null) { + throw new IllegalStateException("mNotifRemovedByUserCallback already set"); + } + mNotifRemovedByUserCallback = callback; + ensureEntryListenerAdded(); + } + + @Override + public void setShadeEmptiedCallback(@NonNull Runnable callback) { + if (mShadeEmptiedCallback != null) { + throw new IllegalStateException("mShadeEmptiedCallback already set"); + } + mShadeEmptiedCallback = callback; + ensureEntryListenerAdded(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt new file mode 100644 index 0000000000000..e24f6a0360955 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 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.collection.render + +/** + * This is an object which provides callbacks for certain important events related to the + * notification shade, such as notifications being removed by the user, or the shade becoming empty. + */ +interface NotifShadeEventSource { + /** + * Registers a callback to be invoked when the last notification has been removed from + * the shade for any reason + */ + fun setShadeEmptiedCallback(callback: Runnable) + + /** + * Registers a callback to be invoked when a notification has been removed from + * the shade by a user action + */ + fun setNotifRemovedByUserCallback(callback: Runnable) +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index dfa1f5f81696a..540216cc662bd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -45,10 +45,12 @@ import com.android.systemui.statusbar.notification.NotificationEntryManagerLogge import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl; import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.coordinator.ShadeEventCoordinator; import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator; import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl; +import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationPresenterExtensions; import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy; import com.android.systemui.statusbar.notification.collection.legacy.OnUserInteractionCallbackImplLegacy; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; @@ -59,6 +61,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl; import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl; import com.android.systemui.statusbar.notification.init.NotificationsControllerStub; @@ -90,7 +93,7 @@ import dagger.Provides; /** * Dagger Module for classes found within the com.android.systemui.statusbar.notification package. */ -@Module(includes = { NotificationSectionHeadersModule.class }) +@Module(includes = {NotificationSectionHeadersModule.class}) public interface NotificationsModule { @Binds StackScrollAlgorithm.SectionProvider bindSectionProvider( @@ -270,6 +273,20 @@ public interface NotificationsModule { return featureFlags.isNewNotifPipelineRenderingEnabled() ? pipeline.get() : entryManager; } + /** + * Provide the active implementation for presenting notifications. + */ + @Provides + @SysUISingleton + static NotifShadeEventSource provideNotifShadeEventSource( + FeatureFlags featureFlags, + Lazy shadeEventCoordinatorLazy, + Lazy legacyNotificationPresenterExtensionsLazy) { + return featureFlags.isNewNotifPipelineRenderingEnabled() + ? shadeEventCoordinatorLazy.get() + : legacyNotificationPresenterExtensionsLazy.get(); + } + /** * Provide a dismissal callback that's triggered when a user manually dismissed a notification * from the notification shade or it gets auto-cancelled by click. 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 e956046abd042..02b1210a28818 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 @@ -1249,6 +1249,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); if (mMenuRow != null && mMenuRow.getMenuView() != null) { mMenuRow.onConfigurationChanged(); } 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 bc50893d9a64b..01188d35623fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -207,6 +207,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; @@ -535,6 +536,7 @@ public class StatusBar extends SystemUI implements private final int[] mAbsPos = new int[2]; + private final NotifShadeEventSource mNotifShadeEventSource; protected final NotificationEntryManager mEntryManager; private final NotificationGutsManager mGutsManager; private final NotificationLogger mNotificationLogger; @@ -718,6 +720,7 @@ public class StatusBar extends SystemUI implements FalsingManager falsingManager, FalsingCollector falsingCollector, BroadcastDispatcher broadcastDispatcher, + NotifShadeEventSource notifShadeEventSource, NotificationEntryManager notificationEntryManager, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, @@ -823,6 +826,7 @@ public class StatusBar extends SystemUI implements mFalsingCollector = falsingCollector; mFalsingManager = falsingManager; mBroadcastDispatcher = broadcastDispatcher; + mNotifShadeEventSource = notifShadeEventSource; mEntryManager = notificationEntryManager; mGutsManager = notificationGutsManager; mNotificationLogger = notificationLogger; @@ -1506,6 +1510,7 @@ public class StatusBar extends SystemUI implements mDynamicPrivacyController, mKeyguardStateController, mKeyguardIndicationController, + mFeatureFlags, this /* statusBar */, mShadeController, mLockscreenShadeTransitionController, @@ -1513,6 +1518,7 @@ public class StatusBar extends SystemUI implements mViewHierarchyManager, mLockscreenUserManager, mStatusBarStateController, + mNotifShadeEventSource, mEntryManager, mMediaManager, mGutsManager, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index c655964e64bc0..cf9b2c6775b80 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -17,9 +17,7 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.statusbar.phone.StatusBar.CLOSE_PANEL_WHEN_EMPTIED; import static com.android.systemui.statusbar.phone.StatusBar.DEBUG; import static com.android.systemui.statusbar.phone.StatusBar.MULTIUSER_DEBUG; -import static com.android.systemui.statusbar.phone.StatusBar.SPEW; -import android.annotation.Nullable; import android.app.KeyguardManager; import android.content.Context; import android.os.RemoteException; @@ -36,7 +34,6 @@ import android.widget.TextView; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.statusbar.IStatusBarService; -import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.widget.MessagingGroup; import com.android.internal.widget.MessagingMessage; import com.android.keyguard.KeyguardUpdateMonitor; @@ -44,6 +41,7 @@ import com.android.systemui.Dependency; import com.android.systemui.ForegroundServiceNotificationListener; import com.android.systemui.InitController; import com.android.systemui.R; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.statusbar.CommandQueue; @@ -59,10 +57,10 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; @@ -88,6 +86,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, private final NotificationViewHierarchyManager mViewHierarchyManager; private final NotificationLockscreenUserManager mLockscreenUserManager; private final SysuiStatusBarStateController mStatusBarStateController; + private final NotifShadeEventSource mNotifShadeEventSource; private final NotificationEntryManager mEntryManager; private final NotificationMediaManager mMediaManager; private final NotificationGutsManager mGutsManager; @@ -100,6 +99,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, private final DozeScrimController mDozeScrimController; private final ScrimController mScrimController; private final KeyguardIndicationController mKeyguardIndicationController; + private final FeatureFlags mFeatureFlags; private final StatusBar mStatusBar; private final ShadeController mShadeController; private final LockscreenShadeTransitionController mShadeTransitionController; @@ -127,6 +127,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, DynamicPrivacyController dynamicPrivacyController, KeyguardStateController keyguardStateController, KeyguardIndicationController keyguardIndicationController, + FeatureFlags featureFlags, StatusBar statusBar, ShadeController shadeController, LockscreenShadeTransitionController shadeTransitionController, @@ -134,6 +135,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, NotificationViewHierarchyManager notificationViewHierarchyManager, NotificationLockscreenUserManager lockscreenUserManager, SysuiStatusBarStateController sysuiStatusBarStateController, + NotifShadeEventSource notifShadeEventSource, NotificationEntryManager notificationEntryManager, NotificationMediaManager notificationMediaManager, NotificationGutsManager notificationGutsManager, @@ -148,6 +150,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mHeadsUpManager = headsUp; mDynamicPrivacyController = dynamicPrivacyController; mKeyguardIndicationController = keyguardIndicationController; + mFeatureFlags = featureFlags; // TODO: use KeyguardStateController#isOccluded to remove this dependency mStatusBar = statusBar; mShadeController = shadeController; @@ -156,6 +159,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mViewHierarchyManager = notificationViewHierarchyManager; mLockscreenUserManager = lockscreenUserManager; mStatusBarStateController = sysuiStatusBarStateController; + mNotifShadeEventSource = notifShadeEventSource; mEntryManager = notificationEntryManager; mMediaManager = notificationMediaManager; mGutsManager = notificationGutsManager; @@ -186,30 +190,18 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mNotificationPanel.createRemoteInputDelegate()); initController.addPostInitTask(() -> { - NotificationEntryListener notificationEntryListener = new NotificationEntryListener() { - @Override - public void onEntryRemoved( - @Nullable NotificationEntry entry, - NotificationVisibility visibility, - boolean removedByUser, - int reason) { - StatusBarNotificationPresenter.this.onNotificationRemoved( - entry.getKey(), entry.getSbn(), reason); - if (removedByUser) { - maybeEndAmbientPulse(); - } - } - }; - mKeyguardIndicationController.init(); mViewHierarchyManager.setUpWithPresenter(this, stackScrollerController.getNotificationListContainer()); - mEntryManager.setUpWithPresenter(this); - mEntryManager.addNotificationEntryListener(notificationEntryListener); - mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager); - mEntryManager.addNotificationLifetimeExtender(mGutsManager); - mEntryManager.addNotificationLifetimeExtenders( - remoteInputManager.getLifetimeExtenders()); + mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied); + mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse); + if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { + mEntryManager.setUpWithPresenter(this); + mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager); + mEntryManager.addNotificationLifetimeExtender(mGutsManager); + mEntryManager.addNotificationLifetimeExtenders( + remoteInputManager.getLifetimeExtenders()); + } notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor); mLockscreenUserManager.setUpWithPresenter(this); mMediaManager.setUpWithPresenter(this); @@ -226,8 +218,21 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, configurationController.addCallback(this); } + /** Called when the shade has been emptied to attempt to close the shade */ + private void maybeClosePanelForShadeEmptied() { + if (CLOSE_PANEL_WHEN_EMPTIED + && !mNotificationPanel.isTracking() + && !mNotificationPanel.isQsExpanded() + && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED + && !isCollapsing()) { + mStatusBarStateController.setState(StatusBarState.KEYGUARD); + } + } + @Override public void onDensityOrFontScaleChanged() { + // TODO(b/145659174): Remove legacy pipeline code + if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return; MessagingMessage.dropCache(); MessagingGroup.dropCache(); if (!mKeyguardUpdateMonitor.isSwitchingUser()) { @@ -239,8 +244,10 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, @Override public void onUiModeChanged() { + // TODO(b/145659174): Remove legacy pipeline code + if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return; if (!mKeyguardUpdateMonitor.isSwitchingUser()) { - updateNotificationOnUiModeChanged(); + updateNotificationsOnUiModeChanged(); } else { mDispatchUiModeChangeOnUserSwitched = true; } @@ -251,7 +258,9 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, onDensityOrFontScaleChanged(); } - private void updateNotificationOnUiModeChanged() { + private void updateNotificationsOnUiModeChanged() { + // TODO(b/145659174): Remove legacy pipeline code + if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return; List userNotifications = mEntryManager.getActiveNotificationsForCurrentUser(); for (int i = 0; i < userNotifications.size(); i++) { @@ -264,6 +273,8 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, } private void updateNotificationsOnDensityOrFontScaleChanged() { + // TODO(b/145659174): Remove legacy pipeline code + if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return; List userNotifications = mEntryManager.getActiveNotificationsForCurrentUser(); for (int i = 0; i < userNotifications.size(); i++) { @@ -276,6 +287,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, } } + @Override public boolean isCollapsing() { return mNotificationPanel.isCollapsing() @@ -308,21 +320,6 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mNotificationPanel.updateNotificationViews(reason); } - private void onNotificationRemoved(String key, StatusBarNotification old, int reason) { - if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); - - if (old != null && CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications() - && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded() - && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED - && !isCollapsing()) { - mStatusBarStateController.setState(StatusBarState.KEYGUARD); - } - } - - public boolean hasActiveNotifications() { - return mEntryManager.hasActiveNotifications(); - } - @Override public void onUserSwitched(int newUserId) { // Begin old BaseStatusBar.userSwitched @@ -335,7 +332,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mReinflateNotificationsOnUserSwitched = false; } if (mDispatchUiModeChangeOnUserSwitched) { - updateNotificationOnUiModeChanged(); + updateNotificationsOnUiModeChanged(); mDispatchUiModeChangeOnUserSwitched = false; } updateNotificationViews("user switched"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index c452a486cbe15..2681d5ec1d307 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -68,6 +68,7 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; @@ -157,6 +158,7 @@ public interface StatusBarPhoneModule { FalsingManager falsingManager, FalsingCollector falsingCollector, BroadcastDispatcher broadcastDispatcher, + NotifShadeEventSource notifShadeEventSource, NotificationEntryManager notificationEntryManager, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, @@ -260,6 +262,7 @@ public interface StatusBarPhoneModule { falsingManager, falsingCollector, broadcastDispatcher, + notifShadeEventSource, notificationEntryManager, notificationGutsManager, notificationLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt new file mode 100644 index 0000000000000..5915cd7823f0d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2021 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.collection.coordinator + +import android.service.notification.NotificationListenerService.REASON_APP_CANCEL +import android.service.notification.NotificationListenerService.REASON_CANCEL +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.util.mockito.argumentCaptor +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations.initMocks + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class ShadeEventCoordinatorTest : SysuiTestCase() { + private lateinit var coordinator: ShadeEventCoordinator + private lateinit var notifCollectionListener: NotifCollectionListener + private lateinit var onBeforeRenderListListener: OnBeforeRenderListListener + + private lateinit var entry1: NotificationEntry + private lateinit var entry2: NotificationEntry + + @Mock private lateinit var pipeline: NotifPipeline + @Mock private lateinit var logger: ShadeEventCoordinatorLogger + @Mock private lateinit var notifRemovedByUserCallback: Runnable + @Mock private lateinit var shadeEmptiedCallback: Runnable + + @Before + fun setUp() { + initMocks(this) + coordinator = ShadeEventCoordinator(logger) + coordinator.attach(pipeline) + notifCollectionListener = argumentCaptor().let { + verify(pipeline).addCollectionListener(it.capture()) + it.value!! + } + onBeforeRenderListListener = argumentCaptor().let { + verify(pipeline).addOnBeforeRenderListListener(it.capture()) + it.value!! + } + coordinator.setNotifRemovedByUserCallback(notifRemovedByUserCallback) + coordinator.setShadeEmptiedCallback(shadeEmptiedCallback) + entry1 = NotificationEntryBuilder().setId(1).build() + entry2 = NotificationEntryBuilder().setId(2).build() + } + + @Test + fun testUserCancelLastNotification() { + notifCollectionListener.onEntryRemoved(entry1, REASON_CANCEL) + verify(shadeEmptiedCallback, never()).run() + verify(notifRemovedByUserCallback, never()).run() + onBeforeRenderListListener.onBeforeRenderList(listOf()) + verify(shadeEmptiedCallback).run() + verify(notifRemovedByUserCallback).run() + } + + @Test + fun testAppCancelLastNotification() { + notifCollectionListener.onEntryRemoved(entry1, REASON_APP_CANCEL) + onBeforeRenderListListener.onBeforeRenderList(listOf()) + verify(shadeEmptiedCallback).run() + verify(notifRemovedByUserCallback, never()).run() + } + + @Test + fun testUserCancelOneOfTwoNotifications() { + notifCollectionListener.onEntryRemoved(entry1, REASON_CANCEL) + onBeforeRenderListListener.onBeforeRenderList(listOf(entry2)) + verify(shadeEmptiedCallback, never()).run() + verify(notifRemovedByUserCallback).run() + } + + @Test + fun testAppCancelOneOfTwoNotifications() { + notifCollectionListener.onEntryRemoved(entry1, REASON_APP_CANCEL) + onBeforeRenderListListener.onBeforeRenderList(listOf(entry2)) + verify(shadeEmptiedCallback, never()).run() + verify(notifRemovedByUserCallback, never()).run() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index c80c07249cc57..4e6b0a26609b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -37,6 +37,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.ForegroundServiceNotificationListener; import com.android.systemui.InitController; import com.android.systemui.SysuiTestCase; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; @@ -51,6 +52,7 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; @@ -109,12 +111,15 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mock(DozeScrimController.class), mock(ScrimController.class), mock(NotificationShadeWindowController.class), mock(DynamicPrivacyController.class), mock(KeyguardStateController.class), - mock(KeyguardIndicationController.class), mStatusBar, + mock(KeyguardIndicationController.class), + mock(FeatureFlags.class), + mStatusBar, mock(ShadeControllerImpl.class), mock(LockscreenShadeTransitionController.class), mCommandQueue, mock(NotificationViewHierarchyManager.class), mock(NotificationLockscreenUserManager.class), mock(SysuiStatusBarStateController.class), + mock(NotifShadeEventSource.class), mock(NotificationEntryManager.class), mock(NotificationMediaManager.class), mock(NotificationGutsManager.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 943d3c79984d0..ca8b6c80d3812 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -121,6 +121,7 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; @@ -210,6 +211,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private NotificationShadeWindowView mNotificationShadeWindowView; @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock private AssistManager mAssistManager; + @Mock private NotifShadeEventSource mNotifShadeEventSource; @Mock private NotificationEntryManager mNotificationEntryManager; @Mock private NotificationGutsManager mNotificationGutsManager; @Mock private NotificationMediaManager mNotificationMediaManager; @@ -377,6 +379,7 @@ public class StatusBarTest extends SysuiTestCase { new FalsingManagerFake(), new FalsingCollectorFake(), mBroadcastDispatcher, + mNotifShadeEventSource, mNotificationEntryManager, mNotificationGutsManager, notificationLogger,