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

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

Merge "Tie game session lifetime to game process lifetime." into tm-dev am: 4359c9d3

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

Change-Id: I2654fce666a5e1ef68f44928fc08daea426f4323
parents aae6dc80 4359c9d3
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.app;

import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.Intent;
@@ -52,6 +53,7 @@ final class GameServiceProviderInstanceFactoryImpl implements GameServiceProvide
                mContext,
                new GameClassifierImpl(mContext.getPackageManager()),
                ActivityManager.getService(),
                LocalServices.getService(ActivityManagerInternal.class),
                ActivityTaskManager.getService(),
                (WindowManagerService) ServiceManager.getService(Context.WINDOW_SERVICE),
                LocalServices.getService(WindowManagerInternal.class),
+179 −5
Original line number Diff line number Diff line
@@ -21,9 +21,11 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManagerInternal;
import android.app.ActivityTaskManager;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
import android.app.IProcessObserver;
import android.app.TaskStackListener;
import android.content.ComponentName;
import android.content.Context;
@@ -45,6 +47,7 @@ import android.service.games.IGameServiceController;
import android.service.games.IGameSession;
import android.service.games.IGameSessionController;
import android.service.games.IGameSessionService;
import android.text.TextUtils;
import android.util.Slog;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost.SurfacePackage;
@@ -143,12 +146,42 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
                GameServiceProviderInstanceImpl.this.onTaskFocusChanged(taskId, focused);
            });
        }
    };

    /**
     * The TaskStackListener declared above gives us good visibility into game task lifecycle.
     * However, it is possible for the Android system to kill all the processes associated with a
     * game task (e.g., when the system is under memory pressure or reaches a background process
     * limit). When this happens, the game task remains (and no TaskStackListener callbacks are
     * invoked), but we would nonetheless want to destroy a game session associated with the task
     * if this were to happen.
     *
     * This process observer gives us visibility into process lifecycles and lets us track all the
     * processes associated with each package so that any game sessions associated with the package
     * are destroyed if the process count for a given package reaches zero (most packages will
     * have at most one task). If processes for a given package are started up again, the destroyed
     * game sessions will be re-created.
     */
    private final IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
        @Override
        public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
            // This callback is used to track how many processes are running for a given package.
            // Then, when a process dies, we will know if it was the only process running for that
            // package and the associated game sessions should be destroyed.
            mBackgroundExecutor.execute(() -> {
                GameServiceProviderInstanceImpl.this.onForegroundActivitiesChanged(pid);
            });
        }

        @Override
        public void onProcessDied(int pid, int uid) {
            mBackgroundExecutor.execute(() -> {
                GameServiceProviderInstanceImpl.this.onProcessDied(pid);
            });
        }

        // TODO(b/204503192): Limit the lifespan of the game session in the Game Service provider
        // to only when the associated task is running. Right now it is possible for a task to
        // move into the background and for all associated processes to die and for the Game Session
        // provider's GameSessionService to continue to be running. Ideally we could unbind the
        // service when this happens.
        @Override
        public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {}
    };

    private final IGameServiceController mGameServiceController =
@@ -192,6 +225,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
    private final Context mContext;
    private final GameClassifier mGameClassifier;
    private final IActivityManager mActivityManager;
    private final ActivityManagerInternal mActivityManagerInternal;
    private final IActivityTaskManager mActivityTaskManager;
    private final WindowManagerService mWindowManagerService;
    private final WindowManagerInternal mWindowManagerInternal;
@@ -203,6 +237,12 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
    private final ConcurrentHashMap<Integer, GameSessionRecord> mGameSessions =
            new ConcurrentHashMap<>();
    @GuardedBy("mLock")
    private final ConcurrentHashMap<Integer, String> mPidToPackageMap = new ConcurrentHashMap<>();
    @GuardedBy("mLock")
    private final ConcurrentHashMap<String, Integer> mPackageNameToProcessCountMap =
            new ConcurrentHashMap<>();

    @GuardedBy("mLock")
    private volatile boolean mIsRunning;

    GameServiceProviderInstanceImpl(
@@ -211,6 +251,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
            @NonNull Context context,
            @NonNull GameClassifier gameClassifier,
            @NonNull IActivityManager activityManager,
            @NonNull ActivityManagerInternal activityManagerInternal,
            @NonNull IActivityTaskManager activityTaskManager,
            @NonNull WindowManagerService windowManagerService,
            @NonNull WindowManagerInternal windowManagerInternal,
@@ -222,6 +263,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
        mContext = context;
        mGameClassifier = gameClassifier;
        mActivityManager = activityManager;
        mActivityManagerInternal = activityManagerInternal;
        mActivityTaskManager = activityTaskManager;
        mWindowManagerService = windowManagerService;
        mWindowManagerInternal = windowManagerInternal;
@@ -263,6 +305,12 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
            Slog.w(TAG, "Failed to register task stack listener", e);
        }

        try {
            mActivityManager.registerProcessObserver(mProcessObserver);
        } catch (RemoteException e) {
            Slog.w(TAG, "Failed to register process observer", e);
        }

        mWindowManagerInternal.registerTaskSystemBarsListener(mTaskSystemBarsVisibilityListener);
    }

@@ -273,6 +321,12 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
        }
        mIsRunning = false;

        try {
            mActivityManager.unregisterProcessObserver(mProcessObserver);
        } catch (RemoteException e) {
            Slog.w(TAG, "Failed to unregister process observer", e);
        }

        try {
            mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
        } catch (RemoteException e) {
@@ -596,6 +650,126 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
        }
    }

    private void onForegroundActivitiesChanged(int pid) {
        synchronized (mLock) {
            onForegroundActivitiesChangedLocked(pid);
        }
    }

    @GuardedBy("mLock")
    private void onForegroundActivitiesChangedLocked(int pid) {
        if (mPidToPackageMap.containsKey(pid)) {
            // We are already tracking this pid, nothing to do.
            return;
        }

        final String packageName = mActivityManagerInternal.getPackageNameByPid(pid);
        if (TextUtils.isEmpty(packageName)) {
            // Game processes should always have a package name.
            return;
        }

        if (!gameSessionExistsForPackageNameLocked(packageName)) {
            // We only need to track processes for tasks with game session records.
            return;
        }

        mPidToPackageMap.put(pid, packageName);
        final int processCountForPackage = mPackageNameToProcessCountMap.getOrDefault(packageName,
                0) + 1;
        mPackageNameToProcessCountMap.put(packageName, processCountForPackage);

        if (DEBUG) {
            Slog.d(TAG, "onForegroundActivitiesChangedLocked: tracking pid " + pid + ", for "
                    + packageName + ". Process count for package: " + processCountForPackage);
        }

        // If there are processes for the package, we may need to re-create game sessions
        // that are associated with the package
        if (processCountForPackage > 0) {
            recreateEndedGameSessionsLocked(packageName);
        }
    }

    @GuardedBy("mLock")
    private void recreateEndedGameSessionsLocked(String packageName) {
        for (GameSessionRecord gameSessionRecord : mGameSessions.values()) {
            if (gameSessionRecord.isGameSessionEndedForProcessDeath() && packageName.equals(
                    gameSessionRecord.getComponentName().getPackageName())) {
                if (DEBUG) {
                    Slog.d(TAG,
                            "recreateGameSessionsLocked(): re-creating game session for: "
                                    + packageName + " with taskId: "
                                    + gameSessionRecord.getTaskId());
                }

                final int taskId = gameSessionRecord.getTaskId();
                mGameSessions.put(taskId, GameSessionRecord.awaitingGameSessionRequest(taskId,
                        gameSessionRecord.getComponentName()));
                createGameSessionLocked(gameSessionRecord.getTaskId());
            }
        }
    }

    private void onProcessDied(int pid) {
        synchronized (mLock) {
            onProcessDiedLocked(pid);
        }
    }

    @GuardedBy("mLock")
    private void onProcessDiedLocked(int pid) {
        final String packageName = mPidToPackageMap.remove(pid);
        if (packageName == null) {
            // We weren't tracking this process.
            return;
        }

        final Integer oldProcessCountForPackage = mPackageNameToProcessCountMap.get(packageName);
        if (oldProcessCountForPackage == null) {
            // This should never happen; we should have a process count for all tracked packages.
            Slog.w(TAG, "onProcessDiedLocked(): Missing process count for package");
            return;
        }

        final int processCountForPackage = oldProcessCountForPackage - 1;
        mPackageNameToProcessCountMap.put(packageName, processCountForPackage);

        // If there are no more processes for the game, then we will terminate any game sessions
        // running for the package.
        if (processCountForPackage <= 0) {
            endGameSessionsForPackageLocked(packageName);
        }
    }

    @GuardedBy("mLock")
    private void endGameSessionsForPackageLocked(String packageName) {
        for (GameSessionRecord gameSessionRecord : mGameSessions.values()) {
            if (gameSessionRecord.getGameSession() != null && packageName.equals(
                    gameSessionRecord.getComponentName().getPackageName())) {
                if (DEBUG) {
                    Slog.d(TAG, "endGameSessionsForPackageLocked(): No more processes for "
                            + packageName + ", ending game session with taskId: "
                            + gameSessionRecord.getTaskId());
                }
                mGameSessions.put(gameSessionRecord.getTaskId(),
                        gameSessionRecord.withGameSessionEndedOnProcessDeath());
                destroyGameSessionFromRecordLocked(gameSessionRecord);
            }
        }
    }

    @GuardedBy("mLock")
    private boolean gameSessionExistsForPackageNameLocked(String packageName) {
        for (GameSessionRecord gameSessionRecord : mGameSessions.values()) {
            if (packageName.equals(gameSessionRecord.getComponentName().getPackageName())) {
                return true;
            }
        }

        return false;
    }

    @Nullable
    private GameSessionViewHostConfiguration createViewHostConfigurationForTask(int taskId) {
        RunningTaskInfo runningTaskInfo = getRunningTaskInfoForTask(taskId);
+18 −0
Original line number Diff line number Diff line
@@ -35,6 +35,10 @@ final class GameSessionRecord {
        // A Game Session is created and attached.
        // GameSessionRecord.getGameSession() != null.
        GAME_SESSION_ATTACHED,
        // A Game Session did exist for a given game task but was destroyed because the last process
        // for the game died.
        // GameSessionRecord.getGameSession() == null.
        GAME_SESSION_ENDED_PROCESS_DEATH,
    }

    private final int mTaskId;
@@ -98,6 +102,20 @@ final class GameSessionRecord {
                surfacePackage);
    }

    @NonNull
    public GameSessionRecord withGameSessionEndedOnProcessDeath() {
        return new GameSessionRecord(
                mTaskId,
                State.GAME_SESSION_ENDED_PROCESS_DEATH,
                mRootComponentName,
                /* gameSession=*/ null,
                /* surfacePackage=*/ null);
    }

    public boolean isGameSessionEndedForProcessDeath() {
        return mState == State.GAME_SESSION_ENDED_PROCESS_DEATH;
    }

    @NonNull
    public int getTaskId() {
        return mTaskId;
+259 −3

File changed.

Preview size limit exceeded, changes collapsed.