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

Commit 3f91d78c authored by Jim Blackler's avatar Jim Blackler
Browse files

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
parent e4241197
Loading
Loading
Loading
Loading
+78 −26
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import android.app.GameModeInfo;
import android.app.GameState;
import android.app.IGameManagerService;
import android.app.IGameModeListener;
import android.app.IUidObserver;
import android.app.StatsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -165,38 +166,19 @@ public final class GameManagerService extends IGameManagerService.Stub {
    private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = 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);
        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)
@@ -227,6 +209,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
@@ -2152,4 +2142,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) {}
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -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" />
+104 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ 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;
@@ -45,6 +46,7 @@ import static org.mockito.Mockito.when;

import android.Manifest;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.GameManager;
import android.app.GameModeConfiguration;
import android.app.GameModeInfo;
@@ -2212,4 +2214,106 @@ public class GameManagerServiceTests {
        assertEquals(expectedInterventionListOutput,
                gameManagerService.getInterventionList(mPackageName, USER_ID_1));
    }

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