Exclude game session overlay from game screenshots

Test: Unit and manual testing
Bug: 215583568
Bug: 202414447
Bug: 202417255
CTS-Coverage-Bug: 206128693
Change-Id: Ifc809772616688c790445f68fe31165395ee5e8b
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 960fbf1..145a298 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -43,6 +43,7 @@
 import android.service.games.IGameSessionController;
 import android.service.games.IGameSessionService;
 import android.util.Slog;
+import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost.SurfacePackage;
 
 import com.android.internal.annotations.GuardedBy;
@@ -237,7 +238,7 @@
                 mTaskSystemBarsVisibilityListener);
 
         for (GameSessionRecord gameSessionRecord : mGameSessions.values()) {
-            destroyGameSessionFromRecord(gameSessionRecord);
+            destroyGameSessionFromRecordLocked(gameSessionRecord);
         }
         mGameSessions.clear();
 
@@ -510,10 +511,11 @@
             }
             return;
         }
-        destroyGameSessionFromRecord(gameSessionRecord);
+        destroyGameSessionFromRecordLocked(gameSessionRecord);
     }
 
-    private void destroyGameSessionFromRecord(@NonNull GameSessionRecord gameSessionRecord) {
+    @GuardedBy("mLock")
+    private void destroyGameSessionFromRecordLocked(@NonNull GameSessionRecord gameSessionRecord) {
         SurfacePackage surfacePackage = gameSessionRecord.getSurfacePackage();
         if (surfacePackage != null) {
             try {
@@ -586,17 +588,29 @@
 
     @VisibleForTesting
     void takeScreenshot(int taskId, @NonNull AndroidFuture callback) {
+        GameSessionRecord gameSessionRecord;
         synchronized (mLock) {
-            boolean isTaskAssociatedWithGameSession = mGameSessions.containsKey(taskId);
-            if (!isTaskAssociatedWithGameSession) {
+            gameSessionRecord = mGameSessions.get(taskId);
+            if (gameSessionRecord == null) {
                 Slog.w(TAG, "No game session found for id: " + taskId);
                 callback.complete(GameScreenshotResult.createInternalErrorResult());
                 return;
             }
         }
 
+        final SurfacePackage overlaySurfacePackage = gameSessionRecord.getSurfacePackage();
+        final SurfaceControl overlaySurfaceControl =
+                overlaySurfacePackage != null ? overlaySurfacePackage.getSurfaceControl() : null;
         mBackgroundExecutor.execute(() -> {
-            final Bitmap bitmap = mWindowManagerService.captureTaskBitmap(taskId);
+            final SurfaceControl.LayerCaptureArgs.Builder layerCaptureArgsBuilder =
+                    new SurfaceControl.LayerCaptureArgs.Builder(/* layer */ null);
+            if (overlaySurfaceControl != null) {
+                SurfaceControl[] excludeLayers = new SurfaceControl[1];
+                excludeLayers[0] = overlaySurfaceControl;
+                layerCaptureArgsBuilder.setExcludeLayers(excludeLayers);
+            }
+            final Bitmap bitmap = mWindowManagerService.captureTaskBitmap(taskId,
+                    layerCaptureArgsBuilder);
             if (bitmap == null) {
                 Slog.w(TAG, "Could not get bitmap for id: " + taskId);
                 callback.complete(GameScreenshotResult.createInternalErrorResult());
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1167cb5..5751777 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3855,14 +3855,20 @@
     }
 
     /**
-     * Generates and returns an up-to-date {@link Bitmap} for the specified taskId. The returned
-     * bitmap will be full size and will not include any secure content.
+     * Generates and returns an up-to-date {@link Bitmap} for the specified taskId.
      *
-     * @param taskId The task ID of the task for which a snapshot is requested.
+     * @param taskId                  The task ID of the task for which a Bitmap is requested.
+     * @param layerCaptureArgsBuilder A {@link SurfaceControl.LayerCaptureArgs.Builder} with
+     *                                arguments for how to capture the Bitmap. The caller can
+     *                                specify any arguments, but this method will ensure that the
+     *                                specified task's SurfaceControl is used and the crop is set to
+     *                                the bounds of that task.
      * @return The Bitmap, or null if no task with the specified ID can be found or the bitmap could
      * not be generated.
      */
-    @Nullable public Bitmap captureTaskBitmap(int taskId) {
+    @Nullable
+    public Bitmap captureTaskBitmap(int taskId,
+            @NonNull SurfaceControl.LayerCaptureArgs.Builder layerCaptureArgsBuilder) {
         if (mTaskSnapshotController.shouldDisableSnapshots()) {
             return null;
         }
@@ -3876,9 +3882,7 @@
             task.getBounds(mTmpRect);
             final SurfaceControl sc = task.getSurfaceControl();
             final SurfaceControl.ScreenshotHardwareBuffer buffer = SurfaceControl.captureLayers(
-                    new SurfaceControl.LayerCaptureArgs.Builder(sc)
-                            .setSourceCrop(mTmpRect)
-                            .build());
+                    layerCaptureArgsBuilder.setLayer(sc).setSourceCrop(mTmpRect).build());
             if (buffer == null) {
                 Slog.w(TAG, "Could not get screenshot buffer for taskId: " + taskId);
                 return null;
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
index 08de62b..7f57119 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -60,6 +60,7 @@
 import android.service.games.IGameSession;
 import android.service.games.IGameSessionController;
 import android.service.games.IGameSessionService;
+import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost.SurfacePackage;
 
 import androidx.test.filters.SmallTest;
@@ -746,6 +747,11 @@
         mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockOverlaySurfacePackage = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockOverlaySurfacePackage));
+
         IGameSessionController gameSessionController = getOnlyElement(
                 mFakeGameSessionService.getCapturedCreateInvocations()).mGameSessionController;
         AndroidFuture<GameScreenshotResult> resultFuture = new AndroidFuture<>();
@@ -754,18 +760,28 @@
         GameScreenshotResult result = resultFuture.get();
         assertEquals(GameScreenshotResult.GAME_SCREENSHOT_ERROR_INTERNAL_ERROR,
                 result.getStatus());
-        verify(mMockWindowManagerService).captureTaskBitmap(10);
+
+        verify(mMockWindowManagerService).captureTaskBitmap(eq(10), any());
     }
 
     @Test
     public void takeScreenshot_success() throws Exception {
-        when(mMockWindowManagerService.captureTaskBitmap(10)).thenReturn(TEST_BITMAP);
+        SurfaceControl mockOverlaySurfaceControl = Mockito.mock(SurfaceControl.class);
+        SurfaceControl[] excludeLayers = new SurfaceControl[1];
+        excludeLayers[0] = mockOverlaySurfaceControl;
+        when(mMockWindowManagerService.captureTaskBitmap(eq(10), any())).thenReturn(TEST_BITMAP);
 
         mGameServiceProviderInstance.start();
         startTask(10, GAME_A_MAIN_ACTIVITY);
         mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
         mFakeGameService.requestCreateGameSession(10);
 
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockOverlaySurfacePackage = Mockito.mock(SurfacePackage.class);
+        when(mockOverlaySurfacePackage.getSurfaceControl()).thenReturn(mockOverlaySurfaceControl);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockOverlaySurfacePackage));
+
         IGameSessionController gameSessionController = getOnlyElement(
                 mFakeGameSessionService.getCapturedCreateInvocations()).mGameSessionController;
         AndroidFuture<GameScreenshotResult> resultFuture = new AndroidFuture<>();