blob: d55e261986d094a3c505e9da169a4d700b3565f1 [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;
Eugene Suslaabdefba2018-11-09 18:06:43 -080034import com.android.internal.util.CollectionUtils;
Hai Zhang33456fb2018-12-05 17:30:35 -080035import com.android.internal.util.dump.DualDumpOutputStream;
Hai Zhangb7776682018-09-25 15:10:57 -070036import com.android.internal.util.function.pooled.PooledLambda;
37
38import libcore.io.IoUtils;
39
40import org.xmlpull.v1.XmlPullParser;
41import org.xmlpull.v1.XmlPullParserException;
42import org.xmlpull.v1.XmlSerializer;
43
44import java.io.File;
45import java.io.FileInputStream;
46import java.io.FileNotFoundException;
47import java.io.FileOutputStream;
48import java.io.IOException;
49import java.nio.charset.StandardCharsets;
Hai Zhang8e60a8f2018-11-20 11:21:09 -080050import java.util.List;
Hai Zhang458cedb2018-12-03 15:41:11 -080051import java.util.Objects;
Hai Zhangb7776682018-09-25 15:10:57 -070052
53/**
54 * Stores the state of roles for a user.
55 */
56public class RoleUserState {
57
58 private static final String LOG_TAG = RoleUserState.class.getSimpleName();
59
60 public static final int VERSION_UNDEFINED = -1;
61
62 private static final String ROLES_FILE_NAME = "roles.xml";
63
Hai Zhangb3e9b9b2018-12-05 18:32:40 -080064 private static final long WRITE_DELAY_MILLIS = 200;
Hai Zhangb3e9b9b2018-12-05 18:32:40 -080065
Hai Zhangb7776682018-09-25 15:10:57 -070066 private static final String TAG_ROLES = "roles";
67 private static final String TAG_ROLE = "role";
68 private static final String TAG_HOLDER = "holder";
69 private static final String ATTRIBUTE_VERSION = "version";
70 private static final String ATTRIBUTE_NAME = "name";
Eugene Suslaabdefba2018-11-09 18:06:43 -080071 private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash";
Hai Zhangb7776682018-09-25 15:10:57 -070072
73 @UserIdInt
74 private final int mUserId;
75
Hai Zhangcdc85c52018-12-06 13:56:55 -080076 @NonNull
Hai Zhang31d06ba2018-12-06 18:14:42 -080077 private final Callback mCallback;
78
79 @NonNull
Hai Zhangcdc85c52018-12-06 13:56:55 -080080 private final Object mLock = new Object();
81
82 @GuardedBy("mLock")
Hai Zhang458cedb2018-12-03 15:41:11 -080083 private int mVersion = VERSION_UNDEFINED;
Hai Zhangb7776682018-09-25 15:10:57 -070084
Hai Zhangcdc85c52018-12-06 13:56:55 -080085 @GuardedBy("mLock")
Hai Zhang458cedb2018-12-03 15:41:11 -080086 @Nullable
Hai Zhang33456fb2018-12-05 17:30:35 -080087 private String mPackagesHash;
Eugene Suslaabdefba2018-11-09 18:06:43 -080088
Hai Zhangb7776682018-09-25 15:10:57 -070089 /**
90 * Maps role names to its holders' package names. The values should never be null.
91 */
Hai Zhangcdc85c52018-12-06 13:56:55 -080092 @GuardedBy("mLock")
Hai Zhang458cedb2018-12-03 15:41:11 -080093 @NonNull
94 private ArrayMap<String, ArraySet<String>> mRoles = new ArrayMap<>();
Hai Zhangb7776682018-09-25 15:10:57 -070095
Hai Zhangcdc85c52018-12-06 13:56:55 -080096 @GuardedBy("mLock")
Hai Zhangbc5430852018-12-06 23:25:43 -080097 private boolean mWriteScheduled;
Hai Zhangb3e9b9b2018-12-05 18:32:40 -080098
Hai Zhangcdc85c52018-12-06 13:56:55 -080099 @GuardedBy("mLock")
Hai Zhangb7776682018-09-25 15:10:57 -0700100 private boolean mDestroyed;
101
Hai Zhang458cedb2018-12-03 15:41:11 -0800102 @NonNull
Hai Zhangb7776682018-09-25 15:10:57 -0700103 private final Handler mWriteHandler = new Handler(BackgroundThread.getHandler().getLooper());
104
Hai Zhang458cedb2018-12-03 15:41:11 -0800105 /**
Hai Zhang31d06ba2018-12-06 18:14:42 -0800106 * Create a new user state, and read its state from disk if previously persisted.
Hai Zhang458cedb2018-12-03 15:41:11 -0800107 *
Hai Zhang31d06ba2018-12-06 18:14:42 -0800108 * @param userId the user id for this user state
109 * @param callback the callback for this user state
Hai Zhang458cedb2018-12-03 15:41:11 -0800110 */
Hai Zhang31d06ba2018-12-06 18:14:42 -0800111 public RoleUserState(@UserIdInt int userId, @NonNull Callback callback) {
Hai Zhangcdc85c52018-12-06 13:56:55 -0800112 mUserId = userId;
Hai Zhang31d06ba2018-12-06 18:14:42 -0800113 mCallback = callback;
Hai Zhangcdc85c52018-12-06 13:56:55 -0800114
115 readFile();
Hai Zhangb7776682018-09-25 15:10:57 -0700116 }
117
118 /**
119 * Get the version of this user state.
120 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800121 public int getVersion() {
122 synchronized (mLock) {
123 throwIfDestroyedLocked();
Hai Zhang31d06ba2018-12-06 18:14:42 -0800124
Hai Zhangcdc85c52018-12-06 13:56:55 -0800125 return mVersion;
126 }
Hai Zhangb7776682018-09-25 15:10:57 -0700127 }
128
129 /**
130 * Set the version of this user state.
131 *
132 * @param version the version to set
133 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800134 public void setVersion(int version) {
135 synchronized (mLock) {
136 throwIfDestroyedLocked();
Hai Zhang31d06ba2018-12-06 18:14:42 -0800137
Hai Zhangcdc85c52018-12-06 13:56:55 -0800138 if (mVersion == version) {
139 return;
140 }
141 mVersion = version;
142 scheduleWriteFileLocked();
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800143 }
Hai Zhangb7776682018-09-25 15:10:57 -0700144 }
145
146 /**
Hai Zhang458cedb2018-12-03 15:41:11 -0800147 * Get the hash representing the state of packages during the last time initial grants was run.
148 *
149 * @return the hash representing the state of packages
Eugene Suslaabdefba2018-11-09 18:06:43 -0800150 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800151 @Nullable
152 public String getPackagesHash() {
153 synchronized (mLock) {
154 return mPackagesHash;
155 }
Eugene Suslaabdefba2018-11-09 18:06:43 -0800156 }
157
158 /**
Hai Zhang458cedb2018-12-03 15:41:11 -0800159 * Set the hash representing the state of packages during the last time initial grants was run.
160 *
Hai Zhang33456fb2018-12-05 17:30:35 -0800161 * @param packagesHash the hash representing the state of packages
Eugene Suslaabdefba2018-11-09 18:06:43 -0800162 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800163 public void setPackagesHash(@Nullable String packagesHash) {
164 synchronized (mLock) {
165 throwIfDestroyedLocked();
Hai Zhang31d06ba2018-12-06 18:14:42 -0800166
Hai Zhangcdc85c52018-12-06 13:56:55 -0800167 if (Objects.equals(mPackagesHash, packagesHash)) {
168 return;
169 }
170 mPackagesHash = packagesHash;
171 scheduleWriteFileLocked();
Hai Zhang458cedb2018-12-03 15:41:11 -0800172 }
Eugene Suslaabdefba2018-11-09 18:06:43 -0800173 }
174
175 /**
Hai Zhangb7776682018-09-25 15:10:57 -0700176 * Get whether the role is available.
177 *
178 * @param roleName the name of the role to get the holders for
179 *
180 * @return whether the role is available
181 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800182 public boolean isRoleAvailable(@NonNull String roleName) {
183 synchronized (mLock) {
184 throwIfDestroyedLocked();
Hai Zhang31d06ba2018-12-06 18:14:42 -0800185
Hai Zhangcdc85c52018-12-06 13:56:55 -0800186 return mRoles.containsKey(roleName);
187 }
Hai Zhangb7776682018-09-25 15:10:57 -0700188 }
189
190 /**
191 * Get the holders of a role.
192 *
193 * @param roleName the name of the role to query for
194 *
195 * @return the set of role holders. {@code null} should not be returned and indicates an issue.
196 */
Hai Zhangb7776682018-09-25 15:10:57 -0700197 @Nullable
Hai Zhangcdc85c52018-12-06 13:56:55 -0800198 public ArraySet<String> getRoleHolders(@NonNull String roleName) {
199 synchronized (mLock) {
200 throwIfDestroyedLocked();
Hai Zhang31d06ba2018-12-06 18:14:42 -0800201
Hai Zhangcdc85c52018-12-06 13:56:55 -0800202 return new ArraySet<>(mRoles.get(roleName));
203 }
Hai Zhangb7776682018-09-25 15:10:57 -0700204 }
205
206 /**
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800207 * Set the names of all available roles.
208 *
209 * @param roleNames the names of all the available roles
210 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800211 public void setRoleNames(@NonNull List<String> roleNames) {
212 synchronized (mLock) {
213 throwIfDestroyedLocked();
Hai Zhang31d06ba2018-12-06 18:14:42 -0800214
Hai Zhangcdc85c52018-12-06 13:56:55 -0800215 boolean changed = false;
Hai Zhang31d06ba2018-12-06 18:14:42 -0800216
Hai Zhangcdc85c52018-12-06 13:56:55 -0800217 for (int i = mRoles.size() - 1; i >= 0; i--) {
218 String roleName = mRoles.keyAt(i);
Hai Zhang31d06ba2018-12-06 18:14:42 -0800219
Hai Zhangcdc85c52018-12-06 13:56:55 -0800220 if (!roleNames.contains(roleName)) {
221 ArraySet<String> packageNames = mRoles.valueAt(i);
222 if (!packageNames.isEmpty()) {
Hai Zhang31d06ba2018-12-06 18:14:42 -0800223 Slog.e(LOG_TAG, "Holders of a removed role should have been cleaned up,"
224 + " role: " + roleName + ", holders: " + packageNames);
Hai Zhangcdc85c52018-12-06 13:56:55 -0800225 }
226 mRoles.removeAt(i);
227 changed = true;
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800228 }
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800229 }
Hai Zhang31d06ba2018-12-06 18:14:42 -0800230
Hai Zhangcdc85c52018-12-06 13:56:55 -0800231 int roleNamesSize = roleNames.size();
232 for (int i = 0; i < roleNamesSize; i++) {
233 String roleName = roleNames.get(i);
Hai Zhang31d06ba2018-12-06 18:14:42 -0800234
Hai Zhangcdc85c52018-12-06 13:56:55 -0800235 if (!mRoles.containsKey(roleName)) {
236 mRoles.put(roleName, new ArraySet<>());
237 Slog.i(LOG_TAG, "Added new role: " + roleName);
238 changed = true;
239 }
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800240 }
Hai Zhang31d06ba2018-12-06 18:14:42 -0800241
Hai Zhangcdc85c52018-12-06 13:56:55 -0800242 if (changed) {
243 scheduleWriteFileLocked();
244 }
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800245 }
246 }
247
248 /**
Hai Zhangb7776682018-09-25 15:10:57 -0700249 * Add a holder to a role.
250 *
251 * @param roleName the name of the role to add the holder to
252 * @param packageName the package name of the new holder
253 *
254 * @return {@code false} only if the set of role holders is null, which should not happen and
255 * indicates an issue.
256 */
257 @CheckResult
Hai Zhangcdc85c52018-12-06 13:56:55 -0800258 public boolean addRoleHolder(@NonNull String roleName, @NonNull String packageName) {
Hai Zhang31d06ba2018-12-06 18:14:42 -0800259 boolean changed;
260
Hai Zhangcdc85c52018-12-06 13:56:55 -0800261 synchronized (mLock) {
262 throwIfDestroyedLocked();
Hai Zhang31d06ba2018-12-06 18:14:42 -0800263
Hai Zhangcdc85c52018-12-06 13:56:55 -0800264 ArraySet<String> roleHolders = mRoles.get(roleName);
265 if (roleHolders == null) {
266 Slog.e(LOG_TAG, "Cannot add role holder for unknown role, role: " + roleName
267 + ", package: " + packageName);
268 return false;
269 }
Hai Zhang31d06ba2018-12-06 18:14:42 -0800270 changed = roleHolders.add(packageName);
Hai Zhangcdc85c52018-12-06 13:56:55 -0800271 if (changed) {
272 scheduleWriteFileLocked();
273 }
Hai Zhangb7776682018-09-25 15:10:57 -0700274 }
Hai Zhang31d06ba2018-12-06 18:14:42 -0800275
276 if (changed) {
277 mCallback.onRoleHoldersChanged(roleName, mUserId);
278 }
279 return true;
Hai Zhangb7776682018-09-25 15:10:57 -0700280 }
281
282 /**
283 * Remove a holder from a role.
284 *
285 * @param roleName the name of the role to remove the holder from
286 * @param packageName the package name of the holder to remove
287 *
288 * @return {@code false} only if the set of role holders is null, which should not happen and
289 * indicates an issue.
290 */
291 @CheckResult
Hai Zhangcdc85c52018-12-06 13:56:55 -0800292 public boolean removeRoleHolder(@NonNull String roleName, @NonNull String packageName) {
Hai Zhang31d06ba2018-12-06 18:14:42 -0800293 boolean changed;
294
Hai Zhangcdc85c52018-12-06 13:56:55 -0800295 synchronized (mLock) {
296 throwIfDestroyedLocked();
Hai Zhang31d06ba2018-12-06 18:14:42 -0800297
Hai Zhangcdc85c52018-12-06 13:56:55 -0800298 ArraySet<String> roleHolders = mRoles.get(roleName);
299 if (roleHolders == null) {
300 Slog.e(LOG_TAG, "Cannot remove role holder for unknown role, role: " + roleName
301 + ", package: " + packageName);
302 return false;
303 }
Hai Zhang31d06ba2018-12-06 18:14:42 -0800304
305 changed = roleHolders.remove(packageName);
Hai Zhangcdc85c52018-12-06 13:56:55 -0800306 if (changed) {
307 scheduleWriteFileLocked();
308 }
Hai Zhangb7776682018-09-25 15:10:57 -0700309 }
Hai Zhang31d06ba2018-12-06 18:14:42 -0800310
311 if (changed) {
312 mCallback.onRoleHoldersChanged(roleName, mUserId);
313 }
314 return true;
Hai Zhangb7776682018-09-25 15:10:57 -0700315 }
316
317 /**
Hai Zhangb7776682018-09-25 15:10:57 -0700318 * Schedule writing the state to file.
319 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800320 @GuardedBy("mLock")
321 private void scheduleWriteFileLocked() {
Hai Zhangb7776682018-09-25 15:10:57 -0700322 throwIfDestroyedLocked();
Hai Zhang458cedb2018-12-03 15:41:11 -0800323
Hai Zhangbc5430852018-12-06 23:25:43 -0800324 if (!mWriteScheduled) {
325 mWriteHandler.sendMessageDelayed(PooledLambda.obtainMessage(RoleUserState::writeFile,
326 this), WRITE_DELAY_MILLIS);
327 mWriteScheduled = true;
Hai Zhangb3e9b9b2018-12-05 18:32:40 -0800328 }
Hai Zhangb7776682018-09-25 15:10:57 -0700329 }
330
331 @WorkerThread
Hai Zhangcdc85c52018-12-06 13:56:55 -0800332 private void writeFile() {
333 int version;
334 String packagesHash;
335 ArrayMap<String, ArraySet<String>> roles;
336 synchronized (mLock) {
337 if (mDestroyed) {
338 return;
339 }
340
Hai Zhangbc5430852018-12-06 23:25:43 -0800341 mWriteScheduled = false;
342
Hai Zhangcdc85c52018-12-06 13:56:55 -0800343 version = mVersion;
344 packagesHash = mPackagesHash;
345 roles = snapshotRolesLocked();
346 }
347
Hai Zhangb295ac42018-11-16 16:08:18 -0800348 AtomicFile atomicFile = new AtomicFile(getFile(mUserId), "roles-" + mUserId);
Hai Zhangb7776682018-09-25 15:10:57 -0700349 FileOutputStream out = null;
350 try {
Hai Zhangb295ac42018-11-16 16:08:18 -0800351 out = atomicFile.startWrite();
Hai Zhangb7776682018-09-25 15:10:57 -0700352
353 XmlSerializer serializer = Xml.newSerializer();
354 serializer.setOutput(out, StandardCharsets.UTF_8.name());
355 serializer.setFeature(
356 "http://xmlpull.org/v1/doc/features.html#indent-output", true);
357 serializer.startDocument(null, true);
358
Hai Zhang458cedb2018-12-03 15:41:11 -0800359 serializeRoles(serializer, version, packagesHash, roles);
Hai Zhangb7776682018-09-25 15:10:57 -0700360
361 serializer.endDocument();
Hai Zhangb295ac42018-11-16 16:08:18 -0800362 atomicFile.finishWrite(out);
Hai Zhangb3e9b9b2018-12-05 18:32:40 -0800363 Slog.i(LOG_TAG, "Wrote roles.xml successfully");
Hai Zhangb295ac42018-11-16 16:08:18 -0800364 } catch (IllegalArgumentException | IllegalStateException | IOException e) {
365 Slog.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup", e);
366 if (out != null) {
367 atomicFile.failWrite(out);
368 }
Hai Zhangb7776682018-09-25 15:10:57 -0700369 } finally {
370 IoUtils.closeQuietly(out);
371 }
372 }
373
374 @WorkerThread
375 private void serializeRoles(@NonNull XmlSerializer serializer, int version,
Hai Zhang458cedb2018-12-03 15:41:11 -0800376 @Nullable String packagesHash, @NonNull ArrayMap<String, ArraySet<String>> roles)
Eugene Suslaabdefba2018-11-09 18:06:43 -0800377 throws IOException {
Hai Zhangb7776682018-09-25 15:10:57 -0700378 serializer.startTag(null, TAG_ROLES);
Hai Zhang458cedb2018-12-03 15:41:11 -0800379
Hai Zhangb7776682018-09-25 15:10:57 -0700380 serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version));
Hai Zhang458cedb2018-12-03 15:41:11 -0800381
382 if (packagesHash != null) {
383 serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash);
384 }
385
Hai Zhangb7776682018-09-25 15:10:57 -0700386 for (int i = 0, size = roles.size(); i < size; ++i) {
387 String roleName = roles.keyAt(i);
388 ArraySet<String> roleHolders = roles.valueAt(i);
Hai Zhang458cedb2018-12-03 15:41:11 -0800389
Hai Zhangb7776682018-09-25 15:10:57 -0700390 serializer.startTag(null, TAG_ROLE);
391 serializer.attribute(null, ATTRIBUTE_NAME, roleName);
392 serializeRoleHolders(serializer, roleHolders);
393 serializer.endTag(null, TAG_ROLE);
394 }
Hai Zhang458cedb2018-12-03 15:41:11 -0800395
Hai Zhangb7776682018-09-25 15:10:57 -0700396 serializer.endTag(null, TAG_ROLES);
397 }
398
399 @WorkerThread
400 private void serializeRoleHolders(@NonNull XmlSerializer serializer,
401 @NonNull ArraySet<String> roleHolders) throws IOException {
402 for (int i = 0, size = roleHolders.size(); i < size; ++i) {
403 String roleHolder = roleHolders.valueAt(i);
Hai Zhang458cedb2018-12-03 15:41:11 -0800404
Hai Zhangb7776682018-09-25 15:10:57 -0700405 serializer.startTag(null, TAG_HOLDER);
406 serializer.attribute(null, ATTRIBUTE_NAME, roleHolder);
407 serializer.endTag(null, TAG_HOLDER);
408 }
409 }
410
411 /**
412 * Read the state from file.
413 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800414 private void readFile() {
415 synchronized (mLock) {
416 File file = getFile(mUserId);
417 try (FileInputStream in = new AtomicFile(file).openRead()) {
418 XmlPullParser parser = Xml.newPullParser();
419 parser.setInput(in, null);
420 parseXmlLocked(parser);
421 Slog.i(LOG_TAG, "Read roles.xml successfully");
422 } catch (FileNotFoundException e) {
423 Slog.i(LOG_TAG, "roles.xml not found");
424 } catch (XmlPullParserException | IOException e) {
425 throw new IllegalStateException("Failed to parse roles.xml: " + file, e);
426 }
Hai Zhangb7776682018-09-25 15:10:57 -0700427 }
428 }
429
430 private void parseXmlLocked(@NonNull XmlPullParser parser) throws IOException,
431 XmlPullParserException {
Hai Zhangb7776682018-09-25 15:10:57 -0700432 int type;
Hai Zhangb295ac42018-11-16 16:08:18 -0800433 int depth;
434 int innerDepth = parser.getDepth() + 1;
Hai Zhangb7776682018-09-25 15:10:57 -0700435 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
Hai Zhangb295ac42018-11-16 16:08:18 -0800436 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
437 if (depth > innerDepth || type != XmlPullParser.START_TAG) {
Hai Zhangb7776682018-09-25 15:10:57 -0700438 continue;
439 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800440
Hai Zhangb7776682018-09-25 15:10:57 -0700441 if (parser.getName().equals(TAG_ROLES)) {
442 parseRolesLocked(parser);
443 return;
444 }
445 }
Hai Zhang458cedb2018-12-03 15:41:11 -0800446 Slog.w(LOG_TAG, "Missing <" + TAG_ROLES + "> in roles.xml");
Hai Zhangb7776682018-09-25 15:10:57 -0700447 }
448
449 private void parseRolesLocked(@NonNull XmlPullParser parser) throws IOException,
450 XmlPullParserException {
451 mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_VERSION));
Hai Zhang33456fb2018-12-05 17:30:35 -0800452 mPackagesHash = parser.getAttributeValue(null, ATTRIBUTE_PACKAGES_HASH);
Hai Zhang458cedb2018-12-03 15:41:11 -0800453 mRoles.clear();
Hai Zhangb295ac42018-11-16 16:08:18 -0800454
Hai Zhangb7776682018-09-25 15:10:57 -0700455 int type;
Hai Zhangb295ac42018-11-16 16:08:18 -0800456 int depth;
457 int innerDepth = parser.getDepth() + 1;
Hai Zhangb7776682018-09-25 15:10:57 -0700458 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
Hai Zhangb295ac42018-11-16 16:08:18 -0800459 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
460 if (depth > innerDepth || type != XmlPullParser.START_TAG) {
Hai Zhangb7776682018-09-25 15:10:57 -0700461 continue;
462 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800463
Hai Zhangb7776682018-09-25 15:10:57 -0700464 if (parser.getName().equals(TAG_ROLE)) {
465 String roleName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
466 ArraySet<String> roleHolders = parseRoleHoldersLocked(parser);
467 mRoles.put(roleName, roleHolders);
468 }
469 }
470 }
471
472 @NonNull
473 private ArraySet<String> parseRoleHoldersLocked(@NonNull XmlPullParser parser)
474 throws IOException, XmlPullParserException {
475 ArraySet<String> roleHolders = new ArraySet<>();
Hai Zhangb295ac42018-11-16 16:08:18 -0800476
Hai Zhangb7776682018-09-25 15:10:57 -0700477 int type;
Hai Zhangb295ac42018-11-16 16:08:18 -0800478 int depth;
479 int innerDepth = parser.getDepth() + 1;
Hai Zhangb7776682018-09-25 15:10:57 -0700480 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
Hai Zhangb295ac42018-11-16 16:08:18 -0800481 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
482 if (depth > innerDepth || type != XmlPullParser.START_TAG) {
Hai Zhangb7776682018-09-25 15:10:57 -0700483 continue;
484 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800485
Hai Zhangb7776682018-09-25 15:10:57 -0700486 if (parser.getName().equals(TAG_HOLDER)) {
487 String roleHolder = parser.getAttributeValue(null, ATTRIBUTE_NAME);
488 roleHolders.add(roleHolder);
489 }
490 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800491
Hai Zhangb7776682018-09-25 15:10:57 -0700492 return roleHolders;
493 }
494
495 /**
Hai Zhang33456fb2018-12-05 17:30:35 -0800496 * Dump this user state.
497 *
498 * @param dumpOutputStream the output stream to dump to
499 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800500 public void dump(@NonNull DualDumpOutputStream dumpOutputStream, @NonNull String fieldName,
501 long fieldId) {
502 int version;
503 String packagesHash;
504 ArrayMap<String, ArraySet<String>> roles;
505 synchronized (mLock) {
506 throwIfDestroyedLocked();
507
508 version = mVersion;
509 packagesHash = mPackagesHash;
510 roles = snapshotRolesLocked();
511 }
Hai Zhang33456fb2018-12-05 17:30:35 -0800512
513 long fieldToken = dumpOutputStream.start(fieldName, fieldId);
514 dumpOutputStream.write("user_id", RoleUserStateProto.USER_ID, mUserId);
Hai Zhangcdc85c52018-12-06 13:56:55 -0800515 dumpOutputStream.write("version", RoleUserStateProto.VERSION, version);
516 dumpOutputStream.write("packages_hash", RoleUserStateProto.PACKAGES_HASH, packagesHash);
Hai Zhang33456fb2018-12-05 17:30:35 -0800517
Hai Zhangcdc85c52018-12-06 13:56:55 -0800518 int rolesSize = roles.size();
Hai Zhang33456fb2018-12-05 17:30:35 -0800519 for (int rolesIndex = 0; rolesIndex < rolesSize; rolesIndex++) {
Hai Zhangcdc85c52018-12-06 13:56:55 -0800520 String roleName = roles.keyAt(rolesIndex);
521 ArraySet<String> roleHolders = roles.valueAt(rolesIndex);
Hai Zhang33456fb2018-12-05 17:30:35 -0800522
523 long rolesToken = dumpOutputStream.start("roles", RoleUserStateProto.ROLES);
524 dumpOutputStream.write("name", RoleProto.NAME, roleName);
525
526 int roleHoldersSize = roleHolders.size();
527 for (int roleHoldersIndex = 0; roleHoldersIndex < roleHoldersSize; roleHoldersIndex++) {
528 String roleHolder = roleHolders.valueAt(roleHoldersIndex);
529
530 dumpOutputStream.write("holders", RoleProto.HOLDERS, roleHolder);
531 }
532
533 dumpOutputStream.end(rolesToken);
534 }
535
536 dumpOutputStream.end(fieldToken);
537 }
538
Hai Zhangcdc85c52018-12-06 13:56:55 -0800539 @GuardedBy("mLock")
540 private ArrayMap<String, ArraySet<String>> snapshotRolesLocked() {
541 ArrayMap<String, ArraySet<String>> roles = new ArrayMap<>();
542 for (int i = 0, size = CollectionUtils.size(mRoles); i < size; ++i) {
543 String roleName = mRoles.keyAt(i);
544 ArraySet<String> roleHolders = mRoles.valueAt(i);
545
546 roleHolders = new ArraySet<>(roleHolders);
547 roles.put(roleName, roleHolders);
548 }
549 return roles;
550 }
551
Hai Zhang33456fb2018-12-05 17:30:35 -0800552 /**
Hai Zhang31d06ba2018-12-06 18:14:42 -0800553 * Destroy this user state and delete the corresponding file. Any pending writes to the file
554 * will be cancelled, and any future interaction with this state will throw an exception.
Hai Zhangb7776682018-09-25 15:10:57 -0700555 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800556 public void destroy() {
557 synchronized (mLock) {
558 throwIfDestroyedLocked();
559 mWriteHandler.removeCallbacksAndMessages(null);
560 getFile(mUserId).delete();
561 mDestroyed = true;
562 }
Hai Zhangb7776682018-09-25 15:10:57 -0700563 }
564
Hai Zhangcdc85c52018-12-06 13:56:55 -0800565 @GuardedBy("mLock")
Hai Zhangb7776682018-09-25 15:10:57 -0700566 private void throwIfDestroyedLocked() {
567 if (mDestroyed) {
568 throw new IllegalStateException("This RoleUserState has already been destroyed");
569 }
570 }
571
572 private static @NonNull File getFile(@UserIdInt int userId) {
573 return new File(Environment.getUserSystemDirectory(userId), ROLES_FILE_NAME);
574 }
Hai Zhang31d06ba2018-12-06 18:14:42 -0800575
576 /**
577 * Callback for a user state.
578 */
579 public interface Callback {
580
581 /**
582 * Called when the holders of roles are changed.
583 *
584 * @param roleName the name of the role whose holders are changed
585 * @param userId the user id for this role holder change
586 */
587 void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId);
588 }
Hai Zhangb7776682018-09-25 15:10:57 -0700589}