From acf3851b8fa242a986ca54f3e3508a273a638d48 Mon Sep 17 00:00:00 2001 From: Tim Van Patten Date: Thu, 20 May 2021 14:57:25 -0600 Subject: [PATCH] Enable ANGLE as a Game Dashboard Intervention Enable ANGLE as a Game Dashboard intervention via Phenotype. Bug: b/188475576 Test: GameManagerServiceTests Change-Id: I91894a76b8dcf7a97244cff679a501001734f671 --- core/java/android/app/GameManager.java | 16 +++ .../java/android/app/IGameManagerService.aidl | 1 + core/java/android/os/GraphicsEnvironment.java | 45 ++++++-- .../server/app/GameManagerService.java | 84 +++++++++++++-- .../server/app/GameManagerServiceTests.java | 102 ++++++++++++++++-- 5 files changed, 224 insertions(+), 24 deletions(-) diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java index 5964f71d28db9..b324fb68ff59d 100644 --- a/core/java/android/app/GameManager.java +++ b/core/java/android/app/GameManager.java @@ -135,6 +135,7 @@ public final class GameManager { throw e.rethrowFromSystemServer(); } } + /** * Returns a list of supported game modes for a given package. *

@@ -151,4 +152,19 @@ public final class GameManager { } } + /** + * Returns if ANGLE is enabled for a given package and user ID. + *

+ * 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(); + } + } } diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl index 4bf8a3f77bca8..189f0a2fca23b 100644 --- a/core/java/android/app/IGameManagerService.aidl +++ b/core/java/android/app/IGameManagerService.aidl @@ -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 diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 1651bfc2db5d2..b3416e98147ef 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -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; } diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index af8d7a6a282be..3003c520254b6 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -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); } } @@ -851,7 +917,7 @@ public final class GameManagerService extends IGameManagerService.Stub { final List packages = mPackageManager.getInstalledPackagesAsUser(0, userId); return packages.stream().filter(e -> e.applicationInfo != null && e.applicationInfo.category - == ApplicationInfo.CATEGORY_GAME) + == ApplicationInfo.CATEGORY_GAME) .map(e -> e.packageName) .toArray(String[]::new); } diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index 4d86c8716bf1f..8840057cc5520 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -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.