From be78c088ae0ad3a3e1093063ad7d86e1051acddd Mon Sep 17 00:00:00 2001 From: Tyler Lacey Date: Wed, 26 Jan 2022 17:02:19 +0000 Subject: [PATCH] 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 --- .../app/GameServiceProviderInstanceImpl.java | 26 ++++++++++++++----- .../server/wm/WindowManagerService.java | 18 ++++++++----- .../GameServiceProviderInstanceImplTest.java | 20 ++++++++++++-- 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java index 960fbf1d9330d..145a298af95e4 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.IGameSession; 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 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan mTaskSystemBarsVisibilityListener); for (GameSessionRecord gameSessionRecord : mGameSessions.values()) { - destroyGameSessionFromRecord(gameSessionRecord); + destroyGameSessionFromRecordLocked(gameSessionRecord); } mGameSessions.clear(); @@ -510,10 +511,11 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } 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 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan @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 1167cb531c761..5751777fc2cdd 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 @@ public class WindowManagerService extends IWindowManager.Stub } /** - * 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 08de62bc65376..7f571195f5f82 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.IGameServiceController; 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 @@ public final class GameServiceProviderInstanceImplTest { 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 resultFuture = new AndroidFuture<>(); @@ -754,18 +760,28 @@ public final class GameServiceProviderInstanceImplTest { 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 resultFuture = new AndroidFuture<>();