diff options
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  |