blob: 68e737e8656af8b6abc8a96a4dc9510e4cb099ab [file] [log] [blame]
Hai Zhangb7776682018-09-25 15:10:57 -07001/*
2 * Copyright (C) 2018 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
17package com.android.server.role;
18
19import android.annotation.CheckResult;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.annotation.UserIdInt;
23import android.annotation.WorkerThread;
24import android.os.Environment;
25import android.os.Handler;
26import android.util.ArrayMap;
27import android.util.ArraySet;
28import android.util.AtomicFile;
29import android.util.Slog;
30import android.util.Xml;
31
32import com.android.internal.annotations.GuardedBy;
33import com.android.internal.os.BackgroundThread;
34import com.android.internal.util.function.pooled.PooledLambda;
35
36import libcore.io.IoUtils;
37
38import org.xmlpull.v1.XmlPullParser;
39import org.xmlpull.v1.XmlPullParserException;
40import org.xmlpull.v1.XmlSerializer;
41
42import java.io.File;
43import java.io.FileInputStream;
44import java.io.FileNotFoundException;
45import java.io.FileOutputStream;
46import java.io.IOException;
47import java.nio.charset.StandardCharsets;
48
49/**
50 * Stores the state of roles for a user.
51 */
52public class RoleUserState {
53
54 private static final String LOG_TAG = RoleUserState.class.getSimpleName();
55
56 public static final int VERSION_UNDEFINED = -1;
57
58 private static final String ROLES_FILE_NAME = "roles.xml";
59
60 private static final String TAG_ROLES = "roles";
61 private static final String TAG_ROLE = "role";
62 private static final String TAG_HOLDER = "holder";
63 private static final String ATTRIBUTE_VERSION = "version";
64 private static final String ATTRIBUTE_NAME = "name";
65
66 @UserIdInt
67 private final int mUserId;
68
69 @GuardedBy("RoleManagerService.mLock")
Hai Zhangb295ac42018-11-16 16:08:18 -080070 private int mVersion;
Hai Zhangb7776682018-09-25 15:10:57 -070071
72 /**
73 * Maps role names to its holders' package names. The values should never be null.
74 */
75 @GuardedBy("RoleManagerService.mLock")
Hai Zhangb295ac42018-11-16 16:08:18 -080076 private ArrayMap<String, ArraySet<String>> mRoles;
Hai Zhangb7776682018-09-25 15:10:57 -070077
78 @GuardedBy("RoleManagerService.mLock")
79 private boolean mDestroyed;
80
81 private final Handler mWriteHandler = new Handler(BackgroundThread.getHandler().getLooper());
82
83 public RoleUserState(@UserIdInt int userId) {
84 mUserId = userId;
85 }
86
87 /**
88 * Get the version of this user state.
89 */
90 @GuardedBy("RoleManagerService.mLock")
91 public int getVersionLocked() {
92 throwIfDestroyedLocked();
93 return mVersion;
94 }
95
96 /**
97 * Set the version of this user state.
98 *
99 * @param version the version to set
100 */
101 @GuardedBy("RoleManagerService.mLock")
102 public void setVersionLocked(int version) {
103 throwIfDestroyedLocked();
104 mVersion = version;
105 }
106
107 /**
108 * Get whether the role is available.
109 *
110 * @param roleName the name of the role to get the holders for
111 *
112 * @return whether the role is available
113 */
114 @GuardedBy("RoleManagerService.mLock")
115 public boolean isRoleAvailableLocked(@NonNull String roleName) {
116 throwIfDestroyedLocked();
117 return mRoles.containsKey(roleName);
118 }
119
120 /**
121 * Get the holders of a role.
122 *
123 * @param roleName the name of the role to query for
124 *
125 * @return the set of role holders. {@code null} should not be returned and indicates an issue.
126 */
127 @GuardedBy("RoleManagerService.mLock")
128 @Nullable
129 public ArraySet<String> getRoleHoldersLocked(@NonNull String roleName) {
130 throwIfDestroyedLocked();
131 return mRoles.get(roleName);
132 }
133
134 /**
135 * Add a holder to a role.
136 *
137 * @param roleName the name of the role to add the holder to
138 * @param packageName the package name of the new holder
139 *
140 * @return {@code false} only if the set of role holders is null, which should not happen and
141 * indicates an issue.
142 */
143 @CheckResult
144 @GuardedBy("RoleManagerService.mLock")
145 public boolean addRoleHolderLocked(@NonNull String roleName, @NonNull String packageName) {
146 throwIfDestroyedLocked();
147 ArraySet<String> roleHolders = mRoles.get(roleName);
148 if (roleHolders == null) {
Hai Zhangb295ac42018-11-16 16:08:18 -0800149 Slog.e(LOG_TAG, "Cannot add role holder for unknown role, role: " + roleName
150 + ", package: " + packageName);
Hai Zhangb7776682018-09-25 15:10:57 -0700151 return false;
152 }
153 roleHolders.add(packageName);
154 return true;
155 }
156
157 /**
158 * Remove a holder from a role.
159 *
160 * @param roleName the name of the role to remove the holder from
161 * @param packageName the package name of the holder to remove
162 *
163 * @return {@code false} only if the set of role holders is null, which should not happen and
164 * indicates an issue.
165 */
166 @CheckResult
167 @GuardedBy("RoleManagerService.mLock")
168 public boolean removeRoleHolderLocked(@NonNull String roleName, @NonNull String packageName) {
169 throwIfDestroyedLocked();
170 ArraySet<String> roleHolders = mRoles.get(roleName);
171 if (roleHolders == null) {
Hai Zhangb295ac42018-11-16 16:08:18 -0800172 Slog.e(LOG_TAG, "Cannot remove role holder for unknown role, role: " + roleName
173 + ", package: " + packageName);
Hai Zhangb7776682018-09-25 15:10:57 -0700174 return false;
175 }
176 roleHolders.remove(packageName);
177 return true;
178 }
179
180 /**
Hai Zhangb7776682018-09-25 15:10:57 -0700181 * Schedule writing the state to file.
182 */
183 @GuardedBy("RoleManagerService.mLock")
184 public void writeAsyncLocked() {
185 throwIfDestroyedLocked();
186 int version = mVersion;
187 ArrayMap<String, ArraySet<String>> roles = new ArrayMap<>();
188 for (int i = 0, size = mRoles.size(); i < size; ++i) {
189 String roleName = mRoles.keyAt(i);
190 ArraySet<String> roleHolders = mRoles.valueAt(i);
191 roleHolders = new ArraySet<>(roleHolders);
192 roles.put(roleName, roleHolders);
193 }
194 mWriteHandler.removeCallbacksAndMessages(null);
Eugene Susla6d4922722018-11-08 16:25:28 -0800195 mWriteHandler.sendMessage(PooledLambda.obtainMessage(
196 RoleUserState::writeSync, this, version, roles));
Hai Zhangb7776682018-09-25 15:10:57 -0700197 }
198
199 @WorkerThread
200 private void writeSync(int version, @NonNull ArrayMap<String, ArraySet<String>> roles) {
Hai Zhangb295ac42018-11-16 16:08:18 -0800201 AtomicFile atomicFile = new AtomicFile(getFile(mUserId), "roles-" + mUserId);
Hai Zhangb7776682018-09-25 15:10:57 -0700202 FileOutputStream out = null;
203 try {
Hai Zhangb295ac42018-11-16 16:08:18 -0800204 out = atomicFile.startWrite();
Hai Zhangb7776682018-09-25 15:10:57 -0700205
206 XmlSerializer serializer = Xml.newSerializer();
207 serializer.setOutput(out, StandardCharsets.UTF_8.name());
208 serializer.setFeature(
209 "http://xmlpull.org/v1/doc/features.html#indent-output", true);
210 serializer.startDocument(null, true);
211
212 serializeRoles(serializer, version, roles);
213
214 serializer.endDocument();
Hai Zhangb295ac42018-11-16 16:08:18 -0800215 atomicFile.finishWrite(out);
216 } catch (IllegalArgumentException | IllegalStateException | IOException e) {
217 Slog.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup", e);
218 if (out != null) {
219 atomicFile.failWrite(out);
220 }
Hai Zhangb7776682018-09-25 15:10:57 -0700221 } finally {
222 IoUtils.closeQuietly(out);
223 }
224 }
225
226 @WorkerThread
227 private void serializeRoles(@NonNull XmlSerializer serializer, int version,
228 @NonNull ArrayMap<String, ArraySet<String>> roles) throws IOException {
229 serializer.startTag(null, TAG_ROLES);
230 serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version));
231 for (int i = 0, size = roles.size(); i < size; ++i) {
232 String roleName = roles.keyAt(i);
233 ArraySet<String> roleHolders = roles.valueAt(i);
234 serializer.startTag(null, TAG_ROLE);
235 serializer.attribute(null, ATTRIBUTE_NAME, roleName);
236 serializeRoleHolders(serializer, roleHolders);
237 serializer.endTag(null, TAG_ROLE);
238 }
239 serializer.endTag(null, TAG_ROLES);
240 }
241
242 @WorkerThread
243 private void serializeRoleHolders(@NonNull XmlSerializer serializer,
244 @NonNull ArraySet<String> roleHolders) throws IOException {
245 for (int i = 0, size = roleHolders.size(); i < size; ++i) {
246 String roleHolder = roleHolders.valueAt(i);
247 serializer.startTag(null, TAG_HOLDER);
248 serializer.attribute(null, ATTRIBUTE_NAME, roleHolder);
249 serializer.endTag(null, TAG_HOLDER);
250 }
251 }
252
253 /**
254 * Read the state from file.
255 */
256 @GuardedBy("RoleManagerService.mLock")
257 public void readSyncLocked() {
258 if (mRoles != null) {
Hai Zhangb295ac42018-11-16 16:08:18 -0800259 throw new IllegalStateException("This RoleUserState has already read the roles.xml");
Hai Zhangb7776682018-09-25 15:10:57 -0700260 }
261
Hai Zhangb295ac42018-11-16 16:08:18 -0800262 File file = getFile(mUserId);
263 try (FileInputStream in = new AtomicFile(file).openRead()) {
Hai Zhangb7776682018-09-25 15:10:57 -0700264 XmlPullParser parser = Xml.newPullParser();
265 parser.setInput(in, null);
266 parseXmlLocked(parser);
Hai Zhangb295ac42018-11-16 16:08:18 -0800267 } catch (FileNotFoundException e) {
268 Slog.i(LOG_TAG, "roles.xml not found");
269 mRoles = new ArrayMap<>();
270 mVersion = VERSION_UNDEFINED;
Hai Zhangb7776682018-09-25 15:10:57 -0700271 } catch (XmlPullParserException | IOException e) {
Hai Zhangb295ac42018-11-16 16:08:18 -0800272 throw new IllegalStateException("Failed to parse roles.xml: " + file, e);
Hai Zhangb7776682018-09-25 15:10:57 -0700273 }
274 }
275
276 private void parseXmlLocked(@NonNull XmlPullParser parser) throws IOException,
277 XmlPullParserException {
Hai Zhangb7776682018-09-25 15:10:57 -0700278 int type;
Hai Zhangb295ac42018-11-16 16:08:18 -0800279 int depth;
280 int innerDepth = parser.getDepth() + 1;
Hai Zhangb7776682018-09-25 15:10:57 -0700281 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
Hai Zhangb295ac42018-11-16 16:08:18 -0800282 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
283 if (depth > innerDepth || type != XmlPullParser.START_TAG) {
Hai Zhangb7776682018-09-25 15:10:57 -0700284 continue;
285 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800286
Hai Zhangb7776682018-09-25 15:10:57 -0700287 if (parser.getName().equals(TAG_ROLES)) {
288 parseRolesLocked(parser);
289 return;
290 }
291 }
292 }
293
294 private void parseRolesLocked(@NonNull XmlPullParser parser) throws IOException,
295 XmlPullParserException {
296 mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_VERSION));
297 mRoles = new ArrayMap<>();
Hai Zhangb295ac42018-11-16 16:08:18 -0800298
Hai Zhangb7776682018-09-25 15:10:57 -0700299 int type;
Hai Zhangb295ac42018-11-16 16:08:18 -0800300 int depth;
301 int innerDepth = parser.getDepth() + 1;
Hai Zhangb7776682018-09-25 15:10:57 -0700302 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
Hai Zhangb295ac42018-11-16 16:08:18 -0800303 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
304 if (depth > innerDepth || type != XmlPullParser.START_TAG) {
Hai Zhangb7776682018-09-25 15:10:57 -0700305 continue;
306 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800307
Hai Zhangb7776682018-09-25 15:10:57 -0700308 if (parser.getName().equals(TAG_ROLE)) {
309 String roleName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
310 ArraySet<String> roleHolders = parseRoleHoldersLocked(parser);
311 mRoles.put(roleName, roleHolders);
312 }
313 }
314 }
315
316 @NonNull
317 private ArraySet<String> parseRoleHoldersLocked(@NonNull XmlPullParser parser)
318 throws IOException, XmlPullParserException {
319 ArraySet<String> roleHolders = new ArraySet<>();
Hai Zhangb295ac42018-11-16 16:08:18 -0800320
Hai Zhangb7776682018-09-25 15:10:57 -0700321 int type;
Hai Zhangb295ac42018-11-16 16:08:18 -0800322 int depth;
323 int innerDepth = parser.getDepth() + 1;
Hai Zhangb7776682018-09-25 15:10:57 -0700324 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
Hai Zhangb295ac42018-11-16 16:08:18 -0800325 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
326 if (depth > innerDepth || type != XmlPullParser.START_TAG) {
Hai Zhangb7776682018-09-25 15:10:57 -0700327 continue;
328 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800329
Hai Zhangb7776682018-09-25 15:10:57 -0700330 if (parser.getName().equals(TAG_HOLDER)) {
331 String roleHolder = parser.getAttributeValue(null, ATTRIBUTE_NAME);
332 roleHolders.add(roleHolder);
333 }
334 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800335
Hai Zhangb7776682018-09-25 15:10:57 -0700336 return roleHolders;
337 }
338
339 /**
340 * Destroy this state and delete the corresponding file. Any pending writes to the file will be
341 * cancelled and any future interaction with this state will throw an exception.
342 */
343 @GuardedBy("RoleManagerService.mLock")
344 public void destroySyncLocked() {
345 throwIfDestroyedLocked();
346 mWriteHandler.removeCallbacksAndMessages(null);
347 getFile(mUserId).delete();
348 mDestroyed = true;
349 }
350
351 @GuardedBy("RoleManagerService.mLock")
352 private void throwIfDestroyedLocked() {
353 if (mDestroyed) {
354 throw new IllegalStateException("This RoleUserState has already been destroyed");
355 }
356 }
357
358 private static @NonNull File getFile(@UserIdInt int userId) {
359 return new File(Environment.getUserSystemDirectory(userId), ROLES_FILE_NAME);
360 }
361}