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);