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
This commit is contained in:
Xiang Wang
2022-08-09 15:44:05 -07:00
parent 1579836943
commit 3815e9c2c0
4 changed files with 127 additions and 92 deletions

View File

@@ -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<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
@GuardedBy("mDeviceConfigLock")
private final ArrayMap<String, GamePackageConfiguration> mConfigs = new ArrayMap<>();
@GuardedBy("mOverrideConfigLock")
private final ArrayMap<String, GamePackageConfiguration> 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<String> 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);
}

View File

@@ -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<String, Integer> mGameModes = new ArrayMap<>();
// PackageName -> GamePackageConfiguration
private final ArrayMap<String, GamePackageConfiguration> 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() {

View File

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

View File

@@ -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 <UID> 0 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.5,fps=60
com.android.app1 <UID> 1 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30
com.android.app0 <UID> 0 2 angle=0,scaling=0.6,fps=120 3 angle=0,scaling=0.7,fps=30
# user 1002:
com.android.app2 <UID> 0 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30
com.android.app1 <UID> 1 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30
com.android.app0 <UID> 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);
});
}
}