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

Commit f9387616 authored by Tim Van Patten's avatar Tim Van Patten Committed by Automerger Merge Worker
Browse files

Merge "Enable ANGLE as a Game Dashboard Intervention" into sc-qpr1-dev am: 4de992c9

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15697303

Change-Id: I1c33f82a510da16fd4566915171ccd8ca35f31a1
parents a8a304da 4de992c9
Loading
Loading
Loading
Loading
+17 −0
Original line number Original line Diff line number Diff line
@@ -135,6 +135,7 @@ public final class GameManager {
            throw e.rethrowFromSystemServer();
            throw e.rethrowFromSystemServer();
        }
        }
    }
    }

    /**
    /**
     * Returns a list of supported game modes for a given package.
     * Returns a list of supported game modes for a given package.
     * <p>
     * <p>
@@ -151,4 +152,20 @@ public final class GameManager {
        }
        }
    }
    }


    /**
     * Returns if ANGLE is enabled for a given package.
     * <p>
     * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
     *
     * @hide
     */
    @UserHandleAware
    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
    public @GameMode boolean isAngleEnabled(@NonNull String packageName) {
        try {
            return mService.getAngleEnabled(packageName, mContext.getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}
}
+1 −0
Original line number Original line Diff line number Diff line
@@ -23,4 +23,5 @@ interface IGameManagerService {
    int getGameMode(String packageName, int userId);
    int getGameMode(String packageName, int userId);
    void setGameMode(String packageName, int gameMode, int userId);
    void setGameMode(String packageName, int gameMode, int userId);
    int[] getAvailableGameModes(String packageName);
    int[] getAvailableGameModes(String packageName);
    boolean getAngleEnabled(String packageName, int userId);
}
}
 No newline at end of file
+36 −9
Original line number Original line Diff line number Diff line
@@ -17,6 +17,7 @@
package android.os;
package android.os;


import android.app.Activity;
import android.app.Activity;
import android.app.GameManager;
import android.content.BroadcastReceiver;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Context;
@@ -113,6 +114,7 @@ public class GraphicsEnvironment {
    private ClassLoader mClassLoader;
    private ClassLoader mClassLoader;
    private String mLibrarySearchPaths;
    private String mLibrarySearchPaths;
    private String mLibraryPermittedPaths;
    private String mLibraryPermittedPaths;
    private GameManager mGameManager;


    private int mAngleOptInIndex = -1;
    private int mAngleOptInIndex = -1;


@@ -125,6 +127,8 @@ public class GraphicsEnvironment {
        final ApplicationInfo appInfoWithMetaData =
        final ApplicationInfo appInfoWithMetaData =
                getAppInfoWithMetadata(context, pm, packageName);
                getAppInfoWithMetadata(context, pm, packageName);


        mGameManager = context.getSystemService(GameManager.class);

        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers");
        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers");
        setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData);
        setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData);
        Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
        Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
@@ -142,6 +146,23 @@ public class GraphicsEnvironment {
        Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
        Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
    }
    }


    /**
     * Query to determine if the Game Mode has enabled ANGLE.
     */
    private boolean isAngleEnabledByGameMode(Context context, String packageName) {
        try {
            final boolean gameModeEnabledAngle =
                    (mGameManager != null) && mGameManager.isAngleEnabled(packageName);
            Log.v(TAG, "ANGLE GameManagerService for " + packageName + ": " + gameModeEnabledAngle);
            return gameModeEnabledAngle;
        } catch (SecurityException e) {
            Log.e(TAG, "Caught exception while querying GameManagerService if ANGLE is enabled "
                    + "for package: " + packageName);
        }

        return false;
    }

    /**
    /**
     * Query to determine if ANGLE should be used
     * Query to determine if ANGLE should be used
     */
     */
@@ -163,7 +184,9 @@ public class GraphicsEnvironment {
            Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn);
            Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn);
        }
        }


        return requested;
        final boolean gameModeEnabledAngle = isAngleEnabledByGameMode(context, packageName);

        return requested || gameModeEnabledAngle;
    }
    }


    private int getVulkanVersion(PackageManager pm) {
    private int getVulkanVersion(PackageManager pm) {
@@ -521,16 +544,20 @@ public class GraphicsEnvironment {


        if (DEBUG) Log.v(TAG, "ANGLE package libs: " + paths);
        if (DEBUG) Log.v(TAG, "ANGLE package libs: " + paths);


        // If the user has set the developer option to something other than default,
        // We need to call setAngleInfo() with the package name and the developer option value
        // we need to call setAngleInfo() with the package name and the developer
        //(native/angle/other). Then later when we are actually trying to load a driver,
        // option value (native/angle/other). Then later when we are actually trying to
        //GraphicsEnv::getShouldUseAngle() has seen the package name before and can confidently
        // load a driver, GraphicsEnv::getShouldUseAngle() has seen the package name before
        //answer yes/no based on the previously set developer option value.
        // and can confidently answer yes/no based on the previously set developer
        final String devOptIn;
        // option value.
        final String devOptIn = getDriverForPackage(context, bundle, packageName);
        final String[] features = getAngleEglFeatures(context, bundle);
        final String[] features = getAngleEglFeatures(context, bundle);

        final boolean gameModeEnabledAngle = isAngleEnabledByGameMode(context, packageName);
        if (gameModeEnabledAngle) {
            devOptIn = ANGLE_GL_DRIVER_CHOICE_ANGLE;
        } else {
            devOptIn = getDriverForPackage(context, bundle, packageName);
        }
        setAngleInfo(paths, packageName, devOptIn, features);
        setAngleInfo(paths, packageName, devOptIn, features);

        return true;
        return true;
    }
    }


+75 −9
Original line number Original line Diff line number Diff line
@@ -271,6 +271,13 @@ public final class GameManagerService extends IGameManagerService.Stub {
        public static final String METADATA_WM_ALLOW_DOWNSCALE =
        public static final String METADATA_WM_ALLOW_DOWNSCALE =
                "com.android.graphics.intervention.wm.allowDownscale";
                "com.android.graphics.intervention.wm.allowDownscale";


        /**
         * Metadata that can be included in the app manifest to allow/disallow any ANGLE
         * interventions. Default value is TRUE.
         */
        public static final String METADATA_ANGLE_ALLOW_ANGLE =
                "com.android.graphics.intervention.angle.allowAngle";

        /**
        /**
         * Metadata that needs to be included in the app manifest to OPT-IN to PERFORMANCE mode.
         * Metadata that needs to be included in the app manifest to OPT-IN to PERFORMANCE mode.
         * This means the app will assume full responsibility for the experience provided by this
         * This means the app will assume full responsibility for the experience provided by this
@@ -294,6 +301,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
        private boolean mPerfModeOptedIn;
        private boolean mPerfModeOptedIn;
        private boolean mBatteryModeOptedIn;
        private boolean mBatteryModeOptedIn;
        private boolean mAllowDownscale;
        private boolean mAllowDownscale;
        private boolean mAllowAngle;


        GamePackageConfiguration(String packageName, int userId) {
        GamePackageConfiguration(String packageName, int userId) {
            mPackageName = packageName;
            mPackageName = packageName;
@@ -305,10 +313,12 @@ public final class GameManagerService extends IGameManagerService.Stub {
                    mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
                    mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
                    mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
                    mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
                    mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
                    mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
                    mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
                } else {
                } else {
                    mPerfModeOptedIn = false;
                    mPerfModeOptedIn = false;
                    mBatteryModeOptedIn = false;
                    mBatteryModeOptedIn = false;
                    mAllowDownscale = true;
                    mAllowDownscale = true;
                    mAllowAngle = true;
                }
                }
            } catch (PackageManager.NameNotFoundException e) {
            } catch (PackageManager.NameNotFoundException e) {
                // Not all packages are installed, hence ignore those that are not installed yet.
                // Not all packages are installed, hence ignore those that are not installed yet.
@@ -340,14 +350,26 @@ public final class GameManagerService extends IGameManagerService.Stub {
            public static final String MODE_KEY = "mode";
            public static final String MODE_KEY = "mode";
            public static final String SCALING_KEY = "downscaleFactor";
            public static final String SCALING_KEY = "downscaleFactor";
            public static final String DEFAULT_SCALING = "1.0";
            public static final String DEFAULT_SCALING = "1.0";
            public static final String ANGLE_KEY = "useAngle";


            private final @GameMode int mGameMode;
            private final @GameMode int mGameMode;
            private final String mScaling;
            private final String mScaling;
            private final boolean mUseAngle;


            GameModeConfiguration(KeyValueListParser parser) {
            GameModeConfiguration(KeyValueListParser parser) {
                mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED);
                mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED);
                mScaling = !mAllowDownscale || isGameModeOptedIn(mGameMode)
                // isGameModeOptedIn() returns if an app will handle all of the changes necessary
                // for a particular game mode. If so, the Android framework (i.e.
                // GameManagerService) will not do anything for the app (like window scaling or
                // using ANGLE).
                mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode)
                        ? DEFAULT_SCALING : parser.getString(SCALING_KEY, DEFAULT_SCALING);
                        ? DEFAULT_SCALING : parser.getString(SCALING_KEY, DEFAULT_SCALING);
                // We only want to use ANGLE if:
                // - We're allowed to use ANGLE (the app hasn't opted out via the manifest) AND
                // - The app has not opted in to performing the work itself AND
                // - The Phenotype config has enabled it.
                mUseAngle = mAllowAngle && !willGamePerformOptimizations(mGameMode)
                        && parser.getBoolean(ANGLE_KEY, false);
            }
            }


            public int getGameMode() {
            public int getGameMode() {
@@ -358,6 +380,10 @@ public final class GameManagerService extends IGameManagerService.Stub {
                return mScaling;
                return mScaling;
            }
            }


            public boolean getUseAngle() {
                return mUseAngle;
            }

            public boolean isValid() {
            public boolean isValid() {
                return (mGameMode == GameManager.GAME_MODE_PERFORMANCE
                return (mGameMode == GameManager.GAME_MODE_PERFORMANCE
                        || mGameMode == GameManager.GAME_MODE_BATTERY)
                        || mGameMode == GameManager.GAME_MODE_BATTERY)
@@ -368,7 +394,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
             * @hide
             * @hide
             */
             */
            public String toString() {
            public String toString() {
                return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + "]";
                return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + ",Use Angle:"
                        + mUseAngle + "]";
            }
            }


            /**
            /**
@@ -384,13 +411,14 @@ public final class GameManagerService extends IGameManagerService.Stub {
        }
        }


        /**
        /**
         * Gets whether a package has opted into a game mode via its manifest.
         * Returns if the app will assume full responsibility for the experience provided by this
         * mode. If True, the system will not perform any interventions for the app.
         *
         *
         * @return True if the app package has specified in its metadata either:
         * @return True if the app package has specified in its metadata either:
         * "com.android.app.gamemode.performance.enabled" or
         * "com.android.app.gamemode.performance.enabled" or
         * "com.android.app.gamemode.battery.enabled" with a value of "true"
         * "com.android.app.gamemode.battery.enabled" with a value of "true"
         */
         */
        public boolean isGameModeOptedIn(@GameMode int gameMode) {
        public boolean willGamePerformOptimizations(@GameMode int gameMode) {
            return (mBatteryModeOptedIn && gameMode == GameManager.GAME_MODE_BATTERY)
            return (mBatteryModeOptedIn && gameMode == GameManager.GAME_MODE_BATTERY)
                    || (mPerfModeOptedIn && gameMode == GameManager.GAME_MODE_PERFORMANCE);
                    || (mPerfModeOptedIn && gameMode == GameManager.GAME_MODE_PERFORMANCE);
        }
        }
@@ -631,7 +659,34 @@ public final class GameManagerService extends IGameManagerService.Stub {
                mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
                mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
            }
            }
        }
        }
        updateCompatModeDownscale(packageName, gameMode);
        updateInterventions(packageName, gameMode);
    }

    /**
     * Get if ANGLE is enabled for the package for the currently enabled game mode.
     * Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}.
     */
    @Override
    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
    public @GameMode boolean getAngleEnabled(String packageName, int userId)
            throws SecurityException {
        final int gameMode = getGameMode(packageName, userId);
        if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
            return false;
        }

        synchronized (mDeviceConfigLock) {
            final GamePackageConfiguration config = mConfigs.get(packageName);
            if (config == null) {
                return false;
            }
            GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
                    config.getGameModeConfiguration(gameMode);
            if (gameModeConfiguration == null) {
                return false;
            }
            return gameModeConfiguration.getUseAngle();
        }
    }
    }


    /**
    /**
@@ -753,7 +808,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
            if (DEBUG) {
            if (DEBUG) {
                Slog.v(TAG, dumpDeviceConfigs());
                Slog.v(TAG, dumpDeviceConfigs());
            }
            }
            if (packageConfig.isGameModeOptedIn(gameMode)) {
            if (packageConfig.willGamePerformOptimizations(gameMode)) {
                disableCompatScale(packageName);
                disableCompatScale(packageName);
                return;
                return;
            }
            }
@@ -782,6 +837,17 @@ public final class GameManagerService extends IGameManagerService.Stub {
        return (bitField & modeToBitmask(gameMode)) != 0;
        return (bitField & modeToBitmask(gameMode)) != 0;
    }
    }


    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
    private void updateUseAngle(String packageName, @GameMode int gameMode) {
        // TODO (b/188475576): Nothing to do yet. Remove if it's still empty when we're ready to
        // ship.
    }

    private void updateInterventions(String packageName, @GameMode int gameMode) {
        updateCompatModeDownscale(packageName, gameMode);
        updateUseAngle(packageName, gameMode);
    }

    /**
    /**
     * @hide
     * @hide
     */
     */
@@ -839,11 +905,11 @@ public final class GameManagerService extends IGameManagerService.Stub {
                    if (newGameMode != gameMode) {
                    if (newGameMode != gameMode) {
                        setGameMode(packageName, newGameMode, userId);
                        setGameMode(packageName, newGameMode, userId);
                    }
                    }
                    updateCompatModeDownscale(packageName, gameMode);
                    updateInterventions(packageName, gameMode);
                }
                }
            }
            }
        } catch (Exception e) {
        } catch (Exception e) {
            Slog.e(TAG, "Failed to update compat modes for user: " + userId);
            Slog.e(TAG, "Failed to update compat modes for user " + userId + ": " + e);
        }
        }
    }
    }


+96 −6
Original line number Original line Diff line number Diff line
@@ -182,7 +182,14 @@ public class GameManagerServiceTests {
    }
    }


    private void mockDeviceConfigPerformance() {
    private void mockDeviceConfigPerformance() {
        String configString = "mode=2,downscaleFactor=0.5";
        String configString = "mode=2,downscaleFactor=0.5,useAngle=false";
        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()))
        when(DeviceConfig.getProperty(anyString(), anyString()))
                .thenReturn(configString);
                .thenReturn(configString);
    }
    }
@@ -212,7 +219,8 @@ public class GameManagerServiceTests {
    }
    }


    private void mockGameModeOptInAll() throws Exception {
    private void mockGameModeOptInAll() throws Exception {
        final ApplicationInfo applicationInfo = new ApplicationInfo();
        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
        Bundle metaDataBundle = new Bundle();
        Bundle metaDataBundle = new Bundle();
        metaDataBundle.putBoolean(
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_PERFORMANCE_MODE_ENABLE, true);
                GameManagerService.GamePackageConfiguration.METADATA_PERFORMANCE_MODE_ENABLE, true);
@@ -224,7 +232,8 @@ public class GameManagerServiceTests {
    }
    }


    private void mockGameModeOptInPerformance() throws Exception {
    private void mockGameModeOptInPerformance() throws Exception {
        final ApplicationInfo applicationInfo = new ApplicationInfo();
        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
        Bundle metaDataBundle = new Bundle();
        Bundle metaDataBundle = new Bundle();
        metaDataBundle.putBoolean(
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_PERFORMANCE_MODE_ENABLE, true);
                GameManagerService.GamePackageConfiguration.METADATA_PERFORMANCE_MODE_ENABLE, true);
@@ -234,7 +243,8 @@ public class GameManagerServiceTests {
    }
    }


    private void mockGameModeOptInBattery() throws Exception {
    private void mockGameModeOptInBattery() throws Exception {
        final ApplicationInfo applicationInfo = new ApplicationInfo();
        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
        Bundle metaDataBundle = new Bundle();
        Bundle metaDataBundle = new Bundle();
        metaDataBundle.putBoolean(
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_BATTERY_MODE_ENABLE, true);
                GameManagerService.GamePackageConfiguration.METADATA_BATTERY_MODE_ENABLE, true);
@@ -244,7 +254,8 @@ public class GameManagerServiceTests {
    }
    }


    private void mockInterventionAllowDownscaleTrue() throws Exception {
    private void mockInterventionAllowDownscaleTrue() throws Exception {
        final ApplicationInfo applicationInfo = new ApplicationInfo();
        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
        Bundle metaDataBundle = new Bundle();
        Bundle metaDataBundle = new Bundle();
        metaDataBundle.putBoolean(
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, true);
                GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, true);
@@ -254,7 +265,8 @@ public class GameManagerServiceTests {
    }
    }


    private void mockInterventionAllowDownscaleFalse() throws Exception {
    private void mockInterventionAllowDownscaleFalse() throws Exception {
        final ApplicationInfo applicationInfo = new ApplicationInfo();
        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
        Bundle metaDataBundle = new Bundle();
        Bundle metaDataBundle = new Bundle();
        metaDataBundle.putBoolean(
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, false);
                GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, false);
@@ -263,6 +275,27 @@ public class GameManagerServiceTests {
                .thenReturn(applicationInfo);
                .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);
    }

    /**
    /**
     * By default game mode is not supported.
     * By default game mode is not supported.
     */
     */
@@ -427,6 +460,19 @@ public class GameManagerServiceTests {
        assertEquals(config.getGameModeConfiguration(gameMode).getScaling(), scaling);
        assertEquals(config.getGameModeConfiguration(gameMode).getScaling(), scaling);
    }
    }


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

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

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

    /**
    /**
     * Phenotype device config exists, but is only propagating the default value.
     * Phenotype device config exists, but is only propagating the default value.
     */
     */
@@ -591,6 +637,50 @@ public class GameManagerServiceTests {
        checkDownscaling(GameManager.GAME_MODE_PERFORMANCE, "0.5");
        checkDownscaling(GameManager.GAME_MODE_PERFORMANCE, "0.5");
    }
    }


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

    /**
     * PERFORMANCE game mode is configured through Phenotype. The app has opted-out of ANGLE.
     */
    @Test
    public void testInterventionAllowAngleFalse() throws Exception {
        GameManagerService gameManagerService = new GameManagerService(mMockContext);
        gameManagerService.onUserStarting(USER_ID_1);
        mockDeviceConfigPerformanceEnableAngle();
        mockInterventionAllowAngleFalse();
        mockModifyGameModeGranted();
        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);
        gameManagerService.onUserStarting(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, but the app has also opted into the
     * 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.
     * same mode. No interventions for this game mode should be available in this case.