summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java104
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt450
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java306
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt231
8 files changed, 1136 insertions, 57 deletions
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
index 380cdadd1361..ae4ba27775b8 100644
--- 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
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import android.os.UserHandle
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags.screenshareNotificationHiding
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
@@ -30,6 +31,7 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import dagger.Binds
import dagger.Module
@@ -55,6 +57,8 @@ class SensitiveContentCoordinatorImpl @Inject constructor(
private val statusBarStateController: StatusBarStateController,
private val keyguardStateController: KeyguardStateController,
private val selectedUserInteractor: SelectedUserInteractor,
+ private val sensitiveNotificationProtectionController:
+ SensitiveNotificationProtectionController,
) : Invalidator("SensitiveContentInvalidator"),
SensitiveContentCoordinator,
DynamicPrivacyController.Listener,
@@ -82,10 +86,13 @@ class SensitiveContentCoordinatorImpl @Inject constructor(
return
}
+ val isSensitiveContentProtectionActive = screenshareNotificationHiding() &&
+ sensitiveNotificationProtectionController.isSensitiveStateActive
val currentUserId = lockscreenUserManager.currentUserId
val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
- val deviceSensitive = devicePublic &&
- !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)
+ val deviceSensitive = (devicePublic &&
+ !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)) ||
+ isSensitiveContentProtectionActive
val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked
for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) {
val notifUserId = entry.sbn.user.identifier
@@ -105,9 +112,13 @@ class SensitiveContentCoordinatorImpl @Inject constructor(
else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId)
}
}
+
+ val shouldProtectNotification = screenshareNotificationHiding() &&
+ sensitiveNotificationProtectionController.shouldProtectNotification(entry)
+
val needsRedaction = lockscreenUserManager.needsRedaction(entry)
val isSensitive = userPublic && needsRedaction
- entry.setSensitive(isSensitive, deviceSensitive)
+ entry.setSensitive(isSensitive || shouldProtectNotification, deviceSensitive)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 6a66bb74f16d..9a8cc0ae33cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -22,6 +22,7 @@ import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_N
import static com.android.app.animation.Interpolators.STANDARD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
+import static com.android.systemui.Flags.screenshareNotificationHiding;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
@@ -135,6 +136,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
@@ -218,6 +220,8 @@ public class NotificationStackScrollLayoutController implements Dumpable {
private final SecureSettings mSecureSettings;
private final NotificationDismissibilityProvider mDismissibilityProvider;
private final ActivityStarter mActivityStarter;
+ private final SensitiveNotificationProtectionController
+ mSensitiveNotificationProtectionController;
private View mLongPressedView;
@@ -295,6 +299,15 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
};
+ private final Runnable mSensitiveStateChangedListener = new Runnable() {
+ @Override
+ public void run() {
+ // Animate false to protect against screen recording capturing content
+ // during the animation
+ updateSensitivenessWithAnimation(false);
+ }
+ };
+
private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> {
if (mView.isExpanded()) {
// The bottom might change because we're using the final actual height of the view
@@ -399,7 +412,20 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
private void updateSensitivenessWithAnimation(boolean animate) {
- mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode());
+ Trace.beginSection("NSSLC.updateSensitivenessWithAnimation");
+ if (screenshareNotificationHiding()) {
+ boolean isAnyProfilePublic = mLockscreenUserManager.isAnyProfilePublicMode();
+ boolean isSensitiveContentProtectionActive =
+ mSensitiveNotificationProtectionController.isSensitiveStateActive();
+ boolean isSensitive = isAnyProfilePublic || isSensitiveContentProtectionActive;
+
+ // Only animate if in a non-sensitive state (not screen sharing)
+ boolean shouldAnimate = animate && !isSensitiveContentProtectionActive;
+ mView.updateSensitiveness(shouldAnimate, isSensitive);
+ } else {
+ mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode());
+ }
+ Trace.endSection();
}
/**
@@ -708,7 +734,8 @@ public class NotificationStackScrollLayoutController implements Dumpable {
SecureSettings secureSettings,
NotificationDismissibilityProvider dismissibilityProvider,
ActivityStarter activityStarter,
- SplitShadeStateController splitShadeStateController) {
+ SplitShadeStateController splitShadeStateController,
+ SensitiveNotificationProtectionController sensitiveNotificationProtectionController) {
mView = view;
mKeyguardTransitionRepo = keyguardTransitionRepo;
mViewBinder = viewBinder;
@@ -756,6 +783,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mSecureSettings = secureSettings;
mDismissibilityProvider = dismissibilityProvider;
mActivityStarter = activityStarter;
+ mSensitiveNotificationProtectionController = sensitiveNotificationProtectionController;
mView.passSplitShadeStateController(splitShadeStateController);
mDumpManager.registerDumpable(this);
updateResources();
@@ -860,6 +888,11 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
mDeviceProvisionedListener.onDeviceProvisionedChanged();
+ if (screenshareNotificationHiding()) {
+ mSensitiveNotificationProtectionController
+ .registerSensitiveStateListener(mSensitiveStateChangedListener);
+ }
+
if (mView.isAttachedToWindow()) {
mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java
new file mode 100644
index 000000000000..970cc75bbb6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 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.policy;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * A controller which provides the current sensitive notification protections status as well as
+ * to assist in feature usage and exemptions
+ */
+public interface SensitiveNotificationProtectionController {
+ /**
+ * Register a runnable that triggers on changes to protection state
+ *
+ * <p> onSensitiveStateChanged not invoked on registration
+ */
+ void registerSensitiveStateListener(Runnable onSensitiveStateChanged);
+
+ /** Unregister a previously registered onSensitiveStateChanged runnable */
+ void unregisterSensitiveStateListener(Runnable onSensitiveStateChanged);
+
+ /** Return {@code true} if device in state in which notifications should be protected */
+ boolean isSensitiveStateActive();
+
+ /** Return {@code true} when notification should be protected */
+ boolean shouldProtectNotification(NotificationEntry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
new file mode 100644
index 000000000000..3c4ca4465874
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 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.policy;
+
+import static com.android.systemui.Flags.screenshareNotificationHiding;
+
+import android.media.projection.MediaProjectionInfo;
+import android.media.projection.MediaProjectionManager;
+import android.os.Handler;
+import android.os.Trace;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.ListenerSet;
+
+import javax.inject.Inject;
+
+/** Implementation of SensitiveNotificationProtectionController. **/
+@SysUISingleton
+public class SensitiveNotificationProtectionControllerImpl
+ implements SensitiveNotificationProtectionController {
+ private final MediaProjectionManager mMediaProjectionManager;
+ private final ListenerSet<Runnable> mListeners = new ListenerSet<>();
+ private volatile MediaProjectionInfo mProjection;
+
+ @VisibleForTesting
+ final MediaProjectionManager.Callback mMediaProjectionCallback =
+ new MediaProjectionManager.Callback() {
+ @Override
+ public void onStart(MediaProjectionInfo info) {
+ Trace.beginSection(
+ "SNPC.onProjectionStart");
+ mProjection = info;
+ mListeners.forEach(Runnable::run);
+ Trace.endSection();
+ }
+
+ @Override
+ public void onStop(MediaProjectionInfo info) {
+ Trace.beginSection(
+ "SNPC.onProjectionStop");
+ mProjection = null;
+ mListeners.forEach(Runnable::run);
+ Trace.endSection();
+ }
+ };
+
+ @Inject
+ public SensitiveNotificationProtectionControllerImpl(
+ MediaProjectionManager mediaProjectionManager,
+ @Main Handler mainHandler) {
+ mMediaProjectionManager = mediaProjectionManager;
+
+ if (screenshareNotificationHiding()) {
+ mMediaProjectionManager.addCallback(mMediaProjectionCallback, mainHandler);
+ }
+ }
+
+ @Override
+ public void registerSensitiveStateListener(Runnable onSensitiveStateChanged) {
+ mListeners.addIfAbsent(onSensitiveStateChanged);
+ }
+
+ @Override
+ public void unregisterSensitiveStateListener(Runnable onSensitiveStateChanged) {
+ mListeners.remove(onSensitiveStateChanged);
+ }
+
+ @Override
+ public boolean isSensitiveStateActive() {
+ // TODO(b/316955558): Add disabled by developer option
+ // TODO(b/316955306): Add feature exemption for sysui and bug handlers
+ // TODO(b/316955346): Add feature exemption for single app screen sharing
+ return mProjection != null;
+ }
+
+ @Override
+ public boolean shouldProtectNotification(NotificationEntry entry) {
+ if (!isSensitiveStateActive()) {
+ return false;
+ }
+
+ // Exempt foreground service notifications from protection in effort to keep screen share
+ // stop actions easily accessible
+ // TODO(b/316955208): Exempt FGS notifications only for app that started projection
+ return !entry.getSbn().getNotification().isFgsOrUij();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 3304b9827fd8..15200bd0ac54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -60,6 +60,8 @@ import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.statusbar.policy.SecurityControllerImpl;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionControllerImpl;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.statusbar.policy.SplitShadeStateControllerImpl;
import com.android.systemui.statusbar.policy.UserInfoController;
@@ -146,6 +148,11 @@ public interface StatusBarPolicyModule {
/** */
@Binds
+ SensitiveNotificationProtectionController provideSensitiveNotificationProtectionController(
+ SensitiveNotificationProtectionControllerImpl controllerImpl);
+
+ /** */
+ @Binds
UserInfoController provideUserInfoContrller(UserInfoControllerImpl controllerImpl);
/** */
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
index df547ae5883e..350ed2d9ff22 100644
--- 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
@@ -17,9 +17,11 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
import android.service.notification.StatusBarNotification
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -33,6 +35,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -55,28 +58,31 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
val statusBarStateController: StatusBarStateController = mock()
val keyguardStateController: KeyguardStateController = mock()
val mSelectedUserInteractor: SelectedUserInteractor = mock()
+ val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController =
+ mock()
val coordinator: SensitiveContentCoordinator =
- DaggerTestSensitiveContentCoordinatorComponent
- .factory()
- .create(
- dynamicPrivacyController,
- lockscreenUserManager,
- keyguardUpdateMonitor,
- statusBarStateController,
- keyguardStateController,
- mSelectedUserInteractor)
- .coordinator
+ DaggerTestSensitiveContentCoordinatorComponent.factory()
+ .create(
+ dynamicPrivacyController,
+ lockscreenUserManager,
+ keyguardUpdateMonitor,
+ statusBarStateController,
+ keyguardStateController,
+ mSelectedUserInteractor,
+ sensitiveNotificationProtectionController
+ )
+ .coordinator
@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 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)
@@ -89,9 +95,10 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
@Test
fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
@@ -105,11 +112,59 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_sensitiveActive() {
+ 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)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_shouldProtectNotification() {
+ 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)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, false)
+ }
+
+ @Test
fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
@@ -123,11 +178,59 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_sensitiveActive() {
+ 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)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_shouldProtectNotification() {
+ 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)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, false)
+ }
+
+ @Test
fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -141,17 +244,87 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_sensitiveActive() {
+ 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)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_shouldProtectNotification() {
+ 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)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, false)
+ }
+
+ @Test
fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ 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
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Suppress("ktlint:standard:max-line-length")
+ fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_sensitiveActive() {
+ 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)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
@@ -159,17 +332,92 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Suppress("ktlint:standard:max-line-length")
+ fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_shouldProtectNotification() {
+ 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)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+
+ @Test
fun onBeforeRenderList_deviceLocked_notifNeedsRedaction() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ 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
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_sensitiveActive() {
+ 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)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_shouldProtectNotification() {
+ 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)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
@@ -179,15 +427,37 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
@Test
fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ 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
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_sensitiveActive() {
+ 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)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
@@ -195,19 +465,96 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Suppress("ktlint:standard:max-line-length")
+ fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_shouldProtectNotification() {
+ 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)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+
+ @Test
fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ 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)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_sensitiveActive() {
+ 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)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Suppress("ktlint:standard:max-line-length")
+ fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_shouldProtectNotification() {
+ 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)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
@@ -217,9 +564,10 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
@Test
fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -227,9 +575,11 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD)
whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any()))
- .thenReturn(true)
-
+ .thenReturn(true)
val entry = fakeNotification(2, true)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+ whenever(sensitiveNotificationProtectionController.shouldProtectNotification(any()))
+ .thenReturn(true)
onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
@@ -237,15 +587,11 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
}
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)
- }
+ 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) {
@@ -268,6 +614,8 @@ interface TestSensitiveContentCoordinatorComponent {
@BindsInstance statusBarStateController: StatusBarStateController,
@BindsInstance keyguardStateController: KeyguardStateController,
@BindsInstance selectedUserInteractor: SelectedUserInteractor,
+ @BindsInstance
+ sensitiveNotificationProtectionController: SensitiveNotificationProtectionController,
): TestSensitiveContentCoordinatorComponent
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 89f826b2049d..1ab4c32c7d08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack;
+import static com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -32,6 +33,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
@@ -39,6 +41,7 @@ import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDis
import android.metrics.LogMaker;
import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -101,6 +104,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.settings.SecureSettings;
@@ -172,10 +176,16 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private ActivityStarter mActivityStarter;
@Mock private KeyguardTransitionRepository mKeyguardTransitionRepo;
@Mock private NotificationListViewBinder mViewBinder;
+ @Mock
+ private SensitiveNotificationProtectionController mSensitiveNotificationProtectionController;
+
+ @Captor
+ private ArgumentCaptor<Runnable> mSensitiveStateListenerArgumentCaptor;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
+
private final ActiveNotificationListRepository mActiveNotificationsRepository =
new ActiveNotificationListRepository();
@@ -386,6 +396,23 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ public void testOnUserChange_verifyNotSensitive() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ initController(/* viewIsAttached= */ true);
+
+ ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+ .forClass(UserChangedListener.class);
+
+ verify(mNotificationLockscreenUserManager)
+ .addUserChangedListener(userChangedCaptor.capture());
+ reset(mNotificationStackScrollLayout);
+
+ UserChangedListener changedListener = userChangedCaptor.getValue();
+ changedListener.onUserChanged(0);
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+ }
+
+ @Test
public void testOnUserChange_verifySensitiveProfile() {
when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
initController(/* viewIsAttached= */ true);
@@ -403,6 +430,80 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnUserChange_verifyNotSensitive_screenshareNotificationHidingEnabled() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+
+ ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+ .forClass(UserChangedListener.class);
+
+ verify(mNotificationLockscreenUserManager)
+ .addUserChangedListener(userChangedCaptor.capture());
+ reset(mNotificationStackScrollLayout);
+
+ UserChangedListener changedListener = userChangedCaptor.getValue();
+ changedListener.onUserChanged(0);
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnUserChange_verifySensitiveProfile_screenshareNotificationHidingEnabled() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+
+ ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+ .forClass(UserChangedListener.class);
+
+ verify(mNotificationLockscreenUserManager)
+ .addUserChangedListener(userChangedCaptor.capture());
+ reset(mNotificationStackScrollLayout);
+
+ UserChangedListener changedListener = userChangedCaptor.getValue();
+ changedListener.onUserChanged(0);
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnUserChange_verifySensitiveActive_screenshareNotificationHidingEnabled() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+ initController(/* viewIsAttached= */ true);
+
+ ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+ .forClass(UserChangedListener.class);
+
+ verify(mNotificationLockscreenUserManager)
+ .addUserChangedListener(userChangedCaptor.capture());
+ reset(mNotificationStackScrollLayout);
+
+ UserChangedListener changedListener = userChangedCaptor.getValue();
+ changedListener.onUserChanged(0);
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ public void testOnStatePostChange_verifyNotSensitive() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+ }
+
+ @Test
public void testOnStatePostChange_verifyIfProfileIsPublic() {
when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
@@ -418,6 +519,194 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_verifyNotSensitive_screenshareNotificationHidingEnabled() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_verifyIfProfileIsPublic_screenshareNotificationHidingEnabled(
+ ) {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_verifyIfSensitiveActive_screenshareNotificationHidingEnabled(
+ ) {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ public void testOnStatePostChange_goingFullShade_verifyNotSensitive() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(true, false);
+ }
+
+ @Test
+ public void testOnStatePostChange_goingFullShade_verifyIfProfileIsPublic() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+ when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(true, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_goingFullShade_verifyNotSensitive_screenshareHideEnabled(
+ ) {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(true, false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_goingFullShade_verifyProfileIsPublic_screenshareHideEnabled(
+ ) {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+ when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(true, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_goingFullShade_verifySensitiveActive_screenshareHideEnabled(
+ ) {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnProjectionStateChanged_verifyNotSensitive() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive())
+ .thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSensitiveNotificationProtectionController)
+ .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture());
+
+ mSensitiveStateListenerArgumentCaptor.getValue().run();
+
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnProjectionStateChanged_verifyIfProfileIsPublic() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSensitiveNotificationProtectionController)
+ .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture());
+
+ mSensitiveStateListenerArgumentCaptor.getValue().run();
+
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnProjectionStateChanged_verifyIfSensitiveActive() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSensitiveNotificationProtectionController)
+ .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture());
+
+ mSensitiveStateListenerArgumentCaptor.getValue().run();
+
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
public void testOnMenuShownLogging() {
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker(
@@ -666,6 +955,20 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean());
}
+ @Test
+ @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void sensitiveNotificationProtectionControllerListenerNotRegistered() {
+ initController(/* viewIsAttached= */ true);
+ verifyZeroInteractions(mSensitiveNotificationProtectionController);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void sensitiveNotificationProtectionControllerListenerRegistered() {
+ initController(/* viewIsAttached= */ true);
+ verify(mSensitiveNotificationProtectionController).registerSensitiveStateListener(any());
+ }
+
private LogMaker logMatcher(int category, int type) {
return argThat(new LogMatcher(category, type));
}
@@ -744,7 +1047,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mSecureSettings,
mock(NotificationDismissibilityProvider.class),
mActivityStarter,
- new ResourcesSplitShadeStateController());
+ new ResourcesSplitShadeStateController(),
+ mSensitiveNotificationProtectionController);
}
static class LogMatcher implements ArgumentMatcher<LogMaker> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
new file mode 100644
index 000000000000..cd5d5ed0d08e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2024 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.policy
+
+import android.app.Notification
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Handler
+import android.service.notification.StatusBarNotification
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
+ @Mock private lateinit var handler: Handler
+
+ @Mock private lateinit var mediaProjectionManager: MediaProjectionManager
+
+ @Mock private lateinit var mediaProjectionInfo: MediaProjectionInfo
+
+ @Mock private lateinit var listener1: Runnable
+ @Mock private lateinit var listener2: Runnable
+ @Mock private lateinit var listener3: Runnable
+
+ @Captor
+ private lateinit var mediaProjectionCallbackCaptor:
+ ArgumentCaptor<MediaProjectionManager.Callback>
+
+ private lateinit var controller: SensitiveNotificationProtectionControllerImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mSetFlagsRule.enableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+
+ controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
+
+ // Obtain useful MediaProjectionCallback
+ verify(mediaProjectionManager).addCallback(mediaProjectionCallbackCaptor.capture(), any())
+ }
+
+ @Test
+ fun init_flagEnabled_registerMediaProjectionManagerCallback() {
+ assertNotNull(mediaProjectionCallbackCaptor.value)
+ }
+
+ @Test
+ fun init_flagDisabled_noRegisterMediaProjectionManagerCallback() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ reset(mediaProjectionManager)
+
+ controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
+
+ verifyZeroInteractions(mediaProjectionManager)
+ }
+
+ @Test
+ fun registerSensitiveStateListener_singleListener() {
+ controller.registerSensitiveStateListener(listener1)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verify(listener1, times(2)).run()
+ }
+
+ @Test
+ fun registerSensitiveStateListener_multipleListeners() {
+ controller.registerSensitiveStateListener(listener1)
+ controller.registerSensitiveStateListener(listener2)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verify(listener1, times(2)).run()
+ verify(listener2, times(2)).run()
+ }
+
+ @Test
+ fun registerSensitiveStateListener_afterProjectionActive() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ controller.registerSensitiveStateListener(listener1)
+ verifyZeroInteractions(listener1)
+
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verify(listener1).run()
+ }
+
+ @Test
+ fun unregisterSensitiveStateListener_singleListener() {
+ controller.registerSensitiveStateListener(listener1)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verify(listener1, times(2)).run()
+
+ controller.unregisterSensitiveStateListener(listener1)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verifyNoMoreInteractions(listener1)
+ }
+
+ @Test
+ fun unregisterSensitiveStateListener_multipleListeners() {
+ controller.registerSensitiveStateListener(listener1)
+ controller.registerSensitiveStateListener(listener2)
+ controller.registerSensitiveStateListener(listener3)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verify(listener1, times(2)).run()
+ verify(listener2, times(2)).run()
+ verify(listener3, times(2)).run()
+
+ controller.unregisterSensitiveStateListener(listener1)
+ controller.unregisterSensitiveStateListener(listener2)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verifyNoMoreInteractions(listener1)
+ verifyNoMoreInteractions(listener2)
+ verify(listener3, times(4)).run()
+ }
+
+ @Test
+ fun isSensitiveStateActive_projectionInactive_false() {
+ assertFalse(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ fun isSensitiveStateActive_projectionActive_true() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ assertTrue(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ fun isSensitiveStateActive_projectionInactiveAfterActive_false() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ assertFalse(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ fun isSensitiveStateActive_projectionActiveAfterInactive_true() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ assertTrue(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ fun shouldProtectNotification_projectionInactive_false() {
+ val notificationEntry = mock(NotificationEntry::class.java)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
+ fun shouldProtectNotification_projectionActive_fgsNotification_false() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ val notificationEntry = mock(NotificationEntry::class.java)
+ val sbn = mock(StatusBarNotification::class.java)
+ val notification = mock(Notification::class.java)
+ `when`(notificationEntry.sbn).thenReturn(sbn)
+ `when`(sbn.notification).thenReturn(notification)
+ `when`(notification.isFgsOrUij).thenReturn(true)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
+ fun shouldProtectNotification_projectionActive_notFgsNotification_true() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ val notificationEntry = mock(NotificationEntry::class.java)
+ val sbn = mock(StatusBarNotification::class.java)
+ val notification = mock(Notification::class.java)
+ `when`(notificationEntry.sbn).thenReturn(sbn)
+ `when`(sbn.notification).thenReturn(notification)
+ `when`(notification.isFgsOrUij).thenReturn(false)
+
+ assertTrue(controller.shouldProtectNotification(notificationEntry))
+ }
+}