Loading packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java +29 −7 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; import android.os.Handler; import android.os.Trace; import android.service.notification.StatusBarNotification; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.SysUISingleton; Loading @@ -46,8 +47,9 @@ public class SensitiveNotificationProtectionControllerImpl public void onStart(MediaProjectionInfo info) { Trace.beginSection( "SNPC.onProjectionStart"); mProjection = info; mListeners.forEach(Runnable::run); // Only enable sensitive content protection if sharing full screen // Launch cookie only set (non-null) if sharing single app/task updateProjectionState((info.getLaunchCookie() == null) ? info : null); Trace.endSection(); } Loading @@ -55,10 +57,22 @@ public class SensitiveNotificationProtectionControllerImpl public void onStop(MediaProjectionInfo info) { Trace.beginSection( "SNPC.onProjectionStop"); mProjection = null; mListeners.forEach(Runnable::run); updateProjectionState(null); Trace.endSection(); } private void updateProjectionState(MediaProjectionInfo info) { // capture previous state boolean wasSensitive = isSensitiveStateActive(); // update internal state mProjection = info; // if either previous or new state is sensitive, notify listeners. if (wasSensitive || isSensitiveStateActive()) { mListeners.forEach(Runnable::run); } } }; @Inject Loading Loading @@ -86,7 +100,6 @@ public class SensitiveNotificationProtectionControllerImpl 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; } Loading @@ -96,9 +109,18 @@ public class SensitiveNotificationProtectionControllerImpl return false; } MediaProjectionInfo projection = mProjection; if (projection == null) { 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(); StatusBarNotification sbn = entry.getSbn(); if (sbn.getNotification().isFgsOrUij()) { return !sbn.getPackageName().equals(projection.getPackageName()); } return true; } } packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt +64 −9 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy import android.app.ActivityOptions import android.app.Notification import android.media.projection.MediaProjectionInfo import android.media.projection.MediaProjectionManager Loading Loading @@ -69,6 +70,8 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) mSetFlagsRule.enableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING) setShareFullScreen() controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler) // Obtain useful MediaProjectionCallback Loading Loading @@ -194,6 +197,14 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { assertTrue(controller.isSensitiveStateActive) } @Test fun isSensitiveStateActive_projectionActive_singleActivity_false() { setShareSingleApp() mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) assertFalse(controller.isSensitiveStateActive) } @Test fun shouldProtectNotification_projectionInactive_false() { val notificationEntry = mock(NotificationEntry::class.java) Loading @@ -202,30 +213,74 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { } @Test fun shouldProtectNotification_projectionActive_fgsNotification_false() { fun shouldProtectNotification_projectionActive_singleActivity_false() { setShareSingleApp() 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) val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME) assertFalse(controller.shouldProtectNotification(notificationEntry)) } @Test fun shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_false() { mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) val notificationEntry = setupFgsNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) assertFalse(controller.shouldProtectNotification(notificationEntry)) } @Test fun shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_true() { mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) val notificationEntry = setupFgsNotificationEntry(TEST_PACKAGE_NAME) assertTrue(controller.shouldProtectNotification(notificationEntry)) } @Test fun shouldProtectNotification_projectionActive_notFgsNotification_true() { mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) assertTrue(controller.shouldProtectNotification(notificationEntry)) } private fun setShareFullScreen() { `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME) `when`(mediaProjectionInfo.launchCookie).thenReturn(null) } private fun setShareSingleApp() { `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME) `when`(mediaProjectionInfo.launchCookie).thenReturn(ActivityOptions.LaunchCookie()) } private fun setupNotificationEntry( packageName: String, isFgs: Boolean = false ): NotificationEntry { 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.packageName).thenReturn(packageName) `when`(sbn.notification).thenReturn(notification) `when`(notification.isFgsOrUij).thenReturn(false) `when`(notification.isFgsOrUij).thenReturn(isFgs) assertTrue(controller.shouldProtectNotification(notificationEntry)) return notificationEntry } private fun setupFgsNotificationEntry(packageName: String): NotificationEntry { return setupNotificationEntry(packageName, /* isFgs= */ true) } companion object { private const val TEST_PROJECTION_PACKAGE_NAME = "com.android.systemui.statusbar.policy.projectionpackage" private const val TEST_PACKAGE_NAME = "com.android.systemui.statusbar.policy.testpackage" } } Loading
packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java +29 −7 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; import android.os.Handler; import android.os.Trace; import android.service.notification.StatusBarNotification; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.SysUISingleton; Loading @@ -46,8 +47,9 @@ public class SensitiveNotificationProtectionControllerImpl public void onStart(MediaProjectionInfo info) { Trace.beginSection( "SNPC.onProjectionStart"); mProjection = info; mListeners.forEach(Runnable::run); // Only enable sensitive content protection if sharing full screen // Launch cookie only set (non-null) if sharing single app/task updateProjectionState((info.getLaunchCookie() == null) ? info : null); Trace.endSection(); } Loading @@ -55,10 +57,22 @@ public class SensitiveNotificationProtectionControllerImpl public void onStop(MediaProjectionInfo info) { Trace.beginSection( "SNPC.onProjectionStop"); mProjection = null; mListeners.forEach(Runnable::run); updateProjectionState(null); Trace.endSection(); } private void updateProjectionState(MediaProjectionInfo info) { // capture previous state boolean wasSensitive = isSensitiveStateActive(); // update internal state mProjection = info; // if either previous or new state is sensitive, notify listeners. if (wasSensitive || isSensitiveStateActive()) { mListeners.forEach(Runnable::run); } } }; @Inject Loading Loading @@ -86,7 +100,6 @@ public class SensitiveNotificationProtectionControllerImpl 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; } Loading @@ -96,9 +109,18 @@ public class SensitiveNotificationProtectionControllerImpl return false; } MediaProjectionInfo projection = mProjection; if (projection == null) { 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(); StatusBarNotification sbn = entry.getSbn(); if (sbn.getNotification().isFgsOrUij()) { return !sbn.getPackageName().equals(projection.getPackageName()); } return true; } }
packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt +64 −9 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy import android.app.ActivityOptions import android.app.Notification import android.media.projection.MediaProjectionInfo import android.media.projection.MediaProjectionManager Loading Loading @@ -69,6 +70,8 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) mSetFlagsRule.enableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING) setShareFullScreen() controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler) // Obtain useful MediaProjectionCallback Loading Loading @@ -194,6 +197,14 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { assertTrue(controller.isSensitiveStateActive) } @Test fun isSensitiveStateActive_projectionActive_singleActivity_false() { setShareSingleApp() mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) assertFalse(controller.isSensitiveStateActive) } @Test fun shouldProtectNotification_projectionInactive_false() { val notificationEntry = mock(NotificationEntry::class.java) Loading @@ -202,30 +213,74 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { } @Test fun shouldProtectNotification_projectionActive_fgsNotification_false() { fun shouldProtectNotification_projectionActive_singleActivity_false() { setShareSingleApp() 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) val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME) assertFalse(controller.shouldProtectNotification(notificationEntry)) } @Test fun shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_false() { mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) val notificationEntry = setupFgsNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) assertFalse(controller.shouldProtectNotification(notificationEntry)) } @Test fun shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_true() { mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) val notificationEntry = setupFgsNotificationEntry(TEST_PACKAGE_NAME) assertTrue(controller.shouldProtectNotification(notificationEntry)) } @Test fun shouldProtectNotification_projectionActive_notFgsNotification_true() { mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo) val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) assertTrue(controller.shouldProtectNotification(notificationEntry)) } private fun setShareFullScreen() { `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME) `when`(mediaProjectionInfo.launchCookie).thenReturn(null) } private fun setShareSingleApp() { `when`(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME) `when`(mediaProjectionInfo.launchCookie).thenReturn(ActivityOptions.LaunchCookie()) } private fun setupNotificationEntry( packageName: String, isFgs: Boolean = false ): NotificationEntry { 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.packageName).thenReturn(packageName) `when`(sbn.notification).thenReturn(notification) `when`(notification.isFgsOrUij).thenReturn(false) `when`(notification.isFgsOrUij).thenReturn(isFgs) assertTrue(controller.shouldProtectNotification(notificationEntry)) return notificationEntry } private fun setupFgsNotificationEntry(packageName: String): NotificationEntry { return setupNotificationEntry(packageName, /* isFgs= */ true) } companion object { private const val TEST_PROJECTION_PACKAGE_NAME = "com.android.systemui.statusbar.policy.projectionpackage" private const val TEST_PACKAGE_NAME = "com.android.systemui.statusbar.policy.testpackage" } }