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

Commit e301bc3d authored by Tim Van Patten's avatar Tim Van Patten Committed by Android (Google) Code Review
Browse files

Merge "Enable ANGLE as a Game Dashboard Intervention"

parents 16eafe26 acf3851b
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -135,6 +135,7 @@ public final class GameManager {
            throw e.rethrowFromSystemServer();
        }
    }

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

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

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

    private int mAngleOptInIndex = -1;

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

        mGameManager = context.getSystemService(GameManager.class);

        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers");
        setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData);
        Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
@@ -142,6 +146,23 @@ public class GraphicsEnvironment {
        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.getAngleEnabled(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
     */
@@ -163,7 +184,9 @@ public class GraphicsEnvironment {
            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) {
@@ -521,16 +544,20 @@ public class GraphicsEnvironment {

        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 (native/angle/other). Then later when we are actually trying to
        // load a driver, GraphicsEnv::getShouldUseAngle() has seen the package name before
        // and can confidently answer yes/no based on the previously set developer
        // option value.
        final String devOptIn = getDriverForPackage(context, bundle, packageName);
        // We need to call setAngleInfo() with the package name and the developer option value
        //(native/angle/other). Then later when we are actually trying to load a driver,
        //GraphicsEnv::getShouldUseAngle() has seen the package name before and can confidently
        //answer yes/no based on the previously set developer option value.
        final String devOptIn;
        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);

        return true;
    }

+75 −9
Original line number Diff line number Diff line
@@ -271,6 +271,13 @@ public final class GameManagerService extends IGameManagerService.Stub {
        public static final String METADATA_WM_ALLOW_DOWNSCALE =
                "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.
         * 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 mBatteryModeOptedIn;
        private boolean mAllowDownscale;
        private boolean mAllowAngle;

        GamePackageConfiguration(String packageName, int userId) {
            mPackageName = packageName;
@@ -305,10 +313,12 @@ public final class GameManagerService extends IGameManagerService.Stub {
                    mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
                    mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
                    mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
                    mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
                } else {
                    mPerfModeOptedIn = false;
                    mBatteryModeOptedIn = false;
                    mAllowDownscale = true;
                    mAllowAngle = true;
                }
            } catch (PackageManager.NameNotFoundException e) {
                // 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 SCALING_KEY = "downscaleFactor";
            public static final String DEFAULT_SCALING = "1.0";
            public static final String ANGLE_KEY = "useAngle";

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

            GameModeConfiguration(KeyValueListParser parser) {
                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);
                // 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() {
@@ -358,6 +380,10 @@ public final class GameManagerService extends IGameManagerService.Stub {
                return mScaling;
            }

            public boolean getUseAngle() {
                return mUseAngle;
            }

            public boolean isValid() {
                return (mGameMode == GameManager.GAME_MODE_PERFORMANCE
                        || mGameMode == GameManager.GAME_MODE_BATTERY)
@@ -368,7 +394,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
             * @hide
             */
            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:
         * "com.android.app.gamemode.performance.enabled" or
         * "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)
                    || (mPerfModeOptedIn && gameMode == GameManager.GAME_MODE_PERFORMANCE);
        }
@@ -631,7 +659,34 @@ public final class GameManagerService extends IGameManagerService.Stub {
                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) {
                Slog.v(TAG, dumpDeviceConfigs());
            }
            if (packageConfig.isGameModeOptedIn(gameMode)) {
            if (packageConfig.willGamePerformOptimizations(gameMode)) {
                disableCompatScale(packageName);
                return;
            }
@@ -782,6 +837,17 @@ public final class GameManagerService extends IGameManagerService.Stub {
        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
     */
@@ -839,11 +905,11 @@ public final class GameManagerService extends IGameManagerService.Stub {
                    if (newGameMode != gameMode) {
                        setGameMode(packageName, newGameMode, userId);
                    }
                    updateCompatModeDownscale(packageName, gameMode);
                    updateInterventions(packageName, gameMode);
                }
            }
        } 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 Diff line number Diff line
@@ -182,7 +182,14 @@ public class GameManagerServiceTests {
    }

    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()))
                .thenReturn(configString);
    }
@@ -212,7 +219,8 @@ public class GameManagerServiceTests {
    }

    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();
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_PERFORMANCE_MODE_ENABLE, true);
@@ -224,7 +232,8 @@ public class GameManagerServiceTests {
    }

    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();
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_PERFORMANCE_MODE_ENABLE, true);
@@ -234,7 +243,8 @@ public class GameManagerServiceTests {
    }

    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();
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_BATTERY_MODE_ENABLE, true);
@@ -244,7 +254,8 @@ public class GameManagerServiceTests {
    }

    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();
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, true);
@@ -254,7 +265,8 @@ public class GameManagerServiceTests {
    }

    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();
        metaDataBundle.putBoolean(
                GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, false);
@@ -263,6 +275,27 @@ public class GameManagerServiceTests {
                .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.
     */
@@ -427,6 +460,19 @@ public class GameManagerServiceTests {
        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.
     */
@@ -591,6 +637,50 @@ public class GameManagerServiceTests {
        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
     * same mode. No interventions for this game mode should be available in this case.