/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.app;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.server.app.GameManagerService.CANCEL_GAME_LOADING_MODE;
import static com.android.server.app.GameManagerService.Injector;
import static com.android.server.app.GameManagerService.LOADING_BOOST_MAX_DURATION;
import static com.android.server.app.GameManagerService.PROPERTY_DEBUG_GFX_GAME_DEFAULT_FRAME_RATE_DISABLED;
import static com.android.server.app.GameManagerService.PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE;
import static com.android.server.app.GameManagerService.SET_GAME_STATE;
import static com.android.server.app.GameManagerService.WRITE_DELAY_MILLIS;
import static com.android.server.app.GameManagerService.WRITE_GAME_MODE_INTERVENTION_LIST_FILE;
import static com.android.server.app.GameManagerService.WRITE_SETTINGS;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.clearInvocations;
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.annotation.Nullable;
import android.app.ActivityManager;
import android.app.GameManager;
import android.app.GameModeConfiguration;
import android.app.GameModeInfo;
import android.app.GameState;
import android.app.IGameModeListener;
import android.app.IGameStateListener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.hardware.power.Mode;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.UserManager;
import android.os.test.FakePermissionEnforcer;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.server.app.Flags;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.server.LocalServices;
import com.android.server.SystemService;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;

import java.io.File;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
public class GameManagerServiceTests {
    @Mock MockContext mMockContext;
    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
                SetFlagsRule.DefaultInitValueType.NULL_DEFAULT);
    private static final String TAG = "GameManagerServiceTests";
    private static final String PACKAGE_NAME_INVALID = "com.android.app";
    private static final int USER_ID_1 = 1001;
    private static final int USER_ID_2 = 1002;
    // to pass the valid package check in some of the server methods
    private static final int DEFAULT_PACKAGE_UID = Binder.getCallingUid();

    private MockitoSession mMockingSession;
    private String mPackageName;
    private Map<String, Integer> mPackageCategories;
    private Map<String, Integer> mPackageUids;
    private TestLooper mTestLooper;
    @Mock
    private PackageManager mMockPackageManager;
    @Mock
    private PowerManagerInternal mMockPowerManager;
    @Mock
    private UserManager mMockUserManager;
    private BroadcastReceiver mShutDownActionReceiver;

    private FakePermissionEnforcer mFakePermissionEnforcer = new FakePermissionEnforcer();

    @Mock
    private GameManagerServiceSystemPropertiesWrapper mSysPropsMock;

    @Captor
    ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor;

    // Stolen from ConnectivityServiceTest.MockContext
    class MockContext extends ContextWrapper {
        private static final String TAG = "MockContext";

        // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
        private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();

        MockContext(Context base) {
            super(base);
        }

        /**
         * Mock checks for the specified permission, and have them behave as per {@code granted}.
         *
         * <p>Passing null reverts to default behavior, which does a real permission check on the
         * test package.
         *
         * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
         *                {@link PackageManager#PERMISSION_DENIED}.
         */
        public void setPermission(String permission, Integer granted) {
            mMockedPermissions.put(permission, granted);
        }

        private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) {
            final Integer granted = mMockedPermissions.get(permission);
            return granted != null ? granted : ifAbsent.get();
        }

        @Override
        public int checkPermission(String permission, int pid, int uid) {
            return checkMockedPermission(
                    permission, () -> super.checkPermission(permission, pid, uid));
        }

        @Override
        public int checkCallingOrSelfPermission(String permission) {
            return checkMockedPermission(
                    permission, () -> super.checkCallingOrSelfPermission(permission));
        }

        @Override
        public void enforceCallingOrSelfPermission(String permission, String message) {
            final Integer granted = mMockedPermissions.get(permission);
            if (granted == null) {
                super.enforceCallingOrSelfPermission(permission, message);
                return;
            }

            if (!granted.equals(PackageManager.PERMISSION_GRANTED)) {
                throw new SecurityException("[Test] permission denied: " + permission);
            }
        }

        @Override
        public PackageManager getPackageManager() {
            return mMockPackageManager;
        }

        @Override
        public Object getSystemService(String name) {
            switch (name) {
                case Context.USER_SERVICE:
                    return mMockUserManager;
                case Context.PERMISSION_ENFORCER_SERVICE:
                    return mFakePermissionEnforcer;
            }
            throw new UnsupportedOperationException("Couldn't find system service: " + name);
        }

        @Override
        public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) {
            mShutDownActionReceiver = receiver;
            return null;
        }
    }

    @Before
    public void setUp() throws Exception {
        mTestLooper = new TestLooper();
        mMockingSession = mockitoSession()
                .initMocks(this)
                .mockStatic(DeviceConfig.class)
                .strictness(Strictness.WARN)
                .startMocking();
        mMockContext = new MockContext(InstrumentationRegistry.getContext());
        mPackageCategories = new HashMap<>();
        mPackageUids = new HashMap<>();
        mPackageName = mMockContext.getPackageName();
        mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME);
        LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);

        mSetFlagsRule.enableFlags(Flags.FLAG_GAME_DEFAULT_FRAME_RATE);
        mSetFlagsRule.disableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
    }

    private void mockAppCategory(String packageName, int packageUid,
            @ApplicationInfo.Category int category)
            throws Exception {
        reset(mMockPackageManager);
        mPackageCategories.put(packageName, category);
        mPackageUids.put(packageName, packageUid);
        final List<PackageInfo> packageInfos = new ArrayList<>();
        for (Map.Entry<String, Integer> entry : mPackageCategories.entrySet()) {

            packageName = entry.getKey();
            packageUid = mPackageUids.get(packageName);
            category = entry.getValue();
            ApplicationInfo applicationInfo = new ApplicationInfo();
            applicationInfo.packageName = packageName;
            applicationInfo.category = category;
            when(mMockPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(), anyInt()))
                    .thenReturn(applicationInfo);

            final PackageInfo pi = new PackageInfo();
            pi.packageName = packageName;
            pi.applicationInfo = applicationInfo;
            packageInfos.add(pi);

            when(mMockPackageManager.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(
                    packageUid);
            when(mMockPackageManager.getPackagesForUid(packageUid)).thenReturn(
                    new String[]{packageName});
        }
        when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
                .thenReturn(packageInfos);
        final Resources resources =
                InstrumentationRegistry.getInstrumentation().getContext().getResources();
        when(mMockPackageManager.getResourcesForApplication(anyString()))
                .thenReturn(resources);
    }

    @After
    public void tearDown() throws Exception {
        LocalServices.removeServiceForTest(PowerManagerInternal.class);
        if (mMockingSession != null) {
            mMockingSession.finishMocking();
        }
        deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
    }

    private void startUser(GameManagerService gameManagerService, int userId) {
        UserInfo userInfo = new UserInfo(userId, "name", 0);
        gameManagerService.onUserStarting(new SystemService.TargetUser(userInfo),
                InstrumentationRegistry.getContext().getFilesDir());
        mTestLooper.dispatchAll();
    }

    private void switchUser(GameManagerService gameManagerService, int from, int to) {
        UserInfo userInfoFrom = new UserInfo(from, "name", 0);
        UserInfo userInfoTo = new UserInfo(to, "name", 0);
        gameManagerService.onUserSwitching(/* from */ new SystemService.TargetUser(userInfoFrom),
                /* to */ new SystemService.TargetUser(userInfoTo));
        mTestLooper.dispatchAll();
    }

    private void mockQueryAllPackageGranted() {
        mMockContext.setPermission(Manifest.permission.QUERY_ALL_PACKAGES,
                PackageManager.PERMISSION_GRANTED);
    }

    private void mockQueryAllPackageDenied() {
        mMockContext.setPermission(Manifest.permission.QUERY_ALL_PACKAGES,
                PackageManager.PERMISSION_DENIED);
    }

    private void mockManageUsersGranted() {
        mMockContext.setPermission(Manifest.permission.MANAGE_USERS,
                PackageManager.PERMISSION_GRANTED);
    }

    private void mockModifyGameModeGranted() {
        mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
                PackageManager.PERMISSION_GRANTED);
    }

    private void mockModifyGameModeDenied() {
        mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
                PackageManager.PERMISSION_DENIED);
    }

    private void mockDeviceConfigDefault() {
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn("");
    }

    private void mockDeviceConfigNone() {
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(null);
    }

    private void mockDeviceConfigPerformance() {
        String configString = "mode=2,downscaleFactor=0.5,useAngle=false,fps=90";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configString);
    }

    // ANGLE will be disabled for most apps, so treat enabling ANGLE as a special case.
    private void mockDeviceConfigPerformanceEnableAngle() {
        String configString = "mode=2,downscaleFactor=0.5,useAngle=true";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configString);
    }

    // Loading boost will be disabled for most apps, so treat enabling loading boost as a special
    // case.
    private void mockDeviceConfigPerformanceEnableLoadingBoost() {
        String configString = "mode=2,downscaleFactor=0.5,loadingBoost=0";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configString);
    }

    private void mockDeviceConfigBattery() {
        String configString = "mode=3,downscaleFactor=0.7,fps=30";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configString);
    }

    private void mockDeviceConfigAll() {
        String configString = "mode=3,downscaleFactor=0.7,fps=30:mode=2,downscaleFactor=0.5,fps=90";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configString);
    }

    private void mockDeviceConfigInvalid() {
        String configString = "";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configString);
    }

    private void mockDeviceConfigMalformed() {
        String configString = "adsljckv=nin3rn9hn1231245:8795tq=21ewuydg";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configString);
    }

    private void mockGameModeOptInAll() throws Exception {
        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
        Bundle metaDataBundle = new Bundle();
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_PERFORMANCE_MODE_ENABLE, true);
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_BATTERY_MODE_ENABLE, true);
        applicationInfo.metaData = metaDataBundle;
        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
                .thenReturn(applicationInfo);
    }

    private void mockGameModeOptInPerformance() throws Exception {
        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
        Bundle metaDataBundle = new Bundle();
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_PERFORMANCE_MODE_ENABLE, true);
        applicationInfo.metaData = metaDataBundle;
        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
                .thenReturn(applicationInfo);
    }

    private void mockGameModeOptInBattery() throws Exception {
        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
        Bundle metaDataBundle = new Bundle();
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_BATTERY_MODE_ENABLE, true);
        applicationInfo.metaData = metaDataBundle;
        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
                .thenReturn(applicationInfo);
    }

    private void mockInterventionAllowDownscaleTrue() throws Exception {
        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
        Bundle metaDataBundle = new Bundle();
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, true);
        applicationInfo.metaData = metaDataBundle;
        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
                .thenReturn(applicationInfo);
    }

    private void mockInterventionAllowDownscaleFalse() throws Exception {
        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
        Bundle metaDataBundle = new Bundle();
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, false);
        applicationInfo.metaData = metaDataBundle;
        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
                .thenReturn(applicationInfo);
    }

    private void mockInterventionAllowAngleTrue() throws Exception {
        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
        Bundle metaDataBundle = new Bundle();
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_ANGLE_ALLOW_ANGLE, true);
        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
                .thenReturn(applicationInfo);
    }

    private void mockInterventionAllowAngleFalse() throws Exception {
        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
        Bundle metaDataBundle = new Bundle();
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_ANGLE_ALLOW_ANGLE, false);
        applicationInfo.metaData = metaDataBundle;
        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
                .thenReturn(applicationInfo);
    }

    private void mockInterventionsEnabledBatteryOptInFromXml() throws Exception {
        seedGameManagerServiceMetaDataFromFile(mPackageName, 123, "res/xml/"
                + "game_manager_service_metadata_config_interventions_enabled_battery_opt_in.xml");
    }

    private void mockInterventionsEnabledPerformanceOptInFromXml() throws Exception {
        seedGameManagerServiceMetaDataFromFile(mPackageName, 123, "res/xml/"
                + "game_manager_service_metadata_config_interventions_enabled_performance_opt_in"
                + ".xml");
    }

    private void mockInterventionsEnabledNoOptInFromXml() throws Exception {
        seedGameManagerServiceMetaDataFromFile(mPackageName, 123,
                "res/xml/game_manager_service_metadata_config_interventions_enabled_no_opt_in.xml");
    }

    private void mockInterventionsEnabledAllOptInFromXml() throws Exception {
        seedGameManagerServiceMetaDataFromFile(mPackageName, 123,
                "res/xml/game_manager_service_metadata_config_interventions_enabled_all_opt_in"
                        + ".xml");
    }

    private void mockInterventionsDisabledNoOptInFromXml() throws Exception {
        seedGameManagerServiceMetaDataFromFile(mPackageName, 123,
                "res/xml/game_manager_service_metadata_config_interventions_disabled_no_opt_in"
                        + ".xml");
    }

    private void mockInterventionsDisabledAllOptInFromXml() throws Exception {
        seedGameManagerServiceMetaDataFromFile(mPackageName, 123,
                "res/xml/game_manager_service_metadata_config_interventions_disabled_all_opt_in"
                        + ".xml");
    }


    private void seedGameManagerServiceMetaDataFromFile(String packageName, int resId,
            String fileName)
            throws Exception {
        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
        Bundle metaDataBundle = new Bundle();
        metaDataBundle.putInt(
                GameManagerService.GamePackageConfiguration.METADATA_GAME_MODE_CONFIG, resId);
        applicationInfo.metaData = metaDataBundle;
        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
                .thenReturn(applicationInfo);
        AssetManager assetManager =
                InstrumentationRegistry.getInstrumentation().getContext().getAssets();
        XmlResourceParser xmlResourceParser =
                assetManager.openXmlResourceParser(fileName);
        when(mMockPackageManager.getXml(eq(packageName), eq(resId), any()))
                .thenReturn(xmlResourceParser);
    }

    /**
     * By default game mode is set to STANDARD
     */
    @Test
    public void testGetGameMode_defaultValue() {
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        mockModifyGameModeGranted();
        assertEquals(GameManager.GAME_MODE_STANDARD,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));
    }

    @Test
    public void testGetGameMode_nonGame() throws Exception {
        mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO);
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        mockModifyGameModeGranted();
        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));
    }

    /**
     * Test the default behaviour for a nonexistent user.
     */
    @Test
    public void testDefaultValueForNonexistentUser() {
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());

        startUser(gameManagerService, USER_ID_1);
        mockModifyGameModeGranted();

        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_2);
        assertEquals(GameManager.GAME_MODE_STANDARD,
                gameManagerService.getGameMode(mPackageName, USER_ID_2));
    }

    /**
     * Test getter and setter of game modes.
     */
    @Test
    public void testGameMode() {
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());


        startUser(gameManagerService, USER_ID_1);
        gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
        mockModifyGameModeGranted();
        assertEquals(GameManager.GAME_MODE_STANDARD,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));
        // We need to make sure the mode is supported before setting it.
        mockDeviceConfigAll();
        gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
        assertEquals(GameManager.GAME_MODE_STANDARD,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
                USER_ID_1);
        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));
    }

    /**
     * Test invalid package name is queried
     */
    @Test
    public void testGetGameModeInvalidPackageName() {
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());

        startUser(gameManagerService, USER_ID_1);
        try {
            when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
                    .thenThrow(new PackageManager.NameNotFoundException());
            assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
                    gameManagerService.getGameMode(PACKAGE_NAME_INVALID,
                            USER_ID_1));
        } catch (PackageManager.NameNotFoundException e) {
            // should never get here as isPackageGame() catches this exception
            // fail this test if we ever get here
            fail("Unexpected NameNotFoundException caught.");
        }
    }

    /**
     * Test permission.MANAGE_GAME_MODE is checked
     */
    @Test
    public void testSetGameModePermissionDenied() {
        mockModifyGameModeGranted();
        mockDeviceConfigAll();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);

        // Update the game mode so we can read back something valid.
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
        assertEquals(GameManager.GAME_MODE_STANDARD,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));

        // Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated.
        mockModifyGameModeDenied();
        try {
            gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
                    USER_ID_1);

            fail("GameManagerService failed to generate SecurityException when "
                    + "permission.MANAGE_GAME_MODE is denied.");
        } catch (SecurityException ignored) {
        }

        // The test should throw an exception, so the test is passing if we get here.
        mockModifyGameModeGranted();
        // Verify that the Game Mode value wasn't updated.
        assertEquals(GameManager.GAME_MODE_STANDARD,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));
    }

    /**
     * Test game modes are user-specific.
     */
    @Test
    public void testGameModeMultipleUsers() {
        mockModifyGameModeGranted();
        mockDeviceConfigAll();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());

        startUser(gameManagerService, USER_ID_1);
        startUser(gameManagerService, USER_ID_2);
        gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
        gameManagerService.updateConfigsForUser(USER_ID_2, true, mPackageName);

        // Set User 1 to Standard
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
        assertEquals(GameManager.GAME_MODE_STANDARD,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));

        // Set User 2 to Performance and verify User 1 is still Standard
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
                USER_ID_2);
        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
                gameManagerService.getGameMode(mPackageName, USER_ID_2));
        assertEquals(GameManager.GAME_MODE_STANDARD,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));

        // Set User 1 to Battery and verify User 2 is still Performance
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY,
                USER_ID_1);
        assertEquals(GameManager.GAME_MODE_BATTERY,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));
        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
                gameManagerService.getGameMode(mPackageName, USER_ID_2));
    }

    private GameManagerService createServiceAndStartUser(int userId) {
        GameManagerService gameManagerService = new GameManagerService(mMockContext,
                mTestLooper.getLooper());
        startUser(gameManagerService, userId);
        return gameManagerService;
    }

    private void checkReportedAvailableGameModes(GameManagerService gameManagerService,
            int... requiredAvailableModes) {
        Arrays.sort(requiredAvailableModes);
        // check getAvailableGameModes
        int[] reportedAvailableModes = gameManagerService.getAvailableGameModes(mPackageName,
                USER_ID_1);
        Arrays.sort(reportedAvailableModes);
        assertArrayEquals(requiredAvailableModes, reportedAvailableModes);

        // check GetModeInfo.getAvailableGameModes
        GameModeInfo info = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
        if (requiredAvailableModes.length == 0) {
            assertNull(info);
        } else {
            assertNotNull(info);
            reportedAvailableModes = info.getAvailableGameModes();
            Arrays.sort(reportedAvailableModes);
            assertArrayEquals(requiredAvailableModes, reportedAvailableModes);
        }
    }

    private void checkReportedOverriddenGameModes(GameManagerService gameManagerService,
            int... requiredOverriddenModes) {
        Arrays.sort(requiredOverriddenModes);
        // check GetModeInfo.getOverriddenGameModes
        GameModeInfo info = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
        assertNotNull(info);
        int[] overriddenModes = info.getOverriddenGameModes();
        Arrays.sort(overriddenModes);
        assertArrayEquals(requiredOverriddenModes, overriddenModes);
    }

    private void checkDownscaling(GameManagerService gameManagerService,
            int gameMode, float scaling) {
        GameManagerService.GamePackageConfiguration config =
                gameManagerService.getConfig(mPackageName, USER_ID_1);
        assertEquals(scaling, config.getGameModeConfiguration(gameMode).getScaling(), 0.01f);
    }

    private void checkAngleEnabled(GameManagerService gameManagerService, int gameMode,
            boolean angleEnabled) {
        gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);

        // Validate GamePackageConfiguration returns the correct value.
        GameManagerService.GamePackageConfiguration config =
                gameManagerService.getConfig(mPackageName, USER_ID_1);
        assertEquals(config.getGameModeConfiguration(gameMode).getUseAngle(), angleEnabled);

        // Validate GameManagerService.isAngleEnabled() returns the correct value.
        assertEquals(gameManagerService.isAngleEnabled(mPackageName, USER_ID_1), angleEnabled);
    }

    private void checkLoadingBoost(GameManagerService gameManagerService, int gameMode,
            int loadingBoost) {
        // Validate GamePackageConfiguration returns the correct value.
        GameManagerService.GamePackageConfiguration config =
                gameManagerService.getConfig(mPackageName, USER_ID_1);
        assertEquals(
                loadingBoost, config.getGameModeConfiguration(gameMode).getLoadingBoostDuration());

        // Validate GameManagerService.getLoadingBoostDuration() returns the correct value.
        assertEquals(
                loadingBoost, gameManagerService.getLoadingBoostDuration(mPackageName, USER_ID_1));
    }

    private void checkFps(GameManagerService gameManagerService, int gameMode, int fps) {
        GameManagerService.GamePackageConfiguration config =
                gameManagerService.getConfig(mPackageName, USER_ID_1);
        assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps());
    }

    private boolean checkOverridden(GameManagerService gameManagerService, int gameMode) {
        GameManagerService.GamePackageConfiguration config =
                gameManagerService.getConfig(mPackageName, USER_ID_1);
        return config.willGamePerformOptimizations(gameMode);
    }

    /**
     * Phenotype device config exists, but is only propagating the default value.
     */
    @Test
    public void testDeviceConfigDefault() {
        mockDeviceConfigDefault();
        mockModifyGameModeGranted();
        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
    }

    /**
     * Phenotype device config does not exists.
     */
    @Test
    public void testDeviceConfigNone() {
        mockDeviceConfigNone();
        mockModifyGameModeGranted();
        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
    }

    /**
     * Phenotype device config contains values that parse correctly but are not valid in game mode.
     */
    @Test
    public void testDeviceConfigInvalid() {
        mockDeviceConfigInvalid();
        mockModifyGameModeGranted();
        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
    }

    /**
     * Phenotype device config is garbage.
     */
    @Test
    public void testDeviceConfigMalformed() {
        mockDeviceConfigMalformed();
        mockModifyGameModeGranted();
        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
    }

    @Test
    public void testDeviceConfig_nonGame() throws Exception {
        mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO);
        mockDeviceConfigAll();
        mockModifyGameModeGranted();
        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1));
    }

    /**
     * Override device config for performance mode exists and is valid.
     */
    @Test
    public void testSetDeviceConfigOverridePerformance() {
        mockDeviceConfigPerformance();
        mockModifyGameModeGranted();

        GameManagerService gameManagerService = new GameManagerService(
                mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                GameManager.GAME_MODE_PERFORMANCE, "120", "0.3");

        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
        checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.3f);
        checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120);
    }

    /**
     * Override device config for battery mode exists and is valid.
     */
    @Test
    public void testSetDeviceConfigOverrideBattery() {
        mockDeviceConfigBattery();
        mockModifyGameModeGranted();

        GameManagerService gameManagerService = new GameManagerService(
                mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                GameManager.GAME_MODE_BATTERY, "60", "0.5");

        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY,
                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
        checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.5f);
        checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 60);
    }

    /**
     * Override device configs for both battery and performance modes exists and are valid.
     */
    @Test
    public void testSetDeviceConfigOverrideAll() {
        mockDeviceConfigAll();
        mockModifyGameModeGranted();

        GameManagerService gameManagerService = new GameManagerService(
                mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                GameManager.GAME_MODE_PERFORMANCE, "120", "0.3");
        gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                GameManager.GAME_MODE_BATTERY, "60", "0.5");

        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
                GameManager.GAME_MODE_CUSTOM);
        checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.3f);
        checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120);
        checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.5f);
        checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 60);
    }

    @Test
    public void testSetBatteryModeConfigOverride_thenUpdateAllDeviceConfig() throws Exception {
        mockModifyGameModeGranted();
        String configStringBefore =
                "mode=2,downscaleFactor=1.0,fps=90:mode=3,downscaleFactor=0.1,fps=30";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configStringBefore);
        mockInterventionsEnabledNoOptInFromXml();
        GameManagerService gameManagerService = new GameManagerService(mMockContext,
                mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);

        checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 1.0f);
        checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);
        checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.1f);
        checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30);

        gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, 3, "40",
                "0.2");

        checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 40);
        checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.2f);

        String configStringAfter =
                "mode=2,downscaleFactor=0.9,fps=60:mode=3,downscaleFactor=0.3,fps=50";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configStringAfter);
        gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);

        // performance mode was not overridden thus it should be updated
        checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.9f);
        checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 60);

        // battery mode was overridden thus it should be the same as the override
        checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.2f);
        checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 40);
    }

    @Test
    public void testSetBatteryModeConfigOverride_thenOptInBatteryMode() throws Exception {
        mockModifyGameModeGranted();
        String configStringBefore =
                "mode=2,downscaleFactor=1.0,fps=90:mode=3,downscaleFactor=0.1,fps=30";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configStringBefore);
        mockInterventionsDisabledNoOptInFromXml();
        GameManagerService gameManagerService = new GameManagerService(mMockContext,
                mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);

        assertFalse(checkOverridden(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
        assertFalse(checkOverridden(gameManagerService, GameManager.GAME_MODE_BATTERY));
        checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0);

        gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, 3, "40",
                "0.2");
        checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0);
        // override will enable the interventions
        checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.2f);
        checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 40);

        mockInterventionsDisabledAllOptInFromXml();
        gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);

        assertTrue(checkOverridden(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
        // opt-in is still false for battery mode as override exists
        assertFalse(checkOverridden(gameManagerService, GameManager.GAME_MODE_BATTERY));
    }

    /**
     * Override device config for performance mode exists and is valid.
     */
    @Test
    public void testResetDeviceConfigOverridePerformance() {
        mockDeviceConfigPerformance();
        mockModifyGameModeGranted();

        GameManagerService gameManagerService = new GameManagerService(
                mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                GameManager.GAME_MODE_PERFORMANCE, "120", "0.3");

        gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1,
                GameManager.GAME_MODE_PERFORMANCE);

        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
        checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.5f);
        checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);
    }

    /**
     * Override device config for battery mode exists and is valid.
     */
    @Test
    public void testResetDeviceConfigOverrideBattery() {
        mockDeviceConfigBattery();
        mockModifyGameModeGranted();

        GameManagerService gameManagerService = new GameManagerService(
                mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                GameManager.GAME_MODE_BATTERY, "60", "0.5");

        gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1,
                GameManager.GAME_MODE_BATTERY);

        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY,
                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
        checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.7f);
        checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30);
    }

    /**
     * Override device configs for both battery and performance modes exists and are valid.
     */
    @Test
    public void testResetDeviceOverrideConfigAll() {
        mockDeviceConfigAll();
        mockModifyGameModeGranted();

        GameManagerService gameManagerService = new GameManagerService(
                mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                GameManager.GAME_MODE_PERFORMANCE, "120", "0.3");
        gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                GameManager.GAME_MODE_BATTERY, "60", "0.5");

        gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1, -1);

        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
                GameManager.GAME_MODE_CUSTOM);
        checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.5f);
        checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);
        checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.7f);
        checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30);
    }

    /**
     * Override device configs for both battery and performance modes exists and are valid.
     * Only one mode is reset, and the other mode still has overridden config
     */
    @Test
    public void testResetDeviceOverrideConfigPartial() {
        mockDeviceConfigAll();
        mockModifyGameModeGranted();

        GameManagerService gameManagerService = new GameManagerService(
                mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);

        gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                GameManager.GAME_MODE_PERFORMANCE, "120", "0.3");
        gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                GameManager.GAME_MODE_BATTERY, "60", "0.5");

        gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1,
                GameManager.GAME_MODE_BATTERY);

        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
                GameManager.GAME_MODE_CUSTOM);
        checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.3f);
        checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120);
        checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.7f);
        checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30);
    }

    /**
     * Game modes are made available only through app manifest opt-in.
     */
    @Test
    public void testGameModeOptInAll() throws Exception {
        mockGameModeOptInAll();
        mockDeviceConfigNone();
        mockModifyGameModeGranted();
        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
    }


    /**
     * BATTERY game mode is available through the app manifest opt-in.
     */
    @Test
    public void testGameModeOptInBattery() throws Exception {
        mockGameModeOptInBattery();
        mockDeviceConfigNone();
        mockModifyGameModeGranted();
        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
                GameManager.GAME_MODE_CUSTOM);
    }

    /**
     * PERFORMANCE game mode is available through the app manifest opt-in.
     */
    @Test
    public void testGameModeOptInPerformance() throws Exception {
        mockGameModeOptInPerformance();
        mockDeviceConfigNone();
        mockModifyGameModeGranted();
        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD,
                GameManager.GAME_MODE_CUSTOM);
    }

    /**
     * BATTERY game mode is available through the app manifest opt-in and PERFORMANCE game mode is
     * available through Phenotype.
     */
    @Test
    public void testGameModeOptInBatteryMixed() throws Exception {
        mockGameModeOptInBattery();
        mockDeviceConfigPerformance();
        mockModifyGameModeGranted();
        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
    }

    /**
     * PERFORMANCE game mode is available through the app manifest opt-in and BATTERY game mode is
     * available through Phenotype.
     */
    @Test
    public void testGameModeOptInPerformanceMixed() throws Exception {
        mockGameModeOptInPerformance();
        mockDeviceConfigBattery();
        mockModifyGameModeGranted();
        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
    }

    /**
     * PERFORMANCE game mode is configured through Phenotype. The app hasn't specified any metadata.
     */
    @Test
    public void testInterventionAllowScalingDefault() throws Exception {
        mockDeviceConfigPerformance();
        mockModifyGameModeGranted();
        checkDownscaling(createServiceAndStartUser(USER_ID_1), GameManager.GAME_MODE_PERFORMANCE,
                0.5f);
    }

    /**
     * PERFORMANCE game mode is configured through Phenotype. The app has opted-out of scaling.
     */
    @Test
    public void testInterventionAllowDownscaleFalse() throws Exception {
        mockDeviceConfigPerformance();
        mockInterventionAllowDownscaleFalse();
        mockModifyGameModeGranted();
        checkDownscaling(createServiceAndStartUser(USER_ID_1), GameManager.GAME_MODE_PERFORMANCE,
                -1.0f);
    }

    /**
     * PERFORMANCE game mode is configured through Phenotype. The app has redundantly specified
     * the downscaling metadata default value of "true".
     */
    @Test
    public void testInterventionAllowDownscaleTrue() throws Exception {
        mockDeviceConfigPerformance();
        mockInterventionAllowDownscaleTrue();
        mockModifyGameModeGranted();
        checkDownscaling(createServiceAndStartUser(USER_ID_1), GameManager.GAME_MODE_PERFORMANCE,
                0.5f);
    }

    /**
     * PERFORMANCE game mode is configured through Phenotype. The app hasn't specified any metadata.
     */
    @Test
    public void testInterventionAllowAngleDefault() throws Exception {
        mockDeviceConfigPerformance();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, false);
    }

    /**
     * PERFORMANCE game mode is configured through Phenotype. The app hasn't specified any
     * metadata.
     */
    @Test
    public void testInterventionAllowLoadingBoostDefault() throws Exception {
        mockDeviceConfigPerformance();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        checkLoadingBoost(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, -1);
    }

    /**
     * PERFORMANCE game mode is configured through Phenotype. The app has opted-out of ANGLE.
     */
    @Test
    public void testInterventionAllowAngleFalse() throws Exception {
        mockDeviceConfigPerformanceEnableAngle();
        mockInterventionAllowAngleFalse();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, false);
    }

    /**
     * PERFORMANCE game mode is configured through Phenotype. The app has redundantly specified
     * the ANGLE metadata default value of "true".
     */
    @Test
    public void testInterventionAllowAngleTrue() throws Exception {
        mockDeviceConfigPerformanceEnableAngle();
        mockInterventionAllowAngleTrue();

        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        mockModifyGameModeGranted();
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));

        checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, true);
    }

    /**
     * PERFORMANCE game mode is configured through Phenotype. The app has redundantly specified the
     * Loading Boost metadata default value of "true".
     */
    @Test
    public void testInterventionAllowLoadingBoost() throws Exception {
        mockDeviceConfigPerformanceEnableLoadingBoost();

        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        mockModifyGameModeGranted();
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));
        mockInterventionsEnabledNoOptInFromXml();
        checkLoadingBoost(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0);
    }

    @Test
    public void testGameModeConfigAllowFpsTrue() throws Exception {
        mockDeviceConfigAll();
        mockModifyGameModeGranted();
        mockInterventionsEnabledNoOptInFromXml();
        GameManagerService gameManagerService = new GameManagerService(mMockContext,
                mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        GameManagerService.GamePackageConfiguration config =
                gameManagerService.getConfig(mPackageName, USER_ID_1);
        assertEquals(90,
                config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps());
        assertEquals(30, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps());
    }

    @Test
    public void testGameModeConfigAllowFpsFalse() throws Exception {
        mockDeviceConfigAll();
        mockModifyGameModeGranted();
        mockInterventionsDisabledNoOptInFromXml();
        GameManagerService gameManagerService = new GameManagerService(mMockContext,
                mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        GameManagerService.GamePackageConfiguration config =
                gameManagerService.getConfig(mPackageName, USER_ID_1);
        assertEquals(0,
                config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps());
        assertEquals(0, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps());
    }

    @Test
    public void testInterventionFps() throws Exception {
        mockDeviceConfigAll();
        mockModifyGameModeGranted();
        GameManagerService service = createServiceAndStartUser(USER_ID_1);
        checkFps(service, GameManager.GAME_MODE_PERFORMANCE, 90);
        checkFps(service, GameManager.GAME_MODE_BATTERY, 30);
    }

    /**
     * PERFORMANCE game mode is configured through Phenotype, but the app has also opted into the
     * same mode. No interventions for this game mode should be available in this case.
     */
    @Test
    public void testDeviceConfigOptInOverlap() throws Exception {
        mockDeviceConfigPerformance();
        mockGameModeOptInPerformance();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
        GameManagerService.GamePackageConfiguration config =
                gameManagerService.getConfig(mPackageName, USER_ID_1);
        assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
    }

    /**
     * Ensure that, if a game no longer supports any game modes, we set the game mode to
     * STANDARD
     */
    @Test
    public void testUnsetInvalidGameMode() throws Exception {
        mockDeviceConfigNone();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
        assertEquals(GameManager.GAME_MODE_STANDARD,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));
    }

    /**
     * Ensure that, if a game no longer supports a specific game mode, but supports STANDARD, we set
     * the game mode to STANDARD.
     */
    @Test
    public void testResetInvalidGameMode() throws Exception {
        mockDeviceConfigPerformance();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
        gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
        assertEquals(GameManager.GAME_MODE_STANDARD,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));
    }

    /**
     * Ensure that if a game supports STANDARD, but is currently set to UNSUPPORTED, we set the game
     * mode to STANDARD
     */
    @Test
    public void testSetValidGameMode() throws Exception {
        mockDeviceConfigPerformance();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_UNSUPPORTED, USER_ID_1);
        gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
        assertEquals(GameManager.GAME_MODE_STANDARD,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));
    }

    static {
        System.loadLibrary("mockingservicestestjni");
    }

    @Test
    public void testGetGameModeInfoPermissionDenied() {
        mockDeviceConfigAll();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);

        // Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated.
        mockModifyGameModeDenied();
        assertThrows(SecurityException.class,
                () -> gameManagerService.getGameModeInfo(mPackageName, USER_ID_1));
    }

    @Test
    public void testGetGameModeInfoWithAllGameModes() {
        mockDeviceConfigAll();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
        assertTrue(gameModeInfo.isDownscalingAllowed());
        assertTrue(gameModeInfo.isFpsOverrideAllowed());

        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
                GameManager.GAME_MODE_CUSTOM);
        checkReportedOverriddenGameModes(gameManagerService);

        assertEquals(new GameModeConfiguration.Builder()
                .setFpsOverride(30)
                .setScalingFactor(0.7f)
                .build(), gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
        assertEquals(new GameModeConfiguration.Builder()
                .setFpsOverride(90)
                .setScalingFactor(0.5f)
                .build(), gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
    }

    @Test
    public void testGetGameModeInfoWithBatteryMode() {
        mockDeviceConfigBattery();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);

        assertEquals(GameManager.GAME_MODE_BATTERY, gameModeInfo.getActiveGameMode());

        checkReportedAvailableGameModes(gameManagerService,
                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
                GameManager.GAME_MODE_CUSTOM);
        checkReportedOverriddenGameModes(gameManagerService);

        assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
    }

    @Test
    public void testGetGameModeInfoWithPerformanceMode() {
        mockDeviceConfigPerformance();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);

        assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameModeInfo.getActiveGameMode());
        checkReportedAvailableGameModes(gameManagerService,
                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD,
                GameManager.GAME_MODE_CUSTOM);
        checkReportedOverriddenGameModes(gameManagerService);

        assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
    }

    @Test
    public void testGetGameModeInfoWithDefaultGameModes() {
        mockDeviceConfigNone();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);

        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
                GameManager.GAME_MODE_CUSTOM);
        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM));
        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_STANDARD));
        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
    }

    @Test
    public void testGetGameModeInfoWithAllGameModesOverridden_noDeviceConfig()
            throws Exception {
        mockModifyGameModeGranted();
        mockInterventionsEnabledAllOptInFromXml();
        mockDeviceConfigNone();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
        verifyAllModesOverriddenAndInterventionsAvailable(gameManagerService, gameModeInfo);

        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
    }

    @Test
    public void testGetGameModeInfoWithAllGameModesOverridden_allDeviceConfig()
            throws Exception {
        mockModifyGameModeGranted();
        mockInterventionsEnabledAllOptInFromXml();
        mockDeviceConfigAll();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
        verifyAllModesOverriddenAndInterventionsAvailable(gameManagerService, gameModeInfo);

        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
    }

    private void verifyAllModesOverriddenAndInterventionsAvailable(
            GameManagerService gameManagerService,
            GameModeInfo gameModeInfo) {
        checkReportedAvailableGameModes(gameManagerService,
                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
        checkReportedOverriddenGameModes(gameManagerService,
                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY);
        assertTrue(gameModeInfo.isFpsOverrideAllowed());
        assertTrue(gameModeInfo.isDownscalingAllowed());
    }

    @Test
    public void testGetGameModeInfoWithBatteryModeOverridden_withBatteryDeviceConfig()
            throws Exception {
        mockModifyGameModeGranted();
        mockInterventionsEnabledBatteryOptInFromXml();
        mockDeviceConfigBattery();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());

        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY,
                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
        checkReportedOverriddenGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY);

        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
    }

    @Test
    public void testGetGameModeInfoWithPerformanceModeOverridden_withAllDeviceConfig()
            throws Exception {
        mockModifyGameModeGranted();
        mockInterventionsEnabledPerformanceOptInFromXml();
        mockDeviceConfigAll();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());

        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
                GameManager.GAME_MODE_CUSTOM);
        checkReportedOverriddenGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE);

        assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
    }

    @Test
    public void testGetGameModeInfoWithInterventionsDisabled() throws Exception {
        mockModifyGameModeGranted();
        mockInterventionsDisabledAllOptInFromXml();
        mockDeviceConfigAll();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
        assertFalse(gameModeInfo.isFpsOverrideAllowed());
        assertFalse(gameModeInfo.isDownscalingAllowed());
    }

    @Test
    public void testSetGameState_loadingRequiresPerformanceMode() {
        mockDeviceConfigNone();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        GameState gameState = new GameState(true, GameState.MODE_NONE);
        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
        mTestLooper.dispatchAll();
        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
    }

    @Test
    public void testSetGameStateLoading_withNoDeviceConfig() {
        mockDeviceConfigNone();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        gameManagerService.setGameMode(
                mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_1),
                GameManager.GAME_MODE_PERFORMANCE);
        int testMode = GameState.MODE_GAMEPLAY_INTERRUPTIBLE;
        int testLabel = 99;
        int testQuality = 123;
        GameState gameState = new GameState(true, testMode, testLabel, testQuality);
        assertEquals(testMode, gameState.getMode());
        assertEquals(testLabel, gameState.getLabel());
        assertEquals(testQuality, gameState.getQuality());
        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
        mTestLooper.dispatchAll();
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
        reset(mMockPowerManager);
        assertTrue(
                gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME_LOADING, false);
        mTestLooper.moveTimeForward(GameManagerService.LOADING_BOOST_MAX_DURATION);
        mTestLooper.dispatchAll();
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
    }

    @Test
    public void testSetGameStateLoading_withDeviceConfig() {
        String configString = "mode=2,loadingBoost=2000";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configString);
        mockModifyGameModeGranted();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        gameManagerService.setGameMode(
                mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        GameState gameState = new GameState(true, GameState.MODE_GAMEPLAY_INTERRUPTIBLE, 99, 123);
        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
        mTestLooper.dispatchAll();
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME_LOADING, false);
        reset(mMockPowerManager);
        assertTrue(
                gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
        mTestLooper.moveTimeForward(2000);
        mTestLooper.dispatchAll();
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
    }

    @Test
    public void testSetGameStateNotLoading() {
        mockDeviceConfigNone();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        gameManagerService.setGameMode(
                mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        int testMode = GameState.MODE_GAMEPLAY_UNINTERRUPTIBLE;
        int testLabel = 99;
        int testQuality = 123;
        GameState gameState = new GameState(false, testMode, testLabel, testQuality);
        assertFalse(gameState.isLoading());
        assertEquals(testMode, gameState.getMode());
        assertEquals(testLabel, gameState.getLabel());
        assertEquals(testQuality, gameState.getQuality());
        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
        assertTrue(gameManagerService.mHandler.hasEqualMessages(SET_GAME_STATE, gameState));
        mTestLooper.dispatchAll();
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
        assertFalse(
                gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
    }

    @Test
    public void testSetGameState_nonGame() throws Exception {
        mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO);
        mockDeviceConfigNone();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        GameState gameState = new GameState(true, GameState.MODE_NONE);
        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
        assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
    }

    @Test
    public void testAddGameStateListener() throws Exception {
        mockModifyGameModeGranted();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        mockDeviceConfigAll();
        startUser(gameManagerService, USER_ID_1);

        IGameStateListener mockListener = Mockito.mock(IGameStateListener.class);
        IBinder binder = Mockito.mock(IBinder.class);
        when(mockListener.asBinder()).thenReturn(binder);
        gameManagerService.addGameStateListener(mockListener);
        verify(binder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());

        mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO);
        GameState gameState = new GameState(true, GameState.MODE_NONE);
        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
        assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
        mTestLooper.dispatchAll();
        verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt());

        mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME);
        gameState = new GameState(true, GameState.MODE_NONE);
        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
        assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
        mTestLooper.dispatchAll();
        verify(mockListener).onGameStateChanged(mPackageName, gameState, USER_ID_1);
        reset(mockListener);

        gameState = new GameState(false, GameState.MODE_CONTENT);
        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
        mTestLooper.dispatchAll();
        verify(mockListener).onGameStateChanged(mPackageName, gameState, USER_ID_1);
        reset(mockListener);

        mDeathRecipientCaptor.getValue().binderDied();
        verify(binder).unlinkToDeath(eq(mDeathRecipientCaptor.getValue()), anyInt());
        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
        assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
        mTestLooper.dispatchAll();
        verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt());
    }

    @Test
    public void testRemoveGameStateListener() throws Exception {
        mockModifyGameModeGranted();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        mockDeviceConfigAll();
        startUser(gameManagerService, USER_ID_1);

        IGameStateListener mockListener = Mockito.mock(IGameStateListener.class);
        IBinder binder = Mockito.mock(IBinder.class);
        when(mockListener.asBinder()).thenReturn(binder);

        gameManagerService.addGameStateListener(mockListener);
        gameManagerService.removeGameStateListener(mockListener);
        mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME);
        GameState gameState = new GameState(false, GameState.MODE_CONTENT);
        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
        assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
        mTestLooper.dispatchAll();
        verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt());
    }

    private List<String> readGameModeInterventionList() throws Exception {
        final File interventionFile = new File(InstrumentationRegistry.getContext().getFilesDir(),
                "system/game_mode_intervention.list");
        assertNotNull(interventionFile);
        List<String> output = Files.readAllLines(interventionFile.toPath());
        return output;
    }

    private void mockInterventionListForMultipleUsers() throws Exception {
        final String[] packageNames = new String[]{"com.android.app0",
                "com.android.app1", "com.android.app2"};
        int i = 1;
        for (String p : packageNames) {
            mockAppCategory(p, DEFAULT_PACKAGE_UID + i++, ApplicationInfo.CATEGORY_GAME);
        }

        final ApplicationInfo[] applicationInfos = new ApplicationInfo[3];
        final PackageInfo[] pis = new PackageInfo[3];
        for (i = 0; i < 3; ++i) {
            applicationInfos[i] = new ApplicationInfo();
            applicationInfos[i].category = ApplicationInfo.CATEGORY_GAME;
            applicationInfos[i].packageName = packageNames[i];

            pis[i] = new PackageInfo();
            pis[i].packageName = packageNames[i];
            pis[i].applicationInfo = applicationInfos[i];
        }

        final List<PackageInfo> userOnePackages = new ArrayList<>();
        final List<PackageInfo> userTwoPackages = new ArrayList<>();
        userOnePackages.add(pis[1]);
        userTwoPackages.add(pis[0]);
        userTwoPackages.add(pis[2]);

        final List<UserInfo> userInfos = new ArrayList<>(2);
        userInfos.add(new UserInfo());
        userInfos.add(new UserInfo());
        userInfos.get(0).id = USER_ID_1;
        userInfos.get(1).id = USER_ID_2;

        when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), eq(USER_ID_1)))
                .thenReturn(userOnePackages);
        when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), eq(USER_ID_2)))
                .thenReturn(userTwoPackages);
        when(mMockUserManager.getUsers()).thenReturn(userInfos);
    }

    @Test
    public void testVerifyInterventionList() throws Exception {
        mockDeviceConfigAll();
        mockInterventionListForMultipleUsers();
        mockManageUsersGranted();
        mockModifyGameModeGranted();
        final Context context = InstrumentationRegistry.getContext();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper(), context.getFilesDir(),
                        new Injector());
        startUser(gameManagerService, USER_ID_1);
        startUser(gameManagerService, USER_ID_2);
        gameManagerService.setGameModeConfigOverride("com.android.app0", USER_ID_2,
                GameManager.GAME_MODE_PERFORMANCE, "120", "0.6");
        gameManagerService.setGameModeConfigOverride("com.android.app2", USER_ID_2,
                GameManager.GAME_MODE_BATTERY, "60", "0.5");
        mTestLooper.dispatchAll();

        /* Expected fileOutput (order may vary)
         # user 1001:
         com.android.app2 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.5,fps=60
         com.android.app1 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
         com.android.app0 <UID>   1   2   angle=0,scaling=0.6,fps=120 3   angle=0,scaling=0.7,fps=30

         # user 1002:
         com.android.app2 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
         com.android.app1 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
         com.android.app0 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
         The current game mode would only be set to non-zero if the current user have that game
         installed.
        */

        List<String> fileOutput = readGameModeInterventionList();
        assertEquals(fileOutput.size(), 3);

        String[] splitLine = fileOutput.get(0).split("\\s+");
        assertEquals(splitLine[0], "com.android.app2");
        assertEquals(splitLine[2], "3");
        assertEquals(splitLine[3], "2");
        assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
        assertEquals(splitLine[5], "3");
        assertEquals(splitLine[6], "angle=0,scaling=0.5,fps=60");
        splitLine = fileOutput.get(1).split("\\s+");
        assertEquals(splitLine[0], "com.android.app1");
        assertEquals(splitLine[2], "1");
        assertEquals(splitLine[3], "2");
        assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
        assertEquals(splitLine[5], "3");
        assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
        splitLine = fileOutput.get(2).split("\\s+");
        assertEquals(splitLine[0], "com.android.app0");
        assertEquals(splitLine[2], "2");
        assertEquals(splitLine[3], "2");
        assertEquals(splitLine[4], "angle=0,scaling=0.6,fps=120");
        assertEquals(splitLine[5], "3");
        assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");

        switchUser(gameManagerService, USER_ID_2, USER_ID_1);
        gameManagerService.setGameMode("com.android.app1",
                GameManager.GAME_MODE_BATTERY, USER_ID_1);
        mTestLooper.dispatchAll();

        fileOutput = readGameModeInterventionList();
        assertEquals(fileOutput.size(), 3);

        splitLine = fileOutput.get(0).split("\\s+");
        assertEquals(splitLine[0], "com.android.app2");
        assertEquals(splitLine[2], "1");
        assertEquals(splitLine[3], "2");
        assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
        assertEquals(splitLine[5], "3");
        assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
        splitLine = fileOutput.get(1).split("\\s+");
        assertEquals(splitLine[0], "com.android.app1");
        assertEquals(splitLine[2], "3");
        assertEquals(splitLine[3], "2");
        assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
        assertEquals(splitLine[5], "3");
        assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
        splitLine = fileOutput.get(2).split("\\s+");
        assertEquals(splitLine[0], "com.android.app0");
        assertEquals(splitLine[2], "1");
        assertEquals(splitLine[3], "2");
        assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
        assertEquals(splitLine[5], "3");
        assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");

    }

    @Test
    public void testSwitchUser() {
        mockManageUsersGranted();
        mockModifyGameModeGranted();

        mockDeviceConfigBattery();
        final Context context = InstrumentationRegistry.getContext();
        GameManagerService gameManagerService = new GameManagerService(mMockContext,
                mTestLooper.getLooper(), context.getFilesDir(), new Injector());
        startUser(gameManagerService, USER_ID_1);
        startUser(gameManagerService, USER_ID_2);
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_CUSTOM);
        assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_1),
                GameManager.GAME_MODE_BATTERY);

        mockDeviceConfigAll();
        switchUser(gameManagerService, USER_ID_1, USER_ID_2);
        assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_2),
                GameManager.GAME_MODE_STANDARD);
        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE,
                GameManager.GAME_MODE_CUSTOM);

        switchUser(gameManagerService, USER_ID_2, USER_ID_1);
        assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_1),
                GameManager.GAME_MODE_BATTERY);
        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE,
                GameManager.GAME_MODE_CUSTOM);
    }

    @Test
    public void testUpdateResolutionScalingFactor() {
        mockModifyGameModeGranted();
        mockDeviceConfigBattery();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        float scalingFactor = 0.123f;
        gameManagerService.updateResolutionScalingFactor(mPackageName,
                GameManager.GAME_MODE_BATTERY, scalingFactor,
                USER_ID_1);
        assertEquals(scalingFactor, gameManagerService.getResolutionScalingFactor(mPackageName,
                GameManager.GAME_MODE_BATTERY, USER_ID_1), 0.001f);
        scalingFactor = 0.321f;
        gameManagerService.updateResolutionScalingFactor(mPackageName,
                GameManager.GAME_MODE_BATTERY, scalingFactor,
                USER_ID_1);
        assertEquals(scalingFactor, gameManagerService.getResolutionScalingFactor(mPackageName,
                GameManager.GAME_MODE_BATTERY, USER_ID_1), 0.001f);
    }

    @Test
    public void testUpdateResolutionScalingFactor_noDeviceConfig() {
        mockModifyGameModeGranted();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        float scalingFactor = 0.123f;
        gameManagerService.updateResolutionScalingFactor(mPackageName,
                GameManager.GAME_MODE_BATTERY, scalingFactor,
                USER_ID_1);
        assertEquals(scalingFactor, gameManagerService.getResolutionScalingFactor(mPackageName,
                GameManager.GAME_MODE_BATTERY, USER_ID_1), 0.001f);
        scalingFactor = 0.321f;
        gameManagerService.updateResolutionScalingFactor(mPackageName,
                GameManager.GAME_MODE_BATTERY, scalingFactor,
                USER_ID_1);
        assertEquals(scalingFactor, gameManagerService.getResolutionScalingFactor(mPackageName,
                GameManager.GAME_MODE_BATTERY,
                USER_ID_1), 0.001f);
    }

    @Test
    public void testUpdateResolutionScalingFactor_permissionDenied() {
        mockModifyGameModeDenied();
        mockDeviceConfigAll();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        float scalingFactor = 0.123f;
        assertThrows(SecurityException.class, () -> {
            gameManagerService.updateResolutionScalingFactor(mPackageName,
                    GameManager.GAME_MODE_BATTERY, scalingFactor,
                    USER_ID_1);
        });
        mockModifyGameModeGranted();
        assertEquals(0.7f, gameManagerService.getResolutionScalingFactor(mPackageName,
                GameManager.GAME_MODE_BATTERY, USER_ID_1), 0.001f);
    }

    @Test
    public void testUpdateResolutionScalingFactor_noUserId() {
        mockModifyGameModeGranted();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_2);
        final float scalingFactor = 0.123f;
        assertThrows(IllegalArgumentException.class, () -> {
            gameManagerService.updateResolutionScalingFactor(mPackageName,
                    GameManager.GAME_MODE_BATTERY, scalingFactor,
                    USER_ID_1);
        });
    }

    @Test
    public void testGetResolutionScalingFactor_permissionDenied() {
        mockModifyGameModeDenied();
        mockDeviceConfigAll();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        assertThrows(SecurityException.class, () -> {
            gameManagerService.getResolutionScalingFactor(mPackageName,
                    GameManager.GAME_MODE_BATTERY, USER_ID_1);
        });
    }

    @Test
    public void testGetResolutionScalingFactor_noUserId() {
        mockModifyGameModeGranted();
        mockDeviceConfigAll();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_2);
        assertThrows(IllegalArgumentException.class, () -> {
            gameManagerService.getResolutionScalingFactor(mPackageName,
                    GameManager.GAME_MODE_BATTERY, USER_ID_1);
        });
    }

    @Test
    public void testUpdateCustomGameModeConfiguration_permissionDenied() {
        mockModifyGameModeDenied();
        mockDeviceConfigAll();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        assertThrows(SecurityException.class, () -> {
            gameManagerService.updateCustomGameModeConfiguration(mPackageName,
                    new GameModeConfiguration.Builder().setScalingFactor(0.5f).build(),
                    USER_ID_1);
        });
    }

    @Test
    public void testUpdateCustomGameModeConfiguration_noUserId() {
        mockModifyGameModeGranted();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_2);
        assertThrows(IllegalArgumentException.class, () -> {
            gameManagerService.updateCustomGameModeConfiguration(mPackageName,
                    new GameModeConfiguration.Builder().setScalingFactor(0.5f).build(),
                    USER_ID_1);
        });
    }

    @Test
    public void testUpdateCustomGameModeConfiguration_nonGame() throws Exception {
        mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_IMAGE);
        mockModifyGameModeGranted();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        gameManagerService.updateCustomGameModeConfiguration(mPackageName,
                new GameModeConfiguration.Builder().setScalingFactor(0.35f).setFpsOverride(
                        60).build(),
                USER_ID_1);
        assertFalse(gameManagerService.mHandler.hasMessages(WRITE_SETTINGS));
        GameManagerService.GamePackageConfiguration pkgConfig = gameManagerService.getConfig(
                mPackageName, USER_ID_1);
        assertNull(pkgConfig);
    }

    @Test
    public void testUpdateCustomGameModeConfiguration() throws InterruptedException {
        mockModifyGameModeGranted();
        GameManagerService gameManagerService = Mockito.spy(createServiceAndStartUser(USER_ID_1));
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_CUSTOM, USER_ID_1);
        gameManagerService.updateCustomGameModeConfiguration(mPackageName,
                new GameModeConfiguration.Builder().setScalingFactor(0.35f).setFpsOverride(
                        60).build(),
                USER_ID_1);
        assertTrue(gameManagerService.mHandler.hasEqualMessages(WRITE_SETTINGS, USER_ID_1));
        assertTrue(
                gameManagerService.mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
                        USER_ID_1));
        Mockito.verify(gameManagerService).setGameModeFrameRateOverride(
                ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
                ArgumentMatchers.eq(60.0f));
        checkFps(gameManagerService, GameManager.GAME_MODE_CUSTOM, 60);

        GameManagerService.GamePackageConfiguration pkgConfig = gameManagerService.getConfig(
                mPackageName, USER_ID_1);
        assertNotNull(pkgConfig);
        GameManagerService.GamePackageConfiguration.GameModeConfiguration modeConfig =
                pkgConfig.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM);
        assertNotNull(modeConfig);
        assertEquals(modeConfig.getScaling(), 0.35f, 0.01f);
        assertEquals(modeConfig.getFps(), 60);
        // creates a new service to check that no data has been stored
        mTestLooper.dispatchAll();
        gameManagerService = createServiceAndStartUser(USER_ID_1);
        pkgConfig = gameManagerService.getConfig(mPackageName, USER_ID_1);
        assertNull(pkgConfig);

        mTestLooper.moveTimeForward(WRITE_DELAY_MILLIS + 500);
        mTestLooper.dispatchAll();
        // creates a new service to check that data is persisted after delay
        gameManagerService = createServiceAndStartUser(USER_ID_1);
        assertEquals(GameManager.GAME_MODE_STANDARD,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));
        pkgConfig = gameManagerService.getConfig(mPackageName, USER_ID_1);
        assertNotNull(pkgConfig);
        modeConfig = pkgConfig.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM);
        assertNotNull(modeConfig);
        assertEquals(modeConfig.getScaling(), 0.35f, 0.01f);
        assertEquals(modeConfig.getFps(), 60);
    }

    @Test
    public void testWritingSettingFile_onShutdown() throws Exception {
        mockModifyGameModeGranted();
        mockDeviceConfigAll();
        GameManagerService gameManagerService = new GameManagerService(mMockContext);
        gameManagerService.onBootCompleted();
        startUser(gameManagerService, USER_ID_1);
        Thread.sleep(500);
        mockAppCategory("com.android.app1", DEFAULT_PACKAGE_UID + 1, ApplicationInfo.CATEGORY_GAME);
        gameManagerService.setGameModeConfigOverride("com.android.app1", USER_ID_1,
                GameManager.GAME_MODE_BATTERY, "60", "0.5");
        gameManagerService.setGameMode("com.android.app1", USER_ID_1,
                GameManager.GAME_MODE_PERFORMANCE);
        GameManagerSettings settings = new GameManagerSettings(
                InstrumentationRegistry.getContext().getFilesDir());
        Thread.sleep(500);
        // no data written as delayed messages are queued
        assertFalse(settings.readPersistentDataLocked());
        assertTrue(gameManagerService.mHandler.hasEqualMessages(WRITE_SETTINGS, USER_ID_1));
        Intent shutdown = new Intent();
        shutdown.setAction(Intent.ACTION_SHUTDOWN);
        mShutDownActionReceiver.onReceive(mMockContext, shutdown);
        Thread.sleep(500);
        // data is written on processing new message with no delay on shutdown,
        // and all queued messages should be removed
        assertTrue(settings.readPersistentDataLocked());
        assertFalse(gameManagerService.mHandler.hasEqualMessages(WRITE_SETTINGS, USER_ID_1));
    }

    @Test
    public void testResetInterventions_onDeviceConfigReset() throws Exception {
        mockModifyGameModeGranted();
        String configStringBefore =
                "mode=2,downscaleFactor=1.0,fps=90";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configStringBefore);
        mockInterventionsEnabledNoOptInFromXml();
        GameManagerService gameManagerService = Mockito.spy(new GameManagerService(mMockContext,
                mTestLooper.getLooper()));
        startUser(gameManagerService, USER_ID_1);
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        Mockito.verify(gameManagerService).setGameModeFrameRateOverride(
                ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
                ArgumentMatchers.eq(90.0f));
        checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);

        String configStringAfter = "";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configStringAfter);
        gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
        Mockito.verify(gameManagerService).setGameModeFrameRateOverride(
                ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
                ArgumentMatchers.eq(0.0f));
    }

    @Test
    public void testResetInterventions_onInterventionsDisabled() throws Exception {
        mockModifyGameModeGranted();
        String configStringBefore =
                "mode=2,downscaleFactor=1.0,fps=90";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configStringBefore);
        mockInterventionsEnabledNoOptInFromXml();
        GameManagerService gameManagerService = Mockito.spy(new GameManagerService(mMockContext,
                mTestLooper.getLooper()));
        startUser(gameManagerService, USER_ID_1);
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        Mockito.verify(gameManagerService).setGameModeFrameRateOverride(
                ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
                ArgumentMatchers.eq(90.0f));
        checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);

        mockInterventionsDisabledNoOptInFromXml();
        gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
        Mockito.verify(gameManagerService).setGameModeFrameRateOverride(
                ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
                ArgumentMatchers.eq(0.0f));
        checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0);
    }

    @Test
    public void testResetInterventions_onGameModeOverridden() throws Exception {
        mockModifyGameModeGranted();
        String configStringBefore =
                "mode=2,downscaleFactor=1.0,fps=90";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configStringBefore);
        mockInterventionsEnabledNoOptInFromXml();
        GameManagerService gameManagerService = Mockito.spy(new GameManagerService(mMockContext,
                mTestLooper.getLooper()));
        startUser(gameManagerService, USER_ID_1);

        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        Mockito.verify(gameManagerService).setGameModeFrameRateOverride(
                ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
                ArgumentMatchers.eq(90.0f));
        checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);

        mockInterventionsEnabledAllOptInFromXml();
        gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
        Mockito.verify(gameManagerService).setGameModeFrameRateOverride(
                ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
                ArgumentMatchers.eq(0.0f));
    }

    @Test
    public void testAddGameModeListener() throws RemoteException {
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        mockDeviceConfigAll();
        startUser(gameManagerService, USER_ID_1);
        mockModifyGameModeGranted();

        IGameModeListener mockListener = Mockito.mock(IGameModeListener.class);
        IBinder binder = Mockito.mock(IBinder.class);
        when(mockListener.asBinder()).thenReturn(binder);
        gameManagerService.addGameModeListener(mockListener);
        verify(binder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());

        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        verify(mockListener).onGameModeChanged(mPackageName, GameManager.GAME_MODE_STANDARD,
                GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        reset(mockListener);
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
        verify(mockListener).onGameModeChanged(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
                GameManager.GAME_MODE_BATTERY, USER_ID_1);
        reset(mockListener);

        mDeathRecipientCaptor.getValue().binderDied();
        verify(binder).unlinkToDeath(eq(mDeathRecipientCaptor.getValue()), anyInt());
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_CUSTOM, USER_ID_1);
        verify(mockListener, never()).onGameModeChanged(anyString(), anyInt(), anyInt(), anyInt());
    }

    @Test
    public void testRemoveGameModeListener() throws RemoteException {
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        mockDeviceConfigAll();
        startUser(gameManagerService, USER_ID_1);
        mockModifyGameModeGranted();

        IGameModeListener mockListener = Mockito.mock(IGameModeListener.class);
        IBinder binder = Mockito.mock(IBinder.class);
        when(mockListener.asBinder()).thenReturn(binder);

        gameManagerService.addGameModeListener(mockListener);
        gameManagerService.removeGameModeListener(mockListener);
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        verify(mockListener, never()).onGameModeChanged(anyString(), anyInt(), anyInt(), anyInt());
    }

    private static void deleteFolder(File folder) {
        File[] files = folder.listFiles();
        if (files != null) {
            for (File file : files) {
                deleteFolder(file);
            }
        }
        folder.delete();
    }

    @Test
    public void testResetGamePowerMode() {
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        gameManagerService.onBootCompleted();
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
    }

    @Test
    public void testNotifyGraphicsEnvironmentSetup() {
        String configString = "mode=2,loadingBoost=2000";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configString);
        mockModifyGameModeGranted();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
        reset(mMockPowerManager);
        assertTrue(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
        mTestLooper.moveTimeForward(2000);
        mTestLooper.dispatchAll();
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
    }

    @Test
    public void testNotifyGraphicsEnvironmentSetup_outOfBoundBoostValue() {
        String configString = "mode=2,loadingBoost=0:mode=3,loadingBoost=7000";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configString);
        mockModifyGameModeGranted();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
        reset(mMockPowerManager);
        assertTrue(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
        mTestLooper.moveTimeForward(100);
        mTestLooper.dispatchAll();
        // 0 loading boost value should still trigger max timeout
        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
        assertTrue(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
        mTestLooper.moveTimeForward(LOADING_BOOST_MAX_DURATION);
        mTestLooper.dispatchAll();
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
        reset(mMockPowerManager);
        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));

        // 7000 loading boost value should exceed the max timeout of 5s and be bounded
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
        reset(mMockPowerManager);
        assertTrue(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
        mTestLooper.moveTimeForward(LOADING_BOOST_MAX_DURATION);
        mTestLooper.dispatchAll();
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
    }

    @Test
    public void testNotifyGraphicsEnvironmentSetup_noDeviceConfig() {
        mockDeviceConfigNone();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
    }

    @Test
    public void testNotifyGraphicsEnvironmentSetup_noLoadingBoostValue() {
        mockDeviceConfigAll();
        mockModifyGameModeGranted();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
    }

    @Test
    public void testNotifyGraphicsEnvironmentSetup_nonGame() throws Exception {
        String configString = "mode=2,loadingBoost=2000";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configString);
        mockModifyGameModeGranted();
        mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_IMAGE);
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
                gameManagerService.getGameMode(mPackageName, USER_ID_1));
        gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
    }

    @Test
    public void testNotifyGraphicsEnvironmentSetup_differentApp() throws Exception {
        String configString = "mode=2,loadingBoost=2000";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configString);
        mockModifyGameModeGranted();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        String someGamePkg = "some.game";
        mockAppCategory(someGamePkg, DEFAULT_PACKAGE_UID + 1,  ApplicationInfo.CATEGORY_GAME);
        gameManagerService.setGameMode(someGamePkg, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
                gameManagerService.getGameMode(someGamePkg, USER_ID_1));
        gameManagerService.notifyGraphicsEnvironmentSetup(someGamePkg, USER_ID_1);
        verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
        assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
    }

    @Test
    public void testGetInterventionList_permissionDenied() throws Exception {
        String configString = "mode=2,downscaleFactor=0.5";
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configString);
        mockQueryAllPackageDenied();
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        assertThrows(SecurityException.class,
                () -> gameManagerService.getInterventionList(mPackageName, USER_ID_1));

        mockQueryAllPackageGranted();
        String expectedInterventionListOutput = "\n[Name:" + mPackageName
                 + " Modes: {2=[Game Mode:2,Scaling:0.5,Use Angle:false,"
                 + "Fps:,Loading Boost Duration:-1]}]";
        assertEquals(expectedInterventionListOutput,
                gameManagerService.getInterventionList(mPackageName, USER_ID_1));
    }

    @Test
    public void testGamePowerMode_twoGames() throws Exception {
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
        String someGamePkg = "some.game";
        int somePackageId = DEFAULT_PACKAGE_UID + 1;
        mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
        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_TOP, 0, 0);
        assertTrue(powerState.get(Mode.GAME));
        gameManagerService.mUidObserver.onUidStateChanged(
                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
        assertFalse(powerState.get(Mode.GAME));
        gameManagerService.mUidObserver.onUidStateChanged(
                somePackageId, ActivityManager.PROCESS_STATE_TOP, 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 someGamePkg = "some.game";
        int somePackageId = DEFAULT_PACKAGE_UID + 1;
        mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
        gameManagerService.mUidObserver.onUidStateChanged(
                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
        gameManagerService.mUidObserver.onUidStateChanged(
                somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
        gameManagerService.mUidObserver.onUidStateChanged(
                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
        gameManagerService.mUidObserver.onUidStateChanged(
                somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
        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_TOP, 0, 0);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
    }

    @Test
    public void testGamePowerMode_gameAndNotGameApps_flagOn() throws Exception {
        mSetFlagsRule.enableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);

        String nonGamePkg1 = "not.game1";
        int nonGameUid1 = DEFAULT_PACKAGE_UID + 1;
        mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE);

        String nonGamePkg2 = "not.game2";
        int nonGameUid2 = DEFAULT_PACKAGE_UID + 2;
        mockAppCategory(nonGamePkg2, nonGameUid2, ApplicationInfo.CATEGORY_IMAGE);

        String gamePkg1 = "game1";
        int gameUid1 = DEFAULT_PACKAGE_UID + 3;
        mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME);

        String gamePkg2 = "game2";
        int gameUid2 = DEFAULT_PACKAGE_UID + 4;
        mockAppCategory(gamePkg2, gameUid2, ApplicationInfo.CATEGORY_GAME);

        // non-game1 top and background with no-op
        gameManagerService.mUidObserver.onUidStateChanged(
                nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
        gameManagerService.mUidObserver.onUidStateChanged(
                nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
        clearInvocations(mMockPowerManager);

        // game1 top to enable game mode
        gameManagerService.mUidObserver.onUidStateChanged(
                gameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
        clearInvocations(mMockPowerManager);

        // non-game1 in foreground to disable game mode
        gameManagerService.mUidObserver.onUidStateChanged(
                nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
        clearInvocations(mMockPowerManager);

        // non-game2 in foreground with no-op
        gameManagerService.mUidObserver.onUidStateChanged(
                nonGameUid2, ActivityManager.PROCESS_STATE_TOP, 0, 0);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
        clearInvocations(mMockPowerManager);

        // game 2 in foreground with no-op
        gameManagerService.mUidObserver.onUidStateChanged(
                gameUid2, ActivityManager.PROCESS_STATE_TOP, 0, 0);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
        clearInvocations(mMockPowerManager);

        // non-game 1 in background with no-op
        gameManagerService.mUidObserver.onUidStateChanged(
                nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
        clearInvocations(mMockPowerManager);

        // non-game2 in background to resume game mode
        gameManagerService.mUidObserver.onUidStateChanged(
                nonGameUid2, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
        clearInvocations(mMockPowerManager);

        // game 1 in background with no-op
        gameManagerService.mUidObserver.onUidStateChanged(
                gameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
        clearInvocations(mMockPowerManager);

        // game 2 in background to stop game mode
        gameManagerService.mUidObserver.onUidStateChanged(
                gameUid2, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
        clearInvocations(mMockPowerManager);
    }

    @Test
    public void testGamePowerMode_gameAndNotGameApps_flagOff() throws Exception {
        mSetFlagsRule.disableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);

        String nonGamePkg1 = "not.game1";
        int nonGameUid1 = DEFAULT_PACKAGE_UID + 1;
        mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE);

        String gamePkg1 = "game1";
        int gameUid1 = DEFAULT_PACKAGE_UID + 3;
        mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME);

        // non-game1 top and background with no-op
        gameManagerService.mUidObserver.onUidStateChanged(
                nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
        gameManagerService.mUidObserver.onUidStateChanged(
                nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
        clearInvocations(mMockPowerManager);

        // game1 top to enable game mode
        gameManagerService.mUidObserver.onUidStateChanged(
                gameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
        clearInvocations(mMockPowerManager);

        // non-game1 in foreground to not interfere
        gameManagerService.mUidObserver.onUidStateChanged(
                nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
        clearInvocations(mMockPowerManager);

        // non-game 1 in background to not interfere
        gameManagerService.mUidObserver.onUidStateChanged(
                nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
        clearInvocations(mMockPowerManager);

        // move non-game1 to foreground again
        gameManagerService.mUidObserver.onUidStateChanged(
                nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);

        // with non-game1 on top, game 1 in background to still disable game mode
        gameManagerService.mUidObserver.onUidStateChanged(
                gameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
        clearInvocations(mMockPowerManager);

        // with non-game1 on top, game 1 in foreground to still enable game mode
        gameManagerService.mUidObserver.onUidStateChanged(
                gameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
        verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
        clearInvocations(mMockPowerManager);
    }

    @Test
    public void testGameDefaultFrameRate_FlagOn() throws Exception {
        mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_GAME_MODE);

        GameManagerService gameManagerService = Mockito.spy(
                new GameManagerService(mMockContext, mTestLooper.getLooper(),
                        InstrumentationRegistry.getContext().getFilesDir(),
                        new Injector(){
                            @Override
                            public GameManagerServiceSystemPropertiesWrapper
                                    createSystemPropertiesWrapper() {
                                return mSysPropsMock;
                            }
                        }));

        when(mSysPropsMock.getInt(
                ArgumentMatchers.eq(PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE),
                anyInt())).thenReturn(60);
        when(mSysPropsMock.getBoolean(
                ArgumentMatchers.eq(PROPERTY_DEBUG_GFX_GAME_DEFAULT_FRAME_RATE_DISABLED),
                ArgumentMatchers.eq(false))).thenReturn(false);
        gameManagerService.onBootCompleted();

        // Set up a game in the foreground.
        gameManagerService.mUidObserver.onUidStateChanged(
                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);

        // Toggle game default frame rate on.
        gameManagerService.toggleGameDefaultFrameRate(true);

        // Verify that:
        // setDefaultFrameRateOverride is called with correct arguments
        Mockito.verify(gameManagerService, times(1))
                .setGameDefaultFrameRateOverride(ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
                                                 ArgumentMatchers.eq(60.0f));

        // Adding another game to the foreground.
        String anotherGamePkg = "another.game";
        int somePackageId = DEFAULT_PACKAGE_UID + 1;
        mockAppCategory(anotherGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);

        gameManagerService.mUidObserver.onUidStateChanged(
                somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);

        // Toggle game default frame rate off.
        gameManagerService.toggleGameDefaultFrameRate(false);

        // Verify that:
        // setDefaultFrameRateOverride is called with correct arguments
        Mockito.verify(gameManagerService).setGameDefaultFrameRateOverride(
                ArgumentMatchers.eq(DEFAULT_PACKAGE_UID), ArgumentMatchers.eq(0.0f));
        Mockito.verify(gameManagerService).setGameDefaultFrameRateOverride(
                ArgumentMatchers.eq(somePackageId), ArgumentMatchers.eq(0.0f));
    }

    @Test
    public void testGameDefaultFrameRate_FlagOff() throws Exception {
        mSetFlagsRule.disableFlags(Flags.FLAG_GAME_DEFAULT_FRAME_RATE);
        mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_GAME_MODE);

        GameManagerService gameManagerService = Mockito.spy(
                new GameManagerService(mMockContext, mTestLooper.getLooper(),
                        InstrumentationRegistry.getContext().getFilesDir(),
                        new Injector(){
                            @Override
                            public GameManagerServiceSystemPropertiesWrapper
                                    createSystemPropertiesWrapper() {
                                return mSysPropsMock;
                            }
                        }));

        // Set up a game in the foreground.
        gameManagerService.mUidObserver.onUidStateChanged(
                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);

        // Toggle game default frame rate on.
        when(mSysPropsMock.getInt(
                ArgumentMatchers.eq(PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE),
                anyInt())).thenReturn(60);

        gameManagerService.toggleGameDefaultFrameRate(true);

        // Verify that:
        // setGameDefaultFrameRateOverride() should never be called if the flag is disabled.
        Mockito.verify(gameManagerService, never())
                .setGameDefaultFrameRateOverride(anyInt(), anyFloat());

        // Toggle game default frame rate off.
        String anotherGamePkg = "another.game";
        int somePackageId = DEFAULT_PACKAGE_UID + 1;
        mockAppCategory(anotherGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
        gameManagerService.mUidObserver.onUidStateChanged(
                somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
        gameManagerService.mUidObserver.onUidStateChanged(
                somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);

        gameManagerService.toggleGameDefaultFrameRate(false);
        // Verify that:
        // setGameDefaultFrameRateOverride() should never be called if the flag is disabled.
        Mockito.verify(gameManagerService, never())
                .setGameDefaultFrameRateOverride(anyInt(), anyFloat());
    }
}
