Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d521c63f authored by Richard MacGregor's avatar Richard MacGregor
Browse files

Add sensitive notification protection exemptions

- Add exemption for FGS notifications from the app that started
  projection
- Add exemption for single activity/task projection session

Bug: 316955208
Bug: 316955346
Test: atest SensitiveNotificationProtectionControllerTest
Flag: ACONFIG com.android.systemui.screenshare_notification_hiding DISABLED
Change-Id: I4dfb3af641f0ce6a89fa95e798cab9a63af96beb
parent 3459cd0e
Loading
Loading
Loading
Loading
+29 −7
Original line number Diff line number Diff line
@@ -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;
@@ -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();
                }

@@ -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
@@ -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;
    }

@@ -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;
    }
}
+64 −9
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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)
@@ -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"
    }
}