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

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

Change-Id: I2654fce666a5e1ef68f44928fc08daea426f4323
This commit is contained in:
Tyler Lacey
2022-03-09 20:55:23 +00:00
committed by Automerger Merge Worker
4 changed files with 458 additions and 8 deletions

View File

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

View File

@@ -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);
});
}
};
// 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.
/**
* 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);
});
}
@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);

View File

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

View File

@@ -19,6 +19,7 @@ package com.android.server.app;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.app.GameServiceProviderInstanceImplTest.FakeGameService.GameServiceState;
@@ -26,19 +27,21 @@ import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verifyZeroInteractions;
import android.Manifest;
import android.annotation.Nullable;
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.ITaskStackListener;
import android.content.ComponentName;
import android.content.Context;
@@ -135,6 +138,8 @@ public final class GameServiceProviderInstanceImplTest {
private MockitoSession mMockingSession;
private GameServiceProviderInstance mGameServiceProviderInstance;
@Mock
private ActivityManagerInternal mMockActivityManagerInternal;
@Mock
private IActivityTaskManager mMockActivityTaskManager;
@Mock
private WindowManagerService mMockWindowManagerService;
@@ -151,6 +156,7 @@ public final class GameServiceProviderInstanceImplTest {
private FakeGameSessionService mFakeGameSessionService;
private FakeServiceConnector<IGameSessionService> mFakeGameSessionServiceConnector;
private ArrayList<ITaskStackListener> mTaskStackListeners;
private ArrayList<IProcessObserver> mProcessObservers;
private ArrayList<TaskSystemBarsListener> mTaskSystemBarsListeners;
private ArrayList<RunningTaskInfo> mRunningTaskInfos;
@@ -185,6 +191,16 @@ public final class GameServiceProviderInstanceImplTest {
return null;
}).when(mMockActivityTaskManager).unregisterTaskStackListener(any());
mProcessObservers = new ArrayList<>();
doAnswer(invocation -> {
mProcessObservers.add(invocation.getArgument(0));
return null;
}).when(mMockActivityManager).registerProcessObserver(any());
doAnswer(invocation -> {
mProcessObservers.remove(invocation.getArgument(0));
return null;
}).when(mMockActivityManager).unregisterProcessObserver(any());
mTaskSystemBarsListeners = new ArrayList<>();
doAnswer(invocation -> {
mTaskSystemBarsListeners.add(invocation.getArgument(0));
@@ -206,6 +222,7 @@ public final class GameServiceProviderInstanceImplTest {
mMockContext,
mFakeGameClassifier,
mMockActivityManager,
mMockActivityManagerInternal,
mMockActivityTaskManager,
mMockWindowManagerService,
mMockWindowManagerInternal,
@@ -428,6 +445,214 @@ public final class GameServiceProviderInstanceImplTest {
verify(mMockWindowManagerInternal).addTrustedTaskOverlay(eq(10), eq(mockSurfacePackage10));
}
@Test
public void gameProcessStopped_soleProcess_destroysGameSession() throws Exception {
int gameProcessId = 1000;
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
startProcessForPackage(gameProcessId, GAME_A_PACKAGE);
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));
assertThat(gameSession10.mIsDestroyed).isFalse();
// Death of the sole game process destroys the game session.
dispatchProcessDied(gameProcessId);
assertThat(gameSession10.mIsDestroyed).isTrue();
}
@Test
public void gameProcessStopped_soleProcess_destroysMultipleGameSessionsForSamePackage()
throws Exception {
int gameProcessId = 1000;
mGameServiceProviderInstance.start();
// Multiple tasks exist for the same package.
startTask(10, GAME_A_MAIN_ACTIVITY);
startTask(11, GAME_A_MAIN_ACTIVITY);
startProcessForPackage(gameProcessId, GAME_A_PACKAGE);
mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
mFakeGameService.requestCreateGameSession(11);
FakeGameSession gameSession10 = new FakeGameSession();
SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
mFakeGameSessionService.removePendingFutureForTaskId(10)
.complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
FakeGameSession gameSession11 = new FakeGameSession();
SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class);
mFakeGameSessionService.removePendingFutureForTaskId(11)
.complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11));
assertThat(gameSession10.mIsDestroyed).isFalse();
assertThat(gameSession11.mIsDestroyed).isFalse();
// Death of the sole game process destroys both game sessions.
dispatchProcessDied(gameProcessId);
assertThat(gameSession10.mIsDestroyed).isTrue();
assertThat(gameSession11.mIsDestroyed).isTrue();
}
@Test
public void gameProcessStopped_multipleProcesses_gameSessionDestroyedWhenAllDead()
throws Exception {
int firstGameProcessId = 1000;
int secondGameProcessId = 1001;
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
startProcessForPackage(firstGameProcessId, GAME_A_PACKAGE);
startProcessForPackage(secondGameProcessId, GAME_A_PACKAGE);
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));
assertThat(gameSession10.mIsDestroyed).isFalse();
// Death of the first process (with the second one still alive) does not destroy the game
// session.
dispatchProcessDied(firstGameProcessId);
assertThat(gameSession10.mIsDestroyed).isFalse();
// Death of the second process does destroy the game session.
dispatchProcessDied(secondGameProcessId);
assertThat(gameSession10.mIsDestroyed).isTrue();
}
@Test
public void gameProcessCreatedAfterInitialProcessDead_newGameSessionCreated() throws Exception {
int firstGameProcessId = 1000;
int secondGameProcessId = 1000;
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
startProcessForPackage(firstGameProcessId, GAME_A_PACKAGE);
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));
assertThat(gameSession10.mIsDestroyed).isFalse();
// After the first game process dies, the game session should be destroyed.
dispatchProcessDied(firstGameProcessId);
assertThat(gameSession10.mIsDestroyed).isTrue();
// However, when a new process for the game starts, a new game session should be created.
startProcessForPackage(secondGameProcessId, GAME_A_PACKAGE);
// Verify that a new pending game session is created for the game's taskId.
assertNotNull(mFakeGameSessionService.removePendingFutureForTaskId(10));
}
@Test
public void gameProcessCreatedAfterInitialProcessDead_multipleGameSessionsCreatedSamePackage()
throws Exception {
int firstGameProcessId = 1000;
int secondGameProcessId = 1000;
mGameServiceProviderInstance.start();
// Multiple tasks exist for the same package.
startTask(10, GAME_A_MAIN_ACTIVITY);
startTask(11, GAME_A_MAIN_ACTIVITY);
startProcessForPackage(firstGameProcessId, GAME_A_PACKAGE);
mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
mFakeGameService.requestCreateGameSession(11);
FakeGameSession gameSession10 = new FakeGameSession();
SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
mFakeGameSessionService.removePendingFutureForTaskId(10)
.complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
FakeGameSession gameSession11 = new FakeGameSession();
SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class);
mFakeGameSessionService.removePendingFutureForTaskId(11)
.complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11));
assertThat(gameSession10.mIsDestroyed).isFalse();
assertThat(gameSession11.mIsDestroyed).isFalse();
// After the first game process dies, both game sessions for the package should be
// destroyed.
dispatchProcessDied(firstGameProcessId);
assertThat(gameSession10.mIsDestroyed).isTrue();
assertThat(gameSession11.mIsDestroyed).isTrue();
// However, when a new process for the game starts, new game sessions for the same
// package should be created.
startProcessForPackage(secondGameProcessId, GAME_A_PACKAGE);
// Verify that new pending game sessions were created for each of the game's taskIds.
assertNotNull(mFakeGameSessionService.removePendingFutureForTaskId(10));
assertNotNull(mFakeGameSessionService.removePendingFutureForTaskId(11));
}
@Test
public void gameProcessStarted_gameSessionNotRequested_doesNothing() throws Exception {
int gameProcessId = 1000;
mGameServiceProviderInstance.start();
// A game task and process are started, but requestCreateGameSession is never called.
startTask(10, GAME_A_MAIN_ACTIVITY);
startProcessForPackage(gameProcessId, GAME_A_PACKAGE);
mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
// No game session should be created.
assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty();
}
@Test
public void processActivityAndDeath_notForGame_gameSessionUnaffected() 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));
// Process activity for a process without a known package is ignored.
startProcessForPackage(1000, /*packageName=*/ null);
dispatchProcessActivity(1000);
dispatchProcessDied(1000);
// Process activity for a process with a different package is ignored
startProcessForPackage(1001, GAME_B_PACKAGE);
dispatchProcessActivity(1001);
dispatchProcessDied(1001);
// Death of a process for which there was no activity is ignored
dispatchProcessDied(1002);
// Despite all the process activity and death, the game session is not destroyed.
assertThat(gameSession10.mIsDestroyed).isFalse();
}
@Test
public void taskSystemBarsListenerChanged_noAssociatedGameSession_doesNothing() {
mGameServiceProviderInstance.start();
@@ -900,7 +1125,8 @@ public final class GameServiceProviderInstanceImplTest {
mFakeGameSessionService.getCapturedCreateInvocations())
.mGameSessionController.restartGame(11);
verifyZeroInteractions(mMockActivityManager);
verify(mMockActivityManager).registerProcessObserver(any());
verifyNoMoreInteractions(mMockActivityManager);
assertThat(mMockContext.getLastStartedIntent()).isNull();
}
@@ -932,7 +1158,6 @@ public final class GameServiceProviderInstanceImplTest {
dispatchTaskRemoved(taskId);
}
private void dispatchTaskRemoved(int taskId) {
dispatchTaskChangeEvent(taskStackListener -> {
taskStackListener.onTaskRemoved(taskId);
@@ -958,6 +1183,37 @@ public final class GameServiceProviderInstanceImplTest {
}
}
private void startProcessForPackage(int processId, @Nullable String packageName) {
if (packageName != null) {
when(mMockActivityManagerInternal.getPackageNameByPid(processId)).thenReturn(
packageName);
}
dispatchProcessActivity(processId);
}
private void dispatchProcessActivity(int processId) {
dispatchProcessChangedEvent(processObserver -> {
// Neither uid nor foregroundActivities are used by the implementation being tested.
processObserver.onForegroundActivitiesChanged(processId, /*uid=*/
0, /*foregroundActivities=*/ false);
});
}
private void dispatchProcessDied(int processId) {
dispatchProcessChangedEvent(processObserver -> {
// The uid param is not used by the implementation being tested.
processObserver.onProcessDied(processId, /*uid=*/ 0);
});
}
private void dispatchProcessChangedEvent(
ThrowingConsumer<IProcessObserver> processObserverConsumer) {
for (IProcessObserver processObserver : mProcessObservers) {
processObserverConsumer.accept(processObserver);
}
}
private void mockPermissionGranted(String permission) {
mMockContext.setPermission(permission, PackageManager.PERMISSION_GRANTED);
}