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