Enable ANGLE as a Game Dashboard Intervention

Enable ANGLE as a Game Dashboard intervention via Phenotype.

Bug: b/188475576
Test: GameManagerServiceTests
Change-Id: I91894a76b8dcf7a97244cff679a501001734f671
This commit is contained in:
Tim Van Patten
2021-05-20 14:57:25 -06:00
parent 20b74d19a9
commit acf3851b8f
5 changed files with 224 additions and 24 deletions

View File

@@ -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();
}
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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<PackageInfo> 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);
}

View File

@@ -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.