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
This commit is contained in:
committed by
Jeff DeCew
parent
91da8a2976
commit
9a4112bfbb
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -314,9 +314,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
|
||||
mShadeController.addPostCollapseAction(() -> updateNotificationViews(reason));
|
||||
return;
|
||||
}
|
||||
|
||||
mViewHierarchyManager.updateNotificationViews();
|
||||
|
||||
mNotificationPanel.updateNotificationViews(reason);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user