From 9a4112bfbb08d08baf73f4e99a834eeb06f7a86b Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Thu, 21 Oct 2021 17:19:34 -0400 Subject: [PATCH] Add sensitive content redaction to notif pipeline This incorporates two behaviors originally handled by the NotificationViewHierarchyManager: 1. A change in DynamicPrivacy would trigger NotificationViewHierarchyManager#updateNotificationViews 2. The aformentioned #updateNotificationViews method was responsible for setting the redaction state on each NotificationEntry A new Coordinator is introduced to the NotifPipeline to handle both of these behaviors. (1) is handled by attaching a new Invalidator that will request a notif list rebuild when dynamic privacy changes. (2) is handled by processing each ListEntry in an OnPreRenderListListener, and setting the redaction state on each NotificationEntry. Fixes: 204127880 Test: manual - turn on setting to hide sensitive notification content on the lockscreen, lock phone, turn screen back on, observe notifications on lock screen Bug: 200025730 Merged-In: I57b4d68f440ea50f13f6da4676144400f875ea8c Change-Id: I57b4d68f440ea50f13f6da4676144400f875ea8c --- .../android/systemui/flags/FeatureFlags.java | 18 ++ .../NotificationViewHierarchyManager.java | 15 ++ .../dagger/StatusBarDependenciesModule.java | 2 + .../collection/NotifPipeline.java | 6 + .../collection/ShadeListBuilder.java | 16 ++ .../coordinator/NotifCoordinators.java | 4 +- .../SensitiveContentCoordinator.kt | 104 +++++++++ .../listbuilder/ShadeListBuilderLogger.kt | 9 + .../listbuilder/pluggable/Invalidator.java | 24 ++ .../dagger/NotificationsModule.java | 6 +- .../phone/StatusBarNotificationPresenter.java | 2 - .../NotificationViewHierarchyManagerTest.java | 8 +- .../collection/ShadeListBuilderTest.java | 7 + .../SensitiveContentCoordinatorTest.kt | 209 ++++++++++++++++++ 14 files changed, 425 insertions(+), 5 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Invalidator.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java index 947a39a32365d..48bb281429c2a 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java @@ -18,6 +18,8 @@ package com.android.systemui.flags; import android.content.Context; import android.util.FeatureFlagUtils; +import android.util.Log; +import android.widget.Toast; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; @@ -86,6 +88,22 @@ public class FeatureFlags { } } + public void assertLegacyPipelineEnabled() { + if (isNewNotifPipelineRenderingEnabled()) { + throw new IllegalStateException("Old pipeline code running w/ new pipeline enabled"); + } + } + + public boolean checkLegacyPipelineEnabled() { + if (!isNewNotifPipelineRenderingEnabled()) { + return true; + } + Log.d("NotifPipeline", "Old pipeline code running w/ new pipeline enabled", + new Exception()); + Toast.makeText(mContext, "Old pipeline code running!", Toast.LENGTH_SHORT).show(); + return false; + } + public boolean isNewNotifPipelineEnabled() { return isEnabled(Flags.NEW_NOTIFICATION_PIPELINE); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 396d86bab825e..be3364befbe6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -27,6 +27,7 @@ import android.view.ViewGroup; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.dagger.StatusBarModule; import com.android.systemui.statusbar.notification.AssistantFeedbackController; @@ -72,6 +73,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle // Dependencies: private final DynamicChildBindController mDynamicChildBindController; + private final FeatureFlags mFeatureFlags; protected final NotificationLockscreenUserManager mLockscreenUserManager; protected final NotificationGroupManagerLegacy mGroupManager; protected final VisualStabilityManager mVisualStabilityManager; @@ -107,6 +109,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle public NotificationViewHierarchyManager( Context context, @Main Handler mainHandler, + FeatureFlags featureFlags, NotificationLockscreenUserManager notificationLockscreenUserManager, NotificationGroupManagerLegacy groupManager, VisualStabilityManager visualStabilityManager, @@ -121,6 +124,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle AssistantFeedbackController assistantFeedbackController) { mContext = context; mHandler = mainHandler; + mFeatureFlags = featureFlags; mLockscreenUserManager = notificationLockscreenUserManager; mBypassController = bypassController; mGroupManager = groupManager; @@ -151,6 +155,10 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle //TODO: Rewrite this to focus on Entries, or some other data object instead of views public void updateNotificationViews() { Assert.isMainThread(); + if (!mFeatureFlags.checkLegacyPipelineEnabled()) { + return; + } + beginUpdate(); List activeNotifications = mEntryManager.getVisibleNotifications(); @@ -425,6 +433,10 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle */ public void updateRowStates() { Assert.isMainThread(); + if (!mFeatureFlags.checkLegacyPipelineEnabled()) { + return; + } + beginUpdate(); updateRowStatesInternal(); endUpdate(); @@ -510,6 +522,9 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle @Override public void onDynamicPrivacyChanged() { + if (!mFeatureFlags.checkLegacyPipelineEnabled()) { + return; + } if (mPerformingUpdate) { Log.w(TAG, "onDynamicPrivacyChanged made a re-entrant call"); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java index d297d9581d6a1..1c9174a33bbc5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java @@ -184,6 +184,7 @@ public interface StatusBarDependenciesModule { static NotificationViewHierarchyManager provideNotificationViewHierarchyManager( Context context, @Main Handler mainHandler, + FeatureFlags featureFlags, NotificationLockscreenUserManager notificationLockscreenUserManager, NotificationGroupManagerLegacy groupManager, VisualStabilityManager visualStabilityManager, @@ -199,6 +200,7 @@ public interface StatusBarDependenciesModule { return new NotificationViewHierarchyManager( context, mainHandler, + featureFlags, notificationLockscreenUserManager, groupManager, visualStabilityManager, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java index 47939f0579f5d..577792547c23a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java @@ -23,6 +23,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; @@ -216,6 +217,11 @@ public class NotifPipeline implements CommonNotifCollection { mShadeListBuilder.addOnBeforeRenderListListener(listener); } + /** Registers an invalidator that can be used to invalidate the entire notif list. */ + public void addPreRenderInvalidator(Invalidator invalidator) { + mShadeListBuilder.addPreRenderInvalidator(invalidator); + } + /** * Returns a read-only view in to the current shade list, i.e. the list of notifications that * are currently present in the shade. If this method is called during pipeline execution it 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 3730524353ec4..9faec7bac40f0 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 @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState; import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; @@ -174,6 +175,13 @@ public class ShadeListBuilder implements Dumpable { mOnBeforeRenderListListeners.add(listener); } + void addPreRenderInvalidator(Invalidator invalidator) { + Assert.isMainThread(); + + mPipelineState.requireState(STATE_IDLE); + invalidator.setInvalidationListener(this::onPreRenderInvalidated); + } + void addPreGroupFilter(NotifFilter filter) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); @@ -256,6 +264,14 @@ public class ShadeListBuilder implements Dumpable { } }; + private void onPreRenderInvalidated(Invalidator invalidator) { + Assert.isMainThread(); + + mLogger.logPreRenderInvalidated(invalidator.getName(), mPipelineState.getState()); + + rebuildListIfBefore(STATE_FINALIZING); + } + private void onPreGroupFilterInvalidated(NotifFilter filter) { Assert.isMainThread(); 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 93059009a56ee..239161696379e 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 @@ -64,7 +64,8 @@ public class NotifCoordinators implements Dumpable { ShadeEventCoordinator shadeEventCoordinator, SmartspaceDedupingCoordinator smartspaceDedupingCoordinator, ViewConfigCoordinator viewConfigCoordinator, - VisualStabilityCoordinator visualStabilityCoordinator) { + VisualStabilityCoordinator visualStabilityCoordinator, + SensitiveContentCoordinator sensitiveContentCoordinator) { dumpManager.registerDumpable(TAG, this); mCoordinators.add(new HideLocallyDismissedNotifsCoordinator()); @@ -79,6 +80,7 @@ public class NotifCoordinators implements Dumpable { mCoordinators.add(shadeEventCoordinator); mCoordinators.add(viewConfigCoordinator); mCoordinators.add(visualStabilityCoordinator); + mCoordinators.add(sensitiveContentCoordinator); if (featureFlags.isSmartspaceDedupingEnabled()) { mCoordinators.add(smartspaceDedupingCoordinator); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt new file mode 100644 index 0000000000000..2da5d205af959 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt @@ -0,0 +1,104 @@ +/* + * 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.os.UserHandle +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.NotificationLockscreenUserManager +import com.android.systemui.statusbar.notification.DynamicPrivacyController +import com.android.systemui.statusbar.notification.collection.GroupEntry +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.listbuilder.OnBeforeRenderListListener +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator +import dagger.Module +import dagger.Provides + +@Module +object SensitiveContentCoordinatorModule { + @Provides + @SysUISingleton + fun provideCoordinator( + dynamicPrivacyController: DynamicPrivacyController, + lockscreenUserManager: NotificationLockscreenUserManager + ): SensitiveContentCoordinator = + SensitiveContentCoordinatorImpl(dynamicPrivacyController, lockscreenUserManager) +} + +/** Coordinates re-inflation and post-processing of sensitive notification content. */ +interface SensitiveContentCoordinator : Coordinator + +private class SensitiveContentCoordinatorImpl( + private val dynamicPrivacyController: DynamicPrivacyController, + private val lockscreenUserManager: NotificationLockscreenUserManager +) : Invalidator("SensitiveContentInvalidator"), + SensitiveContentCoordinator, + DynamicPrivacyController.Listener, + OnBeforeRenderListListener { + + override fun attach(pipeline: NotifPipeline) { + dynamicPrivacyController.addListener(this) + pipeline.addOnBeforeRenderListListener(this) + pipeline.addPreRenderInvalidator(this) + } + + override fun onDynamicPrivacyChanged(): Unit = invalidateList() + + override fun onBeforeRenderList(entries: List) { + val currentUserId = lockscreenUserManager.currentUserId + val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId) + val deviceSensitive = devicePublic && + !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId) + val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked + for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) { + val notifUserId = entry.sbn.user.identifier + val userLockscreen = devicePublic || + lockscreenUserManager.isLockscreenPublicMode(notifUserId) + val userPublic = when { + // if we're not on the lockscreen, we're definitely private + !userLockscreen -> false + // we are on the lockscreen, so unless we're dynamically unlocked, we're + // definitely public + !dynamicallyUnlocked -> true + // we're dynamically unlocked, but check if the notification needs + // a separate challenge if it's from a work profile + else -> when (notifUserId) { + currentUserId -> false + UserHandle.USER_ALL -> false + else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId) + } + } + val needsRedaction = lockscreenUserManager.needsRedaction(entry) + val isSensitive = userPublic && needsRedaction + entry.setSensitive(isSensitive, deviceSensitive) + } + } +} + +private fun extractAllRepresentativeEntries( + entries: List +): Sequence = + entries.asSequence().flatMap(::extractAllRepresentativeEntries) + +private fun extractAllRepresentativeEntries(listEntry: ListEntry): Sequence = + sequence { + listEntry.representativeEntry?.let { yield(it) } + if (listEntry is GroupEntry) { + yieldAll(extractAllRepresentativeEntries(listEntry.children)) + } + } \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt index 5a35127397b45..8fff905047981 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt @@ -47,6 +47,15 @@ class ShadeListBuilderLogger @Inject constructor( }) } + fun logPreRenderInvalidated(filterName: String, pipelineState: Int) { + buffer.log(TAG, DEBUG, { + str1 = filterName + int1 = pipelineState + }, { + """Pre-render Invalidator "$str1" invalidated; pipeline state is $int1""" + }) + } + fun logPreGroupFilterInvalidated(filterName: String, pipelineState: Int) { buffer.log(TAG, DEBUG, { str1 = filterName diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Invalidator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Invalidator.java new file mode 100644 index 0000000000000..d7092ecd536e4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Invalidator.java @@ -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.listbuilder.pluggable; + +/** A {@link Pluggable} that can only invalidate. */ +public abstract class Invalidator extends Pluggable { + protected Invalidator(String name) { + super(name); + } +} 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 540216cc662bd..c4afc524b5c16 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,6 +45,7 @@ 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.SensitiveContentCoordinatorModule; 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; @@ -93,7 +94,10 @@ import dagger.Provides; /** * Dagger Module for classes found within the com.android.systemui.statusbar.notification package. */ -@Module(includes = {NotificationSectionHeadersModule.class}) +@Module(includes = { + NotificationSectionHeadersModule.class, + SensitiveContentCoordinatorModule.class +}) public interface NotificationsModule { @Binds StackScrollAlgorithm.SectionProvider bindSectionProvider( 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 cf9b2c6775b80..ecd5c985154c1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -314,9 +314,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mShadeController.addPostCollapseAction(() -> updateNotificationViews(reason)); return; } - mViewHierarchyManager.updateNotificationViews(); - mNotificationPanel.updateNotificationViews(reason); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java index 7fb7b8667a1b8..cf58c63e3d263 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -36,6 +36,7 @@ import android.widget.LinearLayout; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.statusbar.notification.AssistantFeedbackController; import com.android.systemui.statusbar.notification.DynamicChildBindController; @@ -75,6 +76,7 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { @Spy private FakeListContainer mListContainer = new FakeListContainer(); // Dependency mocks: + @Mock private FeatureFlags mFeatureFlags; @Mock private NotificationEntryManager mEntryManager; @Mock private NotificationLockscreenUserManager mLockscreenUserManager; @Mock private NotificationGroupManagerLegacy mGroupManager; @@ -101,10 +103,14 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { when(mVisualStabilityManager.areGroupChangesAllowed()).thenReturn(true); when(mVisualStabilityManager.isReorderingAllowed()).thenReturn(true); + when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false); + when(mFeatureFlags.checkLegacyPipelineEnabled()).thenReturn(true); + mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)); mViewHierarchyManager = new NotificationViewHierarchyManager(mContext, - mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager, + mHandler, mFeatureFlags, mLockscreenUserManager, mGroupManager, + mVisualStabilityManager, mock(StatusBarStateControllerImpl.class), mEntryManager, mock(KeyguardBypassController.class), Optional.of(mock(Bubbles.class)), 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 e9e6718f5a5c8..190c3521e83ce 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 @@ -56,6 +56,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; @@ -847,11 +848,13 @@ public class ShadeListBuilderTest extends SysuiTestCase { NotifPromoter idPromoter = new IdPromoter(4); NotifSectioner section = new PackageSectioner(PACKAGE_1); NotifComparator hypeComparator = new HypeComparator(PACKAGE_2); + Invalidator preRenderInvalidator = new Invalidator("PreRenderInvalidator") {}; mListBuilder.addPreGroupFilter(packageFilter); mListBuilder.addPromoter(idPromoter); mListBuilder.setSectioners(singletonList(section)); mListBuilder.setComparators(singletonList(hypeComparator)); + mListBuilder.addPreRenderInvalidator(preRenderInvalidator); // GIVEN a set of random notifs addNotif(0, PACKAGE_1); @@ -876,6 +879,10 @@ public class ShadeListBuilderTest extends SysuiTestCase { clearInvocations(mOnRenderListListener); hypeComparator.invalidateList(); verify(mOnRenderListListener).onRenderList(anyList()); + + clearInvocations(mOnRenderListListener); + preRenderInvalidator.invalidateList(); + verify(mOnRenderListListener).onRenderList(anyList()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt new file mode 100644 index 0000000000000..5fd4174af164e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt @@ -0,0 +1,209 @@ +/* + * 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.os.UserHandle +import android.service.notification.StatusBarNotification +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.NotificationLockscreenUserManager +import com.android.systemui.statusbar.notification.DynamicPrivacyController +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.listbuilder.OnBeforeRenderListListener +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable +import com.android.systemui.util.mockito.withArgCaptor +import com.android.systemui.util.mockito.mock +import org.junit.Test +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever + +@SmallTest +class SensitiveContentCoordinatorTest : SysuiTestCase() { + + val dynamicPrivacyController: DynamicPrivacyController = mock() + val lockscreenUserManager: NotificationLockscreenUserManager = mock() + val pipeline: NotifPipeline = mock() + + val coordinator: SensitiveContentCoordinator = SensitiveContentCoordinatorModule + .provideCoordinator(dynamicPrivacyController, lockscreenUserManager) + + @Test + fun onDynamicPrivacyChanged_invokeInvalidationListener() { + coordinator.attach(pipeline) + val invalidator = withArgCaptor { + verify(pipeline).addPreRenderInvalidator(capture()) + } + val dynamicPrivacyListener = withArgCaptor { + verify(dynamicPrivacyController).addListener(capture()) + } + + val invalidationListener = mock>() + invalidator.setInvalidationListener(invalidationListener) + + dynamicPrivacyListener.onDynamicPrivacyChanged() + + verify(invalidationListener).onPluggableInvalidated(invalidator) + } + + @Test + fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = withArgCaptor { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, false) + } + + @Test + fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = withArgCaptor { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, false) + } + + @Test + fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = withArgCaptor { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, false) + } + + @Test + fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = withArgCaptor { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, false) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + } + + @Test + fun onBeforeRenderList_deviceLocked_notifNeedsRedaction() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = withArgCaptor { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) + val entry = fakeNotification(1, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } + + @Test + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = withArgCaptor { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) + val entry = fakeNotification(1, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + } + + @Test + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = withArgCaptor { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) + whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true) + + val entry = fakeNotification(2, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } + + private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry { + val mockUserHandle = mock().apply { + whenever(identifier).thenReturn(notifUserId) + } + val mockSbn: StatusBarNotification = mock().apply { + whenever(user).thenReturn(mockUserHandle) + } + val mockEntry = mock().apply { + whenever(sbn).thenReturn(mockSbn) + } + whenever(lockscreenUserManager.needsRedaction(mockEntry)).thenReturn(needsRedaction) + whenever(mockEntry.rowExists()).thenReturn(true) + return object : ListEntry("key", 0) { + override fun getRepresentativeEntry(): NotificationEntry = mockEntry + } + } +} \ No newline at end of file