blob: 6375b4851283305509d0e0a7324f9337b196356c [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;
Eugene Suslaa2a80322018-12-12 17:09:38 -080050import java.util.ArrayList;
Hai Zhang8e60a8f2018-11-20 11:21:09 -080051import java.util.List;
Hai Zhang458cedb2018-12-03 15:41:11 -080052import java.util.Objects;
Hai Zhangb7776682018-09-25 15:10:57 -070053
54/**
55 * Stores the state of roles for a user.
56 */
57public class RoleUserState {
58
59 private static final String LOG_TAG = RoleUserState.class.getSimpleName();
60
61 public static final int VERSION_UNDEFINED = -1;
62
63 private static final String ROLES_FILE_NAME = "roles.xml";
64
Hai Zhangb3e9b9b2018-12-05 18:32:40 -080065 private static final long WRITE_DELAY_MILLIS = 200;
Hai Zhangb3e9b9b2018-12-05 18:32:40 -080066
Hai Zhangb7776682018-09-25 15:10:57 -070067 private static final String TAG_ROLES = "roles";
68 private static final String TAG_ROLE = "role";
69 private static final String TAG_HOLDER = "holder";
70 private static final String ATTRIBUTE_VERSION = "version";
71 private static final String ATTRIBUTE_NAME = "name";
Eugene Suslaabdefba2018-11-09 18:06:43 -080072 private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash";
Hai Zhangb7776682018-09-25 15:10:57 -070073
74 @UserIdInt
75 private final int mUserId;
76
Hai Zhangcdc85c52018-12-06 13:56:55 -080077 @NonNull
Hai Zhang31d06ba2018-12-06 18:14:42 -080078 private final Callback mCallback;
79
80 @NonNull
Hai Zhangcdc85c52018-12-06 13:56:55 -080081 private final Object mLock = new Object();
82
83 @GuardedBy("mLock")
Hai Zhang458cedb2018-12-03 15:41:11 -080084 private int mVersion = VERSION_UNDEFINED;
Hai Zhangb7776682018-09-25 15:10:57 -070085
Hai Zhangcdc85c52018-12-06 13:56:55 -080086 @GuardedBy("mLock")
Hai Zhang458cedb2018-12-03 15:41:11 -080087 @Nullable
Hai Zhang33456fb2018-12-05 17:30:35 -080088 private String mPackagesHash;
Eugene Suslaabdefba2018-11-09 18:06:43 -080089
Hai Zhangb7776682018-09-25 15:10:57 -070090 /**
91 * Maps role names to its holders' package names. The values should never be null.
92 */
Hai Zhangcdc85c52018-12-06 13:56:55 -080093 @GuardedBy("mLock")
Hai Zhang458cedb2018-12-03 15:41:11 -080094 @NonNull
95 private ArrayMap<String, ArraySet<String>> mRoles = new ArrayMap<>();
Hai Zhangb7776682018-09-25 15:10:57 -070096
Hai Zhangcdc85c52018-12-06 13:56:55 -080097 @GuardedBy("mLock")
Hai Zhangbc5430852018-12-06 23:25:43 -080098 private boolean mWriteScheduled;
Hai Zhangb3e9b9b2018-12-05 18:32:40 -080099
Hai Zhangcdc85c52018-12-06 13:56:55 -0800100 @GuardedBy("mLock")
Hai Zhangb7776682018-09-25 15:10:57 -0700101 private boolean mDestroyed;
102
Hai Zhang458cedb2018-12-03 15:41:11 -0800103 @NonNull
Hai Zhangb7776682018-09-25 15:10:57 -0700104 private final Handler mWriteHandler = new Handler(BackgroundThread.getHandler().getLooper());
105
Hai Zhang458cedb2018-12-03 15:41:11 -0800106 /**
Hai Zhang31d06ba2018-12-06 18:14:42 -0800107 * Create a new user state, and read its state from disk if previously persisted.
Hai Zhang458cedb2018-12-03 15:41:11 -0800108 *
Hai Zhang31d06ba2018-12-06 18:14:42 -0800109 * @param userId the user id for this user state
110 * @param callback the callback for this user state
Hai Zhang458cedb2018-12-03 15:41:11 -0800111 */
Hai Zhang31d06ba2018-12-06 18:14:42 -0800112 public RoleUserState(@UserIdInt int userId, @NonNull Callback callback) {
Hai Zhangcdc85c52018-12-06 13:56:55 -0800113 mUserId = userId;
Hai Zhang31d06ba2018-12-06 18:14:42 -0800114 mCallback = callback;
Hai Zhangcdc85c52018-12-06 13:56:55 -0800115
116 readFile();
Hai Zhangb7776682018-09-25 15:10:57 -0700117 }
118
119 /**
120 * Get the version of this user state.
121 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800122 public int getVersion() {
123 synchronized (mLock) {
124 throwIfDestroyedLocked();
Hai Zhang31d06ba2018-12-06 18:14:42 -0800125
Hai Zhangcdc85c52018-12-06 13:56:55 -0800126 return mVersion;
127 }
Hai Zhangb7776682018-09-25 15:10:57 -0700128 }
129
130 /**
131 * Set the version of this user state.
132 *
133 * @param version the version to set
134 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800135 public void setVersion(int version) {
136 synchronized (mLock) {
137 throwIfDestroyedLocked();
Hai Zhang31d06ba2018-12-06 18:14:42 -0800138
Hai Zhangcdc85c52018-12-06 13:56:55 -0800139 if (mVersion == version) {
140 return;
141 }
142 mVersion = version;
143 scheduleWriteFileLocked();
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800144 }
Hai Zhangb7776682018-09-25 15:10:57 -0700145 }
146
147 /**
Hai Zhang458cedb2018-12-03 15:41:11 -0800148 * Get the hash representing the state of packages during the last time initial grants was run.
149 *
150 * @return the hash representing the state of packages
Eugene Suslaabdefba2018-11-09 18:06:43 -0800151 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800152 @Nullable
153 public String getPackagesHash() {
154 synchronized (mLock) {
155 return mPackagesHash;
156 }
Eugene Suslaabdefba2018-11-09 18:06:43 -0800157 }
158
159 /**
Hai Zhang458cedb2018-12-03 15:41:11 -0800160 * Set the hash representing the state of packages during the last time initial grants was run.
161 *
Hai Zhang33456fb2018-12-05 17:30:35 -0800162 * @param packagesHash the hash representing the state of packages
Eugene Suslaabdefba2018-11-09 18:06:43 -0800163 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800164 public void setPackagesHash(@Nullable String packagesHash) {
165 synchronized (mLock) {
166 throwIfDestroyedLocked();
Hai Zhang31d06ba2018-12-06 18:14:42 -0800167
Hai Zhangcdc85c52018-12-06 13:56:55 -0800168 if (Objects.equals(mPackagesHash, packagesHash)) {
169 return;
170 }
171 mPackagesHash = packagesHash;
172 scheduleWriteFileLocked();
Hai Zhang458cedb2018-12-03 15:41:11 -0800173 }
Eugene Suslaabdefba2018-11-09 18:06:43 -0800174 }
175
176 /**
Hai Zhangb7776682018-09-25 15:10:57 -0700177 * Get whether the role is available.
178 *
179 * @param roleName the name of the role to get the holders for
180 *
181 * @return whether the role is available
182 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800183 public boolean isRoleAvailable(@NonNull String roleName) {
184 synchronized (mLock) {
185 throwIfDestroyedLocked();
Hai Zhang31d06ba2018-12-06 18:14:42 -0800186
Hai Zhangcdc85c52018-12-06 13:56:55 -0800187 return mRoles.containsKey(roleName);
188 }
Hai Zhangb7776682018-09-25 15:10:57 -0700189 }
190
191 /**
192 * Get the holders of a role.
193 *
194 * @param roleName the name of the role to query for
195 *
Hai Zhangb1758642019-01-14 17:43:04 -0800196 * @return the set of role holders, or {@code null} if and only if the role is not found
Hai Zhangb7776682018-09-25 15:10:57 -0700197 */
Hai Zhangb7776682018-09-25 15:10:57 -0700198 @Nullable
Hai Zhangcdc85c52018-12-06 13:56:55 -0800199 public ArraySet<String> getRoleHolders(@NonNull String roleName) {
200 synchronized (mLock) {
201 throwIfDestroyedLocked();
Hai Zhang31d06ba2018-12-06 18:14:42 -0800202
Hai Zhangb1758642019-01-14 17:43:04 -0800203 ArraySet<String> packageNames = mRoles.get(roleName);
204 if (packageNames == null) {
205 return null;
206 }
207 return new ArraySet<>(packageNames);
Hai Zhangcdc85c52018-12-06 13:56:55 -0800208 }
Hai Zhangb7776682018-09-25 15:10:57 -0700209 }
210
211 /**
Eugene Susla4ab95112018-12-17 14:45:11 -0800212 * Adds the given role, effectively marking it as {@link #isRoleAvailable available}
213 *
214 * @param roleName the name of the role
215 *
216 * @return whether any changes were made
217 */
218 public boolean addRoleName(@NonNull String roleName) {
219 synchronized (mLock) {
220 throwIfDestroyedLocked();
221
222 if (!mRoles.containsKey(roleName)) {
223 mRoles.put(roleName, new ArraySet<>());
224 Slog.i(LOG_TAG, "Added new role: " + roleName);
225 scheduleWriteFileLocked();
226 return true;
227 } else {
228 return false;
229 }
230 }
231 }
232
233 /**
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800234 * Set the names of all available roles.
235 *
236 * @param roleNames the names of all the available roles
237 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800238 public void setRoleNames(@NonNull List<String> roleNames) {
239 synchronized (mLock) {
240 throwIfDestroyedLocked();
Hai Zhang31d06ba2018-12-06 18:14:42 -0800241
Hai Zhangcdc85c52018-12-06 13:56:55 -0800242 boolean changed = false;
Hai Zhang31d06ba2018-12-06 18:14:42 -0800243
Hai Zhangcdc85c52018-12-06 13:56:55 -0800244 for (int i = mRoles.size() - 1; i >= 0; i--) {
245 String roleName = mRoles.keyAt(i);
Hai Zhang31d06ba2018-12-06 18:14:42 -0800246
Hai Zhangcdc85c52018-12-06 13:56:55 -0800247 if (!roleNames.contains(roleName)) {
248 ArraySet<String> packageNames = mRoles.valueAt(i);
249 if (!packageNames.isEmpty()) {
Hai Zhang31d06ba2018-12-06 18:14:42 -0800250 Slog.e(LOG_TAG, "Holders of a removed role should have been cleaned up,"
251 + " role: " + roleName + ", holders: " + packageNames);
Hai Zhangcdc85c52018-12-06 13:56:55 -0800252 }
253 mRoles.removeAt(i);
254 changed = true;
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800255 }
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800256 }
Hai Zhang31d06ba2018-12-06 18:14:42 -0800257
Hai Zhangcdc85c52018-12-06 13:56:55 -0800258 int roleNamesSize = roleNames.size();
259 for (int i = 0; i < roleNamesSize; i++) {
Eugene Susla4ab95112018-12-17 14:45:11 -0800260 changed |= addRoleName(roleNames.get(i));
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800261 }
Hai Zhang31d06ba2018-12-06 18:14:42 -0800262
Hai Zhangcdc85c52018-12-06 13:56:55 -0800263 if (changed) {
264 scheduleWriteFileLocked();
265 }
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800266 }
267 }
268
269 /**
Hai Zhangb7776682018-09-25 15:10:57 -0700270 * Add a holder to a role.
271 *
272 * @param roleName the name of the role to add the holder to
273 * @param packageName the package name of the new holder
274 *
Hai Zhangb1758642019-01-14 17:43:04 -0800275 * @return {@code false} if and only if the role is not found
Hai Zhangb7776682018-09-25 15:10:57 -0700276 */
277 @CheckResult
Hai Zhangcdc85c52018-12-06 13:56:55 -0800278 public boolean addRoleHolder(@NonNull String roleName, @NonNull String packageName) {
Hai Zhang31d06ba2018-12-06 18:14:42 -0800279 boolean changed;
280
Hai Zhangcdc85c52018-12-06 13:56:55 -0800281 synchronized (mLock) {
282 throwIfDestroyedLocked();
Hai Zhang31d06ba2018-12-06 18:14:42 -0800283
Hai Zhangcdc85c52018-12-06 13:56:55 -0800284 ArraySet<String> roleHolders = mRoles.get(roleName);
285 if (roleHolders == null) {
286 Slog.e(LOG_TAG, "Cannot add role holder for unknown role, role: " + roleName
287 + ", package: " + packageName);
288 return false;
289 }
Hai Zhang31d06ba2018-12-06 18:14:42 -0800290 changed = roleHolders.add(packageName);
Hai Zhangcdc85c52018-12-06 13:56:55 -0800291 if (changed) {
292 scheduleWriteFileLocked();
293 }
Hai Zhangb7776682018-09-25 15:10:57 -0700294 }
Hai Zhang31d06ba2018-12-06 18:14:42 -0800295
296 if (changed) {
Eugene Susla0fa80492019-07-01 11:06:06 -0700297 mCallback.onRoleHoldersChanged(roleName, mUserId, null, packageName);
Hai Zhang31d06ba2018-12-06 18:14:42 -0800298 }
299 return true;
Hai Zhangb7776682018-09-25 15:10:57 -0700300 }
301
302 /**
303 * Remove a holder from a role.
304 *
305 * @param roleName the name of the role to remove the holder from
306 * @param packageName the package name of the holder to remove
307 *
Hai Zhangb1758642019-01-14 17:43:04 -0800308 * @return {@code false} if and only if the role is not found
Hai Zhangb7776682018-09-25 15:10:57 -0700309 */
310 @CheckResult
Hai Zhangcdc85c52018-12-06 13:56:55 -0800311 public boolean removeRoleHolder(@NonNull String roleName, @NonNull String packageName) {
Hai Zhang31d06ba2018-12-06 18:14:42 -0800312 boolean changed;
313
Hai Zhangcdc85c52018-12-06 13:56:55 -0800314 synchronized (mLock) {
315 throwIfDestroyedLocked();
Hai Zhang31d06ba2018-12-06 18:14:42 -0800316
Hai Zhangcdc85c52018-12-06 13:56:55 -0800317 ArraySet<String> roleHolders = mRoles.get(roleName);
318 if (roleHolders == null) {
319 Slog.e(LOG_TAG, "Cannot remove role holder for unknown role, role: " + roleName
320 + ", package: " + packageName);
321 return false;
322 }
Hai Zhang31d06ba2018-12-06 18:14:42 -0800323
324 changed = roleHolders.remove(packageName);
Hai Zhangcdc85c52018-12-06 13:56:55 -0800325 if (changed) {
326 scheduleWriteFileLocked();
327 }
Hai Zhangb7776682018-09-25 15:10:57 -0700328 }
Hai Zhang31d06ba2018-12-06 18:14:42 -0800329
330 if (changed) {
Eugene Susla0fa80492019-07-01 11:06:06 -0700331 mCallback.onRoleHoldersChanged(roleName, mUserId, packageName, null);
Hai Zhang31d06ba2018-12-06 18:14:42 -0800332 }
333 return true;
Hai Zhangb7776682018-09-25 15:10:57 -0700334 }
335
336 /**
Eugene Suslaa2a80322018-12-12 17:09:38 -0800337 * @see android.app.role.RoleManager#getHeldRolesFromController
338 */
339 @NonNull
340 public List<String> getHeldRoles(@NonNull String packageName) {
341 ArrayList<String> result = new ArrayList<>();
342 int size = mRoles.size();
343 for (int i = 0; i < size; i++) {
344 if (mRoles.valueAt(i).contains(packageName)) {
345 result.add(mRoles.keyAt(i));
346 }
347 }
348 return result;
349 }
350
351 /**
Hai Zhangb7776682018-09-25 15:10:57 -0700352 * Schedule writing the state to file.
353 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800354 @GuardedBy("mLock")
355 private void scheduleWriteFileLocked() {
Hai Zhangb7776682018-09-25 15:10:57 -0700356 throwIfDestroyedLocked();
Hai Zhang458cedb2018-12-03 15:41:11 -0800357
Hai Zhangbc5430852018-12-06 23:25:43 -0800358 if (!mWriteScheduled) {
359 mWriteHandler.sendMessageDelayed(PooledLambda.obtainMessage(RoleUserState::writeFile,
360 this), WRITE_DELAY_MILLIS);
361 mWriteScheduled = true;
Hai Zhangb3e9b9b2018-12-05 18:32:40 -0800362 }
Hai Zhangb7776682018-09-25 15:10:57 -0700363 }
364
365 @WorkerThread
Hai Zhangcdc85c52018-12-06 13:56:55 -0800366 private void writeFile() {
367 int version;
368 String packagesHash;
369 ArrayMap<String, ArraySet<String>> roles;
370 synchronized (mLock) {
371 if (mDestroyed) {
372 return;
373 }
374
Hai Zhangbc5430852018-12-06 23:25:43 -0800375 mWriteScheduled = false;
376
Hai Zhangcdc85c52018-12-06 13:56:55 -0800377 version = mVersion;
378 packagesHash = mPackagesHash;
Philip P. Moltmann538b3612019-01-30 14:22:27 -0800379 roles = snapshotRolesLocked();
Hai Zhangcdc85c52018-12-06 13:56:55 -0800380 }
381
Hai Zhangb295ac42018-11-16 16:08:18 -0800382 AtomicFile atomicFile = new AtomicFile(getFile(mUserId), "roles-" + mUserId);
Hai Zhangb7776682018-09-25 15:10:57 -0700383 FileOutputStream out = null;
384 try {
Hai Zhangb295ac42018-11-16 16:08:18 -0800385 out = atomicFile.startWrite();
Hai Zhangb7776682018-09-25 15:10:57 -0700386
387 XmlSerializer serializer = Xml.newSerializer();
388 serializer.setOutput(out, StandardCharsets.UTF_8.name());
389 serializer.setFeature(
390 "http://xmlpull.org/v1/doc/features.html#indent-output", true);
391 serializer.startDocument(null, true);
392
Hai Zhang458cedb2018-12-03 15:41:11 -0800393 serializeRoles(serializer, version, packagesHash, roles);
Hai Zhangb7776682018-09-25 15:10:57 -0700394
395 serializer.endDocument();
Hai Zhangb295ac42018-11-16 16:08:18 -0800396 atomicFile.finishWrite(out);
Hai Zhangb3e9b9b2018-12-05 18:32:40 -0800397 Slog.i(LOG_TAG, "Wrote roles.xml successfully");
Hai Zhangb295ac42018-11-16 16:08:18 -0800398 } catch (IllegalArgumentException | IllegalStateException | IOException e) {
399 Slog.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup", e);
400 if (out != null) {
401 atomicFile.failWrite(out);
402 }
Hai Zhangb7776682018-09-25 15:10:57 -0700403 } finally {
404 IoUtils.closeQuietly(out);
405 }
406 }
407
408 @WorkerThread
409 private void serializeRoles(@NonNull XmlSerializer serializer, int version,
Hai Zhang458cedb2018-12-03 15:41:11 -0800410 @Nullable String packagesHash, @NonNull ArrayMap<String, ArraySet<String>> roles)
Eugene Suslaabdefba2018-11-09 18:06:43 -0800411 throws IOException {
Hai Zhangb7776682018-09-25 15:10:57 -0700412 serializer.startTag(null, TAG_ROLES);
Hai Zhang458cedb2018-12-03 15:41:11 -0800413
Hai Zhangb7776682018-09-25 15:10:57 -0700414 serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version));
Hai Zhang458cedb2018-12-03 15:41:11 -0800415
416 if (packagesHash != null) {
417 serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash);
418 }
419
Hai Zhangb7776682018-09-25 15:10:57 -0700420 for (int i = 0, size = roles.size(); i < size; ++i) {
421 String roleName = roles.keyAt(i);
422 ArraySet<String> roleHolders = roles.valueAt(i);
Hai Zhang458cedb2018-12-03 15:41:11 -0800423
Hai Zhangb7776682018-09-25 15:10:57 -0700424 serializer.startTag(null, TAG_ROLE);
425 serializer.attribute(null, ATTRIBUTE_NAME, roleName);
426 serializeRoleHolders(serializer, roleHolders);
427 serializer.endTag(null, TAG_ROLE);
428 }
Hai Zhang458cedb2018-12-03 15:41:11 -0800429
Hai Zhangb7776682018-09-25 15:10:57 -0700430 serializer.endTag(null, TAG_ROLES);
431 }
432
433 @WorkerThread
434 private void serializeRoleHolders(@NonNull XmlSerializer serializer,
435 @NonNull ArraySet<String> roleHolders) throws IOException {
436 for (int i = 0, size = roleHolders.size(); i < size; ++i) {
437 String roleHolder = roleHolders.valueAt(i);
Hai Zhang458cedb2018-12-03 15:41:11 -0800438
Hai Zhangb7776682018-09-25 15:10:57 -0700439 serializer.startTag(null, TAG_HOLDER);
440 serializer.attribute(null, ATTRIBUTE_NAME, roleHolder);
441 serializer.endTag(null, TAG_HOLDER);
442 }
443 }
444
445 /**
446 * Read the state from file.
447 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800448 private void readFile() {
449 synchronized (mLock) {
450 File file = getFile(mUserId);
451 try (FileInputStream in = new AtomicFile(file).openRead()) {
452 XmlPullParser parser = Xml.newPullParser();
453 parser.setInput(in, null);
454 parseXmlLocked(parser);
455 Slog.i(LOG_TAG, "Read roles.xml successfully");
456 } catch (FileNotFoundException e) {
457 Slog.i(LOG_TAG, "roles.xml not found");
458 } catch (XmlPullParserException | IOException e) {
459 throw new IllegalStateException("Failed to parse roles.xml: " + file, e);
460 }
Hai Zhangb7776682018-09-25 15:10:57 -0700461 }
462 }
463
464 private void parseXmlLocked(@NonNull XmlPullParser parser) throws IOException,
465 XmlPullParserException {
Hai Zhangb7776682018-09-25 15:10:57 -0700466 int type;
Hai Zhangb295ac42018-11-16 16:08:18 -0800467 int depth;
468 int innerDepth = parser.getDepth() + 1;
Hai Zhangb7776682018-09-25 15:10:57 -0700469 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
Hai Zhangb295ac42018-11-16 16:08:18 -0800470 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
471 if (depth > innerDepth || type != XmlPullParser.START_TAG) {
Hai Zhangb7776682018-09-25 15:10:57 -0700472 continue;
473 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800474
Hai Zhangb7776682018-09-25 15:10:57 -0700475 if (parser.getName().equals(TAG_ROLES)) {
476 parseRolesLocked(parser);
477 return;
478 }
479 }
Hai Zhang458cedb2018-12-03 15:41:11 -0800480 Slog.w(LOG_TAG, "Missing <" + TAG_ROLES + "> in roles.xml");
Hai Zhangb7776682018-09-25 15:10:57 -0700481 }
482
483 private void parseRolesLocked(@NonNull XmlPullParser parser) throws IOException,
484 XmlPullParserException {
485 mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_VERSION));
Hai Zhang33456fb2018-12-05 17:30:35 -0800486 mPackagesHash = parser.getAttributeValue(null, ATTRIBUTE_PACKAGES_HASH);
Hai Zhang458cedb2018-12-03 15:41:11 -0800487 mRoles.clear();
Hai Zhangb295ac42018-11-16 16:08:18 -0800488
Hai Zhangb7776682018-09-25 15:10:57 -0700489 int type;
Hai Zhangb295ac42018-11-16 16:08:18 -0800490 int depth;
491 int innerDepth = parser.getDepth() + 1;
Hai Zhangb7776682018-09-25 15:10:57 -0700492 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
Hai Zhangb295ac42018-11-16 16:08:18 -0800493 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
494 if (depth > innerDepth || type != XmlPullParser.START_TAG) {
Hai Zhangb7776682018-09-25 15:10:57 -0700495 continue;
496 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800497
Hai Zhangb7776682018-09-25 15:10:57 -0700498 if (parser.getName().equals(TAG_ROLE)) {
499 String roleName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
500 ArraySet<String> roleHolders = parseRoleHoldersLocked(parser);
501 mRoles.put(roleName, roleHolders);
502 }
503 }
504 }
505
506 @NonNull
507 private ArraySet<String> parseRoleHoldersLocked(@NonNull XmlPullParser parser)
508 throws IOException, XmlPullParserException {
509 ArraySet<String> roleHolders = new ArraySet<>();
Hai Zhangb295ac42018-11-16 16:08:18 -0800510
Hai Zhangb7776682018-09-25 15:10:57 -0700511 int type;
Hai Zhangb295ac42018-11-16 16:08:18 -0800512 int depth;
513 int innerDepth = parser.getDepth() + 1;
Hai Zhangb7776682018-09-25 15:10:57 -0700514 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
Hai Zhangb295ac42018-11-16 16:08:18 -0800515 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
516 if (depth > innerDepth || type != XmlPullParser.START_TAG) {
Hai Zhangb7776682018-09-25 15:10:57 -0700517 continue;
518 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800519
Hai Zhangb7776682018-09-25 15:10:57 -0700520 if (parser.getName().equals(TAG_HOLDER)) {
521 String roleHolder = parser.getAttributeValue(null, ATTRIBUTE_NAME);
522 roleHolders.add(roleHolder);
523 }
524 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800525
Hai Zhangb7776682018-09-25 15:10:57 -0700526 return roleHolders;
527 }
528
529 /**
Hai Zhang33456fb2018-12-05 17:30:35 -0800530 * Dump this user state.
531 *
532 * @param dumpOutputStream the output stream to dump to
533 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800534 public void dump(@NonNull DualDumpOutputStream dumpOutputStream, @NonNull String fieldName,
535 long fieldId) {
536 int version;
537 String packagesHash;
538 ArrayMap<String, ArraySet<String>> roles;
539 synchronized (mLock) {
540 throwIfDestroyedLocked();
541
542 version = mVersion;
543 packagesHash = mPackagesHash;
Philip P. Moltmann538b3612019-01-30 14:22:27 -0800544 roles = snapshotRolesLocked();
Hai Zhangcdc85c52018-12-06 13:56:55 -0800545 }
Hai Zhang33456fb2018-12-05 17:30:35 -0800546
547 long fieldToken = dumpOutputStream.start(fieldName, fieldId);
548 dumpOutputStream.write("user_id", RoleUserStateProto.USER_ID, mUserId);
Hai Zhangcdc85c52018-12-06 13:56:55 -0800549 dumpOutputStream.write("version", RoleUserStateProto.VERSION, version);
550 dumpOutputStream.write("packages_hash", RoleUserStateProto.PACKAGES_HASH, packagesHash);
Hai Zhang33456fb2018-12-05 17:30:35 -0800551
Hai Zhangcdc85c52018-12-06 13:56:55 -0800552 int rolesSize = roles.size();
Hai Zhang33456fb2018-12-05 17:30:35 -0800553 for (int rolesIndex = 0; rolesIndex < rolesSize; rolesIndex++) {
Hai Zhangcdc85c52018-12-06 13:56:55 -0800554 String roleName = roles.keyAt(rolesIndex);
555 ArraySet<String> roleHolders = roles.valueAt(rolesIndex);
Hai Zhang33456fb2018-12-05 17:30:35 -0800556
557 long rolesToken = dumpOutputStream.start("roles", RoleUserStateProto.ROLES);
558 dumpOutputStream.write("name", RoleProto.NAME, roleName);
559
560 int roleHoldersSize = roleHolders.size();
561 for (int roleHoldersIndex = 0; roleHoldersIndex < roleHoldersSize; roleHoldersIndex++) {
562 String roleHolder = roleHolders.valueAt(roleHoldersIndex);
563
564 dumpOutputStream.write("holders", RoleProto.HOLDERS, roleHolder);
565 }
566
567 dumpOutputStream.end(rolesToken);
568 }
569
570 dumpOutputStream.end(fieldToken);
571 }
572
Philip P. Moltmann70b42ae2019-01-29 16:24:19 -0800573 /**
574 * Get the roles and their holders.
575 *
576 * @return A copy of the roles and their holders
577 */
578 @NonNull
Hai Zhang85fd0622019-02-01 14:06:04 -0800579 public ArrayMap<String, ArraySet<String>> getRolesAndHolders() {
Philip P. Moltmann70b42ae2019-01-29 16:24:19 -0800580 synchronized (mLock) {
Philip P. Moltmann538b3612019-01-30 14:22:27 -0800581 return snapshotRolesLocked();
Hai Zhangcdc85c52018-12-06 13:56:55 -0800582 }
Hai Zhangcdc85c52018-12-06 13:56:55 -0800583 }
584
Philip P. Moltmann538b3612019-01-30 14:22:27 -0800585 @GuardedBy("mLock")
Hai Zhang85fd0622019-02-01 14:06:04 -0800586 @NonNull
Philip P. Moltmann538b3612019-01-30 14:22:27 -0800587 private ArrayMap<String, ArraySet<String>> snapshotRolesLocked() {
588 ArrayMap<String, ArraySet<String>> roles = new ArrayMap<>();
589 for (int i = 0, size = CollectionUtils.size(mRoles); i < size; ++i) {
590 String roleName = mRoles.keyAt(i);
591 ArraySet<String> roleHolders = mRoles.valueAt(i);
592
593 roleHolders = new ArraySet<>(roleHolders);
594 roles.put(roleName, roleHolders);
595 }
596 return roles;
597 }
598
Hai Zhang33456fb2018-12-05 17:30:35 -0800599 /**
Hai Zhang31d06ba2018-12-06 18:14:42 -0800600 * Destroy this user state and delete the corresponding file. Any pending writes to the file
601 * will be cancelled, and any future interaction with this state will throw an exception.
Hai Zhangb7776682018-09-25 15:10:57 -0700602 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800603 public void destroy() {
604 synchronized (mLock) {
605 throwIfDestroyedLocked();
606 mWriteHandler.removeCallbacksAndMessages(null);
607 getFile(mUserId).delete();
608 mDestroyed = true;
609 }
Hai Zhangb7776682018-09-25 15:10:57 -0700610 }
611
Hai Zhangcdc85c52018-12-06 13:56:55 -0800612 @GuardedBy("mLock")
Hai Zhangb7776682018-09-25 15:10:57 -0700613 private void throwIfDestroyedLocked() {
614 if (mDestroyed) {
615 throw new IllegalStateException("This RoleUserState has already been destroyed");
616 }
617 }
618
Hai Zhang85fd0622019-02-01 14:06:04 -0800619 @NonNull
620 private static File getFile(@UserIdInt int userId) {
Hai Zhangb7776682018-09-25 15:10:57 -0700621 return new File(Environment.getUserSystemDirectory(userId), ROLES_FILE_NAME);
622 }
Hai Zhang31d06ba2018-12-06 18:14:42 -0800623
624 /**
625 * Callback for a user state.
626 */
627 public interface Callback {
628
629 /**
630 * Called when the holders of roles are changed.
631 *
632 * @param roleName the name of the role whose holders are changed
633 * @param userId the user id for this role holder change
634 */
Eugene Susla0fa80492019-07-01 11:06:06 -0700635 void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId,
636 @Nullable String removedHolder, @Nullable String addedHolder);
Hai Zhang31d06ba2018-12-06 18:14:42 -0800637 }
Hai Zhangb7776682018-09-25 15:10:57 -0700638}