Loading services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java +2 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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), Loading services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +179 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 = Loading Loading @@ -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; Loading @@ -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( Loading @@ -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, Loading @@ -222,6 +263,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan mContext = context; mGameClassifier = gameClassifier; mActivityManager = activityManager; mActivityManagerInternal = activityManagerInternal; mActivityTaskManager = activityTaskManager; mWindowManagerService = windowManagerService; mWindowManagerInternal = windowManagerInternal; Loading Loading @@ -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); } Loading @@ -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) { Loading Loading @@ -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); Loading services/core/java/com/android/server/app/GameSessionRecord.java +18 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java +259 −3 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java +2 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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), Loading
services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +179 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 = Loading Loading @@ -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; Loading @@ -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( Loading @@ -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, Loading @@ -222,6 +263,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan mContext = context; mGameClassifier = gameClassifier; mActivityManager = activityManager; mActivityManagerInternal = activityManagerInternal; mActivityTaskManager = activityTaskManager; mWindowManagerService = windowManagerService; mWindowManagerInternal = windowManagerInternal; Loading Loading @@ -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); } Loading @@ -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) { Loading Loading @@ -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); Loading
services/core/java/com/android/server/app/GameSessionRecord.java +18 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading
services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java +259 −3 File changed.Preview size limit exceeded, changes collapsed. Show changes