Merge "Exclude game session overlay from game screenshots"

This commit is contained in:
Tyler Lacey
2022-02-03 14:37:44 +00:00
committed by Android (Google) Code Review
3 changed files with 49 additions and 15 deletions

View File

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

View File

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

View File

@@ -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<GameScreenshotResult> 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<GameScreenshotResult> resultFuture = new AndroidFuture<>();