Add Universal Game Mode Hint mode switching to GameManagerService.

Bug: 214405108

Test: atest com.android.server.app.GameManagerServiceTests
Test: Manual with "adb logcat | grep GameManagerService", and opening/backgrounding a game on the attached device.
Change-Id: I0883e6bae316199aeeab9f7dda8934ccd67e7243
Merged-In: I0883e6bae316199aeeab9f7dda8934ccd67e7243
This commit is contained in:
Jim Blackler
2022-12-16 18:16:53 +00:00
parent a1531f2c28
commit c88463fdb5
3 changed files with 211 additions and 28 deletions

View File

@@ -51,6 +51,7 @@ import android.app.GameManager.GameMode;
import android.app.GameModeInfo;
import android.app.GameState;
import android.app.IGameManagerService;
import android.app.IUidObserver;
import android.app.compat.PackageOverride;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -118,6 +119,7 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Service to manage game related features.
@@ -171,40 +173,19 @@ public final class GameManagerService extends IGameManagerService.Stub {
private final ArrayMap<String, GamePackageConfiguration> mOverrideConfigs = new ArrayMap<>();
@Nullable
private final GameServiceController mGameServiceController;
private final Object mUidObserverLock = new Object();
@VisibleForTesting
@Nullable
final UidObserver mUidObserver;
@GuardedBy("mUidObserverLock")
private final Set<Integer> mForegroundGameUids = new HashSet<>();
public GameManagerService(Context context) {
this(context, createServiceThread().getLooper());
}
GameManagerService(Context context, Looper looper) {
mContext = context;
mHandler = new SettingsHandler(looper);
mPackageManager = mContext.getPackageManager();
mUserManager = mContext.getSystemService(UserManager.class);
mPlatformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mSystemDir = new File(Environment.getDataDirectory(), "system");
mSystemDir.mkdirs();
FileUtils.setPermissions(mSystemDir.toString(),
FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH,
-1, -1);
mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir,
GAME_MODE_INTERVENTION_LIST_FILE_NAME));
FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(),
FileUtils.S_IRUSR | FileUtils.S_IWUSR
| FileUtils.S_IRGRP | FileUtils.S_IWGRP,
-1, -1);
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
mGameServiceController = new GameServiceController(
context, BackgroundThread.getExecutor(),
new GameServiceProviderSelectorImpl(
context.getResources(),
context.getPackageManager()),
new GameServiceProviderInstanceFactoryImpl(context));
} else {
mGameServiceController = null;
}
this(context, looper, Environment.getDataDirectory());
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -237,6 +218,14 @@ public final class GameManagerService extends IGameManagerService.Stub {
} else {
mGameServiceController = null;
}
mUidObserver = new UidObserver();
try {
ActivityManager.getService().registerUidObserver(mUidObserver,
ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
ActivityManager.PROCESS_STATE_UNKNOWN, null);
} catch (RemoteException e) {
Slog.w(TAG, "Could not register UidObserver");
}
}
@Override
@@ -1874,4 +1863,66 @@ public final class GameManagerService extends IGameManagerService.Stub {
* load dynamic library for frame rate overriding JNI calls
*/
private static native void nativeSetOverrideFrameRate(int uid, float frameRate);
final class UidObserver extends IUidObserver.Stub {
@Override
public void onUidIdle(int uid, boolean disabled) {}
@Override
public void onUidGone(int uid, boolean disabled) {
synchronized (mUidObserverLock) {
disableGameMode(uid);
}
}
@Override
public void onUidActive(int uid) {}
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
synchronized (mUidObserverLock) {
if (ActivityManager.isProcStateBackground(procState)) {
disableGameMode(uid);
return;
}
final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
if (packages == null || packages.length == 0) {
return;
}
final int userId = mContext.getUserId();
if (!Arrays.stream(packages).anyMatch(p -> isPackageGame(p, userId))) {
return;
}
if (mForegroundGameUids.isEmpty()) {
Slog.v(TAG, "Game power mode ON (process state was changed to foreground)");
mPowerManagerInternal.setPowerMode(Mode.GAME, true);
}
mForegroundGameUids.add(uid);
}
}
private void disableGameMode(int uid) {
synchronized (mUidObserverLock) {
if (!mForegroundGameUids.contains(uid)) {
return;
}
mForegroundGameUids.remove(uid);
if (!mForegroundGameUids.isEmpty()) {
return;
}
Slog.v(TAG,
"Game power mode OFF (process remomved or state changed to background)");
mPowerManagerInternal.setPowerMode(Mode.GAME, false);
}
}
@Override
public void onUidCachedChanged(int uid, boolean cached) {}
@Override
public void onUidProcAdjChanged(int uid) {}
}
}

View File

@@ -43,6 +43,9 @@
<!-- needed by TrustManagerServiceTest to access LockSettings' secure storage -->
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
<!-- needed by GameManagerServiceTest because GameManager creates a UidObserver -->
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<application android:testOnly="true"
android:debuggable="true">
<uses-library android:name="android.test.runner" />

View File

@@ -29,13 +29,16 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.ActivityManager;
import android.app.GameManager;
import android.app.GameModeInfo;
import android.app.GameState;
@@ -203,6 +206,23 @@ public class GameManagerServiceTests {
LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
}
private void mockAppCategory(String packageName, @ApplicationInfo.Category int category)
throws Exception {
reset(mMockPackageManager);
final ApplicationInfo gameApplicationInfo = new ApplicationInfo();
gameApplicationInfo.category = category;
gameApplicationInfo.packageName = packageName;
final PackageInfo pi = new PackageInfo();
pi.packageName = packageName;
pi.applicationInfo = gameApplicationInfo;
final List<PackageInfo> packages = new ArrayList<>();
packages.add(pi);
when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
.thenReturn(packages);
when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
.thenReturn(gameApplicationInfo);
}
@After
public void tearDown() throws Exception {
LocalServices.removeServiceForTest(PowerManagerInternal.class);
@@ -1597,4 +1617,113 @@ public class GameManagerServiceTests {
ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
ArgumentMatchers.eq(0.0f));
}
private GameManagerService createServiceAndStartUser(int userId) {
GameManagerService gameManagerService = new GameManagerService(mMockContext,
mTestLooper.getLooper());
startUser(gameManagerService, userId);
return gameManagerService;
}
@Test
public void testGamePowerMode_gamePackage() throws Exception {
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
String[] packages = {mPackageName};
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
}
@Test
public void testGamePowerMode_twoGames() throws Exception {
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
String[] packages1 = {mPackageName};
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1);
String someGamePkg = "some.game";
String[] packages2 = {someGamePkg};
int somePackageId = DEFAULT_PACKAGE_UID + 1;
when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
HashMap<Integer, Boolean> powerState = new HashMap<>();
doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1)))
.when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean());
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
assertTrue(powerState.get(Mode.GAME));
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
assertTrue(powerState.get(Mode.GAME));
gameManagerService.mUidObserver.onUidStateChanged(
somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
assertFalse(powerState.get(Mode.GAME));
}
@Test
public void testGamePowerMode_twoGamesOverlap() throws Exception {
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
String[] packages1 = {mPackageName};
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1);
String someGamePkg = "some.game";
String[] packages2 = {someGamePkg};
int somePackageId = DEFAULT_PACKAGE_UID + 1;
when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
}
@Test
public void testGamePowerMode_released() throws Exception {
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
String[] packages = {mPackageName};
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
}
@Test
public void testGamePowerMode_noPackage() throws Exception {
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
String[] packages = {};
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true);
}
@Test
public void testGamePowerMode_notAGamePackage() throws Exception {
mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
String[] packages = {"someapp"};
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true);
}
@Test
public void testGamePowerMode_notAGamePackageNotReleased() throws Exception {
mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
String[] packages = {"someapp"};
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, false);
}
}