Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e2f8ab18 authored by Tyler Lacey's avatar Tyler Lacey Committed by Automerger Merge Worker
Browse files

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

parents f45ef072 05fc9440
Loading
Loading
Loading
Loading
+8 −3
Original line number Diff line number Diff line
@@ -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),
+48 −39
Original line number Diff line number Diff line
@@ -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 −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);
    }
}
+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;
    }
}
+40 −4
Original line number Diff line number Diff line
@@ -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) {