• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.role;
18 
19 import android.annotation.CheckResult;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.UserIdInt;
23 import android.annotation.WorkerThread;
24 import android.os.Build;
25 import android.os.Handler;
26 import android.os.UserHandle;
27 import android.permission.internal.compat.UserHandleCompat;
28 import android.util.ArrayMap;
29 import android.util.ArraySet;
30 import android.util.Log;
31 
32 import androidx.annotation.RequiresApi;
33 
34 import com.android.internal.annotations.GuardedBy;
35 import com.android.internal.util.dump.DualDumpOutputStream;
36 import com.android.modules.utils.BackgroundThread;
37 import com.android.permission.util.CollectionUtils;
38 import com.android.role.persistence.RolesPersistence;
39 import com.android.role.persistence.RolesState;
40 import com.android.server.role.RoleServicePlatformHelper;
41 
42 import java.util.ArrayList;
43 import java.util.Collections;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Objects;
47 import java.util.Set;
48 
49 /**
50  * Stores the state of roles for a user.
51  */
52 @RequiresApi(Build.VERSION_CODES.S)
53 class RoleUserState {
54     private static final String LOG_TAG = RoleUserState.class.getSimpleName();
55 
56     public static final int VERSION_UNDEFINED = -1;
57 
58     public static final int VERSION_FALLBACK_STATE_MIGRATED = 1;
59 
60     private static final long WRITE_DELAY_MILLIS = 200;
61 
62     private final RolesPersistence mPersistence = RolesPersistence.createInstance();
63 
64     @UserIdInt
65     private final int mUserId;
66 
67     @NonNull
68     private final RoleServicePlatformHelper mPlatformHelper;
69 
70     @NonNull
71     private final Callback mCallback;
72 
73     @NonNull
74     private final Object mLock = new Object();
75 
76     @GuardedBy("mLock")
77     private int mVersion = VERSION_UNDEFINED;
78 
79     @GuardedBy("mLock")
80     @Nullable
81     private String mPackagesHash;
82 
83     @GuardedBy("mLock")
84     private boolean mBypassingRoleQualification;
85 
86     /**
87      * Maps role names to its holders' package names. The values should never be null.
88      */
89     @GuardedBy("mLock")
90     @NonNull
91     private ArrayMap<String, ArraySet<String>> mRoles = new ArrayMap<>();
92 
93     /**
94      * Role names of the roles with fallback enabled.
95      */
96     @GuardedBy("mLock")
97     @NonNull
98     private ArraySet<String> mFallbackEnabledRoles = new ArraySet<>();
99 
100     @GuardedBy("mLock")
101     @NonNull
102     private ArrayMap<String, Integer> mActiveUserIds = new ArrayMap<>();
103 
104     @GuardedBy("mLock")
105     @NonNull
106     private final Map<String, List<String>> mDefaultHoldersForTest = new ArrayMap<>();
107 
108     @GuardedBy("mLock")
109     @NonNull
110     private final Set<String> mRolesVisibleForTest = new ArraySet<>();
111 
112     @GuardedBy("mLock")
113     private boolean mWriteScheduled;
114 
115     @GuardedBy("mLock")
116     private boolean mDestroyed;
117 
118     @NonNull
119     private final Handler mWriteHandler = new Handler(BackgroundThread.get().getLooper());
120 
121     /**
122      * Create a new user state, and read its state from disk if previously persisted.
123      *
124      * @param userId the user id for this user state
125      * @param platformHelper the platform helper
126      * @param callback the callback for this user state
127      * @param bypassingRoleQualification whether role qualification is being bypassed
128      */
RoleUserState(@serIdInt int userId, @NonNull RoleServicePlatformHelper platformHelper, @NonNull Callback callback, boolean bypassingRoleQualification)129     public RoleUserState(@UserIdInt int userId, @NonNull RoleServicePlatformHelper platformHelper,
130             @NonNull Callback callback, boolean bypassingRoleQualification) {
131         mUserId = userId;
132         mPlatformHelper = platformHelper;
133         mCallback = callback;
134 
135         synchronized (mLock) {
136             mBypassingRoleQualification = bypassingRoleQualification;
137         }
138 
139         readFile();
140     }
141 
142     /**
143      * Get the version of this user state.
144      */
getVersion()145     public int getVersion() {
146         synchronized (mLock) {
147             return mVersion;
148         }
149     }
150 
151     /**
152      * Set the version of this user state.
153      *
154      * @param version the version to set
155      */
setVersion(int version)156     public void setVersion(int version) {
157         synchronized (mLock) {
158             if (mVersion == version) {
159                 return;
160             }
161             mVersion = version;
162             scheduleWriteFileLocked();
163         }
164     }
165 
166     /**
167      * Checks the version and returns whether a version upgrade is needed.
168      */
isVersionUpgradeNeeded()169     public boolean isVersionUpgradeNeeded() {
170         synchronized (mLock) {
171             return mVersion < VERSION_FALLBACK_STATE_MIGRATED;
172         }
173     }
174 
175     /**
176      * Get the hash representing the state of packages during the last time initial grants was run.
177      *
178      * @return the hash representing the state of packages
179      */
180     @Nullable
getPackagesHash()181     public String getPackagesHash() {
182         synchronized (mLock) {
183             return mPackagesHash;
184         }
185     }
186 
187     /**
188      * Set the hash representing the state of packages during the last time initial grants was run.
189      *
190      * @param packagesHash the hash representing the state of packages
191      */
setPackagesHash(@ullable String packagesHash)192     public void setPackagesHash(@Nullable String packagesHash) {
193         synchronized (mLock) {
194             if (Objects.equals(mPackagesHash, packagesHash)) {
195                 return;
196             }
197             mPackagesHash = packagesHash;
198             scheduleWriteFileLocked();
199         }
200     }
201 
202     /**
203      * Check whether role qualifications is being bypassed.
204      *
205      * @return whether role qualifications is being bypassed
206      */
isBypassingRoleQualification()207     public boolean isBypassingRoleQualification() {
208         synchronized (mLock) {
209             return mBypassingRoleQualification;
210         }
211     }
212 
213     /**
214      * Set whether role qualifications is being bypassed.
215      *
216      * @param bypassingRoleQualification whether role qualifications is being bypassed
217      */
setBypassingRoleQualification(boolean bypassingRoleQualification)218     public void setBypassingRoleQualification(boolean bypassingRoleQualification) {
219         synchronized (mLock) {
220             if (mBypassingRoleQualification == bypassingRoleQualification) {
221                 return;
222             }
223             mBypassingRoleQualification = bypassingRoleQualification;
224             scheduleWriteFileLocked();
225         }
226     }
227 
isFallbackEnabled(@onNull String roleName)228     public boolean isFallbackEnabled(@NonNull String roleName) {
229         synchronized (mLock) {
230             return mFallbackEnabledRoles.contains(roleName);
231         }
232     }
233 
setFallbackEnabled(@onNull String roleName, boolean fallbackEnabled)234     public void setFallbackEnabled(@NonNull String roleName, boolean fallbackEnabled) {
235         synchronized (mLock) {
236             if (!mRoles.containsKey(roleName)) {
237                 Log.e(LOG_TAG, "Cannot set fallback enabled for unknown role, role: " + roleName
238                         + ", fallbackEnabled: " + fallbackEnabled);
239                 return;
240             }
241             if (mFallbackEnabledRoles.contains(roleName) == fallbackEnabled) {
242                 return;
243             }
244             if (fallbackEnabled) {
245                 mFallbackEnabledRoles.add(roleName);
246             } else {
247                 mFallbackEnabledRoles.remove(roleName);
248             }
249             scheduleWriteFileLocked();
250         }
251     }
252 
253     /**
254      * Upgrade this user state to the latest version if needed.
255      */
upgradeVersion(@onNull List<String> legacyFallbackDisabledRoles)256     public void upgradeVersion(@NonNull List<String> legacyFallbackDisabledRoles) {
257         synchronized (mLock) {
258             if (mVersion < VERSION_FALLBACK_STATE_MIGRATED) {
259                 mFallbackEnabledRoles.addAll(mRoles.keySet());
260                 int legacyFallbackDisabledRolesSize = legacyFallbackDisabledRoles.size();
261                 for (int i = 0; i < legacyFallbackDisabledRolesSize; i++) {
262                     String roleName = legacyFallbackDisabledRoles.get(i);
263                     mFallbackEnabledRoles.remove(roleName);
264                 }
265                 Log.v(LOG_TAG, "Migrated fallback enabled roles: " + mFallbackEnabledRoles);
266                 mVersion = VERSION_FALLBACK_STATE_MIGRATED;
267                 scheduleWriteFileLocked();
268             }
269         }
270     }
271 
272     /**
273      * Get whether the role is available.
274      *
275      * @param roleName the name of the role to get the holders for
276      *
277      * @return whether the role is available
278      */
isRoleAvailable(@onNull String roleName)279     public boolean isRoleAvailable(@NonNull String roleName) {
280         synchronized (mLock) {
281             return mRoles.containsKey(roleName);
282         }
283     }
284 
285     /**
286      * Get the holders of a role.
287      *
288      * @param roleName the name of the role to query for
289      *
290      * @return the set of role holders, or {@code null} if and only if the role is not found
291      */
292     @Nullable
getRoleHolders(@onNull String roleName)293     public ArraySet<String> getRoleHolders(@NonNull String roleName) {
294         synchronized (mLock) {
295             ArraySet<String> packageNames = mRoles.get(roleName);
296             if (packageNames == null) {
297                 return null;
298             }
299             return new ArraySet<>(packageNames);
300         }
301     }
302 
303     /**
304      * Adds the given role, effectively marking it as {@link #isRoleAvailable available}
305      *
306      * @param roleName the name of the role
307      *
308      * @return whether any changes were made
309      */
addRoleName(@onNull String roleName)310     public boolean addRoleName(@NonNull String roleName) {
311         synchronized (mLock) {
312             if (!mRoles.containsKey(roleName)) {
313                 mRoles.put(roleName, new ArraySet<>());
314                 mFallbackEnabledRoles.add(roleName);
315                 Log.i(LOG_TAG, "Added new role: " + roleName);
316                 scheduleWriteFileLocked();
317                 return true;
318             } else {
319                 return false;
320             }
321         }
322     }
323 
324     /**
325      * Set the names of all available roles.
326      *
327      * @param roleNames the names of all the available roles
328      */
setRoleNames(@onNull List<String> roleNames)329     public void setRoleNames(@NonNull List<String> roleNames) {
330         synchronized (mLock) {
331             boolean changed = false;
332 
333             for (int i = mRoles.size() - 1; i >= 0; i--) {
334                 String roleName = mRoles.keyAt(i);
335 
336                 if (!roleNames.contains(roleName)) {
337                     ArraySet<String> packageNames = mRoles.valueAt(i);
338                     if (!packageNames.isEmpty()) {
339                         Log.e(LOG_TAG, "Holders of a removed role should have been cleaned up,"
340                                 + " role: " + roleName + ", holders: " + packageNames);
341                     }
342                     mRoles.removeAt(i);
343                     mFallbackEnabledRoles.remove(roleName);
344                     changed = true;
345                 }
346             }
347 
348             int roleNamesSize = roleNames.size();
349             for (int i = 0; i < roleNamesSize; i++) {
350                 changed |= addRoleName(roleNames.get(i));
351             }
352 
353             if (changed) {
354                 scheduleWriteFileLocked();
355             }
356         }
357     }
358 
359     /**
360      * Add a holder to a role.
361      *
362      * @param roleName the name of the role to add the holder to
363      * @param packageName the package name of the new holder
364      *
365      * @return {@code false} if and only if the role is not found
366      */
367     @CheckResult
addRoleHolder(@onNull String roleName, @NonNull String packageName)368     public boolean addRoleHolder(@NonNull String roleName, @NonNull String packageName) {
369         boolean changed;
370 
371         synchronized (mLock) {
372             ArraySet<String> roleHolders = mRoles.get(roleName);
373             if (roleHolders == null) {
374                 Log.e(LOG_TAG, "Cannot add role holder for unknown role, role: " + roleName
375                         + ", package: " + packageName);
376                 return false;
377             }
378             changed = roleHolders.add(packageName);
379             if (changed) {
380                 scheduleWriteFileLocked();
381             }
382         }
383 
384         if (changed) {
385             mCallback.onRoleHoldersChanged(roleName, mUserId);
386         }
387         return true;
388     }
389 
390     /**
391      * Remove a holder from a role.
392      *
393      * @param roleName the name of the role to remove the holder from
394      * @param packageName the package name of the holder to remove
395      *
396      * @return {@code false} if and only if the role is not found
397      */
398     @CheckResult
removeRoleHolder(@onNull String roleName, @NonNull String packageName)399     public boolean removeRoleHolder(@NonNull String roleName, @NonNull String packageName) {
400         boolean changed;
401 
402         synchronized (mLock) {
403             ArraySet<String> roleHolders = mRoles.get(roleName);
404             if (roleHolders == null) {
405                 Log.e(LOG_TAG, "Cannot remove role holder for unknown role, role: " + roleName
406                         + ", package: " + packageName);
407                 return false;
408             }
409 
410             changed = roleHolders.remove(packageName);
411             if (changed) {
412                 scheduleWriteFileLocked();
413             }
414         }
415 
416         if (changed) {
417             mCallback.onRoleHoldersChanged(roleName, mUserId);
418         }
419         return true;
420     }
421 
422     /**
423      * @see android.app.role.RoleManager#getHeldRolesFromController
424      */
425     @NonNull
getHeldRoles(@onNull String packageName)426     public List<String> getHeldRoles(@NonNull String packageName) {
427         synchronized (mLock) {
428             List<String> roleNames = new ArrayList<>();
429             int size = mRoles.size();
430             for (int i = 0; i < size; i++) {
431                 if (mRoles.valueAt(i).contains(packageName)) {
432                     roleNames.add(mRoles.keyAt(i));
433                 }
434             }
435             return roleNames;
436         }
437     }
438 
439     /**
440      * Return the active user for the role
441      *
442      * @param roleName the name of the role to get the active user for
443      */
getActiveUserForRole(@onNull String roleName)444     public int getActiveUserForRole(@NonNull String roleName) {
445         synchronized (mLock) {
446             return mActiveUserIds.getOrDefault(roleName, UserHandleCompat.USER_NULL);
447         }
448     }
449 
450     @NonNull
getActiveRolesForUser(@serIdInt int userId)451     public List<String> getActiveRolesForUser(@UserIdInt int userId) {
452         synchronized (mLock) {
453             List<String> activeRoleNames = new ArrayList<>();
454             int activeUserIdsSize = mActiveUserIds.size();
455             for (int i = 0; i < activeUserIdsSize; i++) {
456                 int activeUserId = mActiveUserIds.valueAt(i);
457                 if (activeUserId == userId) {
458                     String roleName = mActiveUserIds.keyAt(i);
459                     activeRoleNames.add(roleName);
460                 }
461             }
462             return activeRoleNames;
463         }
464     }
465 
466     /**
467      * Set the active user for the role
468      *
469      * @param roleName the name of the role to set the active user for
470      * @param userId User id to set as active for this role
471      * @return whether any changes were made
472      */
setActiveUserForRole(@onNull String roleName, @UserIdInt int userId)473     public boolean setActiveUserForRole(@NonNull String roleName, @UserIdInt int userId) {
474         if (!com.android.permission.flags.Flags.crossUserRoleEnabled()) {
475             return false;
476         }
477         synchronized (mLock) {
478             Integer currentActiveUserId = mActiveUserIds.get(roleName);
479             // If we have pre-existing roles that weren't profile group exclusive and don't have an
480             // active user, ensure we set and write value, and return modified, otherwise other
481             // users might not have role holder revoked.
482             if (currentActiveUserId != null && currentActiveUserId == userId) {
483                 return false;
484             }
485             mActiveUserIds.put(roleName, userId);
486             scheduleWriteFileLocked();
487             return true;
488         }
489     }
490 
491     @NonNull
getDefaultHoldersForTest(@onNull String roleName)492     public List<String> getDefaultHoldersForTest(@NonNull String roleName) {
493         synchronized (mLock) {
494             return mDefaultHoldersForTest.getOrDefault(roleName, Collections.emptyList());
495         }
496     }
497 
setDefaultHoldersForTest(@onNull String roleName, @NonNull List<String> packageNames)498     public void setDefaultHoldersForTest(@NonNull String roleName,
499             @NonNull List<String> packageNames) {
500         synchronized (mLock) {
501             mDefaultHoldersForTest.put(roleName, packageNames);
502         }
503     }
504 
isRoleVisibleForTest(@onNull String roleName)505     public boolean isRoleVisibleForTest(@NonNull String roleName) {
506         synchronized (mLock) {
507             return mRolesVisibleForTest.contains(roleName);
508         }
509     }
510 
setRoleVisibleForTest(@onNull String roleName, boolean visible)511     public void setRoleVisibleForTest(@NonNull String roleName, boolean visible) {
512         synchronized (mLock) {
513             if (visible) {
514                 mRolesVisibleForTest.add(roleName);
515             } else {
516                 mRolesVisibleForTest.remove(roleName);
517             }
518         }
519     }
520 
521     /**
522      * Schedule writing the state to file.
523      */
524     @GuardedBy("mLock")
scheduleWriteFileLocked()525     private void scheduleWriteFileLocked() {
526         if (mDestroyed) {
527             return;
528         }
529 
530         if (!mWriteScheduled) {
531             mWriteHandler.postDelayed(this::writeFile, WRITE_DELAY_MILLIS);
532             mWriteScheduled = true;
533         }
534     }
535 
536     @WorkerThread
writeFile()537     private void writeFile() {
538         RolesState roles;
539         synchronized (mLock) {
540             if (mDestroyed) {
541                 return;
542             }
543 
544             mWriteScheduled = false;
545 
546             // Force a reconciliation on next boot if we are bypassing role qualification now.
547             String packagesHash = mBypassingRoleQualification ? null : mPackagesHash;
548             if (com.android.permission.flags.Flags.crossUserRoleEnabled()) {
549                 roles = new RolesState(mVersion, packagesHash,
550                         (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked(),
551                         snapshotFallbackEnabledRoles(), snapshotActiveUserIds());
552             } else {
553                 roles = new RolesState(mVersion, packagesHash,
554                         (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked(),
555                         snapshotFallbackEnabledRoles());
556             }
557         }
558 
559         mPersistence.writeForUser(roles, UserHandle.of(mUserId));
560     }
561 
readFile()562     private void readFile() {
563         synchronized (mLock) {
564             RolesState roleState = mPersistence.readForUser(UserHandle.of(mUserId));
565 
566             Map<String, Set<String>> roles;
567             Set<String> fallbackEnabledRoles;
568             Map<String, Integer> activeUserIds;
569             if (roleState != null) {
570                 mVersion = roleState.getVersion();
571                 mPackagesHash = roleState.getPackagesHash();
572                 roles = roleState.getRoles();
573                 fallbackEnabledRoles = roleState.getFallbackEnabledRoles();
574                 activeUserIds = roleState.getActiveUserIds();
575             } else {
576                 roles = mPlatformHelper.getLegacyRoleState(mUserId);
577                 fallbackEnabledRoles = roles.keySet();
578                 activeUserIds = Collections.emptyMap();
579             }
580             mRoles.clear();
581             for (Map.Entry<String, Set<String>> entry : roles.entrySet()) {
582                 String roleName = entry.getKey();
583                 ArraySet<String> roleHolders = new ArraySet<>(entry.getValue());
584                 mRoles.put(roleName, roleHolders);
585             }
586             mFallbackEnabledRoles.clear();
587             mFallbackEnabledRoles.addAll(fallbackEnabledRoles);
588             mActiveUserIds.clear();
589             if (com.android.permission.flags.Flags.crossUserRoleEnabled()) {
590                 mActiveUserIds.putAll(activeUserIds);
591             }
592             if (roleState == null) {
593                 scheduleWriteFileLocked();
594             }
595         }
596     }
597 
598     /**
599      * Dump this user state.
600      *
601      * @param dumpOutputStream the output stream to dump to
602      */
dump(@onNull DualDumpOutputStream dumpOutputStream, @NonNull String fieldName, long fieldId)603     public void dump(@NonNull DualDumpOutputStream dumpOutputStream, @NonNull String fieldName,
604             long fieldId) {
605         int version;
606         String packagesHash;
607         ArrayMap<String, ArraySet<String>> roles;
608         ArrayMap<String, Integer> activeUserIds;
609         ArraySet<String> fallbackEnabledRoles;
610         synchronized (mLock) {
611             version = mVersion;
612             packagesHash = mPackagesHash;
613             roles = snapshotRolesLocked();
614             fallbackEnabledRoles = snapshotFallbackEnabledRoles();
615             activeUserIds = snapshotActiveUserIds();
616         }
617 
618         long fieldToken = dumpOutputStream.start(fieldName, fieldId);
619         dumpOutputStream.write("user_id", RoleUserStateProto.USER_ID, mUserId);
620         dumpOutputStream.write("version", RoleUserStateProto.VERSION, version);
621         dumpOutputStream.write("packages_hash", RoleUserStateProto.PACKAGES_HASH, packagesHash);
622 
623         int rolesSize = roles.size();
624         for (int rolesIndex = 0; rolesIndex < rolesSize; rolesIndex++) {
625             String roleName = roles.keyAt(rolesIndex);
626             ArraySet<String> roleHolders = roles.valueAt(rolesIndex);
627             boolean fallbackEnabled = fallbackEnabledRoles.contains(roleName);
628             Integer activeUserId = activeUserIds.get(roleName);
629 
630             long rolesToken = dumpOutputStream.start("roles", RoleUserStateProto.ROLES);
631             dumpOutputStream.write("name", RoleProto.NAME, roleName);
632             dumpOutputStream.write("fallback_enabled", RoleProto.FALLBACK_ENABLED, fallbackEnabled);
633             if (activeUserId != null) {
634                 dumpOutputStream.write("active_user_id", RoleProto.ACTIVE_USER_ID, activeUserId);
635             }
636             int roleHoldersSize = roleHolders.size();
637             for (int roleHoldersIndex = 0; roleHoldersIndex < roleHoldersSize; roleHoldersIndex++) {
638                 String roleHolder = roleHolders.valueAt(roleHoldersIndex);
639 
640                 dumpOutputStream.write("holders", RoleProto.HOLDERS, roleHolder);
641             }
642 
643             dumpOutputStream.end(rolesToken);
644         }
645 
646         dumpOutputStream.end(fieldToken);
647     }
648 
649     /**
650      * Get the roles and their holders.
651      *
652      * @return A copy of the roles and their holders
653      */
654     @NonNull
getRolesAndHolders()655     public ArrayMap<String, ArraySet<String>> getRolesAndHolders() {
656         synchronized (mLock) {
657             return snapshotRolesLocked();
658         }
659     }
660 
661     @GuardedBy("mLock")
662     @NonNull
snapshotRolesLocked()663     private ArrayMap<String, ArraySet<String>> snapshotRolesLocked() {
664         ArrayMap<String, ArraySet<String>> roles = new ArrayMap<>();
665         for (int i = 0, size = CollectionUtils.size(mRoles); i < size; ++i) {
666             String roleName = mRoles.keyAt(i);
667             ArraySet<String> roleHolders = mRoles.valueAt(i);
668 
669             roleHolders = new ArraySet<>(roleHolders);
670             roles.put(roleName, roleHolders);
671         }
672         return roles;
673     }
674 
675     @GuardedBy("mLock")
676     @NonNull
snapshotFallbackEnabledRoles()677     private ArraySet<String> snapshotFallbackEnabledRoles() {
678         return new ArraySet<>(mFallbackEnabledRoles);
679     }
680 
681     @GuardedBy("mLock")
682     @NonNull
snapshotActiveUserIds()683     private ArrayMap<String, Integer> snapshotActiveUserIds() {
684         return new ArrayMap<>(mActiveUserIds);
685     }
686 
687     /**
688      * Destroy this user state and delete the corresponding file. Any pending writes to the file
689      * will be cancelled, and any future interaction with this state will throw an exception.
690      */
destroy()691     public void destroy() {
692         synchronized (mLock) {
693             if (mDestroyed) {
694                 throw new IllegalStateException("This RoleUserState has already been destroyed");
695             }
696             mWriteHandler.removeCallbacksAndMessages(null);
697             mPersistence.deleteForUser(UserHandle.of(mUserId));
698             mDestroyed = true;
699         }
700     }
701 
702     /**
703      * Callback for a user state.
704      */
705     public interface Callback {
706 
707         /**
708          * Called when the holders of roles are changed.
709          *
710          * @param roleName the name of the role whose holders are changed
711          * @param userId the user id for this role holder change
712          */
onRoleHoldersChanged(@onNull String roleName, @UserIdInt int userId)713         void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId);
714     }
715 }
716