diff --git a/core/api/current.txt b/core/api/current.txt index dd0614648f8cf..a1226e64b702d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -4221,6 +4221,7 @@ package android.app { method @Deprecated public final void setProgressBarIndeterminate(boolean); method @Deprecated public final void setProgressBarIndeterminateVisibility(boolean); method @Deprecated public final void setProgressBarVisibility(boolean); + method public void setRecentsScreenshotEnabled(boolean); method public void setRequestedOrientation(int); method public final void setResult(int); method public final void setResult(int, android.content.Intent); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 15148a93cbe72..ef8f6d7ef37d7 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2850,6 +2850,7 @@ package android.view { method public default void setShouldShowSystemDecors(int, boolean); method public default void setShouldShowWithInsecureKeyguard(int, boolean); method public default boolean shouldShowSystemDecors(int); + method @Nullable public default android.graphics.Bitmap snapshotTaskForRecents(@IntRange(from=0) int); field public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; // 0x1 field public static final int DISPLAY_IME_POLICY_HIDE = 2; // 0x2 field public static final int DISPLAY_IME_POLICY_LOCAL = 0; // 0x0 diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 530666b8f1d7d..d31cb49714f5b 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -8517,8 +8517,18 @@ public class Activity extends ContextThemeWrapper } /** - * If set to true, this indicates to the system that it should never take a - * screenshot of the activity to be used as a representation while it is not in a started state. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.S, + publicAlternatives = "Use {@link #setRecentsScreenshotEnabled(boolean)} instead.") + public void setDisablePreviewScreenshots(boolean disable) { + setRecentsScreenshotEnabled(!disable); + } + + /** + * If set to false, this indicates to the system that it should never take a + * screenshot of the activity to be used as a representation in recents screen. By default, this + * value is {@code true}. *

* Note that the system may use the window background of the theme instead to represent * the window when it is not running. @@ -8531,12 +8541,10 @@ public class Activity extends ContextThemeWrapper * {@link android.service.voice.VoiceInteractionService} requests a screenshot via * {@link android.service.voice.VoiceInteractionSession#SHOW_WITH_SCREENSHOT}. * - * @param disable {@code true} to disable preview screenshots; {@code false} otherwise. - * @hide + * @param enabled {@code true} to enable recents screenshots; {@code false} otherwise. */ - @UnsupportedAppUsage - public void setDisablePreviewScreenshots(boolean disable) { - ActivityClient.getInstance().setDisablePreviewScreenshots(mToken, disable); + public void setRecentsScreenshotEnabled(boolean enabled) { + ActivityClient.getInstance().setRecentsScreenshotEnabled(mToken, enabled); } /** diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java index 605a1fa822541..4715e0f2cec8e 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -438,9 +438,9 @@ public class ActivityClient { } } - void setDisablePreviewScreenshots(IBinder token, boolean disable) { + void setRecentsScreenshotEnabled(IBinder token, boolean enabled) { try { - getActivityClientController().setDisablePreviewScreenshots(token, disable); + getActivityClientController().setRecentsScreenshotEnabled(token, enabled); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index 396e5528ab0c2..f9439cb6d770b 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -115,8 +115,8 @@ interface IActivityClientController { int enterAnim, int exitAnim, int backgroundColor); int setVrMode(in IBinder token, boolean enabled, in ComponentName packageName); - /** See {@link android.app.Activity#setDisablePreviewScreenshots}. */ - oneway void setDisablePreviewScreenshots(in IBinder token, boolean disable); + /** See {@link android.app.Activity#setRecentsScreenshotEnabled}. */ + oneway void setRecentsScreenshotEnabled(in IBinder token, boolean enabled); /** * It should only be called from home activity to remove its outdated snapshot. The home diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index c5ccc18b0cd4f..36baa04471762 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -941,4 +941,14 @@ interface IWindowManager * @hide */ void unregisterTaskFpsCallback(in IOnFpsCallbackListener listener); + + /** + * Take a snapshot using the same path that's used for Recents. This is used for Testing only. + * + * @param taskId to take the snapshot of + * + * Returns a bitmap of the screenshot or {@code null} if it was unable to screenshot. + * @hide + */ + Bitmap snapshotTaskForRecents(int taskId); } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index ca7f90080c6c2..771d40bdf6550 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -97,6 +97,7 @@ import android.content.ClipData; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Point; @@ -4886,4 +4887,21 @@ public interface WindowManager extends ViewManager { */ @SystemApi default void unregisterTaskFpsCallback(@NonNull TaskFpsCallback callback) {} + + /** + * Take a snapshot using the same path that's used for Recents. This is used for Testing only. + * + * @param taskId to take the snapshot of + * + * @return a bitmap of the screenshot or {@code null} if it was unable to screenshot. The + * screenshot can fail if the taskId is invalid or if there's no SurfaceControl associated with + * that task. + * + * @hide + */ + @TestApi + @Nullable + default Bitmap snapshotTaskForRecents(@IntRange(from = 0) int taskId) { + return null; + } } diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index c16703ef50ef9..f4353eb5b397f 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -32,6 +32,7 @@ import android.app.ResourcesManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.Region; import android.os.Bundle; @@ -439,4 +440,14 @@ public final class WindowManagerImpl implements WindowManager { } catch (RemoteException e) { } } + + @Override + public Bitmap snapshotTaskForRecents(int taskId) { + try { + return WindowManagerGlobal.getWindowManagerService().snapshotTaskForRecents(taskId); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + return null; + } } diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index ff96aebba7086..ec4c58f9aa358 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -1135,13 +1135,13 @@ class ActivityClientController extends IActivityClientController.Stub { } @Override - public void setDisablePreviewScreenshots(IBinder token, boolean disable) { + public void setRecentsScreenshotEnabled(IBinder token, boolean enabled) { final long origId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); if (r != null) { - r.setDisablePreviewScreenshots(disable); + r.setRecentsScreenshotEnabled(enabled); } } } finally { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 3fe616660284e..5b142a52d7d23 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -752,7 +752,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Last visibility state we reported to the app token. boolean reportedVisible; - boolean mDisablePreviewScreenshots; + boolean mEnablePreviewScreenshots = true; // Information about an application starting window if displayed. // Note: these are de-referenced before the starting window animates away. @@ -5100,22 +5100,22 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } /** - * See {@link Activity#setDisablePreviewScreenshots}. + * See {@link Activity#setRecentsScreenshotEnabled}. */ - void setDisablePreviewScreenshots(boolean disable) { - mDisablePreviewScreenshots = disable; + void setRecentsScreenshotEnabled(boolean enabled) { + mEnablePreviewScreenshots = enabled; } /** * Retrieves whether we'd like to generate a snapshot that's based solely on the theme. This is - * the case when preview screenshots are disabled {@link #setDisablePreviewScreenshots} or when + * the case when preview screenshots are disabled {@link #setRecentsScreenshotEnabled} or when * we can't take a snapshot for other reasons, for example, if we have a secure window. * * @return True if we need to generate an app theme snapshot, false if we'd like to take a real * screenshot. */ boolean shouldUseAppThemeSnapshot() { - return mDisablePreviewScreenshots || forAllWindows(WindowState::isSecureLocked, + return !mEnablePreviewScreenshots || forAllWindows(WindowState::isSecureLocked, true /* topToBottom */); } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index bf11ebc8c95f2..a9add59be1f61 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -196,15 +196,21 @@ class TaskSnapshotController { snapshotTasks(tasks, false /* allowSnapshotHome */); } - void recordTaskSnapshot(Task task, boolean allowSnapshotHome) { + /** + * This is different than {@link #recordTaskSnapshot(Task, boolean)} because it doesn't store + * the snapshot to the cache and returns the TaskSnapshot immediately. + * + * This is only used for testing so the snapshot content can be verified. + */ + @VisibleForTesting + TaskSnapshot captureTaskSnapshot(Task task, boolean snapshotHome) { final TaskSnapshot snapshot; - final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome(); if (snapshotHome) { snapshot = snapshotTask(task); } else { switch (getSnapshotMode(task)) { case SNAPSHOT_MODE_NONE: - return; + return null; case SNAPSHOT_MODE_APP_THEME: snapshot = drawAppThemeSnapshot(task); break; @@ -216,19 +222,27 @@ class TaskSnapshotController { break; } } - if (snapshot != null) { - final HardwareBuffer buffer = snapshot.getHardwareBuffer(); - if (buffer.getWidth() == 0 || buffer.getHeight() == 0) { - buffer.close(); - Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x" - + buffer.getHeight()); - } else { - mCache.putSnapshot(task, snapshot); - // Don't persist or notify the change for the temporal snapshot. - if (!snapshotHome) { - mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot); - task.onSnapshotChanged(snapshot); - } + return snapshot; + } + + void recordTaskSnapshot(Task task, boolean allowSnapshotHome) { + final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome(); + final TaskSnapshot snapshot = captureTaskSnapshot(task, snapshotHome); + if (snapshot == null) { + return; + } + + final HardwareBuffer buffer = snapshot.getHardwareBuffer(); + if (buffer.getWidth() == 0 || buffer.getHeight() == 0) { + buffer.close(); + Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x" + + buffer.getHeight()); + } else { + mCache.putSnapshot(task, snapshot); + // Don't persist or notify the change for the temporal snapshot. + if (!snapshotHome) { + mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot); + task.onSnapshotChanged(snapshot); } } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b37cb4f286f9d..23e77dc74f595 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 com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL; import static com.android.server.wm.DisplayContent.IME_TARGET_INPUT; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; +import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; @@ -8861,4 +8862,27 @@ public class WindowManagerService extends IWindowManager.Stub mTaskFpsCallbackController.unregisterCallback(listener); } + + @Override + public Bitmap snapshotTaskForRecents(int taskId) { + if (!checkCallingPermission(READ_FRAME_BUFFER, "snapshotTaskForRecents()")) { + throw new SecurityException("Requires READ_FRAME_BUFFER permission"); + } + + TaskSnapshot taskSnapshot; + synchronized (mGlobalLock) { + Task task = mRoot.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS); + if (task == null) { + throw new IllegalArgumentException( + "Failed to find matching task for taskId=" + taskId); + } + taskSnapshot = mTaskSnapshotController.captureTaskSnapshot(task, false); + } + + if (taskSnapshot == null || taskSnapshot.getHardwareBuffer() == null) { + return null; + } + return Bitmap.wrapHardwareBuffer(taskSnapshot.getHardwareBuffer(), + taskSnapshot.getColorSpace()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java index f573b70352af1..e4336843a35b1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java @@ -116,7 +116,7 @@ public class TaskSnapshotControllerTest extends WindowTestsBase { public void testGetSnapshotMode() { final WindowState disabledWindow = createWindow(null, FIRST_APPLICATION_WINDOW, mDisplayContent, "disabledWindow"); - disabledWindow.mActivityRecord.setDisablePreviewScreenshots(true); + disabledWindow.mActivityRecord.setRecentsScreenshotEnabled(false); assertEquals(SNAPSHOT_MODE_APP_THEME, mWm.mTaskSnapshotController.getSnapshotMode(disabledWindow.getTask())); diff --git a/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java b/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java index fa5724ea64bcb..ca909a4c9b771 100644 --- a/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java +++ b/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java @@ -30,7 +30,7 @@ public class DisableScreenshotsActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setDisablePreviewScreenshots(true); + setRecentsScreenshotEnabled(false); getWindow().getDecorView().setBackgroundColor(Color.RED); }