Override config should trigger intervention ignoring opt-in info

In the past we have an inconsistent behavior regarding override config
as it will ignore intervention setting such as `mAllowDownscale` but
still respect opt-in info as `mPerfModeOptedIn`. This will be confusing
and there is no way for OEM or game developers to test new interventions
for games without any pre-configured game mode device config.

Now they can instead first opt in the game modes temporarily if not
pre-configured (to make them available), then apply the override to
test. But they should reset the opt-in info and overrides after testing,
then communicate the interventions to OEMs.

This also fix the bug below where the override config used to contain
 full information including opt-in info that can be stale. Now it's
lightweight as it only contains GameModeConfiguration(s) and will be
 used together with default config in getConfig call.

Bug: b/253102835
Test: atest GameManagerServiceTests
Change-Id: Iee14d6eed07b16b6adb86b459edebdeca2b03fbc
Merged-In: Iee14d6eed07b16b6adb86b459edebdeca2b03fbc
This commit is contained in:
Xiang Wang
2022-10-12 16:48:17 -07:00
parent 1ddd0c1b95
commit 5abb52c166
5 changed files with 182 additions and 41 deletions

View File

@@ -117,6 +117,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Service to manage game related features. * Service to manage game related features.
@@ -556,16 +557,21 @@ public final class GameManagerService extends IGameManagerService.Stub {
private final String mPackageName; private final String mPackageName;
private final Object mModeConfigLock = new Object(); private final Object mModeConfigLock = new Object();
@GuardedBy("mModeConfigLock") @GuardedBy("mModeConfigLock")
private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs; private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs = new ArrayMap<>();
// if adding new properties or make any of the below overridable, the method
// copyAndApplyOverride should be updated accordingly
private boolean mPerfModeOptedIn = false; private boolean mPerfModeOptedIn = false;
private boolean mBatteryModeOptedIn = false; private boolean mBatteryModeOptedIn = false;
private boolean mAllowDownscale = true; private boolean mAllowDownscale = true;
private boolean mAllowAngle = true; private boolean mAllowAngle = true;
private boolean mAllowFpsOverride = true; private boolean mAllowFpsOverride = true;
GamePackageConfiguration(String packageName) {
mPackageName = packageName;
}
GamePackageConfiguration(String packageName, int userId) { GamePackageConfiguration(String packageName, int userId) {
mPackageName = packageName; mPackageName = packageName;
mModeConfigs = new ArrayMap<>();
try { try {
final ApplicationInfo ai = mPackageManager.getApplicationInfoAsUser(packageName, final ApplicationInfo ai = mPackageManager.getApplicationInfoAsUser(packageName,
@@ -649,6 +655,13 @@ public final class GameManagerService extends IGameManagerService.Stub {
return xmlFound; return xmlFound;
} }
GameModeConfiguration getOrAddDefaultGameModeConfiguration(int gameMode) {
synchronized (mModeConfigLock) {
mModeConfigs.putIfAbsent(gameMode, new GameModeConfiguration(gameMode));
return mModeConfigs.get(gameMode);
}
}
/** /**
* GameModeConfiguration contains all the values for all the interventions associated with * GameModeConfiguration contains all the values for all the interventions associated with
* a game mode. * a game mode.
@@ -661,15 +674,23 @@ public final class GameManagerService extends IGameManagerService.Stub {
public static final String FPS_KEY = "fps"; public static final String FPS_KEY = "fps";
public static final String DEFAULT_SCALING = "1.0"; public static final String DEFAULT_SCALING = "1.0";
public static final String DEFAULT_FPS = ""; public static final String DEFAULT_FPS = "";
public static final boolean DEFAULT_USE_ANGLE = false;
public static final int DEFAULT_LOADING_BOOST_DURATION = -1;
public static final String ANGLE_KEY = "useAngle"; public static final String ANGLE_KEY = "useAngle";
public static final String LOADING_BOOST_KEY = "loadingBoost"; public static final String LOADING_BOOST_KEY = "loadingBoost";
private final @GameMode int mGameMode; private final @GameMode int mGameMode;
private String mScaling; private String mScaling = DEFAULT_SCALING;
private String mFps; private String mFps = DEFAULT_FPS;
private final boolean mUseAngle; private final boolean mUseAngle;
private final int mLoadingBoostDuration; private final int mLoadingBoostDuration;
GameModeConfiguration(int gameMode) {
mGameMode = gameMode;
mUseAngle = DEFAULT_USE_ANGLE;
mLoadingBoostDuration = DEFAULT_LOADING_BOOST_DURATION;
}
GameModeConfiguration(KeyValueListParser parser) { GameModeConfiguration(KeyValueListParser parser) {
mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED); mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED);
// isGameModeOptedIn() returns if an app will handle all of the changes necessary // isGameModeOptedIn() returns if an app will handle all of the changes necessary
@@ -832,6 +853,42 @@ public final class GameManagerService extends IGameManagerService.Stub {
} }
} }
GamePackageConfiguration copyAndApplyOverride(GamePackageConfiguration overrideConfig) {
GamePackageConfiguration copy = new GamePackageConfiguration(mPackageName);
// if a game mode is overridden, we treat it with the highest priority and reset any
// opt-in game modes so that interventions are always executed.
copy.mPerfModeOptedIn = mPerfModeOptedIn && !(overrideConfig != null
&& overrideConfig.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE)
!= null);
copy.mBatteryModeOptedIn = mBatteryModeOptedIn && !(overrideConfig != null
&& overrideConfig.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY)
!= null);
// if any game mode is overridden, we will consider all interventions forced-active,
// this can be done more granular by checking if a specific intervention is
// overridden under each game mode override, but only if necessary.
copy.mAllowDownscale = mAllowDownscale || overrideConfig != null;
copy.mAllowAngle = mAllowAngle || overrideConfig != null;
copy.mAllowFpsOverride = mAllowFpsOverride || overrideConfig != null;
if (overrideConfig != null) {
synchronized (copy.mModeConfigLock) {
synchronized (mModeConfigLock) {
for (Map.Entry<Integer, GameModeConfiguration> entry :
mModeConfigs.entrySet()) {
copy.mModeConfigs.put(entry.getKey(), entry.getValue());
}
}
synchronized (overrideConfig.mModeConfigLock) {
for (Map.Entry<Integer, GameModeConfiguration> entry :
overrideConfig.mModeConfigs.entrySet()) {
copy.mModeConfigs.put(entry.getKey(), entry.getValue());
}
}
}
}
return copy;
}
public String toString() { public String toString() {
synchronized (mModeConfigLock) { synchronized (mModeConfigLock) {
return "[Name:" + mPackageName + " Modes: " + mModeConfigs.toString() + "]"; return "[Name:" + mPackageName + " Modes: " + mModeConfigs.toString() + "]";
@@ -1402,13 +1459,13 @@ public final class GameManagerService extends IGameManagerService.Stub {
// look for the existing override GamePackageConfiguration // look for the existing override GamePackageConfiguration
overrideConfig = mOverrideConfigs.get(packageName); overrideConfig = mOverrideConfigs.get(packageName);
if (overrideConfig == null) { if (overrideConfig == null) {
overrideConfig = new GamePackageConfiguration(packageName, userId); overrideConfig = new GamePackageConfiguration(packageName);
mOverrideConfigs.put(packageName, overrideConfig); mOverrideConfigs.put(packageName, overrideConfig);
} }
} }
// modify GameModeConfiguration intervention settings // modify GameModeConfiguration intervention settings
GamePackageConfiguration.GameModeConfiguration overrideModeConfig = GamePackageConfiguration.GameModeConfiguration overrideModeConfig =
overrideConfig.getGameModeConfiguration(gameMode); overrideConfig.getOrAddDefaultGameModeConfiguration(gameMode);
if (fpsStr != null) { if (fpsStr != null) {
overrideModeConfig.setFpsStr(fpsStr); overrideModeConfig.setFpsStr(fpsStr);
@@ -1704,16 +1761,18 @@ public final class GameManagerService extends IGameManagerService.Stub {
*/ */
@VisibleForTesting @VisibleForTesting
public GamePackageConfiguration getConfig(String packageName) { public GamePackageConfiguration getConfig(String packageName) {
GamePackageConfiguration packageConfig = null; GamePackageConfiguration overrideConfig = null;
synchronized (mOverrideConfigLock) { GamePackageConfiguration config;
packageConfig = mOverrideConfigs.get(packageName);
}
if (packageConfig == null) {
synchronized (mDeviceConfigLock) { synchronized (mDeviceConfigLock) {
packageConfig = mConfigs.get(packageName); config = mConfigs.get(packageName);
} }
synchronized (mOverrideConfigLock) {
overrideConfig = mOverrideConfigs.get(packageName);
} }
return packageConfig; if (overrideConfig == null || config == null) {
return overrideConfig == null ? config : overrideConfig;
}
return config.copyAndApplyOverride(overrideConfig);
} }
private void registerPackageReceiver() { private void registerPackageReceiver() {

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<game-mode-config
xmlns:android="http://schemas.android.com/apk/res/android"
android:supportsPerformanceGameMode="true"
android:supportsBatteryGameMode="true"
android:allowGameAngleDriver="false"
android:allowGameDownscaling="false"
android:allowGameFpsOverride="false"
/>

View File

@@ -19,6 +19,7 @@ package com.android.server.app;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
@@ -369,38 +370,35 @@ public class GameManagerServiceTests {
.thenReturn(applicationInfo); .thenReturn(applicationInfo);
} }
private void mockInterventionsEnabledFromXml() throws Exception { private void mockInterventionsEnabledNoOptInFromXml() throws Exception {
final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser( seedGameManagerServiceMetaDataFromFile(mPackageName, 123,
mPackageName, PackageManager.GET_META_DATA, USER_ID_1); "res/xml/game_manager_service_metadata_config_interventions_enabled_no_opt_in.xml");
Bundle metaDataBundle = new Bundle();
final int resId = 123;
metaDataBundle.putInt(
GameManagerService.GamePackageConfiguration.METADATA_GAME_MODE_CONFIG, resId);
applicationInfo.metaData = metaDataBundle;
when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
.thenReturn(applicationInfo);
seedGameManagerServiceMetaDataFromFile(mPackageName, resId,
"res/xml/gama_manager_service_metadata_config_enabled.xml");
} }
private void mockInterventionsDisabledFromXml() throws Exception { private void mockInterventionsDisabledNoOptInFromXml() throws Exception {
final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser( seedGameManagerServiceMetaDataFromFile(mPackageName, 123,
mPackageName, PackageManager.GET_META_DATA, USER_ID_1); "res/xml/game_manager_service_metadata_config_interventions_disabled_no_opt_in"
Bundle metaDataBundle = new Bundle(); + ".xml");
final int resId = 123; }
metaDataBundle.putInt(
GameManagerService.GamePackageConfiguration.METADATA_GAME_MODE_CONFIG, resId); private void mockInterventionsDisabledAllOptInFromXml() throws Exception {
applicationInfo.metaData = metaDataBundle; seedGameManagerServiceMetaDataFromFile(mPackageName, 123,
when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) "res/xml/game_manager_service_metadata_config_interventions_disabled_all_opt_in"
.thenReturn(applicationInfo); + ".xml");
seedGameManagerServiceMetaDataFromFile(mPackageName, resId,
"res/xml/gama_manager_service_metadata_config_disabled.xml");
} }
private void seedGameManagerServiceMetaDataFromFile(String packageName, int resId, private void seedGameManagerServiceMetaDataFromFile(String packageName, int resId,
String fileName) String fileName)
throws Exception { throws Exception {
final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
Bundle metaDataBundle = new Bundle();
metaDataBundle.putInt(
GameManagerService.GamePackageConfiguration.METADATA_GAME_MODE_CONFIG, resId);
applicationInfo.metaData = metaDataBundle;
when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
.thenReturn(applicationInfo);
AssetManager assetManager = AssetManager assetManager =
InstrumentationRegistry.getInstrumentation().getContext().getAssets(); InstrumentationRegistry.getInstrumentation().getContext().getAssets();
XmlResourceParser xmlResourceParser = XmlResourceParser xmlResourceParser =
@@ -628,6 +626,12 @@ public class GameManagerServiceTests {
assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps()); assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps());
} }
private boolean checkOptedIn(GameManagerService gameManagerService, int gameMode) {
GameManagerService.GamePackageConfiguration config =
gameManagerService.getConfig(mPackageName);
return config.willGamePerformOptimizations(gameMode);
}
/** /**
* Phenotype device config exists, but is only propagating the default value. * Phenotype device config exists, but is only propagating the default value.
*/ */
@@ -743,7 +747,7 @@ public class GameManagerServiceTests {
* Override device configs for both battery and performance modes exists and are valid. * Override device configs for both battery and performance modes exists and are valid.
*/ */
@Test @Test
public void testSetDeviceOverrideConfigAll() { public void testSetDeviceConfigOverrideAll() {
mockDeviceConfigAll(); mockDeviceConfigAll();
mockModifyGameModeGranted(); mockModifyGameModeGranted();
@@ -763,6 +767,75 @@ public class GameManagerServiceTests {
checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 60); checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 60);
} }
@Test
public void testSetBatteryModeConfigOverride_thenUpdateAllDeviceConfig() throws Exception {
mockModifyGameModeGranted();
String configStringBefore =
"mode=2,downscaleFactor=1.0,fps=90:mode=3,downscaleFactor=0.1,fps=30";
when(DeviceConfig.getProperty(anyString(), anyString()))
.thenReturn(configStringBefore);
mockInterventionsEnabledNoOptInFromXml();
GameManagerService gameManagerService = new GameManagerService(mMockContext,
mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "1.0");
checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);
checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.1");
checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30);
gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, 3, "40",
"0.2");
checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 40);
checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.2");
String configStringAfter =
"mode=2,downscaleFactor=0.9,fps=60:mode=3,downscaleFactor=0.3,fps=50";
when(DeviceConfig.getProperty(anyString(), anyString()))
.thenReturn(configStringAfter);
gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
// performance mode was not overridden thus it should be updated
checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.9");
checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 60);
// battery mode was overridden thus it should be the same as the override
checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.2");
checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 40);
}
@Test
public void testSetBatteryModeConfigOverride_thenOptInBatteryMode() throws Exception {
mockModifyGameModeGranted();
String configStringBefore =
"mode=2,downscaleFactor=1.0,fps=90:mode=3,downscaleFactor=0.1,fps=30";
when(DeviceConfig.getProperty(anyString(), anyString()))
.thenReturn(configStringBefore);
mockInterventionsDisabledNoOptInFromXml();
GameManagerService gameManagerService = new GameManagerService(mMockContext,
mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_BATTERY));
checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0);
gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, 3, "40",
"0.2");
checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0);
// override will enable the interventions
checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.2");
checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 40);
mockInterventionsDisabledAllOptInFromXml();
gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
assertTrue(checkOptedIn(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
// opt-in is still false for battery mode as override exists
assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_BATTERY));
}
/** /**
* Override device config for performance mode exists and is valid. * Override device config for performance mode exists and is valid.
*/ */
@@ -1037,7 +1110,7 @@ public class GameManagerServiceTests {
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1); gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
assertEquals(GameManager.GAME_MODE_PERFORMANCE, assertEquals(GameManager.GAME_MODE_PERFORMANCE,
gameManagerService.getGameMode(mPackageName, USER_ID_1)); gameManagerService.getGameMode(mPackageName, USER_ID_1));
mockInterventionsEnabledFromXml(); mockInterventionsEnabledNoOptInFromXml();
checkLoadingBoost(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0); checkLoadingBoost(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0);
} }
@@ -1045,7 +1118,7 @@ public class GameManagerServiceTests {
public void testGameModeConfigAllowFpsTrue() throws Exception { public void testGameModeConfigAllowFpsTrue() throws Exception {
mockDeviceConfigAll(); mockDeviceConfigAll();
mockModifyGameModeGranted(); mockModifyGameModeGranted();
mockInterventionsEnabledFromXml(); mockInterventionsEnabledNoOptInFromXml();
GameManagerService gameManagerService = new GameManagerService(mMockContext, GameManagerService gameManagerService = new GameManagerService(mMockContext,
mTestLooper.getLooper()); mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1); startUser(gameManagerService, USER_ID_1);
@@ -1060,7 +1133,7 @@ public class GameManagerServiceTests {
public void testGameModeConfigAllowFpsFalse() throws Exception { public void testGameModeConfigAllowFpsFalse() throws Exception {
mockDeviceConfigAll(); mockDeviceConfigAll();
mockModifyGameModeGranted(); mockModifyGameModeGranted();
mockInterventionsDisabledFromXml(); mockInterventionsDisabledNoOptInFromXml();
GameManagerService gameManagerService = new GameManagerService(mMockContext, GameManagerService gameManagerService = new GameManagerService(mMockContext,
mTestLooper.getLooper()); mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1); startUser(gameManagerService, USER_ID_1);