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

Commit 3bb6b83a authored by Richard MacGregor's avatar Richard MacGregor Committed by Android (Google) Code Review
Browse files

Merge "Update projection exemption calculation" into main

parents 6739c333 dbb5254f
Loading
Loading
Loading
Loading
+43 −21
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.statusbar.policy;

import static android.permission.flags.Flags.sensitiveNotificationAppProtection;
import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;

import static com.android.server.notification.Flags.screenshareNotificationHiding;
@@ -23,6 +24,7 @@ import static com.android.server.notification.Flags.screenshareNotificationHidin
import android.annotation.MainThread;
import android.app.IActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.ExecutorContentObserver;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
@@ -33,6 +35,9 @@ import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -52,6 +57,7 @@ public class SensitiveNotificationProtectionControllerImpl
        implements SensitiveNotificationProtectionController {
    private static final String LOG_TAG = "SNPC";
    private final SensitiveNotificationProtectionControllerLogger mLogger;
    private final PackageManager mPackageManager;
    private final ArraySet<String> mExemptPackages = new ArraySet<>();
    private final ListenerSet<Runnable> mListeners = new ListenerSet<>();
    private volatile MediaProjectionInfo mProjection;
@@ -64,17 +70,7 @@ public class SensitiveNotificationProtectionControllerImpl
                public void onStart(MediaProjectionInfo info) {
                    Trace.beginSection("SNPC.onProjectionStart");
                    try {
                        if (mDisableScreenShareProtections) {
                            Log.w(LOG_TAG,
                                    "Screen share protections disabled, ignoring projectionstart");
                            mLogger.logProjectionStart(false, info.getPackageName());
                            return;
                        }

                        // Only enable sensitive content protection if sharing full screen
                        // Launch cookie only set (non-null) if sharing single app/task
                        updateProjectionStateAndNotifyListeners(
                                (info.getLaunchCookie() == null) ? info : null);
                        updateProjectionStateAndNotifyListeners(info);
                        mLogger.logProjectionStart(isSensitiveStateActive(), info.getPackageName());
                    } finally {
                        Trace.endSection();
@@ -99,10 +95,12 @@ public class SensitiveNotificationProtectionControllerImpl
            GlobalSettings settings,
            MediaProjectionManager mediaProjectionManager,
            IActivityManager activityManager,
            PackageManager packageManager,
            @Main Handler mainHandler,
            @Background Executor bgExecutor,
            SensitiveNotificationProtectionControllerLogger logger) {
        mLogger = logger;
        mPackageManager = packageManager;

        if (!screenshareNotificationHiding()) {
            return;
@@ -168,7 +166,7 @@ public class SensitiveNotificationProtectionControllerImpl
        mExemptPackages.addAll(exemptPackages);

        if (mProjection != null) {
            mListeners.forEach(Runnable::run);
            updateProjectionStateAndNotifyListeners(mProjection);
        }
    }

@@ -177,13 +175,13 @@ public class SensitiveNotificationProtectionControllerImpl
     * listeners
     */
    @MainThread
    private void updateProjectionStateAndNotifyListeners(MediaProjectionInfo info) {
    private void updateProjectionStateAndNotifyListeners(@Nullable MediaProjectionInfo info) {
        Assert.isMainThread();
        // capture previous state
        boolean wasSensitive = isSensitiveStateActive();

        // update internal state
        mProjection = info;
        mProjection = getNonExemptProjectionInfo(info);

        // if either previous or new state is sensitive, notify listeners.
        if (wasSensitive || isSensitiveStateActive()) {
@@ -191,6 +189,36 @@ public class SensitiveNotificationProtectionControllerImpl
        }
    }

    private MediaProjectionInfo getNonExemptProjectionInfo(@Nullable MediaProjectionInfo info) {
        if (mDisableScreenShareProtections) {
            Log.w(LOG_TAG, "Screen share protections disabled");
            return null;
        } else if (info != null && mExemptPackages.contains(info.getPackageName())) {
            Log.w(LOG_TAG, "Screen share protections exempt for package " + info.getPackageName());
            return null;
        } else if (info != null && canRecordSensitiveContent(info.getPackageName())) {
            Log.w(LOG_TAG, "Screen share protections exempt for package " + info.getPackageName()
                    + " via permission");
            return null;
        } else if (info != null && info.getLaunchCookie() != null) {
            // Only enable sensitive content protection if sharing full screen
            // Launch cookie only set (non-null) if sharing single app/task
            Log.w(LOG_TAG, "Screen share protections exempt for single app screenshare");
            return null;
        }
        return info;
    }

    private boolean canRecordSensitiveContent(@NonNull String packageName) {
        // RECORD_SENSITIVE_CONTENT is flagged api on sensitiveNotificationAppProtection
        if (sensitiveNotificationAppProtection()) {
            return mPackageManager.checkPermission(
                            android.Manifest.permission.RECORD_SENSITIVE_CONTENT, packageName)
                    == PackageManager.PERMISSION_GRANTED;
        }
        return false;
    }

    @Override
    public void registerSensitiveStateListener(Runnable onSensitiveStateChanged) {
        mListeners.addIfAbsent(onSensitiveStateChanged);
@@ -201,15 +229,9 @@ public class SensitiveNotificationProtectionControllerImpl
        mListeners.remove(onSensitiveStateChanged);
    }

    // TODO(b/323396693): opportunity for optimization
    @Override
    public boolean isSensitiveStateActive() {
        MediaProjectionInfo projection = mProjection;
        if (projection == null) {
            return false;
        }

        return !mExemptPackages.contains(projection.getPackageName());
        return mProjection != null;
    }

    @Override
+3 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.policy

import android.app.IActivityManager
import android.content.pm.PackageManager
import android.media.projection.MediaProjectionManager
import android.os.Handler
import android.platform.test.annotations.DisableFlags
@@ -44,6 +45,7 @@ class SensitiveNotificationProtectionControllerFlagDisabledTest : SysuiTestCase(
    @Mock private lateinit var handler: Handler
    @Mock private lateinit var activityManager: IActivityManager
    @Mock private lateinit var mediaProjectionManager: MediaProjectionManager
    @Mock private lateinit var packageManager: PackageManager
    private lateinit var controller: SensitiveNotificationProtectionControllerImpl

    @Before
@@ -56,6 +58,7 @@ class SensitiveNotificationProtectionControllerFlagDisabledTest : SysuiTestCase(
                FakeGlobalSettings(),
                mediaProjectionManager,
                activityManager,
                packageManager,
                handler,
                FakeExecutor(FakeSystemClock()),
                logger
+79 −0
Original line number Diff line number Diff line
@@ -25,9 +25,14 @@ import android.app.Notification.VISIBILITY_PUBLIC
import android.app.NotificationChannel
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE
import android.content.pm.PackageManager
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
import android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION
import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.RequiresFlagsDisabled
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -48,9 +53,11 @@ import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.times
@@ -64,10 +71,13 @@ import org.mockito.MockitoAnnotations
@RunWithLooper
@EnableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
    @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()

    private val logger = SensitiveNotificationProtectionControllerLogger(logcatLogBuffer())

    @Mock private lateinit var activityManager: IActivityManager
    @Mock private lateinit var mediaProjectionManager: MediaProjectionManager
    @Mock private lateinit var packageManager: PackageManager
    @Mock private lateinit var mediaProjectionInfo: MediaProjectionInfo
    @Mock private lateinit var listener1: Runnable
    @Mock private lateinit var listener2: Runnable
@@ -87,6 +97,9 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
        whenever(activityManager.bugreportWhitelistedPackages)
            .thenReturn(listOf(BUGREPORT_PACKAGE_NAME))

        whenever(packageManager.checkPermission(anyString(), anyString()))
            .thenReturn(PackageManager.PERMISSION_DENIED)

        executor = FakeExecutor(FakeSystemClock())
        globalSettings = FakeGlobalSettings()
        controller =
@@ -95,6 +108,7 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
                globalSettings,
                mediaProjectionManager,
                activityManager,
                packageManager,
                mockExecutorHandler(executor),
                executor,
                logger
@@ -236,6 +250,36 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
        assertFalse(controller.isSensitiveStateActive)
    }

    @Test
    @RequiresFlagsDisabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
    fun isSensitiveStateActive_projectionActive_permissionExempt_flagDisabled_true() {
        whenever(
                packageManager.checkPermission(
                    android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
                    mediaProjectionInfo.packageName
                )
            )
            .thenReturn(PackageManager.PERMISSION_GRANTED)
        mediaProjectionCallback.onStart(mediaProjectionInfo)

        assertTrue(controller.isSensitiveStateActive)
    }

    @Test
    @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
    fun isSensitiveStateActive_projectionActive_permissionExempt_false() {
        whenever(
                packageManager.checkPermission(
                    android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
                    mediaProjectionInfo.packageName
                )
            )
            .thenReturn(PackageManager.PERMISSION_GRANTED)
        mediaProjectionCallback.onStart(mediaProjectionInfo)

        assertFalse(controller.isSensitiveStateActive)
    }

    @Test
    fun isSensitiveStateActive_projectionActive_bugReportHandlerExempt_false() {
        whenever(mediaProjectionInfo.packageName).thenReturn(BUGREPORT_PACKAGE_NAME)
@@ -308,6 +352,40 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
        assertFalse(controller.shouldProtectNotification(notificationEntry))
    }

    @Test
    @RequiresFlagsDisabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
    fun shouldProtectNotification_projectionActive_permissionExempt_flagDisabled_true() {
        whenever(
                packageManager.checkPermission(
                    android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
                    mediaProjectionInfo.packageName
                )
            )
            .thenReturn(PackageManager.PERMISSION_GRANTED)
        mediaProjectionCallback.onStart(mediaProjectionInfo)

        val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)

        assertTrue(controller.shouldProtectNotification(notificationEntry))
    }

    @Test
    @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
    fun shouldProtectNotification_projectionActive_permissionExempt_false() {
        whenever(
                packageManager.checkPermission(
                    android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
                    mediaProjectionInfo.packageName
                )
            )
            .thenReturn(PackageManager.PERMISSION_GRANTED)
        mediaProjectionCallback.onStart(mediaProjectionInfo)

        val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)

        assertFalse(controller.shouldProtectNotification(notificationEntry))
    }

    @Test
    fun shouldProtectNotification_projectionActive_bugReportHandlerExempt_false() {
        whenever(mediaProjectionInfo.packageName).thenReturn(BUGREPORT_PACKAGE_NAME)
@@ -327,6 +405,7 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {

        assertFalse(controller.shouldProtectNotification(notificationEntry))
    }

    @Test
    fun shouldProtectNotification_projectionActive_publicNotification_false() {
        mediaProjectionCallback.onStart(mediaProjectionInfo)