summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/java/android/service/games/GameSession.java20
-rw-r--r--core/java/android/service/games/IGameSessionController.aidl5
-rw-r--r--services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java9
-rw-r--r--services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java44
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java107
6 files changed, 179 insertions, 7 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ea2a6418ad54..5e232291374e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -11184,6 +11184,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 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 cb5c19b72bd0..f4baedc18acf 100644
--- a/core/java/android/service/games/GameSession.java
+++ b/core/java/android/service/games/GameSession.java
@@ -20,6 +20,7 @@ import android.annotation.Hide;
import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
import android.content.res.Configuration;
@@ -271,6 +272,25 @@ public abstract class GameSession {
}
/**
+ * Attempts to force stop and relaunch the game associated with the current session. This may
+ * be useful, for example, after applying settings that will not take effect until the game is
+ * restarted.
+ *
+ * @return {@code true} if the game was successfully restarted; otherwise, {@code false}.
+ */
+ @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+ public final boolean restartGame() {
+ try {
+ mGameSessionController.restartGame(mTaskId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to restart game", e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
* Root view of the {@link SurfaceControlViewHost} associated with the {@link GameSession}
* instance. It is responsible for observing changes in the size of the window and resizing
* itself to match.
diff --git a/core/java/android/service/games/IGameSessionController.aidl b/core/java/android/service/games/IGameSessionController.aidl
index fe1d3629918e..84311dc0aedf 100644
--- a/core/java/android/service/games/IGameSessionController.aidl
+++ b/core/java/android/service/games/IGameSessionController.aidl
@@ -16,6 +16,7 @@
package android.service.games;
+import android.content.Intent;
import com.android.internal.infra.AndroidFuture;
/**
@@ -23,4 +24,6 @@ import com.android.internal.infra.AndroidFuture;
*/
oneway interface IGameSessionController {
void takeScreenshot(int taskId, in AndroidFuture gameScreenshotResultFuture);
-} \ No newline at end of file
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)")
+ void restartGame(in int taskId);
+}
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
index b4c43f6f1b93..73278e471062 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
@@ -17,6 +17,7 @@
package com.android.server.app;
import android.annotation.NonNull;
+import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.Intent;
@@ -36,17 +37,19 @@ final class GameServiceProviderInstanceFactoryImpl implements GameServiceProvide
private final Context mContext;
GameServiceProviderInstanceFactoryImpl(@NonNull Context context) {
- this.mContext = context;
+ mContext = context;
}
@NonNull
@Override
- public GameServiceProviderInstance create(@NonNull
- GameServiceProviderConfiguration gameServiceProviderConfiguration) {
+ public GameServiceProviderInstance create(
+ @NonNull GameServiceProviderConfiguration gameServiceProviderConfiguration) {
return new GameServiceProviderInstanceImpl(
gameServiceProviderConfiguration.getUserHandle(),
BackgroundThread.getExecutor(),
+ mContext,
new GameClassifierImpl(mContext.getPackageManager()),
+ ActivityManager.getService(),
ActivityTaskManager.getService(),
(WindowManagerService) ServiceManager.getService(Context.WINDOW_SERVICE),
LocalServices.getService(WindowManagerInternal.class),
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 43c9839a04d8..8578de7ac4b4 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -16,13 +16,18 @@
package com.android.server.app;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
+import android.app.IActivityManager;
import android.app.IActivityTaskManager;
import android.app.TaskStackListener;
import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.RemoteException;
@@ -110,12 +115,23 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
gameScreenshotResultFuture);
});
}
+
+ @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+ public void restartGame(int taskId) {
+ mContext.enforceCallingPermission(Manifest.permission.FORCE_STOP_PACKAGES,
+ "restartGame()");
+ mBackgroundExecutor.execute(() -> {
+ GameServiceProviderInstanceImpl.this.restartGame(taskId);
+ });
+ }
};
private final Object mLock = new Object();
private final UserHandle mUserHandle;
private final Executor mBackgroundExecutor;
+ private final Context mContext;
private final GameClassifier mGameClassifier;
+ private final IActivityManager mActivityManager;
private final IActivityTaskManager mActivityTaskManager;
private final WindowManagerService mWindowManagerService;
private final WindowManagerInternal mWindowManagerInternal;
@@ -131,7 +147,9 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
GameServiceProviderInstanceImpl(
@NonNull UserHandle userHandle,
@NonNull Executor backgroundExecutor,
+ @NonNull Context context,
@NonNull GameClassifier gameClassifier,
+ @NonNull IActivityManager activityManager,
@NonNull IActivityTaskManager activityTaskManager,
@NonNull WindowManagerService windowManagerService,
@NonNull WindowManagerInternal windowManagerInternal,
@@ -139,7 +157,9 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
@NonNull ServiceConnector<IGameSessionService> gameSessionServiceConnector) {
mUserHandle = userHandle;
mBackgroundExecutor = backgroundExecutor;
+ mContext = context;
mGameClassifier = gameClassifier;
+ mActivityManager = activityManager;
mActivityTaskManager = activityTaskManager;
mWindowManagerService = windowManagerService;
mWindowManagerInternal = windowManagerInternal;
@@ -310,6 +330,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
+ ") is not awaiting game session request. Ignoring.");
return;
}
+ mGameSessions.put(taskId, existingGameSessionRecord.withGameSessionRequested());
GameSessionViewHostConfiguration gameSessionViewHostConfiguration =
createViewHostConfigurationForTask(taskId);
@@ -532,4 +553,27 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
}
});
}
+
+ private void restartGame(int taskId) {
+ String packageName;
+
+ synchronized (mLock) {
+ boolean isTaskAssociatedWithGameSession = mGameSessions.containsKey(taskId);
+ if (!isTaskAssociatedWithGameSession) {
+ return;
+ }
+
+ packageName = mGameSessions.get(taskId).getComponentName().getPackageName();
+ }
+
+ try {
+ mActivityManager.forceStopPackage(packageName, UserHandle.USER_CURRENT);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ Intent launchIntent =
+ mContext.getPackageManager().getLaunchIntentForPackage(packageName);
+ mContext.startActivity(launchIntent);
+ }
}
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 bdfa3bfedb55..d5e4710095f2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -18,6 +18,8 @@ package com.android.server.app;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.app.GameServiceProviderInstanceImplTest.FakeGameService.GameServiceState;
import static com.google.common.collect.Iterables.getOnlyElement;
@@ -28,16 +30,19 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verifyZeroInteractions;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
+import android.app.IActivityManager;
import android.app.IActivityTaskManager;
import android.app.ITaskStackListener;
import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Rect;
@@ -57,6 +62,7 @@ import android.service.games.IGameSessionService;
import android.view.SurfaceControlViewHost.SurfacePackage;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.infra.AndroidFuture;
@@ -99,6 +105,10 @@ public final class GameServiceProviderInstanceImplTest {
private static final ComponentName GAME_A_MAIN_ACTIVITY =
new ComponentName(GAME_A_PACKAGE, "com.package.game.a.MainActivity");
+ private static final String GAME_B_PACKAGE = "com.package.game.b";
+ private static final ComponentName GAME_B_MAIN_ACTIVITY =
+ new ComponentName(GAME_B_PACKAGE, "com.package.game.b.MainActivity");
+
private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
private MockitoSession mMockingSession;
@@ -109,6 +119,9 @@ public final class GameServiceProviderInstanceImplTest {
private WindowManagerService mMockWindowManagerService;
@Mock
private WindowManagerInternal mMockWindowManagerInternal;
+ @Mock
+ private IActivityManager mMockActivityManager;
+ private FakeContext mFakeContext;
private FakeGameClassifier mFakeGameClassifier;
private FakeGameService mFakeGameService;
private FakeServiceConnector<IGameService> mFakeGameServiceConnector;
@@ -117,6 +130,9 @@ public final class GameServiceProviderInstanceImplTest {
private ArrayList<ITaskStackListener> mTaskStackListeners;
private ArrayList<RunningTaskInfo> mRunningTaskInfos;
+ @Mock
+ private PackageManager mMockPackageManager;
+
@Before
public void setUp() throws PackageManager.NameNotFoundException, RemoteException {
mMockingSession = mockitoSession()
@@ -124,8 +140,11 @@ public final class GameServiceProviderInstanceImplTest {
.strictness(Strictness.LENIENT)
.startMocking();
+ mFakeContext = new FakeContext(InstrumentationRegistry.getInstrumentation().getContext());
+
mFakeGameClassifier = new FakeGameClassifier();
mFakeGameClassifier.recordGamePackage(GAME_A_PACKAGE);
+ mFakeGameClassifier.recordGamePackage(GAME_B_PACKAGE);
mFakeGameService = new FakeGameService();
mFakeGameServiceConnector = new FakeServiceConnector<>(mFakeGameService);
@@ -150,7 +169,9 @@ public final class GameServiceProviderInstanceImplTest {
mGameServiceProviderInstance = new GameServiceProviderInstanceImpl(
new UserHandle(USER_ID),
ConcurrentUtils.DIRECT_EXECUTOR,
+ mFakeContext,
mFakeGameClassifier,
+ mMockActivityManager,
mMockActivityTaskManager,
mMockWindowManagerService,
mMockWindowManagerInternal,
@@ -660,6 +681,58 @@ public final class GameServiceProviderInstanceImplTest {
assertEquals(TEST_BITMAP, result.getBitmap());
}
+ @Test
+ public void restartGame_taskIdAssociatedWithGame_restartsTargetGame() throws Exception {
+ Intent launchIntent = new Intent("com.test.ACTION_LAUNCH_GAME_PACKAGE")
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ when(mMockPackageManager.getLaunchIntentForPackage(GAME_A_PACKAGE))
+ .thenReturn(launchIntent);
+
+ mGameServiceProviderInstance.start();
+
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ mFakeGameService.requestCreateGameSession(10);
+
+ FakeGameSession gameSession10 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(10)
+ .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+ startTask(11, GAME_B_MAIN_ACTIVITY);
+ mFakeGameService.requestCreateGameSession(11);
+
+ FakeGameSession gameSession11 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(11)
+ .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11));
+
+ mFakeGameSessionService.getCapturedCreateInvocations().get(0)
+ .mGameSessionController.restartGame(10);
+
+ verify(mMockActivityManager).forceStopPackage(GAME_A_PACKAGE, UserHandle.USER_CURRENT);
+ assertThat(mFakeContext.getLastStartedIntent()).isEqualTo(launchIntent);
+ }
+
+ @Test
+ public void restartGame_taskIdNotAssociatedWithGame_noOp() throws Exception {
+ mGameServiceProviderInstance.start();
+
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ mFakeGameService.requestCreateGameSession(10);
+
+ FakeGameSession gameSession10 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(10)
+ .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+ getOnlyElement(
+ mFakeGameSessionService.getCapturedCreateInvocations())
+ .mGameSessionController.restartGame(11);
+
+ verifyZeroInteractions(mMockActivityManager);
+ assertThat(mFakeContext.getLastStartedIntent()).isNull();
+ }
+
private void startTask(int taskId, ComponentName componentName) {
RunningTaskInfo runningTaskInfo = new RunningTaskInfo();
runningTaskInfo.taskId = taskId;
@@ -826,4 +899,32 @@ public final class GameServiceProviderInstanceImplTest {
mIsFocused = focused;
}
}
-} \ No newline at end of file
+
+ private final class FakeContext extends ContextWrapper {
+ private Intent mLastStartedIntent;
+
+ FakeContext(Context base) {
+ super(base);
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mMockPackageManager;
+ }
+
+ @Override
+ public void startActivity(Intent intent) {
+ mLastStartedIntent = intent;
+ }
+
+ @Override
+ public void enforceCallingPermission(String permission, @Nullable String message) {
+ // Do nothing.
+ }
+
+ Intent getLastStartedIntent() {
+ return mLastStartedIntent;
+ }
+ }
+
+}