diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 13c3ededbcd7421bc67324cf8cdaa54ec1e2fe8f..7505372fe295e27f5fa6f0c9794488a481d91c70 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -6558,6 +6558,13 @@ public abstract class Context { @SystemApi public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation"; + /** + * Service to protect sensitive content during screen share. + * @hide + */ + public static final String SENSITIVE_CONTENT_PROTECTION_SERVICE = + "sensitive_content_protection_service"; + /** * Use with {@link #getSystemService(String)} to retrieve a * {@link android.provider.ContactKeysManager} to managing contact keys. diff --git a/core/java/android/view/ISensitiveContentProtectionManager.aidl b/core/java/android/view/ISensitiveContentProtectionManager.aidl new file mode 100644 index 0000000000000000000000000000000000000000..c135ae4b6a9550fcf84b5c213f3a4f9ef4c772c6 --- /dev/null +++ b/core/java/android/view/ISensitiveContentProtectionManager.aidl @@ -0,0 +1,35 @@ +/* + * 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 android.view; + +import android.os.IBinder; + +/** + * @hide + */ +oneway interface ISensitiveContentProtectionManager { + /** + * Block projection for a package's window when the window is showing sensitive content on + * the screen, the projection is unblocked when the window no more shows sensitive content. + * + * @param windowToken window where the content is shown. + * @param packageName package name. + * @param isShowingSensitiveContent whether the window is showing sensitive content. + */ + void setSensitiveContentProtection(in IBinder windowToken, in String packageName, + in boolean isShowingSensitiveContent); +} diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java index fff283dd41bcaf7677e9db3c57ccbe64b2d20e23..0904c47b61f235a7f36348846ffbefcf339d0f10 100644 --- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java +++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java @@ -18,14 +18,17 @@ package com.android.server; 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.internal.util.Preconditions.checkNotNull; +import static android.view.flags.Flags.sensitiveContentAppProtection; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManagerInternal; import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; +import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; import android.os.Trace; import android.os.UserHandle; @@ -35,29 +38,30 @@ import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.Log; +import android.view.ISensitiveContentProtectionManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wm.SensitiveContentPackages.PackageInfo; import com.android.server.wm.WindowManagerInternal; +import java.util.Objects; import java.util.Set; /** - * Service that monitors for notifications with sensitive content and protects content from screen - * sharing + * This service protects sensitive content from screen sharing. The service monitors notifications + * for sensitive content and protects from screen share. The service also protects sensitive + * content rendered on screen during screen share. */ public final class SensitiveContentProtectionManagerService extends SystemService { private static final String TAG = "SensitiveContentProtect"; private static final boolean DEBUG = false; - private static final boolean sNotificationProtectionEnabled = - sensitiveNotificationAppProtection(); @VisibleForTesting @Nullable NotificationListener mNotificationListener; - private @Nullable MediaProjectionManager mProjectionManager; - private @Nullable WindowManagerInternal mWindowManager; + @Nullable private MediaProjectionManager mProjectionManager; + @Nullable private WindowManagerInternal mWindowManager; final Object mSensitiveContentProtectionLock = new Object(); @@ -92,7 +96,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic public SensitiveContentProtectionManagerService(@NonNull Context context) { super(context); - if (sNotificationProtectionEnabled) { + if (sensitiveNotificationAppProtection()) { mNotificationListener = new NotificationListener(); } } @@ -110,14 +114,18 @@ public final class SensitiveContentProtectionManagerService extends SystemServic init(getContext().getSystemService(MediaProjectionManager.class), LocalServices.getService(WindowManagerInternal.class)); + if (sensitiveContentAppProtection()) { + publishBinderService(Context.SENSITIVE_CONTENT_PROTECTION_SERVICE, + new SensitiveContentProtectionManagerServiceBinder()); + } } @VisibleForTesting void init(MediaProjectionManager projectionManager, WindowManagerInternal windowManager) { if (DEBUG) Log.d(TAG, "init"); - checkNotNull(projectionManager, "Failed to get valid MediaProjectionManager"); - checkNotNull(windowManager, "Failed to get valid WindowManagerInternal"); + Objects.requireNonNull(projectionManager); + Objects.requireNonNull(windowManager); mProjectionManager = projectionManager; mWindowManager = windowManager; @@ -126,7 +134,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic // handler, delegate, and binder death recipient mProjectionManager.addCallback(mProjectionCallback, getContext().getMainThreadHandler()); - if (sNotificationProtectionEnabled) { + if (sensitiveNotificationAppProtection()) { try { mNotificationListener.registerAsSystemService( getContext(), @@ -144,7 +152,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic if (mProjectionManager != null) { mProjectionManager.removeCallback(mProjectionCallback); } - if (sNotificationProtectionEnabled) { + if (sensitiveNotificationAppProtection()) { try { mNotificationListener.unregisterAsSystemService(); } catch (RemoteException e) { @@ -169,7 +177,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic synchronized (mSensitiveContentProtectionLock) { mProjectionActive = true; - if (sNotificationProtectionEnabled) { + if (sensitiveNotificationAppProtection()) { updateAppsThatShouldBlockScreenCapture(); } } @@ -305,4 +313,74 @@ public final class SensitiveContentProtectionManagerService extends SystemServic } } } + + /** + * Block projection for a package window when the window is showing sensitive content on + * the screen, the projection is unblocked when window no more shows sensitive content. + * + * @param windowToken window where the content is shown. + * @param packageName package name. + * @param uid uid of the package. + * @param isShowingSensitiveContent whether the window is showing sensitive content. + */ + @VisibleForTesting + void setSensitiveContentProtection(IBinder windowToken, String packageName, int uid, + boolean isShowingSensitiveContent) { + synchronized (mSensitiveContentProtectionLock) { + if (!mProjectionActive) { + return; + } + if (DEBUG) { + Log.d(TAG, "setSensitiveContentProtection - windowToken=" + windowToken + + ", package=" + packageName + ", uid=" + uid + + ", isShowingSensitiveContent=" + isShowingSensitiveContent); + } + + // The window token distinguish this package from packages added for notifications. + PackageInfo packageInfo = new PackageInfo(packageName, uid, windowToken); + ArraySet<PackageInfo> packageInfos = new ArraySet<>(); + packageInfos.add(packageInfo); + if (isShowingSensitiveContent) { + mWindowManager.addBlockScreenCaptureForApps(packageInfos); + } else { + mWindowManager.removeBlockScreenCaptureForApps(packageInfos); + } + } + } + + private final class SensitiveContentProtectionManagerServiceBinder + extends ISensitiveContentProtectionManager.Stub { + private final PackageManagerInternal mPackageManagerInternal; + + SensitiveContentProtectionManagerServiceBinder() { + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + } + + public void setSensitiveContentProtection(IBinder windowToken, String packageName, + boolean isShowingSensitiveContent) { + Trace.beginSection( + "SensitiveContentProtectionManagerService.setSensitiveContentProtection"); + try { + int callingUid = Binder.getCallingUid(); + verifyCallingPackage(callingUid, packageName); + final long identity = Binder.clearCallingIdentity(); + try { + SensitiveContentProtectionManagerService.this.setSensitiveContentProtection( + windowToken, packageName, callingUid, isShowingSensitiveContent); + } finally { + Binder.restoreCallingIdentity(identity); + } + } finally { + Trace.endSection(); + } + } + + private void verifyCallingPackage(int callingUid, String callingPackage) { + if (mPackageManagerInternal.getPackageUid( + callingPackage, 0, UserHandle.getUserId(callingUid)) != callingUid) { + throw new SecurityException("Specified calling package [" + callingPackage + + "] does not match the calling uid " + callingUid); + } + } + } } diff --git a/services/core/java/com/android/server/wm/SensitiveContentPackages.java b/services/core/java/com/android/server/wm/SensitiveContentPackages.java index 5fe48d12d4988e312504d737daec050429f93c36..9ab11b8059a75e8ce3eaa6f7e3f10d27fdfe9322 100644 --- a/services/core/java/com/android/server/wm/SensitiveContentPackages.java +++ b/services/core/java/com/android/server/wm/SensitiveContentPackages.java @@ -16,9 +16,16 @@ package com.android.server.wm; +import static android.permission.flags.Flags.sensitiveNotificationAppProtection; +import static android.view.flags.Flags.sensitiveContentAppProtection; + import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; import android.util.ArraySet; +import com.android.internal.annotations.VisibleForTesting; + import java.io.PrintWriter; import java.util.Objects; @@ -29,12 +36,26 @@ import java.util.Objects; public class SensitiveContentPackages { private final ArraySet<PackageInfo> mProtectedPackages = new ArraySet<>(); - /** Returns {@code true} if package/uid pair should be blocked from screen capture */ - public boolean shouldBlockScreenCaptureForApp(String pkg, int uid) { + /** + * Returns {@code true} if package/uid/window combination should be blocked + * from screen capture. + */ + public boolean shouldBlockScreenCaptureForApp(String pkg, int uid, IBinder windowToken) { + if (!(sensitiveContentAppProtection() || sensitiveNotificationAppProtection())) { + return false; + } + for (int i = 0; i < mProtectedPackages.size(); i++) { PackageInfo info = mProtectedPackages.valueAt(i); if (info != null && info.mPkg.equals(pkg) && info.mUid == uid) { - return true; + // sensitiveContentAppProtection blocks specific window where sensitive content + // is rendered, whereas sensitiveNotificationAppProtection blocks the package + // if the package has a sensitive notification. + if ((sensitiveContentAppProtection() && windowToken == info.getWindowToken()) + || (sensitiveNotificationAppProtection() && info.getWindowToken() == null) + ) { + return true; + } } } return false; @@ -73,31 +94,50 @@ public class SensitiveContentPackages { */ public boolean clearBlockedApps() { if (mProtectedPackages.isEmpty()) { - // set was already empty return false; } mProtectedPackages.clear(); return true; } + /** + * @return the size of protected packages. + */ + @VisibleForTesting + public int size() { + return mProtectedPackages.size(); + } + void dump(PrintWriter pw) { final String innerPrefix = " "; pw.println("SensitiveContentPackages:"); pw.println(innerPrefix + "Packages that should block screen capture (" + mProtectedPackages.size() + "):"); for (PackageInfo info : mProtectedPackages) { - pw.println(innerPrefix + " package=" + info.mPkg + " uid=" + info.mUid); + pw.println(innerPrefix + " package=" + info.mPkg + " uid=" + info.mUid + + " windowToken=" + info.mWindowToken); } } - /** Helper class that represents a package/uid pair */ + /** + * Helper class that represents a package, uid, and window token combination, window token + * is set to block screen capture at window level. + */ public static class PackageInfo { - private String mPkg; - private int mUid; + private final String mPkg; + private final int mUid; + + @Nullable + private final IBinder mWindowToken; public PackageInfo(String pkg, int uid) { + this(pkg, uid, null); + } + + public PackageInfo(String pkg, int uid, IBinder windowToken) { this.mPkg = pkg; this.mUid = uid; + this.mWindowToken = windowToken; } @Override @@ -105,12 +145,30 @@ public class SensitiveContentPackages { if (this == o) return true; if (!(o instanceof PackageInfo)) return false; PackageInfo that = (PackageInfo) o; - return mUid == that.mUid && Objects.equals(mPkg, that.mPkg); + return mUid == that.mUid && Objects.equals(mPkg, that.mPkg) + && Objects.equals(mWindowToken, that.mWindowToken); } @Override public int hashCode() { - return Objects.hash(mPkg, mUid); + return Objects.hash(mPkg, mUid, mWindowToken); + } + + public IBinder getWindowToken() { + return mWindowToken; + } + + public int getUid() { + return mUid; + } + + public String getPkg() { + return mPkg; + } + + @Override + public String toString() { + return "package=" + mPkg + " uid=" + mUid + " windowToken=" + mWindowToken; } } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 934f8a73a19d92c5ec1eb41886ad5aa094cd515c..0e1641a0edbd0f4602a0172272518db0d3c66582 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1897,11 +1897,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return true; } - if (android.permission.flags.Flags.sensitiveNotificationAppProtection()) { - if (mWmService.mSensitiveContentPackages - .shouldBlockScreenCaptureForApp(getOwningPackage(), getOwningUid())) { - return true; - } + // block screen capture to protect sensitive notifications or content on the screen. + if (mWmService.mSensitiveContentPackages.shouldBlockScreenCaptureForApp( + getOwningPackage(), getOwningUid(), getWindowToken())) { + return true; } return !DevicePolicyCache.getInstance().isScreenCaptureAllowed(mShowUserId); diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java new file mode 100644 index 0000000000000000000000000000000000000000..dad36e787360d22ea9b56368a50f235ecc3a3ed3 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java @@ -0,0 +1,145 @@ +/* + * 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.server; + +import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.media.projection.MediaProjectionInfo; +import android.media.projection.MediaProjectionManager; +import android.os.Binder; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; +import android.testing.TestableContext; +import android.util.ArraySet; + +import androidx.test.filters.SmallTest; + +import com.android.server.wm.SensitiveContentPackages.PackageInfo; +import com.android.server.wm.WindowManagerInternal; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RequiresFlagsEnabled(FLAG_SENSITIVE_CONTENT_APP_PROTECTION) +/** + * Test {@link SensitiveContentProtectionManagerService} for sensitive on screen content + * protection, the service protects sensitive content during screen share. + */ +public class SensitiveContentProtectionManagerServiceContentTest { + private final PackageInfo mPackageInfo = + new PackageInfo("test.package", 12345, new Binder()); + private SensitiveContentProtectionManagerService mSensitiveContentProtectionManagerService; + private MediaProjectionManager.Callback mMediaPorjectionCallback; + + @Mock private WindowManagerInternal mWindowManager; + @Mock private MediaProjectionManager mProjectionManager; + @Mock private MediaProjectionInfo mMediaProjectionInfo; + + @Captor + private ArgumentCaptor<MediaProjectionManager.Callback> mMediaProjectionCallbackCaptor; + @Captor + private ArgumentCaptor<ArraySet<PackageInfo>> mPackageInfoCaptor; + + @Rule + public final TestableContext mContext = + new TestableContext(getInstrumentation().getTargetContext(), null); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mSensitiveContentProtectionManagerService = + new SensitiveContentProtectionManagerService(mContext); + mSensitiveContentProtectionManagerService.init(mProjectionManager, mWindowManager); + verify(mProjectionManager).addCallback(mMediaProjectionCallbackCaptor.capture(), any()); + mMediaPorjectionCallback = mMediaProjectionCallbackCaptor.getValue(); + } + + @Test + public void testBlockAppWindowForScreenCapture() { + mMediaPorjectionCallback.onStart(mMediaProjectionInfo); + mSensitiveContentProtectionManagerService.setSensitiveContentProtection( + mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); + verify(mWindowManager, atLeast(1)) + .addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); + assertThat(mPackageInfoCaptor.getValue()).containsExactly(mPackageInfo); + } + + @Test + public void testUnblockAppWindowForScreenCapture() { + mMediaPorjectionCallback.onStart(mMediaProjectionInfo); + mSensitiveContentProtectionManagerService.setSensitiveContentProtection( + mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), false); + verify(mWindowManager).removeBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); + assertThat(mPackageInfoCaptor.getValue()).containsExactly(mPackageInfo); + } + + @Test + public void testAppWindowIsUnblockedBeforeScreenCapture() { + // when screen sharing is not active, no app window should be blocked. + mSensitiveContentProtectionManagerService.setSensitiveContentProtection( + mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); + verifyZeroInteractions(mWindowManager); + } + + @Test + public void testAppWindowsAreUnblockedOnScreenCaptureEnd() { + mMediaPorjectionCallback.onStart(mMediaProjectionInfo); + mSensitiveContentProtectionManagerService.setSensitiveContentProtection( + mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); + // when screen sharing ends, all blocked app windows should be cleared. + mMediaPorjectionCallback.onStop(mMediaProjectionInfo); + verify(mWindowManager).clearBlockedApps(); + } + + @Test + public void testDeveloperOptionDisableFeature() { + mockDisabledViaDeveloperOption(); + mMediaProjectionCallbackCaptor.getValue().onStart(mMediaProjectionInfo); + mSensitiveContentProtectionManagerService.setSensitiveContentProtection( + mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); + verifyZeroInteractions(mWindowManager); + } + + private void mockDisabledViaDeveloperOption() { + Settings.Global.putInt( + mContext.getContentResolver(), + Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, + 1); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java similarity index 99% rename from services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java rename to services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java index c298d516c89e6f64f843a09e6c757928ba3bb019..08050a9b30e0fd0cca16ce745499f92f1ae3314e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java @@ -16,10 +16,10 @@ package com.android.server; -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; - import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doCallRealMethod; @@ -67,7 +67,11 @@ import java.util.Set; @RunWith(AndroidTestingRunner.class) @RunWithLooper @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION) -public class SensitiveContentProtectionManagerServiceTest { +/** + * Test {@link SensitiveContentProtectionManagerService} for sensitive notification protection, + * the service protects sensitive content during screen share. + */ +public class SensitiveContentProtectionManagerServiceNotificationTest { private static final String NOTIFICATION_KEY_1 = "com.android.server.notification.TEST_KEY_1"; private static final String NOTIFICATION_KEY_2 = "com.android.server.notification.TEST_KEY_2"; diff --git a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java index 298637266cc3bda0e5384d1bb83eff43d14df2a6..824b8d7f6727ce9583436967b9115de1883864dd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java @@ -16,10 +16,18 @@ package com.android.server.wm; +import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION; +import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.os.Binder; +import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.ArraySet; import androidx.test.filters.SmallTest; @@ -27,6 +35,7 @@ import androidx.test.filters.SmallTest; import com.android.server.wm.SensitiveContentPackages.PackageInfo; import org.junit.After; +import org.junit.Rule; import org.junit.Test; import java.util.Set; @@ -44,23 +53,30 @@ public class SensitiveContentPackagesTest { private static final int APP_UID_1 = 5; private static final int APP_UID_2 = 6; - private static final int APP_UID_3 = 7; + private static final IBinder WINDOW_TOKEN_1 = new Binder(); + private static final IBinder WINDOW_TOKEN_2 = new Binder(); private final SensitiveContentPackages mSensitiveContentPackages = new SensitiveContentPackages(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @After public void tearDown() { mSensitiveContentPackages.clearBlockedApps(); } + private boolean shouldBlockScreenCaptureForApp(String pkg, int uid, IBinder windowToken) { + return mSensitiveContentPackages.shouldBlockScreenCaptureForApp(pkg, uid, windowToken); + } + @Test - public void addBlockScreenCaptureForApps() { + @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION) + public void shouldBlockScreenCaptureForNotificationApps() { ArraySet<PackageInfo> blockedApps = new ArraySet( Set.of(new PackageInfo(APP_PKG_1, APP_UID_1), - new PackageInfo(APP_PKG_1, APP_UID_2), - new PackageInfo(APP_PKG_2, APP_UID_1), new PackageInfo(APP_PKG_2, APP_UID_2) )); @@ -68,17 +84,50 @@ public class SensitiveContentPackagesTest { assertTrue(modified); - assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1)); - assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3)); + assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1, WINDOW_TOKEN_1)); + assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1, WINDOW_TOKEN_2)); + assertFalse(shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2, WINDOW_TOKEN_2)); + assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_1)); + assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_2)); + assertFalse(shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1, WINDOW_TOKEN_2)); + } + + @Test + @RequiresFlagsEnabled(FLAG_SENSITIVE_CONTENT_APP_PROTECTION) + public void shouldBlockScreenCaptureForSensitiveContentOnScreenApps() { + ArraySet<PackageInfo> blockedApps = new ArraySet( + Set.of(new PackageInfo(APP_PKG_1, APP_UID_1, WINDOW_TOKEN_1), + new PackageInfo(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_2) + )); + + boolean modified = mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps); + assertTrue(modified); + + assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1, WINDOW_TOKEN_1)); + assertFalse(shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1, WINDOW_TOKEN_2)); + + assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_2)); + assertFalse(shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_1)); + } + + @Test + @RequiresFlagsEnabled( + {FLAG_SENSITIVE_CONTENT_APP_PROTECTION, FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION}) + public void shouldBlockScreenCaptureForApps() { + ArraySet<PackageInfo> blockedApps = new ArraySet( + Set.of(new PackageInfo(APP_PKG_1, APP_UID_1, WINDOW_TOKEN_1), + new PackageInfo(APP_PKG_1, APP_UID_1), + new PackageInfo(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_2) + )); + + boolean modified = mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps); + assertTrue(modified); - assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1)); - assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3)); + assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1, WINDOW_TOKEN_1)); + assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1, WINDOW_TOKEN_2)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3)); + assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_2)); + assertFalse(shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_1)); } @Test @@ -92,20 +141,8 @@ public class SensitiveContentPackagesTest { mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps); boolean modified = mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps); - assertFalse(modified); - - assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1)); - assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3)); - - assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1)); - assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3)); - - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3)); + assertTrue(mSensitiveContentPackages.size() == 4); } @Test @@ -121,65 +158,28 @@ public class SensitiveContentPackagesTest { boolean modified = mSensitiveContentPackages .addBlockScreenCaptureForApps( new ArraySet(Set.of(new PackageInfo(APP_PKG_3, APP_UID_1)))); - assertTrue(modified); - - assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1)); - assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3)); - - assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1)); - assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3)); - - assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3)); + assertTrue(mSensitiveContentPackages.size() == 5); } @Test public void clearBlockedApps() { ArraySet<PackageInfo> blockedApps = new ArraySet( Set.of(new PackageInfo(APP_PKG_1, APP_UID_1), - new PackageInfo(APP_PKG_1, APP_UID_2), - new PackageInfo(APP_PKG_2, APP_UID_1), - new PackageInfo(APP_PKG_2, APP_UID_2) + new PackageInfo(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_2) )); mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps); boolean modified = mSensitiveContentPackages.clearBlockedApps(); assertTrue(modified); - - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3)); - - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3)); - - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3)); + assertTrue(mSensitiveContentPackages.size() == 0); } @Test public void clearBlockedApps_alreadyEmpty() { boolean modified = mSensitiveContentPackages.clearBlockedApps(); - assertFalse(modified); - - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3)); - - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3)); - - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2)); - assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3)); + assertTrue(mSensitiveContentPackages.size() == 0); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index c972e518e46dcc869a1e0bcb627115d25760d626..a0e64bf94393df8f4791196167f7efa165020c58 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -21,9 +21,11 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_OWN_FOCUS; import static android.view.Display.INVALID_DISPLAY; +import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY; @@ -80,6 +82,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; import android.util.ArraySet; import android.util.MergedConfiguration; import android.view.ContentRecordingSession; @@ -840,7 +843,8 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test - public void addBlockScreenCaptureForApps() { + @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION) + public void shouldBlockScreenCaptureForNotificationApps() { String testPackage = "test"; int ownerId1 = 20; int ownerId2 = 21; @@ -850,12 +854,59 @@ public class WindowManagerServiceTests extends WindowTestsBase { WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); wmInternal.addBlockScreenCaptureForApps(blockedPackages); + verify(mWm).refreshScreenCaptureDisabled(); + // window client token parameter is ignored for this feature. assertTrue(mWm.mSensitiveContentPackages - .shouldBlockScreenCaptureForApp(testPackage, ownerId1)); + .shouldBlockScreenCaptureForApp(testPackage, ownerId1, new Binder())); assertFalse(mWm.mSensitiveContentPackages - .shouldBlockScreenCaptureForApp(testPackage, ownerId2)); + .shouldBlockScreenCaptureForApp(testPackage, ownerId2, new Binder())); + } + + @Test + @RequiresFlagsEnabled(FLAG_SENSITIVE_CONTENT_APP_PROTECTION) + public void shouldBlockScreenCaptureForSensitiveContentOnScreenApps() { + String testPackage = "test"; + int ownerId1 = 20; + final IBinder windowClientToken = new Binder("window client token"); + PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1, windowClientToken); + ArraySet<PackageInfo> blockedPackages = new ArraySet(); + blockedPackages.add(blockedPackage); + + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + wmInternal.addBlockScreenCaptureForApps(blockedPackages); verify(mWm).refreshScreenCaptureDisabled(); + + assertTrue(mWm.mSensitiveContentPackages + .shouldBlockScreenCaptureForApp(testPackage, ownerId1, windowClientToken)); + assertFalse(mWm.mSensitiveContentPackages + .shouldBlockScreenCaptureForApp(testPackage, ownerId1, new Binder())); + } + + @Test + @RequiresFlagsEnabled( + {FLAG_SENSITIVE_CONTENT_APP_PROTECTION, FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION}) + public void shouldBlockScreenCaptureForApps() { + String testPackage = "test"; + int ownerId1 = 20; + int ownerId2 = 21; + final IBinder windowClientToken = new Binder("window client token"); + PackageInfo blockedPackage1 = new PackageInfo(testPackage, ownerId1); + PackageInfo blockedPackage2 = new PackageInfo(testPackage, ownerId1, windowClientToken); + ArraySet<PackageInfo> blockedPackages = new ArraySet(); + blockedPackages.add(blockedPackage1); + blockedPackages.add(blockedPackage2); + + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + wmInternal.addBlockScreenCaptureForApps(blockedPackages); + verify(mWm).refreshScreenCaptureDisabled(); + + assertTrue(mWm.mSensitiveContentPackages + .shouldBlockScreenCaptureForApp(testPackage, ownerId1, windowClientToken)); + assertTrue(mWm.mSensitiveContentPackages + .shouldBlockScreenCaptureForApp(testPackage, ownerId1, new Binder())); + assertFalse(mWm.mSensitiveContentPackages + .shouldBlockScreenCaptureForApp(testPackage, ownerId2, new Binder())); } @Test @@ -891,10 +942,6 @@ public class WindowManagerServiceTests extends WindowTestsBase { wmInternal.addBlockScreenCaptureForApps(blockedPackages); wmInternal.addBlockScreenCaptureForApps(blockedPackages2); - assertTrue(mWm.mSensitiveContentPackages - .shouldBlockScreenCaptureForApp(testPackage, ownerId1)); - assertTrue(mWm.mSensitiveContentPackages - .shouldBlockScreenCaptureForApp(testPackage, ownerId2)); verify(mWm, times(2)).refreshScreenCaptureDisabled(); } @@ -909,9 +956,6 @@ public class WindowManagerServiceTests extends WindowTestsBase { WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); wmInternal.addBlockScreenCaptureForApps(blockedPackages); wmInternal.clearBlockedApps(); - - assertFalse(mWm.mSensitiveContentPackages - .shouldBlockScreenCaptureForApp(testPackage, ownerId1)); verify(mWm, times(2)).refreshScreenCaptureDisabled(); }