diff options
| -rw-r--r-- | services/core/java/com/android/server/app/GameManagerService.java | 368 | ||||
| -rw-r--r-- | services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java | 326 |
2 files changed, 445 insertions, 249 deletions
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index b7ef10a5d45d..d2c6c6c620dd 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -56,6 +56,7 @@ import android.os.ShellCallback; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.KeyValueListParser; import android.util.Slog; @@ -180,9 +181,14 @@ public final class GameManagerService extends IGameManagerService.Stub { break; } case POPULATE_GAME_MODE_SETTINGS: { + // Scan all game packages and re-enforce the configured compat mode overrides + // as the DeviceConfig may have be wiped/since last reboot and we can't risk + // having overrides configured for packages that no longer have any DeviceConfig + // and thus any way to escape compat mode. removeMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj); - loadDeviceConfigLocked(); - break; + final int userId = (int) msg.obj; + final String[] packageNames = getInstalledGamePackageNames(userId); + updateConfigsForUser(userId, packageNames); } } } @@ -198,28 +204,8 @@ public final class GameManagerService extends IGameManagerService.Stub { @Override public void onPropertiesChanged(Properties properties) { - synchronized (mDeviceConfigLock) { - for (final String packageName : properties.getKeyset()) { - try { - // Check if the package is installed before caching it. - mPackageManager.getPackageInfo(packageName, 0); - final GamePackageConfiguration config = - GamePackageConfiguration.fromProperties(packageName, properties); - if (config.isValid()) { - putConfig(config); - } else { - // This means that we received a bad config, or the config was deleted. - Slog.i(TAG, "Removing config for: " + packageName); - mConfigs.remove(packageName); - disableCompatScale(packageName); - } - } catch (PackageManager.NameNotFoundException e) { - if (DEBUG) { - Slog.v(TAG, "Package name not found", e); - } - } - } - } + final String[] packageNames = properties.getKeyset().toArray(new String[0]); + updateConfigsForUser(mContext.getUserId(), packageNames); } @Override @@ -228,80 +214,166 @@ public final class GameManagerService extends IGameManagerService.Stub { } } - private static class GameModeConfiguration { - public static final String TAG = "GameManagerService_GameModeConfiguration"; - public static final String MODE_KEY = "mode"; - public static final String SCALING_KEY = "downscaleFactor"; + /** + * GamePackageConfiguration manages all game mode config details for its associated package. + */ + @VisibleForTesting + public class GamePackageConfiguration { + public static final String TAG = "GameManagerService_GamePackageConfiguration"; - private final @GameMode int mGameMode; - private final String mScaling; + /** + * Metadata that can be included in the app manifest to allow/disallow any window manager + * downscaling interventions. Default value is TRUE. + */ + public static final String METADATA_WM_ALLOW_DOWNSCALE = + "com.android.graphics.intervention.wm.allowDownscale"; - private GameModeConfiguration(@NonNull int gameMode, - @NonNull String scaling) { - mGameMode = gameMode; - mScaling = scaling; - } + /** + * 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 + * mode and the system will enable no window manager downscaling. + * Default value is FALSE + */ + public static final String METADATA_PERFORMANCE_MODE_ENABLE = + "com.android.app.gamemode.performance.enabled"; - public static GameModeConfiguration fromKeyValueListParser(KeyValueListParser parser) { - return new GameModeConfiguration( - parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED), - parser.getString(SCALING_KEY, "1.0") - ); - } + /** + * Metadata that needs to be included in the app manifest to OPT-IN to BATTERY mode. + * This means the app will assume full responsibility for the experience provided by this + * mode and the system will enable no window manager downscaling. + * Default value is FALSE + */ + public static final String METADATA_BATTERY_MODE_ENABLE = + "com.android.app.gamemode.battery.enabled"; - public int getGameMode() { - return mGameMode; - } + private final String mPackageName; + private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs; + private boolean mPerfModeOptedIn; + private boolean mBatteryModeOptedIn; + private boolean mAllowDownscale; - public String getScaling() { - return mScaling; + GamePackageConfiguration(String packageName, int userId) { + mPackageName = packageName; + mModeConfigs = new ArrayMap<>(); + try { + final ApplicationInfo ai = mPackageManager.getApplicationInfoAsUser(packageName, + PackageManager.GET_META_DATA, userId); + if (ai.metaData != null) { + 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); + } else { + mPerfModeOptedIn = false; + mBatteryModeOptedIn = false; + mAllowDownscale = true; + } + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Failed to get package metadata", e); + } + final String configString = DeviceConfig.getProperty( + DeviceConfig.NAMESPACE_GAME_OVERLAY, packageName); + if (configString != null) { + final String[] gameModeConfigStrings = configString.split(":"); + for (String gameModeConfigString : gameModeConfigStrings) { + try { + final KeyValueListParser parser = new KeyValueListParser(','); + parser.setString(gameModeConfigString); + addModeConfig(new GameModeConfiguration(parser)); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Invalid config string"); + } + } + } } - public boolean isValid() { - return (mGameMode == GameManager.GAME_MODE_PERFORMANCE - || mGameMode == GameManager.GAME_MODE_BATTERY) && getCompatChangeId() != 0; - } + /** + * GameModeConfiguration contains all the values for all the interventions associated with + * a game mode. + */ + @VisibleForTesting + public class GameModeConfiguration { + public static final String TAG = "GameManagerService_GameModeConfiguration"; + public static final String MODE_KEY = "mode"; + public static final String SCALING_KEY = "downscaleFactor"; + public static final String DEFAULT_SCALING = "1.0"; + + private final @GameMode int mGameMode; + private final String mScaling; + + GameModeConfiguration(KeyValueListParser parser) { + mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED); + mScaling = !mAllowDownscale || isGameModeOptedIn(mGameMode) + ? DEFAULT_SCALING : parser.getString(SCALING_KEY, DEFAULT_SCALING); + } - public String toString() { - return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + "]"; - } + public int getGameMode() { + return mGameMode; + } - public long getCompatChangeId() { - switch (mScaling) { - case "0.5": - return DOWNSCALE_50; - case "0.6": - return DOWNSCALE_60; - case "0.7": - return DOWNSCALE_70; - case "0.8": - return DOWNSCALE_80; - case "0.9": - return DOWNSCALE_90; + public String getScaling() { + return mScaling; } - return 0; - } - } - private static class GamePackageConfiguration { - public static final String TAG = "GameManagerService_GamePackageConfiguration"; + public boolean isValid() { + return (mGameMode == GameManager.GAME_MODE_PERFORMANCE + || mGameMode == GameManager.GAME_MODE_BATTERY) + && (!mAllowDownscale || getCompatChangeId() != 0); + } - private final String mPackageName; - private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs; + /** + * @hide + */ + public String toString() { + return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + "]"; + } - private GamePackageConfiguration(String packageName) { - mPackageName = packageName; - mModeConfigs = new ArrayMap<>(); + /** + * Get the corresponding compat change id for the current scaling string. + */ + public long getCompatChangeId() { + switch (mScaling) { + case "0.5": + return DOWNSCALE_50; + case "0.6": + return DOWNSCALE_60; + case "0.7": + return DOWNSCALE_70; + case "0.8": + return DOWNSCALE_80; + case "0.9": + return DOWNSCALE_90; + } + return 0; + } } public String getPackageName() { return mPackageName; } + /** + * Gets whether a package has opted into a game mode via its manifest. + * + * @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) { + return (mBatteryModeOptedIn && gameMode == GameManager.GAME_MODE_BATTERY) + || (mPerfModeOptedIn && gameMode == GameManager.GAME_MODE_PERFORMANCE); + } + public @GameMode int[] getAvailableGameModes() { - if (mModeConfigs.keySet().size() > 0) { - return mModeConfigs.keySet().stream() - .mapToInt(Integer::intValue).toArray(); + ArraySet<Integer> modeSet = new ArraySet<>(mModeConfigs.keySet()); + if (mBatteryModeOptedIn) { + modeSet.add(GameManager.GAME_MODE_BATTERY); + } + if (mPerfModeOptedIn) { + modeSet.add(GameManager.GAME_MODE_PERFORMANCE); + } + if (modeSet.size() > 0) { + modeSet.add(GameManager.GAME_MODE_STANDARD); + return modeSet.stream().mapToInt(Integer::intValue).toArray(); } return new int[]{GameManager.GAME_MODE_UNSUPPORTED}; } @@ -327,30 +399,8 @@ public final class GameManagerService extends IGameManagerService.Stub { } } - /** - * Create a new instance from a package name and DeviceConfig.Properties instance - */ - public static GamePackageConfiguration fromProperties(String key, - Properties properties) { - final GamePackageConfiguration packageConfig = new GamePackageConfiguration(key); - final String configString = properties.getString(key, ""); - final String[] gameModeConfigStrings = configString.split(":"); - for (String gameModeConfigString : gameModeConfigStrings) { - try { - final KeyValueListParser parser = new KeyValueListParser(','); - parser.setString(gameModeConfigString); - final GameModeConfiguration config = - GameModeConfiguration.fromKeyValueListParser(parser); - packageConfig.addModeConfig(config); - } catch (IllegalArgumentException e) { - Slog.e(TAG, "Invalid config string"); - } - } - return packageConfig; - } - public boolean isValid() { - return mModeConfigs.size() > 0; + return mModeConfigs.size() > 0 || mBatteryModeOptedIn || mPerfModeOptedIn; } public String toString() { @@ -534,8 +584,6 @@ public final class GameManagerService extends IGameManagerService.Stub { @VisibleForTesting void onBootCompleted() { Slog.d(TAG, "onBootCompleted"); - final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS); - mHandler.sendMessage(msg); } void onUserStarting(int userId) { @@ -549,6 +597,9 @@ public final class GameManagerService extends IGameManagerService.Stub { mSettings.put(userId, userSettings); userSettings.readPersistentDataLocked(); } + final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS); + msg.obj = userId; + mHandler.sendMessage(msg); } void onUserStopping(int userId) { @@ -562,24 +613,14 @@ public final class GameManagerService extends IGameManagerService.Stub { } } - void loadDeviceConfigLocked() { - final List<PackageInfo> packages = mPackageManager.getInstalledPackages(0); - final String[] packageNames = packages.stream().map(e -> e.packageName) - .toArray(String[]::new); - synchronized (mDeviceConfigLock) { - final Properties properties = DeviceConfig.getProperties( - DeviceConfig.NAMESPACE_GAME_OVERLAY, packageNames); - for (String key : properties.getKeyset()) { - final GamePackageConfiguration config = - GamePackageConfiguration.fromProperties(key, properties); - putConfig(config); - } - } - } - - private void disableCompatScale(String packageName) { + /** + * @hide + */ + @VisibleForTesting + public void disableCompatScale(String packageName) { final long uid = Binder.clearCallingIdentity(); try { + Slog.i(TAG, "Disabling downscale for " + packageName); final ArrayMap<Long, PackageOverride> overrides = new ArrayMap<>(); overrides.put(DOWNSCALED, COMPAT_DISABLED); final CompatibilityOverrideConfig changeConfig = new CompatibilityOverrideConfig( @@ -597,6 +638,7 @@ public final class GameManagerService extends IGameManagerService.Stub { private void enableCompatScale(String packageName, long scaleId) { final long uid = Binder.clearCallingIdentity(); try { + Slog.i(TAG, "Enabling downscale: " + scaleId + " for " + packageName); final ArrayMap<Long, PackageOverride> overrides = new ArrayMap<>(); overrides.put(DOWNSCALED, COMPAT_ENABLED); overrides.put(DOWNSCALE_50, COMPAT_DISABLED); @@ -622,21 +664,25 @@ public final class GameManagerService extends IGameManagerService.Stub { if (gameMode == GameManager.GAME_MODE_STANDARD || gameMode == GameManager.GAME_MODE_UNSUPPORTED) { disableCompatScale(packageName); - Slog.v(TAG, "Disabling downscale"); + return; + } + final GamePackageConfiguration packageConfig = mConfigs.get(packageName); + if (packageConfig == null) { + disableCompatScale(packageName); + Slog.v(TAG, "Package configuration not found for " + packageName); return; } if (DEBUG) { Slog.v(TAG, dumpDeviceConfigs()); } - final GamePackageConfiguration packageConfig = mConfigs.get(packageName); - if (packageConfig == null) { - Slog.w(TAG, "Package configuration not found for " + packageName); + if (packageConfig.isGameModeOptedIn(gameMode)) { + disableCompatScale(packageName); return; } - final GameModeConfiguration modeConfig = packageConfig.getGameModeConfiguration( - gameMode); + final GamePackageConfiguration.GameModeConfiguration modeConfig = + packageConfig.getGameModeConfiguration(gameMode); if (modeConfig == null) { - Slog.w(TAG, "Game mode " + gameMode + " not found for " + packageName); + Slog.i(TAG, "Game mode " + gameMode + " not found for " + packageName); return; } long scaleId = modeConfig.getCompatChangeId(); @@ -645,23 +691,64 @@ public final class GameManagerService extends IGameManagerService.Stub { + packageName); return; } - Slog.i(TAG, "Enabling downscale: " + scaleId + " for " + packageName); + enableCompatScale(packageName, scaleId); } } - private void putConfig(GamePackageConfiguration config) { - if (config.isValid()) { - if (DEBUG) { - Slog.i(TAG, "Adding config: " + config.toString()); + /** + * @hide + */ + @VisibleForTesting + public void updateConfigsForUser(int userId, String ...packageNames) { + try { + synchronized (mDeviceConfigLock) { + for (String packageName : packageNames) { + GamePackageConfiguration config = + new GamePackageConfiguration(packageName, userId); + if (config.isValid()) { + if (DEBUG) { + Slog.i(TAG, "Adding config: " + config.toString()); + } + mConfigs.put(packageName, config); + } else { + Slog.w(TAG, "Invalid package config for " + + config.getPackageName() + ":" + config.toString()); + mConfigs.remove(packageName); + } + } } - mConfigs.put(config.getPackageName(), config); - } else { - Slog.w(TAG, "Invalid package config for " - + config.getPackageName() + ":" + config.toString()); + for (String packageName : packageNames) { + synchronized (mLock) { + if (mSettings.containsKey(userId)) { + GameManagerSettings userSettings = mSettings.get(userId); + updateCompatModeDownscale(packageName, + userSettings.getGameModeLocked(packageName)); + } + } + } + } catch (Exception e) { + Slog.e(TAG, "Failed to update compat modes for user: " + userId); } } + private String[] getInstalledGamePackageNames(int userId) { + final List<PackageInfo> packages = + mPackageManager.getInstalledPackagesAsUser(0, userId); + return packages.stream().filter(e -> e.applicationInfo != null && e.applicationInfo.category + == ApplicationInfo.CATEGORY_GAME) + .map(e -> e.packageName) + .toArray(String[]::new); + } + + /** + * @hide + */ + @VisibleForTesting + public GamePackageConfiguration getConfig(String packageName) { + return mConfigs.get(packageName); + } + private void registerPackageReceiver() { final IntentFilter packageFilter = new IntentFilter(); packageFilter.addAction(ACTION_PACKAGE_ADDED); @@ -677,16 +764,7 @@ public final class GameManagerService extends IGameManagerService.Stub { switch (intent.getAction()) { case ACTION_PACKAGE_ADDED: case ACTION_PACKAGE_CHANGED: - synchronized (mDeviceConfigLock) { - Properties properties = DeviceConfig.getProperties( - DeviceConfig.NAMESPACE_GAME_OVERLAY, packageName); - for (String key : properties.getKeyset()) { - GamePackageConfiguration config = - GamePackageConfiguration.fromProperties(key, - properties); - putConfig(config); - } - } + updateConfigsForUser(mContext.getUserId(), packageName); break; case ACTION_PACKAGE_REMOVED: disableCompatScale(packageName); 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 a8d8a90e8935..96495701811e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -19,6 +19,7 @@ package com.android.server.app; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; @@ -32,8 +33,10 @@ import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.os.Bundle; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; +import android.util.ArraySet; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -140,15 +143,19 @@ public class GameManagerServiceTests { applicationInfo.category = ApplicationInfo.CATEGORY_GAME; final PackageInfo pi = new PackageInfo(); pi.packageName = mPackageName; + pi.applicationInfo = applicationInfo; final List<PackageInfo> packages = new ArrayList<>(); packages.add(pi); - when(mMockPackageManager.getInstalledPackages(anyInt())).thenReturn(packages); + when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt())) + .thenReturn(packages); when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) .thenReturn(applicationInfo); } @After public void tearDown() throws Exception { + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.disableCompatScale(mPackageName); if (mMockingSession != null) { mMockingSession.finishMocking(); } @@ -165,57 +172,95 @@ public class GameManagerServiceTests { } private void mockDeviceConfigDefault() { - DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( - DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, "").build(); - when(DeviceConfig.getProperties(anyString(), anyString())) - .thenReturn(properties); + when(DeviceConfig.getProperty(anyString(), anyString())) + .thenReturn(""); } private void mockDeviceConfigNone() { - DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( - DeviceConfig.NAMESPACE_GAME_OVERLAY).build(); - when(DeviceConfig.getProperties(anyString(), anyString())) - .thenReturn(properties); + when(DeviceConfig.getProperty(anyString(), anyString())) + .thenReturn(null); } private void mockDeviceConfigPerformance() { String configString = "mode=2,downscaleFactor=0.5"; - DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( - DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); - when(DeviceConfig.getProperties(anyString(), anyString())) - .thenReturn(properties); + when(DeviceConfig.getProperty(anyString(), anyString())) + .thenReturn(configString); } private void mockDeviceConfigBattery() { String configString = "mode=3,downscaleFactor=0.7"; - DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( - DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); - when(DeviceConfig.getProperties(anyString(), anyString())) - .thenReturn(properties); + when(DeviceConfig.getProperty(anyString(), anyString())) + .thenReturn(configString); } private void mockDeviceConfigAll() { String configString = "mode=3,downscaleFactor=0.7:mode=2,downscaleFactor=0.5"; - DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( - DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); - when(DeviceConfig.getProperties(anyString(), anyString())) - .thenReturn(properties); + when(DeviceConfig.getProperty(anyString(), anyString())) + .thenReturn(configString); } private void mockDeviceConfigInvalid() { String configString = "mode=2,downscaleFactor=0.55"; - DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( - DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); - when(DeviceConfig.getProperties(anyString(), anyString())) - .thenReturn(properties); + when(DeviceConfig.getProperty(anyString(), anyString())) + .thenReturn(configString); } private void mockDeviceConfigMalformed() { String configString = "adsljckv=nin3rn9hn1231245:8795tq=21ewuydg"; - DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( - DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); - when(DeviceConfig.getProperties(anyString(), anyString())) - .thenReturn(properties); + when(DeviceConfig.getProperty(anyString(), anyString())) + .thenReturn(configString); + } + + private void mockGameModeOptInAll() throws Exception { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + 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 = new ApplicationInfo(); + 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 = new ApplicationInfo(); + 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 = new ApplicationInfo(); + 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 = new ApplicationInfo(); + Bundle metaDataBundle = new Bundle(); + metaDataBundle.putBoolean( + GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, false); + applicationInfo.metaData = metaDataBundle; + when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) + .thenReturn(applicationInfo); } /** @@ -353,136 +398,209 @@ public class GameManagerServiceTests { gameManagerService.getGameMode(mPackageName, USER_ID_2)); } + private void checkReportedModes(int ...requiredModes) { + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName); + ArraySet<Integer> reportedModes = new ArraySet<>(); + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + for (int mode : modes) { + reportedModes.add(mode); + } + assertEquals(requiredModes.length, reportedModes.size()); + for (int requiredMode : reportedModes) { + assertTrue("Required game mode not supported: " + requiredMode, + reportedModes.contains(requiredMode)); + } + } + + private void checkDownscaling(int gameMode, String scaling) { + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName); + GameManagerService.GamePackageConfiguration config = + gameManagerService.getConfig(mPackageName); + assertEquals(config.getGameModeConfiguration(gameMode).getScaling(), scaling); + } + /** - * Phonesky device config exists, but is only propagating the default value. + * Phenotype device config exists, but is only propagating the default value. */ @Test public void testDeviceConfigDefault() { mockDeviceConfigDefault(); mockModifyGameModeGranted(); - GameManagerService gameManagerService = new GameManagerService(mMockContext); - gameManagerService.onUserStarting(USER_ID_1); - gameManagerService.loadDeviceConfigLocked(); - - int[] modes = gameManagerService.getAvailableGameModes(mPackageName); - assertEquals(modes.length, 1); - assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED); + checkReportedModes(GameManager.GAME_MODE_UNSUPPORTED); } /** - * Phonesky device config does not exists. + * Phenotype device config does not exists. */ @Test public void testDeviceConfigNone() { mockDeviceConfigNone(); mockModifyGameModeGranted(); - GameManagerService gameManagerService = new GameManagerService(mMockContext); - gameManagerService.onUserStarting(USER_ID_1); - gameManagerService.loadDeviceConfigLocked(); - - int[] modes = gameManagerService.getAvailableGameModes(mPackageName); - assertEquals(modes.length, 1); - assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED); + checkReportedModes(GameManager.GAME_MODE_UNSUPPORTED); } /** - * Phonesky device config for performance mode exists and is valid. + * Phenotype device config for performance mode exists and is valid. */ @Test public void testDeviceConfigPerformance() { mockDeviceConfigPerformance(); mockModifyGameModeGranted(); - GameManagerService gameManagerService = new GameManagerService(mMockContext); - gameManagerService.onUserStarting(USER_ID_1); - gameManagerService.loadDeviceConfigLocked(); - - boolean perfModeExists = false; - int[] modes = gameManagerService.getAvailableGameModes(mPackageName); - for (int mode : modes) { - if (mode == GameManager.GAME_MODE_PERFORMANCE) { - perfModeExists = true; - } - } - assertEquals(modes.length, 1); - assertTrue(perfModeExists); + checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD); } /** - * Phonesky device config for battery mode exists and is valid. + * Phenotype device config for battery mode exists and is valid. */ @Test public void testDeviceConfigBattery() { mockDeviceConfigBattery(); mockModifyGameModeGranted(); - GameManagerService gameManagerService = new GameManagerService(mMockContext); - gameManagerService.onUserStarting(USER_ID_1); - gameManagerService.loadDeviceConfigLocked(); - - boolean batteryModeExists = false; - int[] modes = gameManagerService.getAvailableGameModes(mPackageName); - for (int mode : modes) { - if (mode == GameManager.GAME_MODE_BATTERY) { - batteryModeExists = true; - } - } - assertEquals(modes.length, 1); - assertTrue(batteryModeExists); + checkReportedModes(GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); } /** - * Phonesky device configs for both battery and performance modes exists and are valid. + * Phenotype device configs for both battery and performance modes exists and are valid. */ @Test public void testDeviceConfigAll() { mockDeviceConfigAll(); mockModifyGameModeGranted(); - GameManagerService gameManagerService = new GameManagerService(mMockContext); - gameManagerService.onUserStarting(USER_ID_1); - gameManagerService.loadDeviceConfigLocked(); - - boolean batteryModeExists = false; - boolean perfModeExists = false; - int[] modes = gameManagerService.getAvailableGameModes(mPackageName); - for (int mode : modes) { - if (mode == GameManager.GAME_MODE_BATTERY) { - batteryModeExists = true; - } else if (mode == GameManager.GAME_MODE_PERFORMANCE) { - perfModeExists = true; - } - } - assertTrue(batteryModeExists); - assertTrue(perfModeExists); + checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, + GameManager.GAME_MODE_STANDARD); } /** - * Phonesky device config contains values that parse correctly but are not valid in game mode. + * Phenotype device config contains values that parse correctly but are not valid in game mode. */ @Test public void testDeviceConfigInvalid() { mockDeviceConfigInvalid(); mockModifyGameModeGranted(); - GameManagerService gameManagerService = new GameManagerService(mMockContext); - gameManagerService.onUserStarting(USER_ID_1); - gameManagerService.loadDeviceConfigLocked(); - - int[] modes = gameManagerService.getAvailableGameModes(mPackageName); - assertEquals(modes.length, 1); - assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED); + checkReportedModes(GameManager.GAME_MODE_UNSUPPORTED); } /** - * Phonesky device config is garbage. + * Phenotype device config is garbage. */ @Test public void testDeviceConfigMalformed() { mockDeviceConfigMalformed(); mockModifyGameModeGranted(); + checkReportedModes(GameManager.GAME_MODE_UNSUPPORTED); + } + + /** + * Game modes are made available only through app manifest opt-in. + */ + @Test + public void testGameModeOptInAll() throws Exception { + mockGameModeOptInAll(); + mockDeviceConfigNone(); + mockModifyGameModeGranted(); + checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, + GameManager.GAME_MODE_STANDARD); + } + + /** + * BATTERY game mode is available through the app manifest opt-in. + */ + @Test + public void testGameModeOptInBattery() throws Exception { + mockGameModeOptInBattery(); + mockDeviceConfigNone(); + mockModifyGameModeGranted(); + checkReportedModes(GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); + } + + /** + * PERFORMANCE game mode is available through the app manifest opt-in. + */ + @Test + public void testGameModeOptInPerformance() throws Exception { + mockGameModeOptInPerformance(); + mockDeviceConfigNone(); + mockModifyGameModeGranted(); + checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD); + } + + /** + * 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(); + checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, + GameManager.GAME_MODE_STANDARD); + } + + /** + * 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(); + checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, + GameManager.GAME_MODE_STANDARD); + } + + /** + * PERFORMANCE game mode is configured through Phenotype. The app hasn't specified any metadata. + */ + @Test + public void testInterventionAllowScalingDefault() throws Exception { + mockDeviceConfigPerformance(); + mockModifyGameModeGranted(); + checkDownscaling(GameManager.GAME_MODE_PERFORMANCE, "0.5"); + } + + /** + * PERFORMANCE game mode is configured through Phenotype. The app has opted-out of scaling. + */ + @Test + public void testInterventionAllowDownscaleFalse() throws Exception { + mockDeviceConfigPerformance(); + mockInterventionAllowDownscaleFalse(); + mockModifyGameModeGranted(); + checkDownscaling(GameManager.GAME_MODE_PERFORMANCE, "1.0"); + } + + /** + * 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(GameManager.GAME_MODE_PERFORMANCE, "0.5"); + } + + /** + * 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); gameManagerService.onUserStarting(USER_ID_1); - gameManagerService.loadDeviceConfigLocked(); - - int[] modes = gameManagerService.getAvailableGameModes(mPackageName); - assertEquals(modes.length, 1); - assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED); + gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName); + GameManagerService.GamePackageConfiguration config = + gameManagerService.getConfig(mPackageName); + assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE)); } } |