diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index bcf423537bb66..85e33c3674d0b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -14127,6 +14127,32 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val */ public static final String DISABLE_SECURE_WINDOWS = "disable_secure_windows"; + /** + * Whether user-initiated screenshots should capture secure windows. + * + *

When this setting is set to a non-zero value, screenshots capture content in windows + * with {@link android.view.WindowManager.LayoutParams#FLAG_SECURE}. Every other + * FLAG_SECURE behaviour, such as recents thumbnails and casting, is left unchanged. + * + * @hide + */ + public static final String FORCE_SCREENSHOT_SECURE_WINDOWS = + "force_screenshot_secure_windows"; + + /** + * Whether the built-in screen recorder should capture secure windows. + * + *

When this setting is set to a non-zero value, recordings started from the system + * screen recorder capture content in windows with + * {@link android.view.WindowManager.LayoutParams#FLAG_SECURE}. Recordings made by other + * apps through {@link android.media.projection.MediaProjection} are left unchanged, as is + * every other FLAG_SECURE behaviour. + * + * @hide + */ + public static final String FORCE_SCREEN_RECORD_SECURE_WINDOWS = + "force_screen_record_secure_windows"; + /** * Controls if the adaptive authentication feature should be disabled, which * will attempt to lock the device after a number of consecutive authentication diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index ae8d064e68d9c..f8a778dbb0aa7 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -706,6 +706,8 @@ public class SettingsBackupTest { Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES, Settings.Secure.ENABLED_PRINT_SERVICES, + Settings.Secure.FORCE_SCREEN_RECORD_SECURE_WINDOWS, + Settings.Secure.FORCE_SCREENSHOT_SECURE_WINDOWS, Settings.Secure.GLOBAL_ACTIONS_PANEL_AVAILABLE, Settings.Secure.GLOBAL_ACTIONS_PANEL_DEBUG_ENABLED, Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR, diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index d2a7ba046774f..98563763300d5 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -183,6 +183,7 @@ + diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java index 1aad015c0c08a..6c3d7f018b115 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java @@ -49,6 +49,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.provider.MediaStore; +import android.provider.Settings; import android.text.format.DateUtils; import android.util.DisplayMetrics; import android.util.Log; @@ -189,12 +190,16 @@ private void prepare() throws IOException, RemoteException, RuntimeException { mMediaRecorder.prepare(); // Create surface mInputSurface = mMediaRecorder.getSurface(); + int displayFlags = DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; + if (shouldCaptureSecureWindows()) { + displayFlags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE; + } mVirtualDisplay = mMediaProjection.createVirtualDisplay( "Recording Display", videoParameters.mWidth, videoParameters.mHeight, metrics.densityDpi, - DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, + displayFlags, mInputSurface, new VirtualDisplay.Callback() { @Override @@ -215,6 +220,11 @@ public void onStopped() { } + private boolean shouldCaptureSecureWindows() { + return Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.FORCE_SCREEN_RECORD_SECURE_WINDOWS, 0) != 0; + } + /** * Find the highest supported screen resolution and refresh rate for the given dimensions on * this device, up to actual size and given rate. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b50c541b83298..3461eba1cc9e1 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -114,6 +114,7 @@ import static android.view.flags.Flags.sensitiveContentAppProtection; import static android.window.ScreenCapture.ScreenCaptureParams.CAPTURE_MODE_REQUIRE_OPTIMIZED; import static android.window.ScreenCapture.ScreenCaptureParams.PROTECTED_CONTENT_POLICY_THROW_EXCEPTION; +import static android.window.ScreenCapture.ScreenCaptureParams.SECURE_CONTENT_POLICY_CAPTURE; import static android.window.ScreenCapture.ScreenCaptureParams.SECURE_CONTENT_POLICY_THROW_EXCEPTION; import static android.window.WindowProviderService.isWindowProviderService; @@ -844,6 +845,8 @@ final class SettingsObserver extends ContentObserver { Settings.Secure.getUriFor(Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS); private final Uri mDisableSecureWindowsUri = Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS); + private final Uri mForceScreenshotSecureWindowsUri = + Settings.Secure.getUriFor(Settings.Secure.FORCE_SCREENSHOT_SECURE_WINDOWS); private final Uri mMagnifyImeEnabledUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MAGNIFY_NAV_AND_IME); private final Uri mPolicyControlUri = @@ -878,6 +881,8 @@ public SettingsObserver() { UserHandle.USER_ALL); resolver.registerContentObserver(mDisableSecureWindowsUri, false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(mForceScreenshotSecureWindowsUri, false, this, + UserHandle.USER_ALL); resolver.registerContentObserver(mMagnifyImeEnabledUri, false, this, UserHandle.USER_ALL); resolver.registerContentObserver(mPolicyControlUri, false, this, UserHandle.USER_ALL); @@ -941,6 +946,11 @@ public void onChange(boolean selfChange, Uri uri) { return; } + if (mForceScreenshotSecureWindowsUri.equals(uri)) { + updateForceScreenshotSecureWindows(); + return; + } + if (mMagnifyImeEnabledUri.equals(uri)) { updateMagnifyIme(); } @@ -969,6 +979,7 @@ public void onChange(boolean selfChange, Uri uri) { void loadSettings() { updateMaximumObscuringOpacityForTouch(); updateDisableSecureWindows(); + updateForceScreenshotSecureWindows(); updateMagnifyIme(); } @@ -1079,6 +1090,15 @@ void updateDisableSecureWindows() { } } + void updateForceScreenshotSecureWindows() { + // Get the value of the current user. The Settings toggle writes the value to the + // user that changed it. + mForceScreenshotSecureWindows = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.FORCE_SCREENSHOT_SECURE_WINDOWS, /* def= */ 0, + UserHandle.USER_CURRENT) != 0; + } + void updateMagnifyIme() { boolean enabledMagnifyIme = Settings.Secure.getIntForUser( mContext.getContentResolver(), @@ -1284,6 +1304,7 @@ public void onAppTransitionFinishedLocked(IBinder token) { private final ScreenRecordingCallbackController mScreenRecordingCallbackController; private volatile boolean mDisableSecureWindows = false; + private volatile boolean mForceScreenshotSecureWindows = false; /** Creates an instance of the WindowManagerService for the system server. */ public static WindowManagerService main(@NonNull final Context context, @@ -11095,10 +11116,14 @@ ScreenCaptureInternal.LayerCaptureArgs getCaptureArgs( } } - return new ScreenCaptureInternal.LayerCaptureArgs.Builder( + final ScreenCaptureInternal.LayerCaptureArgs.Builder builder = + new ScreenCaptureInternal.LayerCaptureArgs.Builder( displaySurfaceControl, captureArgs) - .setSourceCrop(mTmpRect) - .build(); + .setSourceCrop(mTmpRect); + if (mForceScreenshotSecureWindows) { + builder.setSecureContentPolicy(SECURE_CONTENT_POLICY_CAPTURE); + } + return builder.build(); } @Override 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 b2e2500d62f07..12d551eda52ce 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -92,6 +92,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.ContentResolver; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; @@ -132,6 +133,7 @@ import android.window.ConfigurationChangeSetting; import android.window.IDisplayEngagementModeCallback; import android.window.InputTransferToken; +import android.window.ScreenCapture; import android.window.ScreenCaptureInternal; import android.window.WindowContainerToken; @@ -1566,6 +1568,31 @@ public void testCaptureDisplay() { assertEquals(validRect, resultingArgs.mSourceCrop); } + @Test + public void testCaptureDisplay_forceScreenshotSecureWindows() { + Rect displayBounds = new Rect(0, 0, 100, 200); + spyOn(mDisplayContent); + when(mDisplayContent.getBounds()).thenReturn(displayBounds); + ContentResolver cr = useFakeSettingsProvider(); + + // secureContentPolicy should be REDACT (default) when setting is disabled + Settings.Secure.putInt(cr, Settings.Secure.FORCE_SCREENSHOT_SECURE_WINDOWS, 0); + mWm.mSettingsObserver.onChange(false, + Settings.Secure.getUriFor(Settings.Secure.FORCE_SCREENSHOT_SECURE_WINDOWS)); + ScreenCaptureInternal.LayerCaptureArgs resultingArgs = + mWm.getCaptureArgs(DEFAULT_DISPLAY, null); + assertEquals(ScreenCapture.ScreenCaptureParams.SECURE_CONTENT_POLICY_REDACT, + resultingArgs.mSecureContentPolicy); + + // secureContentPolicy should be CAPTURE when setting is enabled + Settings.Secure.putInt(cr, Settings.Secure.FORCE_SCREENSHOT_SECURE_WINDOWS, 1); + mWm.mSettingsObserver.onChange(false, + Settings.Secure.getUriFor(Settings.Secure.FORCE_SCREENSHOT_SECURE_WINDOWS)); + resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, null); + assertEquals(ScreenCapture.ScreenCaptureParams.SECURE_CONTENT_POLICY_CAPTURE, + resultingArgs.mSecureContentPolicy); + } + @Test public void testGrantInputChannel_sanitizeSpyWindowForApplications() { final Session session = mock(Session.class);