summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/app/GameManagerService.java368
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java326
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));
}
}