diff options
| author | 2023-01-27 22:34:43 +0000 | |
|---|---|---|
| committer | 2023-01-27 22:34:43 +0000 | |
| commit | 0825f6b4d084ac96f01fa9f8ad5be096a3daa044 (patch) | |
| tree | 45806050b390bf0b2c38088b401d9b3ce262032c | |
| parent | c52342b106b5a71212d8b7816c03c05b7f85df75 (diff) | |
| parent | c88463fdb585fecc83e2daf09add7eefc8957503 (diff) | |
Merge "Add Universal Game Mode Hint mode switching to GameManagerService." into tm-qpr-dev
3 files changed, 211 insertions, 28 deletions
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 3584f16c6de1..ac782289606a 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -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) {} + } } diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml index 33ac73560968..ea0481e03b97 100644 --- a/services/tests/mockingservicestests/AndroidManifest.xml +++ b/services/tests/mockingservicestests/AndroidManifest.xml @@ -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" /> diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index fa4a9de62a88..2d5f0b01ab4c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -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); + } } |