blob: 5189017f5bf0a61de95ae4c9816c880ea0418de7 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.app.GameManagerService.GamePackageConfiguration;
import com.android.server.app.GameManagerService.GamePackageConfiguration.GameModeConfiguration;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Persists all GameService related settings.
*
* @hide
*/
public class GameManagerSettings {
public static final String TAG = "GameManagerService_GameManagerSettings";
// The XML file follows the below format:
// <?xml>
// <packages>
// <package name="" gameMode="">
// <gameModeConfig gameMode="" fps="" scaling="" useAngle="" loadingBoost="">
// </gameModeConfig>
// ...
// </package>
// ...
// </packages>
private static final String GAME_SERVICE_FILE_NAME = "game-manager-service.xml";
private static final String TAG_PACKAGE = "package";
private static final String TAG_PACKAGES = "packages";
private static final String TAG_GAME_MODE_CONFIG = "gameModeConfig";
private static final String ATTR_NAME = "name";
private static final String ATTR_GAME_MODE = "gameMode";
private static final String ATTR_SCALING = "scaling";
private static final String ATTR_FPS = "fps";
private static final String ATTR_USE_ANGLE = "useAngle";
private static final String ATTR_LOADING_BOOST_DURATION = "loadingBoost";
private final File mSystemDir;
@VisibleForTesting
final AtomicFile mSettingsFile;
// 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");
mSystemDir.mkdirs();
FileUtils.setPermissions(mSystemDir.toString(),
FileUtils.S_IRWXU | FileUtils.S_IRWXG
| FileUtils.S_IROTH | FileUtils.S_IXOTH,
-1, -1);
mSettingsFile = new AtomicFile(new File(mSystemDir, GAME_SERVICE_FILE_NAME));
}
/**
* Returns the game mode of a given package.
* This operation must be synced with an external lock.
*/
int getGameModeLocked(String packageName) {
if (mGameModes.containsKey(packageName)) {
final int gameMode = mGameModes.get(packageName);
if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
// force replace cached UNSUPPORTED mode with STANDARD starting in U
return GameManager.GAME_MODE_STANDARD;
}
return gameMode;
}
return GameManager.GAME_MODE_STANDARD;
}
/**
* Sets the game mode of a given package.
* This operation must be synced with an external lock.
*/
void setGameModeLocked(String packageName, int gameMode) {
mGameModes.put(packageName, gameMode);
}
/**
* 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);
}
/**
* 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() {
FileOutputStream fstr = null;
try {
fstr = mSettingsFile.startWrite();
final TypedXmlSerializer serializer = Xml.resolveSerializer(fstr);
serializer.startDocument(null, true);
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
serializer.startTag(null, TAG_PACKAGES);
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);
if (mGameModes.containsKey(packageName)) {
serializer.attributeInt(null, ATTR_GAME_MODE, mGameModes.get(packageName));
}
writeGameModeConfigTags(serializer, mConfigOverrides.get(packageName));
serializer.endTag(null, TAG_PACKAGE);
}
serializer.endTag(null, TAG_PACKAGES);
serializer.endDocument();
mSettingsFile.finishWrite(fstr);
FileUtils.setPermissions(mSettingsFile.toString(),
FileUtils.S_IRUSR | FileUtils.S_IWUSR
| FileUtils.S_IRGRP | FileUtils.S_IWGRP,
-1, -1);
return;
} catch (java.io.IOException e) {
mSettingsFile.failWrite(fstr);
Slog.wtf(TAG, "Unable to write game manager service settings, "
+ "current changes will be lost at reboot", e);
}
}
private void writeGameModeConfigTags(TypedXmlSerializer serializer,
GamePackageConfiguration config) throws IOException {
if (config == null) {
return;
}
final int[] gameModes = config.getAvailableGameModes();
for (final int mode : gameModes) {
final GameModeConfiguration modeConfig = config.getGameModeConfiguration(mode);
if (modeConfig != null) {
serializer.startTag(null, TAG_GAME_MODE_CONFIG);
serializer.attributeInt(null, ATTR_GAME_MODE, mode);
serializer.attributeBoolean(null, ATTR_USE_ANGLE, modeConfig.getUseAngle());
serializer.attribute(null, ATTR_FPS, modeConfig.getFpsStr());
serializer.attributeFloat(null, ATTR_SCALING, modeConfig.getScaling());
serializer.attributeInt(null, ATTR_LOADING_BOOST_DURATION,
modeConfig.getLoadingBoostDuration());
serializer.endTag(null, TAG_GAME_MODE_CONFIG);
}
}
}
/**
* Reads game service settings from the disk.
* This operation must be synced with an external lock.
*/
boolean readPersistentDataLocked() {
mGameModes.clear();
if (!mSettingsFile.exists()) {
Slog.v(TAG, "Settings file doesn't exist, skip reading");
return false;
}
try {
final FileInputStream str = mSettingsFile.openRead();
final TypedXmlPullParser parser = Xml.resolvePullParser(str);
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
// Do nothing
}
if (type != XmlPullParser.START_TAG) {
Slog.wtf(TAG, "No start tag found in game manager settings");
return false;
}
int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
if (type == XmlPullParser.START_TAG && TAG_PACKAGE.equals(tagName)) {
readPackage(parser);
} else {
XmlUtils.skipCurrentTag(parser);
Slog.w(TAG, "Unknown element under packages tag: " + tagName + " with type: "
+ type);
}
}
} catch (XmlPullParserException | java.io.IOException e) {
Slog.wtf(TAG, "Error reading game manager settings", e);
return false;
}
return true;
}
// this must be called on tag of type START_TAG.
private void readPackage(TypedXmlPullParser parser) throws XmlPullParserException,
IOException {
final String name = parser.getAttributeValue(null, ATTR_NAME);
if (name == null) {
Slog.wtf(TAG, "No package name found in package tag");
XmlUtils.skipCurrentTag(parser);
return;
}
try {
final int gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
mGameModes.put(name, gameMode);
} catch (XmlPullParserException e) {
Slog.v(TAG, "No game mode selected by user for package" + name);
}
final int packageTagDepth = parser.getDepth();
int type;
final GamePackageConfiguration config = new GamePackageConfiguration(name);
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > packageTagDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
final String tagName = parser.getName();
if (type == XmlPullParser.START_TAG && TAG_GAME_MODE_CONFIG.equals(tagName)) {
readGameModeConfig(parser, config);
} else {
XmlUtils.skipCurrentTag(parser);
Slog.w(TAG, "Unknown element under package tag: " + tagName + " with type: "
+ type);
}
}
if (config.hasActiveGameModeConfig()) {
mConfigOverrides.put(name, config);
}
}
// this must be called on tag of type START_TAG.
private void readGameModeConfig(TypedXmlPullParser parser, GamePackageConfiguration config) {
final int gameMode;
try {
gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
} catch (XmlPullParserException e) {
Slog.wtf(TAG, "Invalid game mode value in config tag: " + parser.getAttributeValue(null,
ATTR_GAME_MODE), e);
return;
}
final GameModeConfiguration modeConfig = config.getOrAddDefaultGameModeConfiguration(
gameMode);
try {
final float scaling = parser.getAttributeFloat(null, ATTR_SCALING);
modeConfig.setScaling(scaling);
} catch (XmlPullParserException e) {
final String rawScaling = parser.getAttributeValue(null, ATTR_SCALING);
if (rawScaling != null) {
Slog.wtf(TAG, "Invalid scaling value in config tag: " + rawScaling, e);
}
}
final String fps = parser.getAttributeValue(null, ATTR_FPS);
modeConfig.setFpsStr(fps != null ? fps : GameModeConfiguration.DEFAULT_FPS);
try {
final boolean useAngle = parser.getAttributeBoolean(null, ATTR_USE_ANGLE);
modeConfig.setUseAngle(useAngle);
} catch (XmlPullParserException e) {
final String rawUseAngle = parser.getAttributeValue(null, ATTR_USE_ANGLE);
if (rawUseAngle != null) {
Slog.wtf(TAG, "Invalid useAngle value in config tag: " + rawUseAngle, e);
}
}
try {
final int loadingBoostDuration = parser.getAttributeInt(null,
ATTR_LOADING_BOOST_DURATION);
modeConfig.setLoadingBoostDuration(loadingBoostDuration);
} catch (XmlPullParserException e) {
final String rawLoadingBoost = parser.getAttributeValue(null,
ATTR_LOADING_BOOST_DURATION);
if (rawLoadingBoost != null) {
Slog.wtf(TAG, "Invalid loading boost in config tag: " + rawLoadingBoost, e);
}
}
}
}