From 3815e9c2c0456bb5cf3f429ec525d692c409324d Mon Sep 17 00:00:00 2001 From: Xiang Wang Date: Tue, 9 Aug 2022 15:44:05 -0700 Subject: [PATCH] Support per user game config override * Note that only supported game modes of a package are allowed to override through shell commands, while the resolution scaling APIs support all game modes at least until we have new game mode GAME_MODE_CUSTOM Bug: b/240335717 Test: atest GameManagerServiceTests Change-Id: Id145fe3a4af7f05812aa8fb3fd5d27be6211502c --- .../server/app/GameManagerService.java | 147 +++++++++--------- .../server/app/GameManagerSettings.java | 38 ++++- .../server/app/GameManagerShellCommand.java | 3 +- .../server/app/GameManagerServiceTests.java | 31 ++-- 4 files changed, 127 insertions(+), 92 deletions(-) diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 854b818c095f3..a8d209c6ab2bd 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -130,7 +130,6 @@ public final class GameManagerService extends IGameManagerService.Stub { private final Context mContext; private final Object mLock = new Object(); private final Object mDeviceConfigLock = new Object(); - private final Object mOverrideConfigLock = new Object(); private final Handler mHandler; private final PackageManager mPackageManager; private final UserManager mUserManager; @@ -143,8 +142,6 @@ public final class GameManagerService extends IGameManagerService.Stub { private final ArrayMap mSettings = new ArrayMap<>(); @GuardedBy("mDeviceConfigLock") private final ArrayMap mConfigs = new ArrayMap<>(); - @GuardedBy("mOverrideConfigLock") - private final ArrayMap mOverrideConfigs = new ArrayMap<>(); @Nullable private final GameServiceController mGameServiceController; @@ -236,7 +233,7 @@ public final class GameManagerService extends IGameManagerService.Stub { final int userId = ActivityManager.getCurrentUser(); String[] packageList = getInstalledGamePackageNames(userId); for (final String packageName : packageList) { - pw.println(getInterventionList(packageName)); + pw.println(getInterventionList(packageName, userId)); } } @@ -759,7 +756,7 @@ public final class GameManagerService extends IGameManagerService.Stub { } /** - * Inserts a new GameModeConfiguration + * Inserts a new GameModeConfiguration. */ public void addModeConfig(GameModeConfiguration config) { if (config.isActive()) { @@ -772,6 +769,15 @@ public final class GameManagerService extends IGameManagerService.Stub { } } + /** + * Removes the GameModeConfiguration. + */ + public void removeModeConfig(int mode) { + synchronized (mModeConfigLock) { + mModeConfigs.remove(mode); + } + } + public boolean isActive() { synchronized (mModeConfigLock) { return mModeConfigs.size() > 0 || mBatteryModeOptedIn || mPerfModeOptedIn; @@ -860,7 +866,10 @@ public final class GameManagerService extends IGameManagerService.Stub { } private @GameMode int[] getAvailableGameModesUnchecked(String packageName) { - final GamePackageConfiguration config = getConfig(packageName); + final GamePackageConfiguration config; + synchronized (mDeviceConfigLock) { + config = mConfigs.get(packageName); + } if (config == null) { return new int[]{}; } @@ -1165,7 +1174,7 @@ public final class GameManagerService extends IGameManagerService.Stub { } float getResolutionScalingFactorInternal(String packageName, int gameMode, int userId) { - final GamePackageConfiguration packageConfig = getConfig(packageName); + final GamePackageConfiguration packageConfig = getConfig(packageName, userId); if (packageConfig == null) { return GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING; } @@ -1305,7 +1314,7 @@ public final class GameManagerService extends IGameManagerService.Stub { resetFps(packageName, userId); return; } - final GamePackageConfiguration packageConfig = getConfig(packageName); + final GamePackageConfiguration packageConfig = getConfig(packageName, userId); if (packageConfig == null) { Slog.v(TAG, "Package configuration not found for " + packageName); return; @@ -1318,7 +1327,7 @@ public final class GameManagerService extends IGameManagerService.Stub { } /** - * Set the override Game Mode Configuration. + * Set the Game Mode Configuration override. * Update the config if exists, create one if not. */ @VisibleForTesting @@ -1326,95 +1335,86 @@ public final class GameManagerService extends IGameManagerService.Stub { public void setGameModeConfigOverride(String packageName, @UserIdInt int userId, @GameMode int gameMode, String fpsStr, String scaling) throws SecurityException { checkPermission(Manifest.permission.MANAGE_GAME_MODE); + // Adding game mode config override of the given package name + GamePackageConfiguration configOverride; synchronized (mLock) { if (!mSettings.containsKey(userId)) { return; } - } - // Adding override game mode configuration of the given package name - GamePackageConfiguration overrideConfig; - synchronized (mOverrideConfigLock) { - // look for the existing override GamePackageConfiguration - overrideConfig = mOverrideConfigs.get(packageName); - if (overrideConfig == null) { - overrideConfig = new GamePackageConfiguration(packageName, userId); - mOverrideConfigs.put(packageName, overrideConfig); + final GameManagerSettings settings = mSettings.get(userId); + // look for the existing GamePackageConfiguration override + configOverride = settings.getConfigOverride(packageName); + if (configOverride == null) { + configOverride = new GamePackageConfiguration(packageName, userId); + settings.setConfigOverride(packageName, configOverride); } } // modify GameModeConfiguration intervention settings - GamePackageConfiguration.GameModeConfiguration overrideModeConfig = - overrideConfig.getOrAddDefaultGameModeConfiguration(gameMode); + GamePackageConfiguration.GameModeConfiguration modeConfigOverride = + configOverride.getOrAddDefaultGameModeConfiguration(gameMode); if (fpsStr != null) { - overrideModeConfig.setFpsStr(fpsStr); + modeConfigOverride.setFpsStr(fpsStr); } else { - overrideModeConfig.setFpsStr( + modeConfigOverride.setFpsStr( GamePackageConfiguration.GameModeConfiguration.DEFAULT_FPS); } if (scaling != null) { - overrideModeConfig.setScaling(Float.parseFloat(scaling)); + modeConfigOverride.setScaling(Float.parseFloat(scaling)); } Slog.i(TAG, "Package Name: " + packageName - + " FPS: " + String.valueOf(overrideModeConfig.getFps()) - + " Scaling: " + overrideModeConfig.getScaling()); + + " FPS: " + String.valueOf(modeConfigOverride.getFps()) + + " Scaling: " + modeConfigOverride.getScaling()); setGameMode(packageName, gameMode, userId); } /** * Reset the overridden gameModeConfiguration of the given mode. - * Remove the override config if game mode is not specified. + * Remove the config override if game mode is not specified. */ @VisibleForTesting @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public void resetGameModeConfigOverride(String packageName, @UserIdInt int userId, @GameMode int gameModeToReset) throws SecurityException { checkPermission(Manifest.permission.MANAGE_GAME_MODE); - synchronized (mLock) { - if (!mSettings.containsKey(userId)) { - return; - } + final GamePackageConfiguration deviceConfig; + synchronized (mDeviceConfigLock) { + deviceConfig = mConfigs.get(packageName); } // resets GamePackageConfiguration of a given packageName. // If a gameMode is specified, only reset the GameModeConfiguration of the gameMode. - if (gameModeToReset != -1) { - GamePackageConfiguration overrideConfig = null; - synchronized (mOverrideConfigLock) { - overrideConfig = mOverrideConfigs.get(packageName); - } - - GamePackageConfiguration config = null; - synchronized (mDeviceConfigLock) { - config = mConfigs.get(packageName); - } - - int[] modes = overrideConfig.getAvailableGameModes(); - - // First check if the mode to reset exists - boolean isGameModeExist = false; - for (int mode : modes) { - if (gameModeToReset == mode) { - isGameModeExist = true; - } - } - if (!isGameModeExist) { + synchronized (mLock) { + if (!mSettings.containsKey(userId)) { return; } - - // If the game mode to reset is the only mode other than standard mode, - // the override config is removed. - if (modes.length <= 2) { - synchronized (mOverrideConfigLock) { - mOverrideConfigs.remove(packageName); + final GameManagerSettings settings = mSettings.get(userId); + if (gameModeToReset != -1) { + final GamePackageConfiguration configOverride = settings.getConfigOverride( + packageName); + if (configOverride == null) { + return; + } + final int modesBitfield = configOverride.getAvailableGameModesBitfield(); + if (!bitFieldContainsModeBitmask(modesBitfield, gameModeToReset)) { + return; + } + // if the game mode to reset is the only mode other than standard mode or there + // is device config, the config override is removed. + if (Integer.bitCount(modesBitfield) <= 2 || deviceConfig == null) { + settings.removeConfigOverride(packageName); + } else { + final GamePackageConfiguration.GameModeConfiguration defaultModeConfig = + deviceConfig.getGameModeConfiguration(gameModeToReset); + // otherwise we reset the mode by copying the original config. + if (defaultModeConfig == null) { + configOverride.removeModeConfig(gameModeToReset); + } else { + configOverride.addModeConfig(defaultModeConfig); + } } } else { - // otherwise we reset the mode by copying the original config. - overrideConfig.addModeConfig(config.getGameModeConfiguration(gameModeToReset)); - } - } else { - synchronized (mOverrideConfigLock) { - // remove override config if there is one - mOverrideConfigs.remove(packageName); + settings.removeConfigOverride(packageName); } } @@ -1422,7 +1422,7 @@ public final class GameManagerService extends IGameManagerService.Stub { // If not, set the game mode to standard int gameMode = getGameMode(packageName, userId); - final GamePackageConfiguration config = getConfig(packageName); + final GamePackageConfiguration config = getConfig(packageName, userId); final int newGameMode = getNewGameMode(gameMode, config); if (gameMode != newGameMode) { setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId); @@ -1460,8 +1460,8 @@ public final class GameManagerService extends IGameManagerService.Stub { /** * Returns the string listing all the interventions currently set to a game. */ - public String getInterventionList(String packageName) { - final GamePackageConfiguration packageConfig = getConfig(packageName); + public String getInterventionList(String packageName, int userId) { + final GamePackageConfiguration packageConfig = getConfig(packageName, userId); final StringBuilder listStrSb = new StringBuilder(); if (packageConfig == null) { listStrSb.append("\n No intervention found for package ") @@ -1556,7 +1556,7 @@ public final class GameManagerService extends IGameManagerService.Stub { final StringBuilder sb = new StringBuilder(); final List installedGamesList = getInstalledGamePackageNamesByAllUsers(userId); for (final String packageName : installedGamesList) { - GamePackageConfiguration packageConfig = getConfig(packageName); + GamePackageConfiguration packageConfig = getConfig(packageName, userId); if (packageConfig == null) { continue; } @@ -1635,10 +1635,12 @@ public final class GameManagerService extends IGameManagerService.Stub { * @hide */ @VisibleForTesting - public GamePackageConfiguration getConfig(String packageName) { + public GamePackageConfiguration getConfig(String packageName, int userId) { GamePackageConfiguration packageConfig = null; - synchronized (mOverrideConfigLock) { - packageConfig = mOverrideConfigs.get(packageName); + synchronized (mLock) { + if (mSettings.containsKey(userId)) { + packageConfig = mSettings.get(userId).getConfigOverride(packageName); + } } if (packageConfig == null) { synchronized (mDeviceConfigLock) { @@ -1679,9 +1681,6 @@ public final class GameManagerService extends IGameManagerService.Stub { break; case ACTION_PACKAGE_REMOVED: if (!intent.getBooleanExtra(EXTRA_REPLACING, false)) { - synchronized (mOverrideConfigLock) { - mOverrideConfigs.remove(packageName); - } synchronized (mDeviceConfigLock) { mConfigs.remove(packageName); } diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java index 1455a6119c7ea..62ff03bdb1fb5 100644 --- a/services/core/java/com/android/server/app/GameManagerSettings.java +++ b/services/core/java/com/android/server/app/GameManagerSettings.java @@ -27,6 +27,7 @@ import android.util.Xml; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; +import com.android.server.app.GameManagerService.GamePackageConfiguration; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -62,6 +63,8 @@ public class GameManagerSettings { // PackageName -> GameMode private final ArrayMap mGameModes = new ArrayMap<>(); + // PackageName -> GamePackageConfiguration + private final ArrayMap mConfigOverrides = new ArrayMap<>(); GameManagerSettings(File dataDir) { mSystemDir = new File(dataDir, "system"); @@ -74,7 +77,7 @@ public class GameManagerSettings { } /** - * Return the game mode of a given package. + * Returns the game mode of a given package. * This operation must be synced with an external lock. */ int getGameModeLocked(String packageName) { @@ -85,7 +88,7 @@ public class GameManagerSettings { } /** - * Set the game mode of a given package. + * Sets the game mode of a given package. * This operation must be synced with an external lock. */ void setGameModeLocked(String packageName, int gameMode) { @@ -93,15 +96,40 @@ public class GameManagerSettings { } /** - * Remove the game mode of a given package. + * Removes all game settings of a given package. * This operation must be synced with an external lock. */ void removeGame(String packageName) { mGameModes.remove(packageName); + mConfigOverrides.remove(packageName); } /** - * Write all current game service settings into disk. + * Returns the game config override of a given package or null if absent. + * This operation must be synced with an external lock. + */ + GamePackageConfiguration getConfigOverride(String packageName) { + return mConfigOverrides.get(packageName); + } + + /** + * Sets the game config override of a given package. + * This operation must be synced with an external lock. + */ + void setConfigOverride(String packageName, GamePackageConfiguration configOverride) { + mConfigOverrides.put(packageName, configOverride); + } + + /** + * Removes the game mode config override of a given package. + * This operation must be synced with an external lock. + */ + void removeConfigOverride(String packageName) { + mConfigOverrides.remove(packageName); + } + + /** + * Writes all current game service settings into disk. * This operation must be synced with an external lock. */ void writePersistentDataLocked() { @@ -139,7 +167,7 @@ public class GameManagerSettings { } /** - * Read game service settings from the disk. + * Reads game service settings from the disk. * This operation must be synced with an external lock. */ boolean readPersistentDataLocked() { diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java index 487d19aa6aba5..2fb7832768cd4 100644 --- a/services/core/java/com/android/server/app/GameManagerShellCommand.java +++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java @@ -81,7 +81,8 @@ public class GameManagerShellCommand extends ShellCommand { final GameManagerService gameManagerService = (GameManagerService) ServiceManager.getService(Context.GAME_SERVICE); - final String listStr = gameManagerService.getInterventionList(packageName); + final String listStr = gameManagerService.getInterventionList(packageName, + ActivityManager.getCurrentUser()); if (listStr == null) { pw.println("No interventions found for " + 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 b53a2c69708d9..27fc0946b8270 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -584,7 +584,7 @@ public class GameManagerServiceTests { gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName); } GameManagerService.GamePackageConfiguration config = - gameManagerService.getConfig(mPackageName); + gameManagerService.getConfig(mPackageName, USER_ID_1); assertEquals(scaling, config.getGameModeConfiguration(gameMode).getScaling(), 0.01f); } @@ -594,7 +594,7 @@ public class GameManagerServiceTests { // Validate GamePackageConfiguration returns the correct value. GameManagerService.GamePackageConfiguration config = - gameManagerService.getConfig(mPackageName); + gameManagerService.getConfig(mPackageName, USER_ID_1); assertEquals(config.getGameModeConfiguration(gameMode).getUseAngle(), angleEnabled); // Validate GameManagerService.isAngleEnabled() returns the correct value. @@ -607,7 +607,7 @@ public class GameManagerServiceTests { // Validate GamePackageConfiguration returns the correct value. GameManagerService.GamePackageConfiguration config = - gameManagerService.getConfig(mPackageName); + gameManagerService.getConfig(mPackageName, USER_ID_1); assertEquals( loadingBoost, config.getGameModeConfiguration(gameMode).getLoadingBoostDuration()); @@ -623,7 +623,7 @@ public class GameManagerServiceTests { gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName); } GameManagerService.GamePackageConfiguration config = - gameManagerService.getConfig(mPackageName); + gameManagerService.getConfig(mPackageName, USER_ID_1); assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps()); } @@ -1049,7 +1049,7 @@ public class GameManagerServiceTests { mTestLooper.getLooper()); startUser(gameManagerService, USER_ID_1); GameManagerService.GamePackageConfiguration config = - gameManagerService.getConfig(mPackageName); + gameManagerService.getConfig(mPackageName, USER_ID_1); assertEquals(90, config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps()); assertEquals(30, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps()); @@ -1064,7 +1064,7 @@ public class GameManagerServiceTests { mTestLooper.getLooper()); startUser(gameManagerService, USER_ID_1); GameManagerService.GamePackageConfiguration config = - gameManagerService.getConfig(mPackageName); + gameManagerService.getConfig(mPackageName, USER_ID_1); assertEquals(0, config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps()); assertEquals(0, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps()); @@ -1092,7 +1092,7 @@ public class GameManagerServiceTests { startUser(gameManagerService, USER_ID_1); gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName); GameManagerService.GamePackageConfiguration config = - gameManagerService.getConfig(mPackageName); + gameManagerService.getConfig(mPackageName, USER_ID_1); assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE)); } @@ -1339,10 +1339,15 @@ public class GameManagerServiceTests { mTestLooper.dispatchAll(); /* Expected fileOutput (order may vary) + # user 1001: com.android.app2 0 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.5,fps=60 com.android.app1 1 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30 com.android.app0 0 2 angle=0,scaling=0.6,fps=120 3 angle=0,scaling=0.7,fps=30 + # user 1002: + com.android.app2 0 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30 + com.android.app1 1 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30 + com.android.app0 0 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30 The current game mode would only be set to non-zero if the current user have that game installed. */ @@ -1386,7 +1391,7 @@ public class GameManagerServiceTests { assertEquals(splitLine[3], "2"); assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90"); assertEquals(splitLine[5], "3"); - assertEquals(splitLine[6], "angle=0,scaling=0.5,fps=60"); + assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30"); splitLine = fileOutput.get(1).split("\\s+"); assertEquals(splitLine[0], "com.android.app1"); assertEquals(splitLine[2], "3"); @@ -1398,7 +1403,7 @@ public class GameManagerServiceTests { assertEquals(splitLine[0], "com.android.app0"); assertEquals(splitLine[2], "0"); assertEquals(splitLine[3], "2"); - assertEquals(splitLine[4], "angle=0,scaling=0.6,fps=120"); + assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90"); assertEquals(splitLine[5], "3"); assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30"); @@ -1493,12 +1498,14 @@ public class GameManagerServiceTests { @Test public void testGetResolutionScalingFactor_noUserId() { - mockModifyGameModeDenied(); + mockModifyGameModeGranted(); mockDeviceConfigAll(); GameManagerService gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper()); startUser(gameManagerService, USER_ID_2); - assertEquals(-1f, gameManagerService.getResolutionScalingFactor(mPackageName, - GameManager.GAME_MODE_BATTERY, USER_ID_1), 0.001f); + assertThrows(IllegalArgumentException.class, () -> { + gameManagerService.getResolutionScalingFactor(mPackageName, + GameManager.GAME_MODE_BATTERY, USER_ID_1); + }); } }