diff options
| author | 2021-10-21 17:19:34 -0400 | |
|---|---|---|
| committer | 2021-10-30 21:49:06 -0400 | |
| commit | 9a4112bfbb08d08baf73f4e99a834eeb06f7a86b (patch) | |
| tree | 7c05a65a38a35948ea00cf514850380152585bbd | |
| parent | 91da8a29767ef992f93ee1b96dfc7fec4f4d9ab4 (diff) | |
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
14 files changed, 425 insertions, 5 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java index 947a39a32365..48bb281429c2 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 396d86bab825..be3364befbe6 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<NotificationEntry> 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 d297d9581d6a..1c9174a33bbc 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 47939f0579f5..577792547c23 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 3730524353ec..9faec7bac40f 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 93059009a56e..239161696379 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 000000000000..2da5d205af95 --- /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<ListEntry>) { + 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<ListEntry> +): Sequence<NotificationEntry> = + entries.asSequence().flatMap(::extractAllRepresentativeEntries) + +private fun extractAllRepresentativeEntries(listEntry: ListEntry): Sequence<NotificationEntry> = + 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 5a35127397b4..8fff90504798 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 000000000000..d7092ecd536e --- /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<Invalidator> { + 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 540216cc662b..c4afc524b5c1 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 cf9b2c6775b8..ecd5c985154c 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 7fb7b8667a1b..cf58c63e3d26 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 e9e6718f5a5c..190c3521e83c 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 000000000000..5fd4174af164 --- /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<Invalidator> { + verify(pipeline).addPreRenderInvalidator(capture()) + } + val dynamicPrivacyListener = withArgCaptor<DynamicPrivacyController.Listener> { + verify(dynamicPrivacyController).addListener(capture()) + } + + val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>() + invalidator.setInvalidationListener(invalidationListener) + + dynamicPrivacyListener.onDynamicPrivacyChanged() + + verify(invalidationListener).onPluggableInvalidated(invalidator) + } + + @Test + fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { + 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<OnBeforeRenderListListener> { + 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<OnBeforeRenderListListener> { + 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<OnBeforeRenderListListener> { + 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<OnBeforeRenderListListener> { + 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<OnBeforeRenderListListener> { + 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<OnBeforeRenderListListener> { + 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<UserHandle>().apply { + whenever(identifier).thenReturn(notifUserId) + } + val mockSbn: StatusBarNotification = mock<StatusBarNotification>().apply { + whenever(user).thenReturn(mockUserHandle) + } + val mockEntry = mock<NotificationEntry>().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 |