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

Commit e83680e2 authored by Jim Blackler's avatar Jim Blackler Committed by Android (Google) Code Review
Browse files

Merge "Add Universal Game Mode Hint mode switching to GameManagerService."

parents 2fd7d4b3 3f91d78c
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);
    }
}