diff options
5 files changed, 70 insertions, 14 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index c47c37b0bc7e..ec39e9f4df2d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -166,6 +166,7 @@ package android { field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING"; field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS"; field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION"; + field public static final String MANAGE_GAME_ACTIVITY = "android.permission.MANAGE_GAME_ACTIVITY"; field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE"; field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION"; field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS"; @@ -11060,7 +11061,7 @@ package android.service.games { method public void onCreate(); method public void onDestroy(); method public void onGameTaskFocusChanged(boolean); - method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public final boolean restartGame(); + method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) public final boolean restartGame(); method public void setTaskOverlayView(@NonNull android.view.View, @NonNull android.view.ViewGroup.LayoutParams); method public void takeScreenshot(@NonNull java.util.concurrent.Executor, @NonNull android.service.games.GameSession.ScreenshotCallback); } diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java index f4baedc18acf..9590933cf2da 100644 --- a/core/java/android/service/games/GameSession.java +++ b/core/java/android/service/games/GameSession.java @@ -278,7 +278,7 @@ public abstract class GameSession { * * @return {@code true} if the game was successfully restarted; otherwise, {@code false}. */ - @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) + @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) public final boolean restartGame() { try { mGameSessionController.restartGame(mTaskId); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 92f7f2921bee..7df9ca73b0d5 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6174,6 +6174,12 @@ <permission android:name="android.permission.ACCESS_FPS_COUNTER" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows the GameService provider to create GameSession and call GameSession + APIs and overlay a view on top of the game's Activity. + @hide --> + <permission android:name="android.permission.MANAGE_GAME_ACTIVITY" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows the holder to register callbacks to inform the RebootReadinessManager when they are performing reboot-blocking work. @hide --> diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java index 913621913e95..ec37134acd01 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java @@ -116,9 +116,10 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan }); } - @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) + @Override + @RequiresPermission(Manifest.permission.MANAGE_GAME_ACTIVITY) public void restartGame(int taskId) { - mContext.enforceCallingPermission(Manifest.permission.FORCE_STOP_PACKAGES, + mContext.enforceCallingPermission(Manifest.permission.MANAGE_GAME_ACTIVITY, "restartGame()"); mBackgroundExecutor.execute(() -> { GameServiceProviderInstanceImpl.this.restartGame(taskId); @@ -372,12 +373,12 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan }, mBackgroundExecutor); AndroidFuture<Void> unusedPostCreateGameSessionFuture = - mGameSessionServiceConnector.post(gameService -> { + mGameSessionServiceConnector.post(gameSessionService -> { CreateGameSessionRequest createGameSessionRequest = new CreateGameSessionRequest( taskId, existingGameSessionRecord.getComponentName().getPackageName()); - gameService.create( + gameSessionService.create( mGameSessionController, createGameSessionRequest, gameSessionViewHostConfiguration, 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 d5e4710095f2..b0ffcf34ac07 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java @@ -26,6 +26,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -33,6 +34,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; +import android.Manifest; import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; @@ -83,6 +85,7 @@ import org.mockito.quality.Strictness; import java.util.ArrayList; import java.util.HashMap; +import java.util.Objects; /** @@ -121,7 +124,7 @@ public final class GameServiceProviderInstanceImplTest { private WindowManagerInternal mMockWindowManagerInternal; @Mock private IActivityManager mMockActivityManager; - private FakeContext mFakeContext; + private MockContext mMockContext; private FakeGameClassifier mFakeGameClassifier; private FakeGameService mFakeGameService; private FakeServiceConnector<IGameService> mFakeGameServiceConnector; @@ -140,7 +143,7 @@ public final class GameServiceProviderInstanceImplTest { .strictness(Strictness.LENIENT) .startMocking(); - mFakeContext = new FakeContext(InstrumentationRegistry.getInstrumentation().getContext()); + mMockContext = new MockContext(InstrumentationRegistry.getInstrumentation().getContext()); mFakeGameClassifier = new FakeGameClassifier(); mFakeGameClassifier.recordGamePackage(GAME_A_PACKAGE); @@ -169,7 +172,7 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance = new GameServiceProviderInstanceImpl( new UserHandle(USER_ID), ConcurrentUtils.DIRECT_EXECUTOR, - mFakeContext, + mMockContext, mFakeGameClassifier, mMockActivityManager, mMockActivityTaskManager, @@ -683,6 +686,7 @@ public final class GameServiceProviderInstanceImplTest { @Test public void restartGame_taskIdAssociatedWithGame_restartsTargetGame() throws Exception { + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); Intent launchIntent = new Intent("com.test.ACTION_LAUNCH_GAME_PACKAGE") .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); when(mMockPackageManager.getLaunchIntentForPackage(GAME_A_PACKAGE)) @@ -710,11 +714,12 @@ public final class GameServiceProviderInstanceImplTest { .mGameSessionController.restartGame(10); verify(mMockActivityManager).forceStopPackage(GAME_A_PACKAGE, UserHandle.USER_CURRENT); - assertThat(mFakeContext.getLastStartedIntent()).isEqualTo(launchIntent); + assertThat(mMockContext.getLastStartedIntent()).isEqualTo(launchIntent); } @Test public void restartGame_taskIdNotAssociatedWithGame_noOp() throws Exception { + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); @@ -730,7 +735,19 @@ public final class GameServiceProviderInstanceImplTest { .mGameSessionController.restartGame(11); verifyZeroInteractions(mMockActivityManager); - assertThat(mFakeContext.getLastStartedIntent()).isNull(); + assertThat(mMockContext.getLastStartedIntent()).isNull(); + } + + @Test + public void restartGame_failurePermissionDenied() throws Exception { + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + IGameSessionController gameSessionController = Objects.requireNonNull(getOnlyElement( + mFakeGameSessionService.getCapturedCreateInvocations())).mGameSessionController; + assertThrows(SecurityException.class, + () -> gameSessionController.restartGame(10)); } private void startTask(int taskId, ComponentName componentName) { @@ -774,6 +791,14 @@ public final class GameServiceProviderInstanceImplTest { } } + private void mockPermissionGranted(String permission) { + mMockContext.setPermission(permission, PackageManager.PERMISSION_GRANTED); + } + + private void mockPermissionDenied(String permission) { + mMockContext.setPermission(permission, PackageManager.PERMISSION_DENIED); + } + static final class FakeGameService extends IGameService.Stub { private IGameServiceController mGameServiceController; @@ -900,13 +925,28 @@ public final class GameServiceProviderInstanceImplTest { } } - private final class FakeContext extends ContextWrapper { + private final class MockContext extends ContextWrapper { private Intent mLastStartedIntent; + // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant + private final HashMap<String, Integer> mMockedPermissions = new HashMap<>(); - FakeContext(Context base) { + MockContext(Context base) { super(base); } + /** + * Mock checks for the specified permission, and have them behave as per {@code granted}. + * + * <p>Passing null reverts to default behavior, which does a real permission check on the + * test package. + * + * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or + * {@link PackageManager#PERMISSION_DENIED}. + */ + public void setPermission(String permission, Integer granted) { + mMockedPermissions.put(permission, granted); + } + @Override public PackageManager getPackageManager() { return mMockPackageManager; @@ -919,7 +959,15 @@ public final class GameServiceProviderInstanceImplTest { @Override public void enforceCallingPermission(String permission, @Nullable String message) { - // Do nothing. + final Integer granted = mMockedPermissions.get(permission); + if (granted == null) { + super.enforceCallingOrSelfPermission(permission, message); + return; + } + + if (!granted.equals(PackageManager.PERMISSION_GRANTED)) { + throw new SecurityException("[Test] permission denied: " + permission); + } } Intent getLastStartedIntent() { |