Persist custom game mode configuration in settings

Allow storing <package> element with only config as user may only set
custom game mode configuration while never set game mode.

Bug: b/243448953
Test: atest GameManagerServiceTests GameManagerServiceSettingsTests
Change-Id: I17f973dd8ba8b27f2a4f0a4e6324eb56b8ad6345
This commit is contained in:
Xiang Wang
2022-11-16 18:00:20 -08:00
parent 9cae44ac78
commit 60e153c3a6
4 changed files with 142 additions and 39 deletions

View File

@@ -118,6 +118,14 @@ import java.util.Set;
*/
public final class GameManagerService extends IGameManagerService.Stub {
public static final String TAG = "GameManagerService";
// event strings used for logging
private static final String EVENT_SET_GAME_MODE = "SET_GAME_MODE";
private static final String EVENT_UPDATE_CUSTOM_GAME_MODE_CONFIG =
"UPDATE_CUSTOM_GAME_MODE_CONFIG";
private static final String EVENT_RECEIVE_SHUTDOWN_INDENT = "RECEIVE_SHUTDOWN_INDENT";
private static final String EVENT_ON_USER_STARTING = "ON_USER_STARTING";
private static final String EVENT_ON_USER_SWITCHING = "ON_USER_SWITCHING";
private static final String EVENT_ON_USER_STOPPING = "ON_USER_STOPPING";
private static final boolean DEBUG = false;
@@ -1154,9 +1162,9 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
}
}
sendUserMessage(userId, WRITE_SETTINGS, "SET_GAME_MODE", WRITE_DELAY_MILLIS);
sendUserMessage(userId, WRITE_SETTINGS, EVENT_SET_GAME_MODE, WRITE_DELAY_MILLIS);
sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
"SET_GAME_MODE", 0 /*delayMillis*/);
EVENT_SET_GAME_MODE, 0 /*delayMillis*/);
int gameUid = -1;
try {
gameUid = mPackageManager.getPackageUidAsUser(packageName, userId);
@@ -1399,6 +1407,11 @@ public final class GameManagerService extends IGameManagerService.Stub {
Slog.i(TAG, "Updated custom game mode config for package: " + packageName
+ " with FPS=" + internalConfig.getFps() + ";Scaling="
+ internalConfig.getScaling() + " under user " + userId);
sendUserMessage(userId, WRITE_SETTINGS, EVENT_UPDATE_CUSTOM_GAME_MODE_CONFIG,
WRITE_DELAY_MILLIS);
sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
EVENT_UPDATE_CUSTOM_GAME_MODE_CONFIG, WRITE_DELAY_MILLIS /*delayMillis*/);
}
/**
@@ -1473,9 +1486,10 @@ public final class GameManagerService extends IGameManagerService.Stub {
for (Map.Entry<Integer, GameManagerSettings> entry : mSettings.entrySet()) {
final int userId = entry.getKey();
sendUserMessage(userId, WRITE_SETTINGS,
Intent.ACTION_SHUTDOWN, 0 /*delayMillis*/);
EVENT_RECEIVE_SHUTDOWN_INDENT, 0 /*delayMillis*/);
sendUserMessage(userId,
WRITE_GAME_MODE_INTERVENTION_LIST_FILE, Intent.ACTION_SHUTDOWN,
WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
EVENT_RECEIVE_SHUTDOWN_INDENT,
0 /*delayMillis*/);
}
}
@@ -1500,7 +1514,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
userSettings.readPersistentDataLocked();
}
}
sendUserMessage(userId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_STARTING", 0 /*delayMillis*/);
sendUserMessage(userId, POPULATE_GAME_MODE_SETTINGS, EVENT_ON_USER_STARTING,
0 /*delayMillis*/);
if (mGameServiceController != null) {
mGameServiceController.notifyUserStarted(user);
@@ -1520,7 +1535,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
if (!mSettings.containsKey(userId)) {
return;
}
sendUserMessage(userId, REMOVE_SETTINGS, "ON_USER_STOPPING", 0 /*delayMillis*/);
sendUserMessage(userId, REMOVE_SETTINGS, EVENT_ON_USER_STOPPING, 0 /*delayMillis*/);
}
if (mGameServiceController != null) {
@@ -1533,7 +1548,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
// we want to re-populate the setting when switching user as the device config may have
// changed, which will only update for the previous user, see
// DeviceConfigListener#onPropertiesChanged.
sendUserMessage(toUserId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_SWITCHING",
sendUserMessage(toUserId, POPULATE_GAME_MODE_SETTINGS, EVENT_ON_USER_SWITCHING,
0 /*delayMillis*/);
if (mGameServiceController != null) {

View File

@@ -19,6 +19,7 @@ package com.android.server.app;
import android.app.GameManager;
import android.os.FileUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.Xml;
@@ -37,7 +38,6 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
/**
* Persists all GameService related settings.
@@ -49,7 +49,11 @@ public class GameManagerSettings {
// The XML file follows the below format:
// <?xml>
// <packages>
// <package></package>
// <package name="" gameMode="">
// <gameModeConfig gameMode="" fps="" scaling="" useAngle="" loadingBoost="">
// </gameModeConfig>
// ...
// </package>
// ...
// </packages>
private static final String GAME_SERVICE_FILE_NAME = "game-manager-service.xml";
@@ -155,11 +159,14 @@ public class GameManagerSettings {
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
serializer.startTag(null, TAG_PACKAGES);
for (Map.Entry<String, Integer> entry : mGameModes.entrySet()) {
String packageName = entry.getKey();
final ArraySet<String> packageNames = new ArraySet<>(mGameModes.keySet());
packageNames.addAll(mConfigOverrides.keySet());
for (String packageName : packageNames) {
serializer.startTag(null, TAG_PACKAGE);
serializer.attribute(null, ATTR_NAME, packageName);
serializer.attributeInt(null, ATTR_GAME_MODE, entry.getValue());
if (mGameModes.containsKey(packageName)) {
serializer.attributeInt(null, ATTR_GAME_MODE, mGameModes.get(packageName));
}
writeGameModeConfigTags(serializer, mConfigOverrides.get(packageName));
serializer.endTag(null, TAG_PACKAGE);
}
@@ -224,7 +231,7 @@ public class GameManagerSettings {
// Do nothing
}
if (type != XmlPullParser.START_TAG) {
Slog.wtf(TAG, "No start tag found in package manager settings");
Slog.wtf(TAG, "No start tag found in game manager settings");
return false;
}
@@ -245,7 +252,7 @@ public class GameManagerSettings {
}
}
} catch (XmlPullParserException | java.io.IOException e) {
Slog.wtf(TAG, "Error reading package manager settings", e);
Slog.wtf(TAG, "Error reading game manager settings", e);
return false;
}
return true;
@@ -260,15 +267,12 @@ public class GameManagerSettings {
XmlUtils.skipCurrentTag(parser);
return;
}
int gameMode;
try {
gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
final int gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
mGameModes.put(name, gameMode);
} catch (XmlPullParserException e) {
Slog.wtf(TAG, "Invalid game mode in package tag: "
+ parser.getAttributeValue(null, ATTR_GAME_MODE), e);
return;
Slog.v(TAG, "No game mode selected by user for package" + name);
}
mGameModes.put(name, gameMode);
final int packageTagDepth = parser.getDepth();
int type;
final GamePackageConfiguration config = new GamePackageConfiguration(name);

View File

@@ -20,6 +20,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess
import static com.android.server.app.GameManagerService.CANCEL_GAME_LOADING_MODE;
import static com.android.server.app.GameManagerService.LOADING_BOOST_MAX_DURATION;
import static com.android.server.app.GameManagerService.SET_GAME_STATE;
import static com.android.server.app.GameManagerService.WRITE_DELAY_MILLIS;
import static com.android.server.app.GameManagerService.WRITE_GAME_MODE_INTERVENTION_LIST_FILE;
import static com.android.server.app.GameManagerService.WRITE_SETTINGS;
import static org.junit.Assert.assertArrayEquals;
@@ -1835,9 +1837,7 @@ public class GameManagerServiceTests {
public void testUpdateCustomGameModeConfiguration_permissionDenied() {
mockModifyGameModeDenied();
mockDeviceConfigAll();
GameManagerService gameManagerService =
new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
assertThrows(SecurityException.class, () -> {
gameManagerService.updateCustomGameModeConfiguration(mPackageName,
new GameModeConfiguration.Builder().setScalingFactor(0.5f).build(),
@@ -1848,9 +1848,7 @@ public class GameManagerServiceTests {
@Test
public void testUpdateCustomGameModeConfiguration_noUserId() {
mockModifyGameModeGranted();
GameManagerService gameManagerService =
new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_2);
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_2);
assertThrows(IllegalArgumentException.class, () -> {
gameManagerService.updateCustomGameModeConfiguration(mPackageName,
new GameModeConfiguration.Builder().setScalingFactor(0.5f).build(),
@@ -1873,6 +1871,48 @@ public class GameManagerServiceTests {
assertNull(pkgConfig);
}
@Test
public void testUpdateCustomGameModeConfiguration() throws InterruptedException {
mockModifyGameModeGranted();
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
gameManagerService.updateCustomGameModeConfiguration(mPackageName,
new GameModeConfiguration.Builder().setScalingFactor(0.35f).setFpsOverride(
60).build(),
USER_ID_1);
assertTrue(gameManagerService.mHandler.hasEqualMessages(WRITE_SETTINGS, USER_ID_1));
assertTrue(
gameManagerService.mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
USER_ID_1));
GameManagerService.GamePackageConfiguration pkgConfig = gameManagerService.getConfig(
mPackageName, USER_ID_1);
assertNotNull(pkgConfig);
GameManagerService.GamePackageConfiguration.GameModeConfiguration modeConfig =
pkgConfig.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM);
assertNotNull(modeConfig);
assertEquals(modeConfig.getScaling(), 0.35f, 0.01f);
assertEquals(modeConfig.getFps(), 60);
// creates a new service to check that no data has been stored
mTestLooper.dispatchAll();
gameManagerService = createServiceAndStartUser(USER_ID_1);
pkgConfig = gameManagerService.getConfig(mPackageName, USER_ID_1);
assertNull(pkgConfig);
mTestLooper.moveTimeForward(WRITE_DELAY_MILLIS + 500);
mTestLooper.dispatchAll();
// creates a new service to check that data is persisted after delay
gameManagerService = createServiceAndStartUser(USER_ID_1);
assertEquals(GameManager.GAME_MODE_STANDARD,
gameManagerService.getGameMode(mPackageName, USER_ID_1));
pkgConfig = gameManagerService.getConfig(mPackageName, USER_ID_1);
assertNotNull(pkgConfig);
modeConfig = pkgConfig.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM);
assertNotNull(modeConfig);
assertEquals(modeConfig.getScaling(), 0.35f, 0.01f);
assertEquals(modeConfig.getFps(), 60);
}
@Test
public void testWritingSettingFile_onShutdown() throws InterruptedException {
mockModifyGameModeGranted();

View File

@@ -52,6 +52,8 @@ public class GameManagerServiceSettingsTests {
private static final String PACKAGE_NAME_1 = "com.android.app1";
private static final String PACKAGE_NAME_2 = "com.android.app2";
private static final String PACKAGE_NAME_3 = "com.android.app3";
private static final String PACKAGE_NAME_4 = "com.android.app4";
private void writeFile(File file, byte[] data) {
file.mkdirs();
@@ -69,16 +71,23 @@ public class GameManagerServiceSettingsTests {
writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(),
"system/game-manager-service.xml"),
("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+ "<packages>\n"
+ " <package name=\"com.android.app1\" gameMode=\"1\">\n"
+ " </package>\n"
+ "<packages>"
+ "\n" // app1: no package config setting
+ "\n" // app2: performance mode is selected with override
+ " <package name=\"com.android.app2\" gameMode=\"2\">\n"
+ " <gameModeConfig gameMode=\"2\" scaling=\"0.99\" "
+ "useAngle=\"true\" fps=\"90\" loadingBoost=\"123\"></gameModeConfig>\n"
+ " <gameModeConfig gameMode=\"3\"></gameModeConfig>\n"
+ " </package>\n"
+ " </package>"
+ "\n" // app3: only battery mode is selected
+ " <package name=\"com.android.app3\" gameMode=\"3\">\n"
+ " </package>\n"
+ " </package>"
+ "\n" // app4: no game mode selected but custom game mode config
+ " <package name=\"com.android.app4\">\n"
+ " <gameModeConfig gameMode=\"4\" scaling=\"0.4\" "
+ "fps=\"30\"></gameModeConfig>\n"
+ " </package>"
+ "\n"
+ "</packages>\n").getBytes());
}
@@ -115,14 +124,15 @@ public class GameManagerServiceSettingsTests {
assertTrue(settings.readPersistentDataLocked());
// test game modes
assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_1));
assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2));
assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_3));
assertEquals(GameManager.GAME_MODE_STANDARD, settings.getGameModeLocked(PACKAGE_NAME_1));
assertEquals(GameManager.GAME_MODE_PERFORMANCE, settings.getGameModeLocked(PACKAGE_NAME_2));
assertEquals(GameManager.GAME_MODE_BATTERY, settings.getGameModeLocked(PACKAGE_NAME_3));
assertEquals(GameManager.GAME_MODE_STANDARD, settings.getGameModeLocked(PACKAGE_NAME_4));
// test game mode configs
assertNull(settings.getConfigOverride(PACKAGE_NAME_1));
assertNull(settings.getConfigOverride(PACKAGE_NAME_3));
final GamePackageConfiguration config = settings.getConfigOverride(PACKAGE_NAME_2);
GamePackageConfiguration config = settings.getConfigOverride(PACKAGE_NAME_2);
assertNotNull(config);
assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_STANDARD));
@@ -141,6 +151,14 @@ public class GameManagerServiceSettingsTests {
GameModeConfiguration.DEFAULT_LOADING_BOOST_DURATION);
assertEquals(batteryConfig.getFpsStr(), GameModeConfiguration.DEFAULT_FPS);
assertFalse(batteryConfig.getUseAngle());
config = settings.getConfigOverride(PACKAGE_NAME_4);
assertNotNull(config);
GameModeConfiguration customConfig = config.getGameModeConfiguration(
GameManager.GAME_MODE_CUSTOM);
assertNotNull(customConfig);
assertEquals(customConfig.getScaling(), 0.4f, 0.1f);
assertEquals(customConfig.getFps(), 30);
}
@Test
@@ -176,16 +194,20 @@ public class GameManagerServiceSettingsTests {
writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(),
"system/game-manager-service.xml"),
("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+ "<packages>\n"
+ "<packages>"
+ "\n" // missing package name
+ " <package gameMode=\"1\">\n"
+ " </package>\n"
+ " </package>"
+ "\n" // app2 with unknown sub element
+ " <package name=\"com.android.app2\" gameMode=\"2\">\n"
+ " <unknown></unknown>"
+ " <gameModeConfig gameMode=\"3\" fps=\"90\"></gameModeConfig>\n"
+ " foo bar"
+ " </package>\n"
+ " </package>"
+ "\n" // unknown package element
+ " <unknownTag></unknownTag>\n"
+ " foo bar\n"
+ " foo bar"
+ "\n" // app3 after unknown element
+ " <package name=\"com.android.app3\" gameMode=\"3\">\n"
+ " </package>\n"
+ "</packages>\n").getBytes());
@@ -214,6 +236,8 @@ public class GameManagerServiceSettingsTests {
settings.setGameModeLocked(PACKAGE_NAME_1, GameManager.GAME_MODE_BATTERY);
settings.setGameModeLocked(PACKAGE_NAME_2, GameManager.GAME_MODE_PERFORMANCE);
settings.setGameModeLocked(PACKAGE_NAME_3, GameManager.GAME_MODE_STANDARD);
// set config for app2
GamePackageConfiguration config = new GamePackageConfiguration(PACKAGE_NAME_2);
GameModeConfiguration performanceConfig = config.getOrAddDefaultGameModeConfiguration(
GameManager.GAME_MODE_PERFORMANCE);
@@ -225,18 +249,29 @@ public class GameManagerServiceSettingsTests {
GameManager.GAME_MODE_BATTERY);
batteryConfig.setScaling(0.77f);
settings.setConfigOverride(PACKAGE_NAME_2, config);
// set config for app4
config = new GamePackageConfiguration(PACKAGE_NAME_4);
GameModeConfiguration customConfig = config.getOrAddDefaultGameModeConfiguration(
GameManager.GAME_MODE_CUSTOM);
customConfig.setScaling(0.4f);
customConfig.setFpsStr("30");
settings.setConfigOverride(PACKAGE_NAME_4, config);
settings.writePersistentDataLocked();
// clear the settings in memory
settings.removeGame(PACKAGE_NAME_1);
settings.removeGame(PACKAGE_NAME_2);
settings.removeGame(PACKAGE_NAME_3);
settings.removeGame(PACKAGE_NAME_4);
// read back in and verify
assertTrue(settings.readPersistentDataLocked());
assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_1));
assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2));
assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_3));
assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_4));
config = settings.getConfigOverride(PACKAGE_NAME_1);
assertNull(config);
@@ -256,5 +291,14 @@ public class GameManagerServiceSettingsTests {
assertEquals(performanceConfig.getLoadingBoostDuration(), 321);
assertEquals(performanceConfig.getFpsStr(), "60");
assertTrue(performanceConfig.getUseAngle());
config = settings.getConfigOverride(PACKAGE_NAME_4);
assertNotNull(config);
customConfig = config.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM);
assertNotNull(customConfig);
assertEquals(customConfig.getScaling(), 0.4f, 0.1f);
assertEquals(customConfig.getFps(), 30);
assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
}
}