blob: 81adb1180f1a9d5988c29856cf9d47560e804b8e [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;
65 private static final long MAX_WRITE_DELAY_MILLIS = 2000;
66
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
78 private final Object mLock = new Object();
79
80 @GuardedBy("mLock")
Hai Zhang458cedb2018-12-03 15:41:11 -080081 private int mVersion = VERSION_UNDEFINED;
Hai Zhangb7776682018-09-25 15:10:57 -070082
Hai Zhangcdc85c52018-12-06 13:56:55 -080083 @GuardedBy("mLock")
Hai Zhang458cedb2018-12-03 15:41:11 -080084 @Nullable
Hai Zhang33456fb2018-12-05 17:30:35 -080085 private String mPackagesHash;
Eugene Suslaabdefba2018-11-09 18:06:43 -080086
Hai Zhangb7776682018-09-25 15:10:57 -070087 /**
88 * Maps role names to its holders' package names. The values should never be null.
89 */
Hai Zhangcdc85c52018-12-06 13:56:55 -080090 @GuardedBy("mLock")
Hai Zhang458cedb2018-12-03 15:41:11 -080091 @NonNull
92 private ArrayMap<String, ArraySet<String>> mRoles = new ArrayMap<>();
Hai Zhangb7776682018-09-25 15:10:57 -070093
Hai Zhangcdc85c52018-12-06 13:56:55 -080094 @GuardedBy("mLock")
Hai Zhangb3e9b9b2018-12-05 18:32:40 -080095 private long mWritePendingSinceMillis;
96
Hai Zhangcdc85c52018-12-06 13:56:55 -080097 @GuardedBy("mLock")
Hai Zhangb7776682018-09-25 15:10:57 -070098 private boolean mDestroyed;
99
Hai Zhang458cedb2018-12-03 15:41:11 -0800100 @NonNull
Hai Zhangb7776682018-09-25 15:10:57 -0700101 private final Handler mWriteHandler = new Handler(BackgroundThread.getHandler().getLooper());
102
Hai Zhang458cedb2018-12-03 15:41:11 -0800103 /**
104 * Create a new instance of user state, and read its state from disk if previously persisted.
105 *
106 * @param userId the user id for the new user state
Hai Zhang458cedb2018-12-03 15:41:11 -0800107 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800108 public RoleUserState(@UserIdInt int userId) {
109 mUserId = userId;
110
111 readFile();
Hai Zhangb7776682018-09-25 15:10:57 -0700112 }
113
114 /**
115 * Get the version of this user state.
116 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800117 public int getVersion() {
118 synchronized (mLock) {
119 throwIfDestroyedLocked();
120 return mVersion;
121 }
Hai Zhangb7776682018-09-25 15:10:57 -0700122 }
123
124 /**
125 * Set the version of this user state.
126 *
127 * @param version the version to set
128 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800129 public void setVersion(int version) {
130 synchronized (mLock) {
131 throwIfDestroyedLocked();
132 if (mVersion == version) {
133 return;
134 }
135 mVersion = version;
136 scheduleWriteFileLocked();
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800137 }
Hai Zhangb7776682018-09-25 15:10:57 -0700138 }
139
140 /**
Hai Zhang458cedb2018-12-03 15:41:11 -0800141 * Get the hash representing the state of packages during the last time initial grants was run.
142 *
143 * @return the hash representing the state of packages
Eugene Suslaabdefba2018-11-09 18:06:43 -0800144 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800145 @Nullable
146 public String getPackagesHash() {
147 synchronized (mLock) {
148 return mPackagesHash;
149 }
Eugene Suslaabdefba2018-11-09 18:06:43 -0800150 }
151
152 /**
Hai Zhang458cedb2018-12-03 15:41:11 -0800153 * Set the hash representing the state of packages during the last time initial grants was run.
154 *
Hai Zhang33456fb2018-12-05 17:30:35 -0800155 * @param packagesHash the hash representing the state of packages
Eugene Suslaabdefba2018-11-09 18:06:43 -0800156 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800157 public void setPackagesHash(@Nullable String packagesHash) {
158 synchronized (mLock) {
159 throwIfDestroyedLocked();
160 if (Objects.equals(mPackagesHash, packagesHash)) {
161 return;
162 }
163 mPackagesHash = packagesHash;
164 scheduleWriteFileLocked();
Hai Zhang458cedb2018-12-03 15:41:11 -0800165 }
Eugene Suslaabdefba2018-11-09 18:06:43 -0800166 }
167
168 /**
Hai Zhangb7776682018-09-25 15:10:57 -0700169 * Get whether the role is available.
170 *
171 * @param roleName the name of the role to get the holders for
172 *
173 * @return whether the role is available
174 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800175 public boolean isRoleAvailable(@NonNull String roleName) {
176 synchronized (mLock) {
177 throwIfDestroyedLocked();
178 return mRoles.containsKey(roleName);
179 }
Hai Zhangb7776682018-09-25 15:10:57 -0700180 }
181
182 /**
183 * Get the holders of a role.
184 *
185 * @param roleName the name of the role to query for
186 *
187 * @return the set of role holders. {@code null} should not be returned and indicates an issue.
188 */
Hai Zhangb7776682018-09-25 15:10:57 -0700189 @Nullable
Hai Zhangcdc85c52018-12-06 13:56:55 -0800190 public ArraySet<String> getRoleHolders(@NonNull String roleName) {
191 synchronized (mLock) {
192 throwIfDestroyedLocked();
193 return new ArraySet<>(mRoles.get(roleName));
194 }
Hai Zhangb7776682018-09-25 15:10:57 -0700195 }
196
197 /**
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800198 * Set the names of all available roles.
199 *
200 * @param roleNames the names of all the available roles
201 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800202 public void setRoleNames(@NonNull List<String> roleNames) {
203 synchronized (mLock) {
204 throwIfDestroyedLocked();
205 boolean changed = false;
206 for (int i = mRoles.size() - 1; i >= 0; i--) {
207 String roleName = mRoles.keyAt(i);
208 if (!roleNames.contains(roleName)) {
209 ArraySet<String> packageNames = mRoles.valueAt(i);
210 if (!packageNames.isEmpty()) {
211 Slog.e(LOG_TAG,
212 "Holders of a removed role should have been cleaned up, role: "
213 + roleName + ", holders: " + packageNames);
214 }
215 mRoles.removeAt(i);
216 changed = true;
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800217 }
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800218 }
Hai Zhangcdc85c52018-12-06 13:56:55 -0800219 int roleNamesSize = roleNames.size();
220 for (int i = 0; i < roleNamesSize; i++) {
221 String roleName = roleNames.get(i);
222 if (!mRoles.containsKey(roleName)) {
223 mRoles.put(roleName, new ArraySet<>());
224 Slog.i(LOG_TAG, "Added new role: " + roleName);
225 changed = true;
226 }
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800227 }
Hai Zhangcdc85c52018-12-06 13:56:55 -0800228 if (changed) {
229 scheduleWriteFileLocked();
230 }
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800231 }
232 }
233
234 /**
Hai Zhangb7776682018-09-25 15:10:57 -0700235 * Add a holder to a role.
236 *
237 * @param roleName the name of the role to add the holder to
238 * @param packageName the package name of the new holder
239 *
240 * @return {@code false} only if the set of role holders is null, which should not happen and
241 * indicates an issue.
242 */
243 @CheckResult
Hai Zhangcdc85c52018-12-06 13:56:55 -0800244 public boolean addRoleHolder(@NonNull String roleName, @NonNull String packageName) {
245 synchronized (mLock) {
246 throwIfDestroyedLocked();
247 ArraySet<String> roleHolders = mRoles.get(roleName);
248 if (roleHolders == null) {
249 Slog.e(LOG_TAG, "Cannot add role holder for unknown role, role: " + roleName
250 + ", package: " + packageName);
251 return false;
252 }
253 boolean changed = roleHolders.add(packageName);
254 if (changed) {
255 scheduleWriteFileLocked();
256 }
257 return true;
Hai Zhangb7776682018-09-25 15:10:57 -0700258 }
Hai Zhangb7776682018-09-25 15:10:57 -0700259 }
260
261 /**
262 * Remove a holder from a role.
263 *
264 * @param roleName the name of the role to remove the holder from
265 * @param packageName the package name of the holder to remove
266 *
267 * @return {@code false} only if the set of role holders is null, which should not happen and
268 * indicates an issue.
269 */
270 @CheckResult
Hai Zhangcdc85c52018-12-06 13:56:55 -0800271 public boolean removeRoleHolder(@NonNull String roleName, @NonNull String packageName) {
272 synchronized (mLock) {
273 throwIfDestroyedLocked();
274 ArraySet<String> roleHolders = mRoles.get(roleName);
275 if (roleHolders == null) {
276 Slog.e(LOG_TAG, "Cannot remove role holder for unknown role, role: " + roleName
277 + ", package: " + packageName);
278 return false;
279 }
280 boolean changed = roleHolders.remove(packageName);
281 if (changed) {
282 scheduleWriteFileLocked();
283 }
284 return true;
Hai Zhangb7776682018-09-25 15:10:57 -0700285 }
Hai Zhangb7776682018-09-25 15:10:57 -0700286 }
287
288 /**
Hai Zhangb7776682018-09-25 15:10:57 -0700289 * Schedule writing the state to file.
290 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800291 @GuardedBy("mLock")
292 private void scheduleWriteFileLocked() {
Hai Zhangb7776682018-09-25 15:10:57 -0700293 throwIfDestroyedLocked();
Hai Zhang458cedb2018-12-03 15:41:11 -0800294
Hai Zhangb3e9b9b2018-12-05 18:32:40 -0800295 long currentTimeMillis = System.currentTimeMillis();
296 long writeDelayMillis;
297 if (!mWriteHandler.hasMessagesOrCallbacks()) {
298 mWritePendingSinceMillis = currentTimeMillis;
299 writeDelayMillis = WRITE_DELAY_MILLIS;
300 } else {
301 mWriteHandler.removeCallbacksAndMessages(null);
302 long writePendingDurationMillis = currentTimeMillis - mWritePendingSinceMillis;
303 if (writePendingDurationMillis >= MAX_WRITE_DELAY_MILLIS) {
304 writeDelayMillis = 0;
305 } else {
306 long maxWriteDelayMillis = Math.max(MAX_WRITE_DELAY_MILLIS
307 - writePendingDurationMillis, 0);
308 writeDelayMillis = Math.min(WRITE_DELAY_MILLIS, maxWriteDelayMillis);
309 }
310 }
311
Hai Zhangcdc85c52018-12-06 13:56:55 -0800312 mWriteHandler.sendMessageDelayed(PooledLambda.obtainMessage(RoleUserState::writeFile, this),
313 writeDelayMillis);
Hai Zhangb3e9b9b2018-12-05 18:32:40 -0800314 Slog.i(LOG_TAG, "Scheduled writing roles.xml");
Hai Zhangb7776682018-09-25 15:10:57 -0700315 }
316
317 @WorkerThread
Hai Zhangcdc85c52018-12-06 13:56:55 -0800318 private void writeFile() {
319 int version;
320 String packagesHash;
321 ArrayMap<String, ArraySet<String>> roles;
322 synchronized (mLock) {
323 if (mDestroyed) {
324 return;
325 }
326
327 version = mVersion;
328 packagesHash = mPackagesHash;
329 roles = snapshotRolesLocked();
330 }
331
Hai Zhangb295ac42018-11-16 16:08:18 -0800332 AtomicFile atomicFile = new AtomicFile(getFile(mUserId), "roles-" + mUserId);
Hai Zhangb7776682018-09-25 15:10:57 -0700333 FileOutputStream out = null;
334 try {
Hai Zhangb295ac42018-11-16 16:08:18 -0800335 out = atomicFile.startWrite();
Hai Zhangb7776682018-09-25 15:10:57 -0700336
337 XmlSerializer serializer = Xml.newSerializer();
338 serializer.setOutput(out, StandardCharsets.UTF_8.name());
339 serializer.setFeature(
340 "http://xmlpull.org/v1/doc/features.html#indent-output", true);
341 serializer.startDocument(null, true);
342
Hai Zhang458cedb2018-12-03 15:41:11 -0800343 serializeRoles(serializer, version, packagesHash, roles);
Hai Zhangb7776682018-09-25 15:10:57 -0700344
345 serializer.endDocument();
Hai Zhangb295ac42018-11-16 16:08:18 -0800346 atomicFile.finishWrite(out);
Hai Zhangb3e9b9b2018-12-05 18:32:40 -0800347 Slog.i(LOG_TAG, "Wrote roles.xml successfully");
Hai Zhangb295ac42018-11-16 16:08:18 -0800348 } catch (IllegalArgumentException | IllegalStateException | IOException e) {
349 Slog.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup", e);
350 if (out != null) {
351 atomicFile.failWrite(out);
352 }
Hai Zhangb7776682018-09-25 15:10:57 -0700353 } finally {
354 IoUtils.closeQuietly(out);
355 }
356 }
357
358 @WorkerThread
359 private void serializeRoles(@NonNull XmlSerializer serializer, int version,
Hai Zhang458cedb2018-12-03 15:41:11 -0800360 @Nullable String packagesHash, @NonNull ArrayMap<String, ArraySet<String>> roles)
Eugene Suslaabdefba2018-11-09 18:06:43 -0800361 throws IOException {
Hai Zhangb7776682018-09-25 15:10:57 -0700362 serializer.startTag(null, TAG_ROLES);
Hai Zhang458cedb2018-12-03 15:41:11 -0800363
Hai Zhangb7776682018-09-25 15:10:57 -0700364 serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version));
Hai Zhang458cedb2018-12-03 15:41:11 -0800365
366 if (packagesHash != null) {
367 serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash);
368 }
369
Hai Zhangb7776682018-09-25 15:10:57 -0700370 for (int i = 0, size = roles.size(); i < size; ++i) {
371 String roleName = roles.keyAt(i);
372 ArraySet<String> roleHolders = roles.valueAt(i);
Hai Zhang458cedb2018-12-03 15:41:11 -0800373
Hai Zhangb7776682018-09-25 15:10:57 -0700374 serializer.startTag(null, TAG_ROLE);
375 serializer.attribute(null, ATTRIBUTE_NAME, roleName);
376 serializeRoleHolders(serializer, roleHolders);
377 serializer.endTag(null, TAG_ROLE);
378 }
Hai Zhang458cedb2018-12-03 15:41:11 -0800379
Hai Zhangb7776682018-09-25 15:10:57 -0700380 serializer.endTag(null, TAG_ROLES);
381 }
382
383 @WorkerThread
384 private void serializeRoleHolders(@NonNull XmlSerializer serializer,
385 @NonNull ArraySet<String> roleHolders) throws IOException {
386 for (int i = 0, size = roleHolders.size(); i < size; ++i) {
387 String roleHolder = roleHolders.valueAt(i);
Hai Zhang458cedb2018-12-03 15:41:11 -0800388
Hai Zhangb7776682018-09-25 15:10:57 -0700389 serializer.startTag(null, TAG_HOLDER);
390 serializer.attribute(null, ATTRIBUTE_NAME, roleHolder);
391 serializer.endTag(null, TAG_HOLDER);
392 }
393 }
394
395 /**
396 * Read the state from file.
397 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800398 private void readFile() {
399 synchronized (mLock) {
400 File file = getFile(mUserId);
401 try (FileInputStream in = new AtomicFile(file).openRead()) {
402 XmlPullParser parser = Xml.newPullParser();
403 parser.setInput(in, null);
404 parseXmlLocked(parser);
405 Slog.i(LOG_TAG, "Read roles.xml successfully");
406 } catch (FileNotFoundException e) {
407 Slog.i(LOG_TAG, "roles.xml not found");
408 } catch (XmlPullParserException | IOException e) {
409 throw new IllegalStateException("Failed to parse roles.xml: " + file, e);
410 }
Hai Zhangb7776682018-09-25 15:10:57 -0700411 }
412 }
413
414 private void parseXmlLocked(@NonNull XmlPullParser parser) throws IOException,
415 XmlPullParserException {
Hai Zhangb7776682018-09-25 15:10:57 -0700416 int type;
Hai Zhangb295ac42018-11-16 16:08:18 -0800417 int depth;
418 int innerDepth = parser.getDepth() + 1;
Hai Zhangb7776682018-09-25 15:10:57 -0700419 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
Hai Zhangb295ac42018-11-16 16:08:18 -0800420 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
421 if (depth > innerDepth || type != XmlPullParser.START_TAG) {
Hai Zhangb7776682018-09-25 15:10:57 -0700422 continue;
423 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800424
Hai Zhangb7776682018-09-25 15:10:57 -0700425 if (parser.getName().equals(TAG_ROLES)) {
426 parseRolesLocked(parser);
427 return;
428 }
429 }
Hai Zhang458cedb2018-12-03 15:41:11 -0800430 Slog.w(LOG_TAG, "Missing <" + TAG_ROLES + "> in roles.xml");
Hai Zhangb7776682018-09-25 15:10:57 -0700431 }
432
433 private void parseRolesLocked(@NonNull XmlPullParser parser) throws IOException,
434 XmlPullParserException {
435 mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_VERSION));
Hai Zhang33456fb2018-12-05 17:30:35 -0800436 mPackagesHash = parser.getAttributeValue(null, ATTRIBUTE_PACKAGES_HASH);
Hai Zhang458cedb2018-12-03 15:41:11 -0800437 mRoles.clear();
Hai Zhangb295ac42018-11-16 16:08:18 -0800438
Hai Zhangb7776682018-09-25 15:10:57 -0700439 int type;
Hai Zhangb295ac42018-11-16 16:08:18 -0800440 int depth;
441 int innerDepth = parser.getDepth() + 1;
Hai Zhangb7776682018-09-25 15:10:57 -0700442 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
Hai Zhangb295ac42018-11-16 16:08:18 -0800443 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
444 if (depth > innerDepth || type != XmlPullParser.START_TAG) {
Hai Zhangb7776682018-09-25 15:10:57 -0700445 continue;
446 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800447
Hai Zhangb7776682018-09-25 15:10:57 -0700448 if (parser.getName().equals(TAG_ROLE)) {
449 String roleName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
450 ArraySet<String> roleHolders = parseRoleHoldersLocked(parser);
451 mRoles.put(roleName, roleHolders);
452 }
453 }
454 }
455
456 @NonNull
457 private ArraySet<String> parseRoleHoldersLocked(@NonNull XmlPullParser parser)
458 throws IOException, XmlPullParserException {
459 ArraySet<String> roleHolders = new ArraySet<>();
Hai Zhangb295ac42018-11-16 16:08:18 -0800460
Hai Zhangb7776682018-09-25 15:10:57 -0700461 int type;
Hai Zhangb295ac42018-11-16 16:08:18 -0800462 int depth;
463 int innerDepth = parser.getDepth() + 1;
Hai Zhangb7776682018-09-25 15:10:57 -0700464 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
Hai Zhangb295ac42018-11-16 16:08:18 -0800465 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
466 if (depth > innerDepth || type != XmlPullParser.START_TAG) {
Hai Zhangb7776682018-09-25 15:10:57 -0700467 continue;
468 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800469
Hai Zhangb7776682018-09-25 15:10:57 -0700470 if (parser.getName().equals(TAG_HOLDER)) {
471 String roleHolder = parser.getAttributeValue(null, ATTRIBUTE_NAME);
472 roleHolders.add(roleHolder);
473 }
474 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800475
Hai Zhangb7776682018-09-25 15:10:57 -0700476 return roleHolders;
477 }
478
479 /**
Hai Zhang33456fb2018-12-05 17:30:35 -0800480 * Dump this user state.
481 *
482 * @param dumpOutputStream the output stream to dump to
483 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800484 public void dump(@NonNull DualDumpOutputStream dumpOutputStream, @NonNull String fieldName,
485 long fieldId) {
486 int version;
487 String packagesHash;
488 ArrayMap<String, ArraySet<String>> roles;
489 synchronized (mLock) {
490 throwIfDestroyedLocked();
491
492 version = mVersion;
493 packagesHash = mPackagesHash;
494 roles = snapshotRolesLocked();
495 }
Hai Zhang33456fb2018-12-05 17:30:35 -0800496
497 long fieldToken = dumpOutputStream.start(fieldName, fieldId);
498 dumpOutputStream.write("user_id", RoleUserStateProto.USER_ID, mUserId);
Hai Zhangcdc85c52018-12-06 13:56:55 -0800499 dumpOutputStream.write("version", RoleUserStateProto.VERSION, version);
500 dumpOutputStream.write("packages_hash", RoleUserStateProto.PACKAGES_HASH, packagesHash);
Hai Zhang33456fb2018-12-05 17:30:35 -0800501
Hai Zhangcdc85c52018-12-06 13:56:55 -0800502 int rolesSize = roles.size();
Hai Zhang33456fb2018-12-05 17:30:35 -0800503 for (int rolesIndex = 0; rolesIndex < rolesSize; rolesIndex++) {
Hai Zhangcdc85c52018-12-06 13:56:55 -0800504 String roleName = roles.keyAt(rolesIndex);
505 ArraySet<String> roleHolders = roles.valueAt(rolesIndex);
Hai Zhang33456fb2018-12-05 17:30:35 -0800506
507 long rolesToken = dumpOutputStream.start("roles", RoleUserStateProto.ROLES);
508 dumpOutputStream.write("name", RoleProto.NAME, roleName);
509
510 int roleHoldersSize = roleHolders.size();
511 for (int roleHoldersIndex = 0; roleHoldersIndex < roleHoldersSize; roleHoldersIndex++) {
512 String roleHolder = roleHolders.valueAt(roleHoldersIndex);
513
514 dumpOutputStream.write("holders", RoleProto.HOLDERS, roleHolder);
515 }
516
517 dumpOutputStream.end(rolesToken);
518 }
519
520 dumpOutputStream.end(fieldToken);
521 }
522
Hai Zhangcdc85c52018-12-06 13:56:55 -0800523 @GuardedBy("mLock")
524 private ArrayMap<String, ArraySet<String>> snapshotRolesLocked() {
525 ArrayMap<String, ArraySet<String>> roles = new ArrayMap<>();
526 for (int i = 0, size = CollectionUtils.size(mRoles); i < size; ++i) {
527 String roleName = mRoles.keyAt(i);
528 ArraySet<String> roleHolders = mRoles.valueAt(i);
529
530 roleHolders = new ArraySet<>(roleHolders);
531 roles.put(roleName, roleHolders);
532 }
533 return roles;
534 }
535
Hai Zhang33456fb2018-12-05 17:30:35 -0800536 /**
Hai Zhangb7776682018-09-25 15:10:57 -0700537 * Destroy this state and delete the corresponding file. Any pending writes to the file will be
538 * cancelled and any future interaction with this state will throw an exception.
539 */
Hai Zhangcdc85c52018-12-06 13:56:55 -0800540 public void destroy() {
541 synchronized (mLock) {
542 throwIfDestroyedLocked();
543 mWriteHandler.removeCallbacksAndMessages(null);
544 getFile(mUserId).delete();
545 mDestroyed = true;
546 }
Hai Zhangb7776682018-09-25 15:10:57 -0700547 }
548
Hai Zhangcdc85c52018-12-06 13:56:55 -0800549 @GuardedBy("mLock")
Hai Zhangb7776682018-09-25 15:10:57 -0700550 private void throwIfDestroyedLocked() {
551 if (mDestroyed) {
552 throw new IllegalStateException("This RoleUserState has already been destroyed");
553 }
554 }
555
556 private static @NonNull File getFile(@UserIdInt int userId) {
557 return new File(Environment.getUserSystemDirectory(userId), ROLES_FILE_NAME);
558 }
559}