Merge "If necessary, re-create game session when game task is focused." into tm-dev am: 05fc944050

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17756696

Change-Id: Ie10edebce4917bbac09eed2a1e17957057f1887e
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Tyler Lacey
2022-04-27 20:02:08 +00:00
committed by Automerger Merge Worker
5 changed files with 283 additions and 46 deletions

View File

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

View File

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

View File

@@ -0,0 +1,66 @@
/*
* 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);
}
}

View File

@@ -0,0 +1,121 @@
/*
* 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;
}
}

View File

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