diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index dc8e44fe3ec56..62e2a9b8d40cc 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -117,6 +117,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; /** * Service to manage game related features. @@ -556,16 +557,21 @@ public final class GameManagerService extends IGameManagerService.Stub { private final String mPackageName; private final Object mModeConfigLock = new Object(); @GuardedBy("mModeConfigLock") - private final ArrayMap mModeConfigs; + private final ArrayMap 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 mBatteryModeOptedIn = false; private boolean mAllowDownscale = true; private boolean mAllowAngle = true; private boolean mAllowFpsOverride = true; + GamePackageConfiguration(String packageName) { + mPackageName = packageName; + } + GamePackageConfiguration(String packageName, int userId) { mPackageName = packageName; - mModeConfigs = new ArrayMap<>(); try { final ApplicationInfo ai = mPackageManager.getApplicationInfoAsUser(packageName, @@ -649,6 +655,13 @@ public final class GameManagerService extends IGameManagerService.Stub { 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 * 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 DEFAULT_SCALING = "1.0"; 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 LOADING_BOOST_KEY = "loadingBoost"; private final @GameMode int mGameMode; - private String mScaling; - private String mFps; + private String mScaling = DEFAULT_SCALING; + private String mFps = DEFAULT_FPS; private final boolean mUseAngle; private final int mLoadingBoostDuration; + GameModeConfiguration(int gameMode) { + mGameMode = gameMode; + mUseAngle = DEFAULT_USE_ANGLE; + mLoadingBoostDuration = DEFAULT_LOADING_BOOST_DURATION; + } + GameModeConfiguration(KeyValueListParser parser) { mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED); // 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 entry : + mModeConfigs.entrySet()) { + copy.mModeConfigs.put(entry.getKey(), entry.getValue()); + } + } + synchronized (overrideConfig.mModeConfigLock) { + for (Map.Entry entry : + overrideConfig.mModeConfigs.entrySet()) { + copy.mModeConfigs.put(entry.getKey(), entry.getValue()); + } + } + } + } + return copy; + } + public String toString() { synchronized (mModeConfigLock) { return "[Name:" + mPackageName + " Modes: " + mModeConfigs.toString() + "]"; @@ -1402,13 +1459,13 @@ public final class GameManagerService extends IGameManagerService.Stub { // look for the existing override GamePackageConfiguration overrideConfig = mOverrideConfigs.get(packageName); if (overrideConfig == null) { - overrideConfig = new GamePackageConfiguration(packageName, userId); + overrideConfig = new GamePackageConfiguration(packageName); mOverrideConfigs.put(packageName, overrideConfig); } } // modify GameModeConfiguration intervention settings GamePackageConfiguration.GameModeConfiguration overrideModeConfig = - overrideConfig.getGameModeConfiguration(gameMode); + overrideConfig.getOrAddDefaultGameModeConfiguration(gameMode); if (fpsStr != null) { overrideModeConfig.setFpsStr(fpsStr); @@ -1704,16 +1761,18 @@ public final class GameManagerService extends IGameManagerService.Stub { */ @VisibleForTesting public GamePackageConfiguration getConfig(String packageName) { - GamePackageConfiguration packageConfig = null; + GamePackageConfiguration overrideConfig = null; + GamePackageConfiguration config; + synchronized (mDeviceConfigLock) { + config = mConfigs.get(packageName); + } synchronized (mOverrideConfigLock) { - packageConfig = mOverrideConfigs.get(packageName); + overrideConfig = mOverrideConfigs.get(packageName); } - if (packageConfig == null) { - synchronized (mDeviceConfigLock) { - packageConfig = mConfigs.get(packageName); - } + if (overrideConfig == null || config == null) { + return overrideConfig == null ? config : overrideConfig; } - return packageConfig; + return config.copyAndApplyOverride(overrideConfig); } private void registerPackageReceiver() { diff --git a/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_disabled_all_opt_in.xml b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_disabled_all_opt_in.xml new file mode 100644 index 0000000000000..77fe786f812f9 --- /dev/null +++ b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_disabled_all_opt_in.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_disabled.xml b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_disabled_no_opt_in.xml similarity index 100% rename from services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_disabled.xml rename to services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_disabled_no_opt_in.xml diff --git a/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_enabled.xml b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_no_opt_in.xml similarity index 100% rename from services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_enabled.xml rename to services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_no_opt_in.xml 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 d325e47f76879..adc4b843e24c1 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.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; @@ -369,38 +370,35 @@ public class GameManagerServiceTests { .thenReturn(applicationInfo); } - private void mockInterventionsEnabledFromXml() throws Exception { - final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser( - mPackageName, PackageManager.GET_META_DATA, USER_ID_1); - 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 mockInterventionsEnabledNoOptInFromXml() throws Exception { + seedGameManagerServiceMetaDataFromFile(mPackageName, 123, + "res/xml/game_manager_service_metadata_config_interventions_enabled_no_opt_in.xml"); } - private void mockInterventionsDisabledFromXml() throws Exception { - final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser( - mPackageName, PackageManager.GET_META_DATA, USER_ID_1); - 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_disabled.xml"); + private void mockInterventionsDisabledNoOptInFromXml() throws Exception { + seedGameManagerServiceMetaDataFromFile(mPackageName, 123, + "res/xml/game_manager_service_metadata_config_interventions_disabled_no_opt_in" + + ".xml"); + } + + private void mockInterventionsDisabledAllOptInFromXml() throws Exception { + seedGameManagerServiceMetaDataFromFile(mPackageName, 123, + "res/xml/game_manager_service_metadata_config_interventions_disabled_all_opt_in" + + ".xml"); } private void seedGameManagerServiceMetaDataFromFile(String packageName, int resId, String fileName) 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 = InstrumentationRegistry.getInstrumentation().getContext().getAssets(); XmlResourceParser xmlResourceParser = @@ -628,6 +626,12 @@ public class GameManagerServiceTests { 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. */ @@ -743,7 +747,7 @@ public class GameManagerServiceTests { * Override device configs for both battery and performance modes exists and are valid. */ @Test - public void testSetDeviceOverrideConfigAll() { + public void testSetDeviceConfigOverrideAll() { mockDeviceConfigAll(); mockModifyGameModeGranted(); @@ -763,6 +767,75 @@ public class GameManagerServiceTests { 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. */ @@ -1037,7 +1110,7 @@ public class GameManagerServiceTests { gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1); assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameManagerService.getGameMode(mPackageName, USER_ID_1)); - mockInterventionsEnabledFromXml(); + mockInterventionsEnabledNoOptInFromXml(); checkLoadingBoost(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0); } @@ -1045,7 +1118,7 @@ public class GameManagerServiceTests { public void testGameModeConfigAllowFpsTrue() throws Exception { mockDeviceConfigAll(); mockModifyGameModeGranted(); - mockInterventionsEnabledFromXml(); + mockInterventionsEnabledNoOptInFromXml(); GameManagerService gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper()); startUser(gameManagerService, USER_ID_1); @@ -1060,7 +1133,7 @@ public class GameManagerServiceTests { public void testGameModeConfigAllowFpsFalse() throws Exception { mockDeviceConfigAll(); mockModifyGameModeGranted(); - mockInterventionsDisabledFromXml(); + mockInterventionsDisabledNoOptInFromXml(); GameManagerService gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper()); startUser(gameManagerService, USER_ID_1);