Loading services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java +8 −3 Original line number Diff line number Diff line Loading @@ -20,9 +20,11 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.content.Context; import android.content.Intent; import android.os.ServiceManager; import android.os.UserHandle; import android.service.games.GameService; import android.service.games.GameSessionService; import android.service.games.IGameService; Loading @@ -47,14 +49,17 @@ final class GameServiceProviderInstanceFactoryImpl implements GameServiceProvide @Override public GameServiceProviderInstance create( @NonNull GameServiceComponentConfiguration configuration) { final UserHandle userHandle = configuration.getUserHandle(); final IActivityTaskManager activityTaskManager = ActivityTaskManager.getService(); return new GameServiceProviderInstanceImpl( configuration.getUserHandle(), userHandle, BackgroundThread.getExecutor(), mContext, new GameClassifierImpl(mContext.getPackageManager()), new GameTaskInfoProvider(userHandle, activityTaskManager, new GameClassifierImpl(mContext.getPackageManager())), ActivityManager.getService(), LocalServices.getService(ActivityManagerInternal.class), ActivityTaskManager.getService(), activityTaskManager, (WindowManagerService) ServiceManager.getService(Context.WINDOW_SERVICE), LocalServices.getService(WindowManagerInternal.class), new GameServiceConnector(mContext, configuration), Loading services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +48 −39 Original line number Diff line number Diff line Loading @@ -64,7 +64,6 @@ import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener; import com.android.server.wm.WindowManagerService; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; Loading Loading @@ -218,7 +217,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan private final UserHandle mUserHandle; private final Executor mBackgroundExecutor; private final Context mContext; private final GameClassifier mGameClassifier; private final GameTaskInfoProvider mGameTaskInfoProvider; private final IActivityManager mActivityManager; private final ActivityManagerInternal mActivityManagerInternal; private final IActivityTaskManager mActivityTaskManager; Loading @@ -244,7 +243,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan @NonNull UserHandle userHandle, @NonNull Executor backgroundExecutor, @NonNull Context context, @NonNull GameClassifier gameClassifier, @NonNull GameTaskInfoProvider gameTaskInfoProvider, @NonNull IActivityManager activityManager, @NonNull ActivityManagerInternal activityManagerInternal, @NonNull IActivityTaskManager activityTaskManager, Loading @@ -256,7 +255,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan mUserHandle = userHandle; mBackgroundExecutor = backgroundExecutor; mContext = context; mGameClassifier = gameClassifier; mGameTaskInfoProvider = gameTaskInfoProvider; mActivityManager = activityManager; mActivityManagerInternal = activityManagerInternal; mActivityTaskManager = activityTaskManager; Loading Loading @@ -344,13 +343,14 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } private void onTaskCreated(int taskId, @NonNull ComponentName componentName) { String packageName = componentName.getPackageName(); if (!mGameClassifier.isGame(packageName, mUserHandle)) { final GameTaskInfo taskInfo = mGameTaskInfoProvider.get(taskId, componentName); if (!taskInfo.mIsGameTask) { return; } synchronized (mLock) { gameTaskStartedLocked(taskId, componentName); gameTaskStartedLocked(taskInfo); } } Loading @@ -367,7 +367,17 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } final GameSessionRecord gameSessionRecord = mGameSessions.get(taskId); if (gameSessionRecord == null || gameSessionRecord.getGameSession() == null) { if (gameSessionRecord == null) { if (focused) { // The game session for a game task may have been destroyed when the game task // was put into the background by pressing the back button. If the task is restored // via the Recents UI there will be no TaskStackListener#onCreated call for the // restoration, so this focus event is the first opportunity to re-create the game // session. maybeCreateGameSessionForFocusedTaskLocked(taskId); } return; } else if (gameSessionRecord.getGameSession() == null) { return; } Loading @@ -379,30 +389,50 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } @GuardedBy("mLock") private void gameTaskStartedLocked(int taskId, @NonNull ComponentName componentName) { private void maybeCreateGameSessionForFocusedTaskLocked(int taskId) { if (DEBUG) { Slog.i(TAG, "gameStartedLocked() id: " + taskId + " component: " + componentName); Slog.d(TAG, "maybeRecreateGameSessionForFocusedTaskLocked() id: " + taskId); } final GameTaskInfo taskInfo = mGameTaskInfoProvider.get(taskId); if (taskInfo == null) { Slog.w(TAG, "No task info for focused task: " + taskId); return; } if (!taskInfo.mIsGameTask) { return; } gameTaskStartedLocked(taskInfo); } @GuardedBy("mLock") private void gameTaskStartedLocked(@NonNull GameTaskInfo gameTaskInfo) { if (DEBUG) { Slog.i(TAG, "gameStartedLocked(): " + gameTaskInfo); } if (!mIsRunning) { return; } GameSessionRecord existingGameSessionRecord = mGameSessions.get(taskId); GameSessionRecord existingGameSessionRecord = mGameSessions.get(gameTaskInfo.mTaskId); if (existingGameSessionRecord != null) { Slog.w(TAG, "Existing game session found for task (id: " + taskId Slog.w(TAG, "Existing game session found for task (id: " + gameTaskInfo.mTaskId + ") creation. Ignoring."); return; } GameSessionRecord gameSessionRecord = GameSessionRecord.awaitingGameSessionRequest( taskId, componentName); mGameSessions.put(taskId, gameSessionRecord); gameTaskInfo.mTaskId, gameTaskInfo.mComponentName); mGameSessions.put(gameTaskInfo.mTaskId, gameSessionRecord); AndroidFuture<Void> unusedPostGameStartedFuture = mGameServiceConnector.post( gameService -> { gameService.gameStarted( new GameStartedEvent(taskId, componentName.getPackageName())); new GameStartedEvent(gameTaskInfo.mTaskId, gameTaskInfo.mComponentName.getPackageName())); }); } Loading Loading @@ -769,7 +799,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan @Nullable private GameSessionViewHostConfiguration createViewHostConfigurationForTask(int taskId) { RunningTaskInfo runningTaskInfo = getRunningTaskInfoForTask(taskId); RunningTaskInfo runningTaskInfo = mGameTaskInfoProvider.getRunningTaskInfo(taskId); if (runningTaskInfo == null) { return null; } Loading @@ -781,28 +811,6 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan bounds.height()); } @Nullable private RunningTaskInfo getRunningTaskInfoForTask(int taskId) { List<RunningTaskInfo> runningTaskInfos; try { runningTaskInfos = mActivityTaskManager.getTasks( /* maxNum= */ Integer.MAX_VALUE, /* filterOnlyVisibleRecents= */ true, /* keepIntentExtra= */ false); } catch (RemoteException ex) { Slog.w(TAG, "Failed to fetch running tasks"); return null; } for (RunningTaskInfo taskInfo : runningTaskInfos) { if (taskInfo.taskId == taskId) { return taskInfo; } } return null; } @VisibleForTesting void takeScreenshot(int taskId, @NonNull AndroidFuture callback) { GameSessionRecord gameSessionRecord; Loading Loading @@ -834,7 +842,8 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } else { final Bundle bundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle( bitmap); final RunningTaskInfo runningTaskInfo = getRunningTaskInfoForTask(taskId); final RunningTaskInfo runningTaskInfo = mGameTaskInfoProvider.getRunningTaskInfo(taskId); if (runningTaskInfo == null) { Slog.w(TAG, "Could not get running task info for id: " + taskId); callback.complete(GameScreenshotResult.createInternalErrorResult()); Loading services/core/java/com/android/server/app/GameTaskInfo.java 0 → 100644 +66 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.app; import android.content.ComponentName; import java.util.Objects; final class GameTaskInfo { final int mTaskId; final boolean mIsGameTask; final ComponentName mComponentName; GameTaskInfo(int taskId, boolean isGameTask, ComponentName componentName) { mTaskId = taskId; mIsGameTask = isGameTask; mComponentName = componentName; } @Override public String toString() { return "GameTaskInfo{" + "mTaskId=" + mTaskId + ", mIsGameTask=" + mIsGameTask + ", mComponentName=" + mComponentName + '}'; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof GameTaskInfo)) { return false; } GameTaskInfo that = (GameTaskInfo) o; return mTaskId == that.mTaskId && mIsGameTask == that.mIsGameTask && mComponentName.equals(that.mComponentName); } @Override public int hashCode() { return Objects.hash(mTaskId, mIsGameTask, mComponentName); } } services/core/java/com/android/server/app/GameTaskInfoProvider.java 0 → 100644 +121 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.app; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; import android.app.IActivityTaskManager; import android.content.ComponentName; import android.os.RemoteException; import android.os.UserHandle; import android.util.LruCache; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import java.util.List; final class GameTaskInfoProvider { private static final String TAG = "GameTaskInfoProvider"; private static final int TASK_INFO_CACHE_MAX_SIZE = 50; private final Object mLock = new Object(); @GuardedBy("mLock") private final LruCache<Integer, GameTaskInfo> mGameTaskInfoCache = new LruCache<>( TASK_INFO_CACHE_MAX_SIZE); private final UserHandle mUserHandle; private final IActivityTaskManager mActivityTaskManager; private final GameClassifier mGameClassifier; GameTaskInfoProvider(@NonNull UserHandle userHandle, @NonNull IActivityTaskManager activityTaskManager, @NonNull GameClassifier gameClassifier) { mUserHandle = userHandle; mActivityTaskManager = activityTaskManager; mGameClassifier = gameClassifier; } @Nullable GameTaskInfo get(int taskId) { synchronized (mLock) { final GameTaskInfo cachedTaskInfo = mGameTaskInfoCache.get(taskId); if (cachedTaskInfo != null) { return cachedTaskInfo; } } final RunningTaskInfo runningTaskInfo = getRunningTaskInfo(taskId); if (runningTaskInfo == null || runningTaskInfo.baseActivity == null) { return null; } return generateGameInfo(taskId, runningTaskInfo.baseActivity); } GameTaskInfo get(int taskId, @NonNull ComponentName componentName) { synchronized (mLock) { final GameTaskInfo cachedTaskInfo = mGameTaskInfoCache.get(taskId); if (cachedTaskInfo != null) { if (cachedTaskInfo.mComponentName.equals(componentName)) { Slog.w(TAG, "Found cached task info for taskId " + taskId + " but cached component name " + cachedTaskInfo.mComponentName + " does not match " + componentName); } else { return cachedTaskInfo; } } } return generateGameInfo(taskId, componentName); } @Nullable RunningTaskInfo getRunningTaskInfo(int taskId) { List<RunningTaskInfo> runningTaskInfos; try { runningTaskInfos = mActivityTaskManager.getTasks( /* maxNum= */ Integer.MAX_VALUE, /* filterOnlyVisibleRecents= */ false, /* keepIntentExtra= */ false); } catch (RemoteException ex) { Slog.w(TAG, "Failed to fetch running tasks"); return null; } for (RunningTaskInfo taskInfo : runningTaskInfos) { if (taskInfo.taskId == taskId) { return taskInfo; } } return null; } private GameTaskInfo generateGameInfo(int taskId, @NonNull ComponentName componentName) { final GameTaskInfo gameTaskInfo = new GameTaskInfo(taskId, mGameClassifier.isGame(componentName.getPackageName(), mUserHandle), componentName); synchronized (mLock) { mGameTaskInfoCache.put(taskId, gameTaskInfo); } return gameTaskInfo; } } services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java +40 −4 Original line number Diff line number Diff line Loading @@ -216,11 +216,12 @@ public final class GameServiceProviderInstanceImplTest { mRunningTaskInfos); final UserHandle userHandle = new UserHandle(USER_ID); mGameServiceProviderInstance = new GameServiceProviderInstanceImpl( new UserHandle(USER_ID), userHandle, ConcurrentUtils.DIRECT_EXECUTOR, mMockContext, mFakeGameClassifier, new GameTaskInfoProvider(userHandle, mMockActivityTaskManager, mFakeGameClassifier), mMockActivityManager, mMockActivityManagerInternal, mMockActivityTaskManager, Loading Loading @@ -787,6 +788,36 @@ public final class GameServiceProviderInstanceImplTest { assertThat(gameSession10.mIsDestroyed).isTrue(); } @Test public void gameTaskFocusedWithCreateAfterRemoved_gameSessionRecreated() throws Exception { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); mFakeGameSessionService.removePendingFutureForTaskId(10) .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); stopTask(10); assertThat(gameSession10.mIsDestroyed).isTrue(); // If the game task is restored via the Recents UI, the task will be running again but // we would not expect any call to TaskStackListener#onTaskCreated. addRunningTaskInfo(10, GAME_A_MAIN_ACTIVITY); // We now receive a task focused event for the task. This will occur if the game task is // restored via the Recents UI. dispatchTaskFocused(10, /*focused=*/ true); mFakeGameService.requestCreateGameSession(10); // Verify that a new pending game session is created for the game's taskId. assertNotNull(mFakeGameSessionService.removePendingFutureForTaskId(10)); } @Test public void gameTaskRemoved_removesTaskOverlay() throws Exception { mGameServiceProviderInstance.start(); Loading Loading @@ -1144,13 +1175,18 @@ public final class GameServiceProviderInstanceImplTest { } private void startTask(int taskId, ComponentName componentName) { addRunningTaskInfo(taskId, componentName); dispatchTaskCreated(taskId, componentName); } private void addRunningTaskInfo(int taskId, ComponentName componentName) { RunningTaskInfo runningTaskInfo = new RunningTaskInfo(); runningTaskInfo.taskId = taskId; runningTaskInfo.baseActivity = componentName; runningTaskInfo.displayId = 1; runningTaskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 500, 800)); mRunningTaskInfos.add(runningTaskInfo); dispatchTaskCreated(taskId, componentName); } private void stopTask(int taskId) { Loading Loading
services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java +8 −3 Original line number Diff line number Diff line Loading @@ -20,9 +20,11 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.content.Context; import android.content.Intent; import android.os.ServiceManager; import android.os.UserHandle; import android.service.games.GameService; import android.service.games.GameSessionService; import android.service.games.IGameService; Loading @@ -47,14 +49,17 @@ final class GameServiceProviderInstanceFactoryImpl implements GameServiceProvide @Override public GameServiceProviderInstance create( @NonNull GameServiceComponentConfiguration configuration) { final UserHandle userHandle = configuration.getUserHandle(); final IActivityTaskManager activityTaskManager = ActivityTaskManager.getService(); return new GameServiceProviderInstanceImpl( configuration.getUserHandle(), userHandle, BackgroundThread.getExecutor(), mContext, new GameClassifierImpl(mContext.getPackageManager()), new GameTaskInfoProvider(userHandle, activityTaskManager, new GameClassifierImpl(mContext.getPackageManager())), ActivityManager.getService(), LocalServices.getService(ActivityManagerInternal.class), ActivityTaskManager.getService(), activityTaskManager, (WindowManagerService) ServiceManager.getService(Context.WINDOW_SERVICE), LocalServices.getService(WindowManagerInternal.class), new GameServiceConnector(mContext, configuration), Loading
services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +48 −39 Original line number Diff line number Diff line Loading @@ -64,7 +64,6 @@ import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener; import com.android.server.wm.WindowManagerService; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; Loading Loading @@ -218,7 +217,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan private final UserHandle mUserHandle; private final Executor mBackgroundExecutor; private final Context mContext; private final GameClassifier mGameClassifier; private final GameTaskInfoProvider mGameTaskInfoProvider; private final IActivityManager mActivityManager; private final ActivityManagerInternal mActivityManagerInternal; private final IActivityTaskManager mActivityTaskManager; Loading @@ -244,7 +243,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan @NonNull UserHandle userHandle, @NonNull Executor backgroundExecutor, @NonNull Context context, @NonNull GameClassifier gameClassifier, @NonNull GameTaskInfoProvider gameTaskInfoProvider, @NonNull IActivityManager activityManager, @NonNull ActivityManagerInternal activityManagerInternal, @NonNull IActivityTaskManager activityTaskManager, Loading @@ -256,7 +255,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan mUserHandle = userHandle; mBackgroundExecutor = backgroundExecutor; mContext = context; mGameClassifier = gameClassifier; mGameTaskInfoProvider = gameTaskInfoProvider; mActivityManager = activityManager; mActivityManagerInternal = activityManagerInternal; mActivityTaskManager = activityTaskManager; Loading Loading @@ -344,13 +343,14 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } private void onTaskCreated(int taskId, @NonNull ComponentName componentName) { String packageName = componentName.getPackageName(); if (!mGameClassifier.isGame(packageName, mUserHandle)) { final GameTaskInfo taskInfo = mGameTaskInfoProvider.get(taskId, componentName); if (!taskInfo.mIsGameTask) { return; } synchronized (mLock) { gameTaskStartedLocked(taskId, componentName); gameTaskStartedLocked(taskInfo); } } Loading @@ -367,7 +367,17 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } final GameSessionRecord gameSessionRecord = mGameSessions.get(taskId); if (gameSessionRecord == null || gameSessionRecord.getGameSession() == null) { if (gameSessionRecord == null) { if (focused) { // The game session for a game task may have been destroyed when the game task // was put into the background by pressing the back button. If the task is restored // via the Recents UI there will be no TaskStackListener#onCreated call for the // restoration, so this focus event is the first opportunity to re-create the game // session. maybeCreateGameSessionForFocusedTaskLocked(taskId); } return; } else if (gameSessionRecord.getGameSession() == null) { return; } Loading @@ -379,30 +389,50 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } @GuardedBy("mLock") private void gameTaskStartedLocked(int taskId, @NonNull ComponentName componentName) { private void maybeCreateGameSessionForFocusedTaskLocked(int taskId) { if (DEBUG) { Slog.i(TAG, "gameStartedLocked() id: " + taskId + " component: " + componentName); Slog.d(TAG, "maybeRecreateGameSessionForFocusedTaskLocked() id: " + taskId); } final GameTaskInfo taskInfo = mGameTaskInfoProvider.get(taskId); if (taskInfo == null) { Slog.w(TAG, "No task info for focused task: " + taskId); return; } if (!taskInfo.mIsGameTask) { return; } gameTaskStartedLocked(taskInfo); } @GuardedBy("mLock") private void gameTaskStartedLocked(@NonNull GameTaskInfo gameTaskInfo) { if (DEBUG) { Slog.i(TAG, "gameStartedLocked(): " + gameTaskInfo); } if (!mIsRunning) { return; } GameSessionRecord existingGameSessionRecord = mGameSessions.get(taskId); GameSessionRecord existingGameSessionRecord = mGameSessions.get(gameTaskInfo.mTaskId); if (existingGameSessionRecord != null) { Slog.w(TAG, "Existing game session found for task (id: " + taskId Slog.w(TAG, "Existing game session found for task (id: " + gameTaskInfo.mTaskId + ") creation. Ignoring."); return; } GameSessionRecord gameSessionRecord = GameSessionRecord.awaitingGameSessionRequest( taskId, componentName); mGameSessions.put(taskId, gameSessionRecord); gameTaskInfo.mTaskId, gameTaskInfo.mComponentName); mGameSessions.put(gameTaskInfo.mTaskId, gameSessionRecord); AndroidFuture<Void> unusedPostGameStartedFuture = mGameServiceConnector.post( gameService -> { gameService.gameStarted( new GameStartedEvent(taskId, componentName.getPackageName())); new GameStartedEvent(gameTaskInfo.mTaskId, gameTaskInfo.mComponentName.getPackageName())); }); } Loading Loading @@ -769,7 +799,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan @Nullable private GameSessionViewHostConfiguration createViewHostConfigurationForTask(int taskId) { RunningTaskInfo runningTaskInfo = getRunningTaskInfoForTask(taskId); RunningTaskInfo runningTaskInfo = mGameTaskInfoProvider.getRunningTaskInfo(taskId); if (runningTaskInfo == null) { return null; } Loading @@ -781,28 +811,6 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan bounds.height()); } @Nullable private RunningTaskInfo getRunningTaskInfoForTask(int taskId) { List<RunningTaskInfo> runningTaskInfos; try { runningTaskInfos = mActivityTaskManager.getTasks( /* maxNum= */ Integer.MAX_VALUE, /* filterOnlyVisibleRecents= */ true, /* keepIntentExtra= */ false); } catch (RemoteException ex) { Slog.w(TAG, "Failed to fetch running tasks"); return null; } for (RunningTaskInfo taskInfo : runningTaskInfos) { if (taskInfo.taskId == taskId) { return taskInfo; } } return null; } @VisibleForTesting void takeScreenshot(int taskId, @NonNull AndroidFuture callback) { GameSessionRecord gameSessionRecord; Loading Loading @@ -834,7 +842,8 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } else { final Bundle bundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle( bitmap); final RunningTaskInfo runningTaskInfo = getRunningTaskInfoForTask(taskId); final RunningTaskInfo runningTaskInfo = mGameTaskInfoProvider.getRunningTaskInfo(taskId); if (runningTaskInfo == null) { Slog.w(TAG, "Could not get running task info for id: " + taskId); callback.complete(GameScreenshotResult.createInternalErrorResult()); Loading
services/core/java/com/android/server/app/GameTaskInfo.java 0 → 100644 +66 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.app; import android.content.ComponentName; import java.util.Objects; final class GameTaskInfo { final int mTaskId; final boolean mIsGameTask; final ComponentName mComponentName; GameTaskInfo(int taskId, boolean isGameTask, ComponentName componentName) { mTaskId = taskId; mIsGameTask = isGameTask; mComponentName = componentName; } @Override public String toString() { return "GameTaskInfo{" + "mTaskId=" + mTaskId + ", mIsGameTask=" + mIsGameTask + ", mComponentName=" + mComponentName + '}'; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof GameTaskInfo)) { return false; } GameTaskInfo that = (GameTaskInfo) o; return mTaskId == that.mTaskId && mIsGameTask == that.mIsGameTask && mComponentName.equals(that.mComponentName); } @Override public int hashCode() { return Objects.hash(mTaskId, mIsGameTask, mComponentName); } }
services/core/java/com/android/server/app/GameTaskInfoProvider.java 0 → 100644 +121 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.app; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; import android.app.IActivityTaskManager; import android.content.ComponentName; import android.os.RemoteException; import android.os.UserHandle; import android.util.LruCache; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import java.util.List; final class GameTaskInfoProvider { private static final String TAG = "GameTaskInfoProvider"; private static final int TASK_INFO_CACHE_MAX_SIZE = 50; private final Object mLock = new Object(); @GuardedBy("mLock") private final LruCache<Integer, GameTaskInfo> mGameTaskInfoCache = new LruCache<>( TASK_INFO_CACHE_MAX_SIZE); private final UserHandle mUserHandle; private final IActivityTaskManager mActivityTaskManager; private final GameClassifier mGameClassifier; GameTaskInfoProvider(@NonNull UserHandle userHandle, @NonNull IActivityTaskManager activityTaskManager, @NonNull GameClassifier gameClassifier) { mUserHandle = userHandle; mActivityTaskManager = activityTaskManager; mGameClassifier = gameClassifier; } @Nullable GameTaskInfo get(int taskId) { synchronized (mLock) { final GameTaskInfo cachedTaskInfo = mGameTaskInfoCache.get(taskId); if (cachedTaskInfo != null) { return cachedTaskInfo; } } final RunningTaskInfo runningTaskInfo = getRunningTaskInfo(taskId); if (runningTaskInfo == null || runningTaskInfo.baseActivity == null) { return null; } return generateGameInfo(taskId, runningTaskInfo.baseActivity); } GameTaskInfo get(int taskId, @NonNull ComponentName componentName) { synchronized (mLock) { final GameTaskInfo cachedTaskInfo = mGameTaskInfoCache.get(taskId); if (cachedTaskInfo != null) { if (cachedTaskInfo.mComponentName.equals(componentName)) { Slog.w(TAG, "Found cached task info for taskId " + taskId + " but cached component name " + cachedTaskInfo.mComponentName + " does not match " + componentName); } else { return cachedTaskInfo; } } } return generateGameInfo(taskId, componentName); } @Nullable RunningTaskInfo getRunningTaskInfo(int taskId) { List<RunningTaskInfo> runningTaskInfos; try { runningTaskInfos = mActivityTaskManager.getTasks( /* maxNum= */ Integer.MAX_VALUE, /* filterOnlyVisibleRecents= */ false, /* keepIntentExtra= */ false); } catch (RemoteException ex) { Slog.w(TAG, "Failed to fetch running tasks"); return null; } for (RunningTaskInfo taskInfo : runningTaskInfos) { if (taskInfo.taskId == taskId) { return taskInfo; } } return null; } private GameTaskInfo generateGameInfo(int taskId, @NonNull ComponentName componentName) { final GameTaskInfo gameTaskInfo = new GameTaskInfo(taskId, mGameClassifier.isGame(componentName.getPackageName(), mUserHandle), componentName); synchronized (mLock) { mGameTaskInfoCache.put(taskId, gameTaskInfo); } return gameTaskInfo; } }
services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java +40 −4 Original line number Diff line number Diff line Loading @@ -216,11 +216,12 @@ public final class GameServiceProviderInstanceImplTest { mRunningTaskInfos); final UserHandle userHandle = new UserHandle(USER_ID); mGameServiceProviderInstance = new GameServiceProviderInstanceImpl( new UserHandle(USER_ID), userHandle, ConcurrentUtils.DIRECT_EXECUTOR, mMockContext, mFakeGameClassifier, new GameTaskInfoProvider(userHandle, mMockActivityTaskManager, mFakeGameClassifier), mMockActivityManager, mMockActivityManagerInternal, mMockActivityTaskManager, Loading Loading @@ -787,6 +788,36 @@ public final class GameServiceProviderInstanceImplTest { assertThat(gameSession10.mIsDestroyed).isTrue(); } @Test public void gameTaskFocusedWithCreateAfterRemoved_gameSessionRecreated() throws Exception { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); mFakeGameSessionService.removePendingFutureForTaskId(10) .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); stopTask(10); assertThat(gameSession10.mIsDestroyed).isTrue(); // If the game task is restored via the Recents UI, the task will be running again but // we would not expect any call to TaskStackListener#onTaskCreated. addRunningTaskInfo(10, GAME_A_MAIN_ACTIVITY); // We now receive a task focused event for the task. This will occur if the game task is // restored via the Recents UI. dispatchTaskFocused(10, /*focused=*/ true); mFakeGameService.requestCreateGameSession(10); // Verify that a new pending game session is created for the game's taskId. assertNotNull(mFakeGameSessionService.removePendingFutureForTaskId(10)); } @Test public void gameTaskRemoved_removesTaskOverlay() throws Exception { mGameServiceProviderInstance.start(); Loading Loading @@ -1144,13 +1175,18 @@ public final class GameServiceProviderInstanceImplTest { } private void startTask(int taskId, ComponentName componentName) { addRunningTaskInfo(taskId, componentName); dispatchTaskCreated(taskId, componentName); } private void addRunningTaskInfo(int taskId, ComponentName componentName) { RunningTaskInfo runningTaskInfo = new RunningTaskInfo(); runningTaskInfo.taskId = taskId; runningTaskInfo.baseActivity = componentName; runningTaskInfo.displayId = 1; runningTaskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 500, 800)); mRunningTaskInfos.add(runningTaskInfo); dispatchTaskCreated(taskId, componentName); } private void stopTask(int taskId) { Loading