blob: 3e3e1566b2dd9d3789c2ea0b693a1ac077000d94 [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 Zhangb7776682018-09-25 15:10:57 -070035import com.android.internal.util.function.pooled.PooledLambda;
36
37import libcore.io.IoUtils;
38
39import org.xmlpull.v1.XmlPullParser;
40import org.xmlpull.v1.XmlPullParserException;
41import org.xmlpull.v1.XmlSerializer;
42
43import java.io.File;
44import java.io.FileInputStream;
45import java.io.FileNotFoundException;
46import java.io.FileOutputStream;
47import java.io.IOException;
48import java.nio.charset.StandardCharsets;
Hai Zhang8e60a8f2018-11-20 11:21:09 -080049import java.util.List;
Hai Zhang458cedb2018-12-03 15:41:11 -080050import java.util.Objects;
Hai Zhangb7776682018-09-25 15:10:57 -070051
52/**
53 * Stores the state of roles for a user.
54 */
55public class RoleUserState {
56
57 private static final String LOG_TAG = RoleUserState.class.getSimpleName();
58
59 public static final int VERSION_UNDEFINED = -1;
60
61 private static final String ROLES_FILE_NAME = "roles.xml";
62
63 private static final String TAG_ROLES = "roles";
64 private static final String TAG_ROLE = "role";
65 private static final String TAG_HOLDER = "holder";
66 private static final String ATTRIBUTE_VERSION = "version";
67 private static final String ATTRIBUTE_NAME = "name";
Eugene Suslaabdefba2018-11-09 18:06:43 -080068 private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash";
Hai Zhangb7776682018-09-25 15:10:57 -070069
70 @UserIdInt
71 private final int mUserId;
72
73 @GuardedBy("RoleManagerService.mLock")
Hai Zhang458cedb2018-12-03 15:41:11 -080074 private int mVersion = VERSION_UNDEFINED;
Hai Zhangb7776682018-09-25 15:10:57 -070075
Eugene Suslaabdefba2018-11-09 18:06:43 -080076 @GuardedBy("RoleManagerService.mLock")
Hai Zhang458cedb2018-12-03 15:41:11 -080077 @Nullable
78 private String mLastGrantPackagesHash;
Eugene Suslaabdefba2018-11-09 18:06:43 -080079
Hai Zhangb7776682018-09-25 15:10:57 -070080 /**
81 * Maps role names to its holders' package names. The values should never be null.
82 */
83 @GuardedBy("RoleManagerService.mLock")
Hai Zhang458cedb2018-12-03 15:41:11 -080084 @NonNull
85 private ArrayMap<String, ArraySet<String>> mRoles = new ArrayMap<>();
Hai Zhangb7776682018-09-25 15:10:57 -070086
87 @GuardedBy("RoleManagerService.mLock")
88 private boolean mDestroyed;
89
Hai Zhang458cedb2018-12-03 15:41:11 -080090 @NonNull
Hai Zhangb7776682018-09-25 15:10:57 -070091 private final Handler mWriteHandler = new Handler(BackgroundThread.getHandler().getLooper());
92
Hai Zhang458cedb2018-12-03 15:41:11 -080093 private RoleUserState(@UserIdInt int userId) {
Hai Zhangb7776682018-09-25 15:10:57 -070094 mUserId = userId;
Hai Zhang458cedb2018-12-03 15:41:11 -080095
96 readSyncLocked();
97 }
98
99 /**
100 * Create a new instance of user state, and read its state from disk if previously persisted.
101 *
102 * @param userId the user id for the new user state
103 *
104 * @return the new user state
105 */
106 @GuardedBy("RoleManagerService.mLock")
107 public static RoleUserState newInstanceLocked(@UserIdInt int userId) {
108 return new RoleUserState(userId);
Hai Zhangb7776682018-09-25 15:10:57 -0700109 }
110
111 /**
112 * Get the version of this user state.
113 */
114 @GuardedBy("RoleManagerService.mLock")
115 public int getVersionLocked() {
116 throwIfDestroyedLocked();
117 return mVersion;
118 }
119
120 /**
121 * Set the version of this user state.
122 *
123 * @param version the version to set
124 */
125 @GuardedBy("RoleManagerService.mLock")
126 public void setVersionLocked(int version) {
127 throwIfDestroyedLocked();
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800128 if (mVersion == version) {
129 return;
130 }
Hai Zhangb7776682018-09-25 15:10:57 -0700131 mVersion = version;
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800132 writeAsyncLocked();
Hai Zhangb7776682018-09-25 15:10:57 -0700133 }
134
135 /**
Hai Zhang458cedb2018-12-03 15:41:11 -0800136 * Get the hash representing the state of packages during the last time initial grants was run.
137 *
138 * @return the hash representing the state of packages
Eugene Suslaabdefba2018-11-09 18:06:43 -0800139 */
140 @GuardedBy("RoleManagerService.mLock")
141 public String getLastGrantPackagesHashLocked() {
142 return mLastGrantPackagesHash;
143 }
144
145 /**
Hai Zhang458cedb2018-12-03 15:41:11 -0800146 * Set the hash representing the state of packages during the last time initial grants was run.
147 *
148 * @param lastGrantPackagesHash the hash representing the state of packages
Eugene Suslaabdefba2018-11-09 18:06:43 -0800149 */
150 @GuardedBy("RoleManagerService.mLock")
Hai Zhang458cedb2018-12-03 15:41:11 -0800151 public void setLastGrantPackagesHashLocked(@Nullable String lastGrantPackagesHash) {
152 throwIfDestroyedLocked();
153 if (Objects.equals(mLastGrantPackagesHash, lastGrantPackagesHash)) {
154 return;
155 }
Eugene Suslaabdefba2018-11-09 18:06:43 -0800156 mLastGrantPackagesHash = lastGrantPackagesHash;
157 writeAsyncLocked();
158 }
159
160 /**
Hai Zhangb7776682018-09-25 15:10:57 -0700161 * Get whether the role is available.
162 *
163 * @param roleName the name of the role to get the holders for
164 *
165 * @return whether the role is available
166 */
167 @GuardedBy("RoleManagerService.mLock")
168 public boolean isRoleAvailableLocked(@NonNull String roleName) {
169 throwIfDestroyedLocked();
170 return mRoles.containsKey(roleName);
171 }
172
173 /**
174 * Get the holders of a role.
175 *
176 * @param roleName the name of the role to query for
177 *
178 * @return the set of role holders. {@code null} should not be returned and indicates an issue.
179 */
180 @GuardedBy("RoleManagerService.mLock")
181 @Nullable
182 public ArraySet<String> getRoleHoldersLocked(@NonNull String roleName) {
183 throwIfDestroyedLocked();
184 return mRoles.get(roleName);
185 }
186
187 /**
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800188 * Set the names of all available roles.
189 *
190 * @param roleNames the names of all the available roles
191 */
192 @GuardedBy("RoleManagerService.mLock")
193 public void setRoleNamesLocked(@NonNull List<String> roleNames) {
194 throwIfDestroyedLocked();
195 boolean changed = false;
196 for (int i = mRoles.size() - 1; i >= 0; i--) {
197 String roleName = mRoles.keyAt(i);
198 if (!roleNames.contains(roleName)) {
199 ArraySet<String> packageNames = mRoles.valueAt(i);
200 if (!packageNames.isEmpty()) {
201 Slog.e(LOG_TAG, "Holders of a removed role should have been cleaned up, role: "
202 + roleName + ", holders: " + packageNames);
203 }
204 mRoles.removeAt(i);
205 changed = true;
206 }
207 }
208 int roleNamesSize = roleNames.size();
209 for (int i = 0; i < roleNamesSize; i++) {
210 String roleName = roleNames.get(i);
211 if (!mRoles.containsKey(roleName)) {
212 mRoles.put(roleName, new ArraySet<>());
213 Slog.i(LOG_TAG, "Added new role: " + roleName);
214 changed = true;
215 }
216 }
217 if (changed) {
218 writeAsyncLocked();
219 }
220 }
221
222 /**
Hai Zhangb7776682018-09-25 15:10:57 -0700223 * Add a holder to a role.
224 *
225 * @param roleName the name of the role to add the holder to
226 * @param packageName the package name of the new holder
227 *
228 * @return {@code false} only if the set of role holders is null, which should not happen and
229 * indicates an issue.
230 */
231 @CheckResult
232 @GuardedBy("RoleManagerService.mLock")
233 public boolean addRoleHolderLocked(@NonNull String roleName, @NonNull String packageName) {
234 throwIfDestroyedLocked();
235 ArraySet<String> roleHolders = mRoles.get(roleName);
236 if (roleHolders == null) {
Hai Zhangb295ac42018-11-16 16:08:18 -0800237 Slog.e(LOG_TAG, "Cannot add role holder for unknown role, role: " + roleName
238 + ", package: " + packageName);
Hai Zhangb7776682018-09-25 15:10:57 -0700239 return false;
240 }
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800241 boolean changed = roleHolders.add(packageName);
242 if (changed) {
243 writeAsyncLocked();
244 }
Hai Zhangb7776682018-09-25 15:10:57 -0700245 return true;
246 }
247
248 /**
249 * Remove a holder from a role.
250 *
251 * @param roleName the name of the role to remove the holder from
252 * @param packageName the package name of the holder to remove
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
258 @GuardedBy("RoleManagerService.mLock")
259 public boolean removeRoleHolderLocked(@NonNull String roleName, @NonNull String packageName) {
260 throwIfDestroyedLocked();
261 ArraySet<String> roleHolders = mRoles.get(roleName);
262 if (roleHolders == null) {
Hai Zhangb295ac42018-11-16 16:08:18 -0800263 Slog.e(LOG_TAG, "Cannot remove role holder for unknown role, role: " + roleName
264 + ", package: " + packageName);
Hai Zhangb7776682018-09-25 15:10:57 -0700265 return false;
266 }
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800267 boolean changed = roleHolders.remove(packageName);
268 if (changed) {
269 writeAsyncLocked();
270 }
Hai Zhangb7776682018-09-25 15:10:57 -0700271 return true;
272 }
273
274 /**
Hai Zhangb7776682018-09-25 15:10:57 -0700275 * Schedule writing the state to file.
276 */
277 @GuardedBy("RoleManagerService.mLock")
Hai Zhang458cedb2018-12-03 15:41:11 -0800278 private void writeAsyncLocked() {
Hai Zhangb7776682018-09-25 15:10:57 -0700279 throwIfDestroyedLocked();
Hai Zhang458cedb2018-12-03 15:41:11 -0800280
Hai Zhangb7776682018-09-25 15:10:57 -0700281 ArrayMap<String, ArraySet<String>> roles = new ArrayMap<>();
Eugene Suslaabdefba2018-11-09 18:06:43 -0800282 for (int i = 0, size = CollectionUtils.size(mRoles); i < size; ++i) {
Hai Zhangb7776682018-09-25 15:10:57 -0700283 String roleName = mRoles.keyAt(i);
284 ArraySet<String> roleHolders = mRoles.valueAt(i);
285 roleHolders = new ArraySet<>(roleHolders);
286 roles.put(roleName, roleHolders);
287 }
Hai Zhang458cedb2018-12-03 15:41:11 -0800288
Hai Zhangb7776682018-09-25 15:10:57 -0700289 mWriteHandler.removeCallbacksAndMessages(null);
Hai Zhang8e60a8f2018-11-20 11:21:09 -0800290 // TODO: Throttle writes.
Hai Zhang458cedb2018-12-03 15:41:11 -0800291 mWriteHandler.sendMessage(PooledLambda.obtainMessage(RoleUserState::writeSync, this,
292 mVersion, mLastGrantPackagesHash, roles));
Hai Zhangb7776682018-09-25 15:10:57 -0700293 }
294
295 @WorkerThread
Hai Zhang458cedb2018-12-03 15:41:11 -0800296 private void writeSync(int version, @Nullable String packagesHash,
297 @NonNull ArrayMap<String, ArraySet<String>> roles) {
Hai Zhangb295ac42018-11-16 16:08:18 -0800298 AtomicFile atomicFile = new AtomicFile(getFile(mUserId), "roles-" + mUserId);
Hai Zhangb7776682018-09-25 15:10:57 -0700299 FileOutputStream out = null;
300 try {
Hai Zhangb295ac42018-11-16 16:08:18 -0800301 out = atomicFile.startWrite();
Hai Zhangb7776682018-09-25 15:10:57 -0700302
303 XmlSerializer serializer = Xml.newSerializer();
304 serializer.setOutput(out, StandardCharsets.UTF_8.name());
305 serializer.setFeature(
306 "http://xmlpull.org/v1/doc/features.html#indent-output", true);
307 serializer.startDocument(null, true);
308
Hai Zhang458cedb2018-12-03 15:41:11 -0800309 serializeRoles(serializer, version, packagesHash, roles);
Hai Zhangb7776682018-09-25 15:10:57 -0700310
311 serializer.endDocument();
Hai Zhangb295ac42018-11-16 16:08:18 -0800312 atomicFile.finishWrite(out);
313 } catch (IllegalArgumentException | IllegalStateException | IOException e) {
314 Slog.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup", e);
315 if (out != null) {
316 atomicFile.failWrite(out);
317 }
Hai Zhangb7776682018-09-25 15:10:57 -0700318 } finally {
319 IoUtils.closeQuietly(out);
320 }
321 }
322
323 @WorkerThread
324 private void serializeRoles(@NonNull XmlSerializer serializer, int version,
Hai Zhang458cedb2018-12-03 15:41:11 -0800325 @Nullable String packagesHash, @NonNull ArrayMap<String, ArraySet<String>> roles)
Eugene Suslaabdefba2018-11-09 18:06:43 -0800326 throws IOException {
Hai Zhangb7776682018-09-25 15:10:57 -0700327 serializer.startTag(null, TAG_ROLES);
Hai Zhang458cedb2018-12-03 15:41:11 -0800328
Hai Zhangb7776682018-09-25 15:10:57 -0700329 serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version));
Hai Zhang458cedb2018-12-03 15:41:11 -0800330
331 if (packagesHash != null) {
332 serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash);
333 }
334
Hai Zhangb7776682018-09-25 15:10:57 -0700335 for (int i = 0, size = roles.size(); i < size; ++i) {
336 String roleName = roles.keyAt(i);
337 ArraySet<String> roleHolders = roles.valueAt(i);
Hai Zhang458cedb2018-12-03 15:41:11 -0800338
Hai Zhangb7776682018-09-25 15:10:57 -0700339 serializer.startTag(null, TAG_ROLE);
340 serializer.attribute(null, ATTRIBUTE_NAME, roleName);
341 serializeRoleHolders(serializer, roleHolders);
342 serializer.endTag(null, TAG_ROLE);
343 }
Hai Zhang458cedb2018-12-03 15:41:11 -0800344
Hai Zhangb7776682018-09-25 15:10:57 -0700345 serializer.endTag(null, TAG_ROLES);
346 }
347
348 @WorkerThread
349 private void serializeRoleHolders(@NonNull XmlSerializer serializer,
350 @NonNull ArraySet<String> roleHolders) throws IOException {
351 for (int i = 0, size = roleHolders.size(); i < size; ++i) {
352 String roleHolder = roleHolders.valueAt(i);
Hai Zhang458cedb2018-12-03 15:41:11 -0800353
Hai Zhangb7776682018-09-25 15:10:57 -0700354 serializer.startTag(null, TAG_HOLDER);
355 serializer.attribute(null, ATTRIBUTE_NAME, roleHolder);
356 serializer.endTag(null, TAG_HOLDER);
357 }
358 }
359
360 /**
361 * Read the state from file.
362 */
363 @GuardedBy("RoleManagerService.mLock")
Hai Zhang458cedb2018-12-03 15:41:11 -0800364 private void readSyncLocked() {
Hai Zhangb295ac42018-11-16 16:08:18 -0800365 File file = getFile(mUserId);
366 try (FileInputStream in = new AtomicFile(file).openRead()) {
Hai Zhangb7776682018-09-25 15:10:57 -0700367 XmlPullParser parser = Xml.newPullParser();
368 parser.setInput(in, null);
369 parseXmlLocked(parser);
Hai Zhangb295ac42018-11-16 16:08:18 -0800370 } catch (FileNotFoundException e) {
371 Slog.i(LOG_TAG, "roles.xml not found");
Hai Zhangb7776682018-09-25 15:10:57 -0700372 } catch (XmlPullParserException | IOException e) {
Hai Zhangb295ac42018-11-16 16:08:18 -0800373 throw new IllegalStateException("Failed to parse roles.xml: " + file, e);
Hai Zhangb7776682018-09-25 15:10:57 -0700374 }
375 }
376
377 private void parseXmlLocked(@NonNull XmlPullParser parser) throws IOException,
378 XmlPullParserException {
Hai Zhangb7776682018-09-25 15:10:57 -0700379 int type;
Hai Zhangb295ac42018-11-16 16:08:18 -0800380 int depth;
381 int innerDepth = parser.getDepth() + 1;
Hai Zhangb7776682018-09-25 15:10:57 -0700382 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
Hai Zhangb295ac42018-11-16 16:08:18 -0800383 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
384 if (depth > innerDepth || type != XmlPullParser.START_TAG) {
Hai Zhangb7776682018-09-25 15:10:57 -0700385 continue;
386 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800387
Hai Zhangb7776682018-09-25 15:10:57 -0700388 if (parser.getName().equals(TAG_ROLES)) {
389 parseRolesLocked(parser);
390 return;
391 }
392 }
Hai Zhang458cedb2018-12-03 15:41:11 -0800393 Slog.w(LOG_TAG, "Missing <" + TAG_ROLES + "> in roles.xml");
Hai Zhangb7776682018-09-25 15:10:57 -0700394 }
395
396 private void parseRolesLocked(@NonNull XmlPullParser parser) throws IOException,
397 XmlPullParserException {
398 mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_VERSION));
Eugene Suslaabdefba2018-11-09 18:06:43 -0800399 mLastGrantPackagesHash = parser.getAttributeValue(null, ATTRIBUTE_PACKAGES_HASH);
Hai Zhang458cedb2018-12-03 15:41:11 -0800400 mRoles.clear();
Hai Zhangb295ac42018-11-16 16:08:18 -0800401
Hai Zhangb7776682018-09-25 15:10:57 -0700402 int type;
Hai Zhangb295ac42018-11-16 16:08:18 -0800403 int depth;
404 int innerDepth = parser.getDepth() + 1;
Hai Zhangb7776682018-09-25 15:10:57 -0700405 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
Hai Zhangb295ac42018-11-16 16:08:18 -0800406 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
407 if (depth > innerDepth || type != XmlPullParser.START_TAG) {
Hai Zhangb7776682018-09-25 15:10:57 -0700408 continue;
409 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800410
Hai Zhangb7776682018-09-25 15:10:57 -0700411 if (parser.getName().equals(TAG_ROLE)) {
412 String roleName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
413 ArraySet<String> roleHolders = parseRoleHoldersLocked(parser);
414 mRoles.put(roleName, roleHolders);
415 }
416 }
417 }
418
419 @NonNull
420 private ArraySet<String> parseRoleHoldersLocked(@NonNull XmlPullParser parser)
421 throws IOException, XmlPullParserException {
422 ArraySet<String> roleHolders = new ArraySet<>();
Hai Zhangb295ac42018-11-16 16:08:18 -0800423
Hai Zhangb7776682018-09-25 15:10:57 -0700424 int type;
Hai Zhangb295ac42018-11-16 16:08:18 -0800425 int depth;
426 int innerDepth = parser.getDepth() + 1;
Hai Zhangb7776682018-09-25 15:10:57 -0700427 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
Hai Zhangb295ac42018-11-16 16:08:18 -0800428 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
429 if (depth > innerDepth || type != XmlPullParser.START_TAG) {
Hai Zhangb7776682018-09-25 15:10:57 -0700430 continue;
431 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800432
Hai Zhangb7776682018-09-25 15:10:57 -0700433 if (parser.getName().equals(TAG_HOLDER)) {
434 String roleHolder = parser.getAttributeValue(null, ATTRIBUTE_NAME);
435 roleHolders.add(roleHolder);
436 }
437 }
Hai Zhangb295ac42018-11-16 16:08:18 -0800438
Hai Zhangb7776682018-09-25 15:10:57 -0700439 return roleHolders;
440 }
441
442 /**
443 * Destroy this state and delete the corresponding file. Any pending writes to the file will be
444 * cancelled and any future interaction with this state will throw an exception.
445 */
446 @GuardedBy("RoleManagerService.mLock")
447 public void destroySyncLocked() {
448 throwIfDestroyedLocked();
449 mWriteHandler.removeCallbacksAndMessages(null);
450 getFile(mUserId).delete();
451 mDestroyed = true;
452 }
453
454 @GuardedBy("RoleManagerService.mLock")
455 private void throwIfDestroyedLocked() {
456 if (mDestroyed) {
457 throw new IllegalStateException("This RoleUserState has already been destroyed");
458 }
459 }
460
461 private static @NonNull File getFile(@UserIdInt int userId) {
462 return new File(Environment.getUserSystemDirectory(userId), ROLES_FILE_NAME);
463 }
464}