1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.app;
18 
19 import android.app.GameManager;
20 import android.os.FileUtils;
21 import android.util.ArrayMap;
22 import android.util.AtomicFile;
23 import android.util.Slog;
24 import android.util.TypedXmlPullParser;
25 import android.util.TypedXmlSerializer;
26 import android.util.Xml;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.util.XmlUtils;
30 
31 import org.xmlpull.v1.XmlPullParser;
32 import org.xmlpull.v1.XmlPullParserException;
33 
34 import java.io.File;
35 import java.io.FileInputStream;
36 import java.io.FileOutputStream;
37 import java.io.IOException;
38 import java.util.Map;
39 
40 /**
41  * Persists all GameService related settings.
42  * @hide
43  */
44 public class GameManagerSettings {
45 
46     // The XML file follows the below format:
47     // <?xml>
48     // <packages>
49     //     <package></package>
50     //     ...
51     // </packages>
52     private static final String GAME_SERVICE_FILE_NAME = "game-manager-service.xml";
53 
54     private static final String TAG_PACKAGE = "package";
55     private static final String TAG_PACKAGES = "packages";
56     private static final String ATTR_NAME = "name";
57     private static final String ATTR_GAME_MODE = "gameMode";
58 
59     private final File mSystemDir;
60     @VisibleForTesting
61     final AtomicFile mSettingsFile;
62 
63     // PackageName -> GameMode
64     private final ArrayMap<String, Integer> mGameModes = new ArrayMap<>();
65 
GameManagerSettings(File dataDir)66     GameManagerSettings(File dataDir) {
67         mSystemDir = new File(dataDir, "system");
68         mSystemDir.mkdirs();
69         FileUtils.setPermissions(mSystemDir.toString(),
70                 FileUtils.S_IRWXU | FileUtils.S_IRWXG
71                         | FileUtils.S_IROTH | FileUtils.S_IXOTH,
72                 -1, -1);
73         mSettingsFile = new AtomicFile(new File(mSystemDir, GAME_SERVICE_FILE_NAME));
74     }
75 
76     /**
77      * Return the game mode of a given package.
78      * This operation must be synced with an external lock.
79      */
getGameModeLocked(String packageName)80     int getGameModeLocked(String packageName) {
81         if (mGameModes.containsKey(packageName)) {
82             return mGameModes.get(packageName);
83         }
84         return GameManager.GAME_MODE_UNSUPPORTED;
85     }
86 
87     /**
88      * Set the game mode of a given package.
89      * This operation must be synced with an external lock.
90      */
setGameModeLocked(String packageName, int gameMode)91     void setGameModeLocked(String packageName, int gameMode) {
92         mGameModes.put(packageName, gameMode);
93     }
94 
95     /**
96      * Write all current game service settings into disk.
97      * This operation must be synced with an external lock.
98      */
writePersistentDataLocked()99     void writePersistentDataLocked() {
100         FileOutputStream fstr = null;
101         try {
102             fstr = mSettingsFile.startWrite();
103 
104             final TypedXmlSerializer serializer = Xml.resolveSerializer(fstr);
105             serializer.startDocument(null, true);
106             serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
107 
108             serializer.startTag(null, TAG_PACKAGES);
109             for (Map.Entry<String, Integer> entry : mGameModes.entrySet()) {
110                 serializer.startTag(null, TAG_PACKAGE);
111                 serializer.attribute(null, ATTR_NAME, entry.getKey());
112                 serializer.attributeInt(null, ATTR_GAME_MODE, entry.getValue());
113                 serializer.endTag(null, TAG_PACKAGE);
114             }
115             serializer.endTag(null, TAG_PACKAGES);
116 
117             serializer.endDocument();
118 
119             mSettingsFile.finishWrite(fstr);
120 
121             FileUtils.setPermissions(mSettingsFile.toString(),
122                     FileUtils.S_IRUSR | FileUtils.S_IWUSR
123                             | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
124                     -1, -1);
125             return;
126         } catch (java.io.IOException e) {
127             mSettingsFile.failWrite(fstr);
128             Slog.wtf(GameManagerService.TAG, "Unable to write game manager service settings, "
129                     + "current changes will be lost at reboot", e);
130         }
131     }
132 
133     /**
134      * Read game service settings from the disk.
135      * This operation must be synced with an external lock.
136      */
readPersistentDataLocked()137     boolean readPersistentDataLocked() {
138         mGameModes.clear();
139 
140         if (!mSettingsFile.exists()) {
141             Slog.v(GameManagerService.TAG, "Settings file doesn't exists, skip reading");
142             return false;
143         }
144 
145         try {
146             final FileInputStream str = mSettingsFile.openRead();
147 
148             final TypedXmlPullParser parser = Xml.resolvePullParser(str);
149             int type;
150             while ((type = parser.next()) != XmlPullParser.START_TAG
151                     && type != XmlPullParser.END_DOCUMENT) {
152                 // Do nothing
153             }
154             if (type != XmlPullParser.START_TAG) {
155                 Slog.wtf(GameManagerService.TAG,
156                         "No start tag found in package manager settings");
157                 return false;
158             }
159 
160             int outerDepth = parser.getDepth();
161             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
162                     && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
163                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
164                     continue;
165                 }
166 
167                 String tagName = parser.getName();
168                 if (tagName.equals(TAG_PACKAGE)) {
169                     readPackage(parser);
170                 } else {
171                     Slog.w(GameManagerService.TAG, "Unknown element: " + parser.getName());
172                     XmlUtils.skipCurrentTag(parser);
173                 }
174             }
175         } catch (XmlPullParserException | java.io.IOException e) {
176             Slog.wtf(GameManagerService.TAG, "Error reading package manager settings", e);
177             return false;
178         }
179 
180         return true;
181     }
182 
readPackage(TypedXmlPullParser parser)183     private void readPackage(TypedXmlPullParser parser) throws XmlPullParserException,
184             IOException {
185         String name = null;
186         int gameMode = GameManager.GAME_MODE_UNSUPPORTED;
187         try {
188             name = parser.getAttributeValue(null, ATTR_NAME);
189             gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
190         } catch (XmlPullParserException e) {
191             Slog.wtf(GameManagerService.TAG, "Error reading game mode", e);
192         }
193         if (name != null) {
194             mGameModes.put(name, gameMode);
195         } else {
196             XmlUtils.skipCurrentTag(parser);
197         }
198     }
199 }
200