Merge "Add support for manifest based opt-in for game modes" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-05-04 18:14:47 +00:00
committed by Android (Google) Code Review
2 changed files with 451 additions and 255 deletions

View File

@@ -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";
private final @GameMode int mGameMode;
private final String mScaling;
private GameModeConfiguration(@NonNull int gameMode,
@NonNull String scaling) {
mGameMode = gameMode;
mScaling = scaling;
}
public static GameModeConfiguration fromKeyValueListParser(KeyValueListParser parser) {
return new GameModeConfiguration(
parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED),
parser.getString(SCALING_KEY, "1.0")
);
}
public int getGameMode() {
return mGameMode;
}
public String getScaling() {
return mScaling;
}
public boolean isValid() {
return (mGameMode == GameManager.GAME_MODE_PERFORMANCE
|| mGameMode == GameManager.GAME_MODE_BATTERY) && getCompatChangeId() != 0;
}
public String toString() {
return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + "]";
}
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;
}
}
private static class GamePackageConfiguration {
/**
* GamePackageConfiguration manages all game mode config details for its associated package.
*/
@VisibleForTesting
public class GamePackageConfiguration {
public static final String TAG = "GameManagerService_GamePackageConfiguration";
/**
* 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";
/**
* 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";
/**
* 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";
private final String mPackageName;
private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs;
private boolean mPerfModeOptedIn;
private boolean mBatteryModeOptedIn;
private boolean mAllowDownscale;
private GamePackageConfiguration(String packageName) {
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");
}
}
}
}
/**
* 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 int getGameMode() {
return mGameMode;
}
public String getScaling() {
return mScaling;
}
public boolean isValid() {
return (mGameMode == GameManager.GAME_MODE_PERFORMANCE
|| mGameMode == GameManager.GAME_MODE_BATTERY)
&& (!mAllowDownscale || getCompatChangeId() != 0);
}
/**
* @hide
*/
public String toString() {
return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + "]";
}
/**
* 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);

View File

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