Merge "If necessary, re-create game session when game task is focused." into tm-dev
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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());
|
||||
|
||||
66
services/core/java/com/android/server/app/GameTaskInfo.java
Normal file
66
services/core/java/com/android/server/app/GameTaskInfo.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user