From fc40850f0ea3e3652eee9e5d59cf7ee356d5ddce Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Tue, 28 Sep 2021 16:27:26 +0000 Subject: [PATCH] New pipeline: Fix notification re-inflation on dark mode change. Fixes: 201329618 Fixes: 201413798 Test: tap dark mode QS tile Test: atest PreparationCoordinatorTest ShadeEventCoordinatorTest 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 18f3b457890bd..cd6f35bc1ffbc 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, CommunalCoordinator communalCoordinator) { dumpManager.registerDumpable(TAG, this); @@ -75,6 +77,8 @@ public class NotifCoordinators implements Dumpable { mCoordinators.add(bubbleCoordinator); mCoordinators.add(conversationCoordinator); mCoordinators.add(mediaCoordinator); + mCoordinators.add(shadeEventCoordinator); + mCoordinators.add(viewConfigCoordinator); mCoordinators.add(visualStabilityCoordinator); mCoordinators.add(communalCoordinator); 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 0e75a453c5139..bce6cccfee5f2 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,