1 /* 2 * Copyright (C) 2017 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 18 package com.android.server.companion; 19 20 import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; 21 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; 22 import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; 23 import static android.content.pm.PackageManager.CERT_INPUT_SHA256; 24 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 25 import static android.os.Process.SYSTEM_UID; 26 import static android.os.UserHandle.getCallingUserId; 27 28 import static com.android.internal.util.CollectionUtils.any; 29 import static com.android.internal.util.Preconditions.checkState; 30 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; 31 import static com.android.server.companion.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED; 32 import static com.android.server.companion.MetricUtils.logRemoveAssociation; 33 import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature; 34 import static com.android.server.companion.PackageUtils.getPackageInfo; 35 import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice; 36 import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; 37 import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageCompanionDevice; 38 import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr; 39 import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId; 40 import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks; 41 import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation; 42 43 import static java.util.Objects.requireNonNull; 44 import static java.util.concurrent.TimeUnit.DAYS; 45 import static java.util.concurrent.TimeUnit.MINUTES; 46 47 import android.annotation.NonNull; 48 import android.annotation.Nullable; 49 import android.annotation.SuppressLint; 50 import android.annotation.UserIdInt; 51 import android.app.ActivityManager; 52 import android.app.ActivityManager.RunningAppProcessInfo; 53 import android.app.ActivityManagerInternal; 54 import android.app.AppOpsManager; 55 import android.app.NotificationManager; 56 import android.app.PendingIntent; 57 import android.bluetooth.BluetoothDevice; 58 import android.companion.AssociationInfo; 59 import android.companion.AssociationRequest; 60 import android.companion.DeviceNotAssociatedException; 61 import android.companion.IAssociationRequestCallback; 62 import android.companion.ICompanionDeviceManager; 63 import android.companion.IOnAssociationsChangedListener; 64 import android.companion.IOnMessageReceivedListener; 65 import android.companion.IOnTransportsChangedListener; 66 import android.companion.ISystemDataTransferCallback; 67 import android.companion.datatransfer.PermissionSyncRequest; 68 import android.companion.utils.FeatureUtils; 69 import android.content.ComponentName; 70 import android.content.Context; 71 import android.content.SharedPreferences; 72 import android.content.pm.PackageInfo; 73 import android.content.pm.PackageManager; 74 import android.content.pm.PackageManagerInternal; 75 import android.content.pm.UserInfo; 76 import android.net.MacAddress; 77 import android.net.NetworkPolicyManager; 78 import android.os.Binder; 79 import android.os.Environment; 80 import android.os.Handler; 81 import android.os.Message; 82 import android.os.Parcel; 83 import android.os.ParcelFileDescriptor; 84 import android.os.PowerWhitelistManager; 85 import android.os.RemoteCallbackList; 86 import android.os.RemoteException; 87 import android.os.ResultReceiver; 88 import android.os.ServiceManager; 89 import android.os.ShellCallback; 90 import android.os.SystemProperties; 91 import android.os.UserHandle; 92 import android.os.UserManager; 93 import android.util.ArraySet; 94 import android.util.ExceptionUtils; 95 import android.util.Log; 96 import android.util.Slog; 97 import android.util.SparseArray; 98 import android.util.SparseBooleanArray; 99 100 import com.android.internal.annotations.GuardedBy; 101 import com.android.internal.app.IAppOpsService; 102 import com.android.internal.content.PackageMonitor; 103 import com.android.internal.infra.PerUser; 104 import com.android.internal.notification.NotificationAccessConfirmationActivityContract; 105 import com.android.internal.os.BackgroundThread; 106 import com.android.internal.util.ArrayUtils; 107 import com.android.internal.util.DumpUtils; 108 import com.android.server.FgThread; 109 import com.android.server.LocalServices; 110 import com.android.server.SystemService; 111 import com.android.server.companion.datatransfer.SystemDataTransferProcessor; 112 import com.android.server.companion.datatransfer.SystemDataTransferRequestStore; 113 import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall; 114 import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController; 115 import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback; 116 import com.android.server.companion.presence.CompanionDevicePresenceMonitor; 117 import com.android.server.companion.transport.CompanionTransportManager; 118 import com.android.server.pm.UserManagerInternal; 119 import com.android.server.wm.ActivityTaskManagerInternal; 120 121 import java.io.File; 122 import java.io.FileDescriptor; 123 import java.io.PrintWriter; 124 import java.util.ArrayList; 125 import java.util.Collection; 126 import java.util.Collections; 127 import java.util.HashMap; 128 import java.util.HashSet; 129 import java.util.List; 130 import java.util.Map; 131 import java.util.Set; 132 133 @SuppressLint("LongLogTag") 134 public class CompanionDeviceManagerService extends SystemService { 135 static final String TAG = "CDM_CompanionDeviceManagerService"; 136 static final boolean DEBUG = false; 137 138 /** Range of Association IDs allocated for a user.*/ 139 private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000; 140 private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min 141 142 private static final String PREF_FILE_NAME = "companion_device_preferences.xml"; 143 private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done"; 144 private static final String SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW = 145 "debug.cdm.cdmservice.removal_time_window"; 146 147 private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90); 148 private static final int MAX_CN_LENGTH = 500; 149 150 private final ActivityManager mActivityManager; 151 private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener; 152 153 private PersistentDataStore mPersistentStore; 154 private final PersistUserStateHandler mUserPersistenceHandler; 155 156 private final AssociationStoreImpl mAssociationStore; 157 private final SystemDataTransferRequestStore mSystemDataTransferRequestStore; 158 private AssociationRequestsProcessor mAssociationRequestsProcessor; 159 private SystemDataTransferProcessor mSystemDataTransferProcessor; 160 private CompanionDevicePresenceMonitor mDevicePresenceMonitor; 161 private CompanionApplicationController mCompanionAppController; 162 private CompanionTransportManager mTransportManager; 163 164 private final ActivityTaskManagerInternal mAtmInternal; 165 private final ActivityManagerInternal mAmInternal; 166 private final IAppOpsService mAppOpsManager; 167 private final PowerWhitelistManager mPowerWhitelistManager; 168 private final UserManager mUserManager; 169 final PackageManagerInternal mPackageManagerInternal; 170 171 /** 172 * A structure that consists of two nested maps, and effectively maps (userId + packageName) to 173 * a list of IDs that have been previously assigned to associations for that package. 174 * We maintain this structure so that we never re-use association IDs for the same package 175 * (until it's uninstalled). 176 */ 177 @GuardedBy("mPreviouslyUsedIds") 178 private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>(); 179 180 /** 181 * A structure that consists of a set of revoked associations that pending for role holder 182 * removal per each user. 183 * 184 * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo) 185 * @see #addToPendingRoleHolderRemoval(AssociationInfo) 186 * @see #removeFromPendingRoleHolderRemoval(AssociationInfo) 187 * @see #getPendingRoleHolderRemovalAssociationsForUser(int) 188 */ 189 @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval") 190 private final PerUserAssociationSet mRevokedAssociationsPendingRoleHolderRemoval = 191 new PerUserAssociationSet(); 192 /** 193 * Contains uid-s of packages pending to be removed from the role holder list (after 194 * revocation of an association), which will happen one the package is no longer visible to the 195 * user. 196 * For quicker uid -> (userId, packageName) look-up this is not a {@code Set<Integer>} but 197 * a {@code Map<Integer, String>} which maps uid-s to packageName-s (userId-s can be derived 198 * from uid-s using {@link UserHandle#getUserId(int)}). 199 * 200 * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo) 201 * @see #addToPendingRoleHolderRemoval(AssociationInfo) 202 * @see #removeFromPendingRoleHolderRemoval(AssociationInfo) 203 */ 204 @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval") 205 private final Map<Integer, String> mUidsPendingRoleHolderRemoval = new HashMap<>(); 206 207 private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners = 208 new RemoteCallbackList<>(); 209 210 private CrossDeviceSyncController mCrossDeviceSyncController; 211 CompanionDeviceManagerService(Context context)212 public CompanionDeviceManagerService(Context context) { 213 super(context); 214 215 mActivityManager = context.getSystemService(ActivityManager.class); 216 mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class); 217 mAppOpsManager = IAppOpsService.Stub.asInterface( 218 ServiceManager.getService(Context.APP_OPS_SERVICE)); 219 mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); 220 mAmInternal = LocalServices.getService(ActivityManagerInternal.class); 221 mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); 222 mUserManager = context.getSystemService(UserManager.class); 223 224 mUserPersistenceHandler = new PersistUserStateHandler(); 225 mAssociationStore = new AssociationStoreImpl(); 226 mSystemDataTransferRequestStore = new SystemDataTransferRequestStore(); 227 228 mOnPackageVisibilityChangeListener = 229 new OnPackageVisibilityChangeListener(mActivityManager); 230 } 231 232 @Override onStart()233 public void onStart() { 234 final Context context = getContext(); 235 236 mPersistentStore = new PersistentDataStore(); 237 238 loadAssociationsFromDisk(); 239 mAssociationStore.registerListener(mAssociationStoreChangeListener); 240 241 mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(mUserManager, 242 mAssociationStore, mDevicePresenceCallback); 243 244 mAssociationRequestsProcessor = new AssociationRequestsProcessor( 245 /* cdmService */this, mAssociationStore); 246 mCompanionAppController = new CompanionApplicationController( 247 context, mAssociationStore, mDevicePresenceMonitor); 248 mTransportManager = new CompanionTransportManager(context, mAssociationStore); 249 mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, 250 mPackageManagerInternal, mAssociationStore, 251 mSystemDataTransferRequestStore, mTransportManager); 252 // TODO(b/279663946): move context sync to a dedicated system service 253 mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager); 254 255 // Publish "binder" service. 256 final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl(); 257 publishBinderService(Context.COMPANION_DEVICE_SERVICE, impl); 258 259 // Publish "local" service. 260 LocalServices.addService(CompanionDeviceManagerServiceInternal.class, new LocalService()); 261 } 262 loadAssociationsFromDisk()263 void loadAssociationsFromDisk() { 264 final Set<AssociationInfo> allAssociations = new ArraySet<>(); 265 synchronized (mPreviouslyUsedIds) { 266 // The data is stored in DE directories, so we can read the data for all users now 267 // (which would not be possible if the data was stored to CE directories). 268 mPersistentStore.readStateForUsers( 269 mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds); 270 } 271 272 final Set<AssociationInfo> activeAssociations = 273 new ArraySet<>(/* capacity */ allAssociations.size()); 274 // A set contains the userIds that need to persist state after remove the app 275 // from the list of role holders. 276 final Set<Integer> usersToPersistStateFor = new ArraySet<>(); 277 278 for (AssociationInfo association : allAssociations) { 279 if (!association.isRevoked()) { 280 activeAssociations.add(association); 281 } else if (maybeRemoveRoleHolderForAssociation(association)) { 282 // Nothing more to do here, but we'll need to persist all the associations to the 283 // disk afterwards. 284 usersToPersistStateFor.add(association.getUserId()); 285 } else { 286 addToPendingRoleHolderRemoval(association); 287 } 288 } 289 290 mAssociationStore.setAssociations(activeAssociations); 291 292 // IMPORTANT: only do this AFTER mAssociationStore.setAssociations(), because 293 // persistStateForUser() queries AssociationStore. 294 // (If persistStateForUser() is invoked before mAssociationStore.setAssociations() it 295 // would effectively just clear-out all the persisted associations). 296 for (int userId : usersToPersistStateFor) { 297 persistStateForUser(userId); 298 } 299 } 300 301 @Override onBootPhase(int phase)302 public void onBootPhase(int phase) { 303 final Context context = getContext(); 304 if (phase == PHASE_SYSTEM_SERVICES_READY) { 305 // WARNING: moving PackageMonitor to another thread (Looper) may introduce significant 306 // delays (even in case of the Main Thread). It may be fine overall, but would require 307 // updating the tests (adding a delay there). 308 mPackageMonitor.register(context, FgThread.get().getLooper(), UserHandle.ALL, true); 309 mDevicePresenceMonitor.init(context); 310 } else if (phase == PHASE_BOOT_COMPLETED) { 311 // Run the Inactive Association Removal job service daily. 312 InactiveAssociationsRemovalService.schedule(getContext()); 313 mCrossDeviceSyncController.onBootCompleted(); 314 } 315 } 316 317 @Override onUserUnlocking(@onNull TargetUser user)318 public void onUserUnlocking(@NonNull TargetUser user) { 319 final int userId = user.getUserIdentifier(); 320 final List<AssociationInfo> associations = mAssociationStore.getAssociationsForUser(userId); 321 322 if (associations.isEmpty()) return; 323 324 updateAtm(userId, associations); 325 326 BackgroundThread.getHandler().sendMessageDelayed( 327 obtainMessage(CompanionDeviceManagerService::maybeGrantAutoRevokeExemptions, this), 328 MINUTES.toMillis(10)); 329 } 330 331 @Override onUserUnlocked(@onNull TargetUser user)332 public void onUserUnlocked(@NonNull TargetUser user) { 333 // Notify and bind the app after the phone is unlocked. 334 final int userId = user.getUserIdentifier(); 335 final Set<BluetoothDevice> blueToothDevices = 336 mDevicePresenceMonitor.getPendingConnectedDevices().get(userId); 337 if (blueToothDevices != null) { 338 for (BluetoothDevice bluetoothDevice : blueToothDevices) { 339 for (AssociationInfo ai: 340 mAssociationStore.getAssociationsByAddress(bluetoothDevice.getAddress())) { 341 Slog.i(TAG, "onUserUnlocked, device id( " + ai.getId() + " ) is connected"); 342 mDevicePresenceMonitor.onBluetoothCompanionDeviceConnected(ai.getId()); 343 } 344 } 345 } 346 } 347 348 @NonNull getAssociationWithCallerChecks( @serIdInt int userId, @NonNull String packageName, @NonNull String macAddress)349 AssociationInfo getAssociationWithCallerChecks( 350 @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) { 351 AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress( 352 userId, packageName, macAddress); 353 association = sanitizeWithCallerChecks(getContext(), association); 354 if (association != null) { 355 return association; 356 } else { 357 throw new IllegalArgumentException("Association does not exist " 358 + "or the caller does not have permissions to manage it " 359 + "(ie. it belongs to a different package or a different user)."); 360 } 361 } 362 363 @NonNull getAssociationWithCallerChecks(int associationId)364 AssociationInfo getAssociationWithCallerChecks(int associationId) { 365 AssociationInfo association = mAssociationStore.getAssociationById(associationId); 366 association = sanitizeWithCallerChecks(getContext(), association); 367 if (association != null) { 368 return association; 369 } else { 370 throw new IllegalArgumentException("Association does not exist " 371 + "or the caller does not have permissions to manage it " 372 + "(ie. it belongs to a different package or a different user)."); 373 } 374 } 375 onDeviceAppearedInternal(int associationId)376 private void onDeviceAppearedInternal(int associationId) { 377 if (DEBUG) Log.i(TAG, "onDevice_Appeared_Internal() id=" + associationId); 378 379 final AssociationInfo association = mAssociationStore.getAssociationById(associationId); 380 if (DEBUG) Log.d(TAG, " association=" + association); 381 382 if (!association.shouldBindWhenPresent()) return; 383 384 final int userId = association.getUserId(); 385 final String packageName = association.getPackageName(); 386 // Set bindImportant to true when the association is self-managed to avoid the target 387 // service being killed. 388 final boolean bindImportant = association.isSelfManaged(); 389 390 if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { 391 mCompanionAppController.bindCompanionApplication(userId, packageName, bindImportant); 392 } else if (DEBUG) { 393 Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound"); 394 } 395 mCompanionAppController.notifyCompanionApplicationDeviceAppeared(association); 396 } 397 onDeviceDisappearedInternal(int associationId)398 private void onDeviceDisappearedInternal(int associationId) { 399 if (DEBUG) Log.i(TAG, "onDevice_Disappeared_Internal() id=" + associationId); 400 401 final AssociationInfo association = mAssociationStore.getAssociationById(associationId); 402 if (DEBUG) Log.d(TAG, " association=" + association); 403 404 final int userId = association.getUserId(); 405 final String packageName = association.getPackageName(); 406 407 if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { 408 if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound"); 409 return; 410 } 411 412 if (association.shouldBindWhenPresent()) { 413 mCompanionAppController.notifyCompanionApplicationDeviceDisappeared(association); 414 } 415 416 // Check if there are other devices associated to the app that are present. 417 if (shouldBindPackage(userId, packageName)) return; 418 419 mCompanionAppController.unbindCompanionApplication(userId, packageName); 420 } 421 422 /** 423 * @return whether the package should be bound (i.e. at least one of the devices associated with 424 * the package is currently present). 425 */ shouldBindPackage(@serIdInt int userId, @NonNull String packageName)426 private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) { 427 final List<AssociationInfo> packageAssociations = 428 mAssociationStore.getAssociationsForPackage(userId, packageName); 429 for (AssociationInfo association : packageAssociations) { 430 if (!association.shouldBindWhenPresent()) continue; 431 if (mDevicePresenceMonitor.isDevicePresent(association.getId())) return true; 432 } 433 return false; 434 } 435 onAssociationChangedInternal( @ssociationStore.ChangeType int changeType, AssociationInfo association)436 private void onAssociationChangedInternal( 437 @AssociationStore.ChangeType int changeType, AssociationInfo association) { 438 final int id = association.getId(); 439 final int userId = association.getUserId(); 440 final String packageName = association.getPackageName(); 441 442 if (changeType == AssociationStore.CHANGE_TYPE_REMOVED) { 443 markIdAsPreviouslyUsedForPackage(id, userId, packageName); 444 } 445 446 final List<AssociationInfo> updatedAssociations = 447 mAssociationStore.getAssociationsForUser(userId); 448 449 mUserPersistenceHandler.postPersistUserState(userId); 450 451 // Notify listeners if ADDED, REMOVED or UPDATED_ADDRESS_CHANGED. 452 // Do NOT notify when UPDATED_ADDRESS_UNCHANGED, which means a minor tweak in association's 453 // configs, which "listeners" won't (and shouldn't) be able to see. 454 if (changeType != CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED) { 455 notifyListeners(userId, updatedAssociations); 456 } 457 updateAtm(userId, updatedAssociations); 458 } 459 persistStateForUser(@serIdInt int userId)460 private void persistStateForUser(@UserIdInt int userId) { 461 // We want to store both active associations and the revoked (removed) association that we 462 // are keeping around for the final clean-up (delayed role holder removal). 463 final List<AssociationInfo> allAssociations; 464 // Start with the active associations - these we can get from the AssociationStore. 465 allAssociations = new ArrayList<>( 466 mAssociationStore.getAssociationsForUser(userId)); 467 // ... and add the revoked (removed) association, that are yet to be permanently removed. 468 allAssociations.addAll(getPendingRoleHolderRemovalAssociationsForUser(userId)); 469 470 final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId); 471 472 mPersistentStore.persistStateForUser(userId, allAssociations, usedIdsForUser); 473 } 474 notifyListeners( @serIdInt int userId, @NonNull List<AssociationInfo> associations)475 private void notifyListeners( 476 @UserIdInt int userId, @NonNull List<AssociationInfo> associations) { 477 mListeners.broadcast((listener, callbackUserId) -> { 478 if ((int) callbackUserId == userId) { 479 try { 480 listener.onAssociationsChanged(associations); 481 } catch (RemoteException ignored) { 482 } 483 } 484 }); 485 } 486 markIdAsPreviouslyUsedForPackage( int associationId, @UserIdInt int userId, @NonNull String packageName)487 private void markIdAsPreviouslyUsedForPackage( 488 int associationId, @UserIdInt int userId, @NonNull String packageName) { 489 synchronized (mPreviouslyUsedIds) { 490 Map<String, Set<Integer>> usedIdsForUser = mPreviouslyUsedIds.get(userId); 491 if (usedIdsForUser == null) { 492 usedIdsForUser = new HashMap<>(); 493 mPreviouslyUsedIds.put(userId, usedIdsForUser); 494 } 495 496 final Set<Integer> usedIdsForPackage = 497 usedIdsForUser.computeIfAbsent(packageName, it -> new HashSet<>()); 498 usedIdsForPackage.add(associationId); 499 } 500 } 501 onPackageRemoveOrDataClearedInternal( @serIdInt int userId, @NonNull String packageName)502 private void onPackageRemoveOrDataClearedInternal( 503 @UserIdInt int userId, @NonNull String packageName) { 504 if (DEBUG) { 505 Log.i(TAG, "onPackageRemove_Or_DataCleared() u" + userId + "/" 506 + packageName); 507 } 508 509 // Clear associations. 510 final List<AssociationInfo> associationsForPackage = 511 mAssociationStore.getAssociationsForPackage(userId, packageName); 512 for (AssociationInfo association : associationsForPackage) { 513 mAssociationStore.removeAssociation(association.getId()); 514 } 515 // Clear role holders 516 for (AssociationInfo association : associationsForPackage) { 517 maybeRemoveRoleHolderForAssociation(association); 518 } 519 520 mCompanionAppController.onPackagesChanged(userId); 521 } 522 onPackageModifiedInternal(@serIdInt int userId, @NonNull String packageName)523 private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) { 524 if (DEBUG) Log.i(TAG, "onPackageModified() u" + userId + "/" + packageName); 525 526 final List<AssociationInfo> associationsForPackage = 527 mAssociationStore.getAssociationsForPackage(userId, packageName); 528 for (AssociationInfo association : associationsForPackage) { 529 updateSpecialAccessPermissionForAssociatedPackage(association); 530 } 531 532 mCompanionAppController.onPackagesChanged(userId); 533 } 534 535 // Revoke associations if the selfManaged companion device does not connect for 3 months. removeInactiveSelfManagedAssociations()536 void removeInactiveSelfManagedAssociations() { 537 final long currentTime = System.currentTimeMillis(); 538 long removalWindow = SystemProperties.getLong(SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW, -1); 539 if (removalWindow <= 0) { 540 // 0 or negative values indicate that the sysprop was never set or should be ignored. 541 removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT; 542 } 543 544 for (AssociationInfo association : mAssociationStore.getAssociations()) { 545 if (!association.isSelfManaged()) continue; 546 547 final boolean isInactive = 548 currentTime - association.getLastTimeConnectedMs() >= removalWindow; 549 if (!isInactive) continue; 550 551 final int id = association.getId(); 552 553 Slog.i(TAG, "Removing inactive self-managed association id=" + id); 554 disassociateInternal(id); 555 } 556 } 557 558 class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub { 559 @Override onTransact(int code, Parcel data, Parcel reply, int flags)560 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) 561 throws RemoteException { 562 try { 563 return super.onTransact(code, data, reply, flags); 564 } catch (Throwable e) { 565 Slog.e(TAG, "Error during IPC", e); 566 throw ExceptionUtils.propagate(e, RemoteException.class); 567 } 568 } 569 570 @Override associate(AssociationRequest request, IAssociationRequestCallback callback, String packageName, int userId)571 public void associate(AssociationRequest request, IAssociationRequestCallback callback, 572 String packageName, int userId) throws RemoteException { 573 Slog.i(TAG, "associate() " 574 + "request=" + request + ", " 575 + "package=u" + userId + "/" + packageName); 576 enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName, 577 "create associations"); 578 579 mAssociationRequestsProcessor.processNewAssociationRequest( 580 request, packageName, userId, callback); 581 } 582 583 @Override buildAssociationCancellationIntent(String packageName, int userId)584 public PendingIntent buildAssociationCancellationIntent(String packageName, 585 int userId) throws RemoteException { 586 Slog.i(TAG, "buildAssociationCancellationIntent() " 587 + "package=u" + userId + "/" + packageName); 588 enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName, 589 "build association cancellation intent"); 590 591 return mAssociationRequestsProcessor.buildAssociationCancellationIntent( 592 packageName, userId); 593 } 594 595 @Override getAssociations(String packageName, int userId)596 public List<AssociationInfo> getAssociations(String packageName, int userId) { 597 enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName, 598 "get associations"); 599 600 if (!checkCallerCanManageCompanionDevice(getContext())) { 601 // If the caller neither is system nor holds MANAGE_COMPANION_DEVICES: it needs to 602 // request the feature (also: the caller is the app itself). 603 enforceUsesCompanionDeviceFeature(getContext(), userId, packageName); 604 } 605 606 return mAssociationStore.getAssociationsForPackage(userId, packageName); 607 } 608 609 @Override getAllAssociationsForUser(int userId)610 public List<AssociationInfo> getAllAssociationsForUser(int userId) throws RemoteException { 611 enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId); 612 enforceCallerCanManageCompanionDevice(getContext(), "getAllAssociationsForUser"); 613 614 return mAssociationStore.getAssociationsForUser(userId); 615 } 616 617 @Override addOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId)618 public void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener, 619 int userId) { 620 enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId); 621 enforceCallerCanManageCompanionDevice(getContext(), 622 "addOnAssociationsChangedListener"); 623 624 mListeners.register(listener, userId); 625 } 626 627 @Override removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId)628 public void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, 629 int userId) { 630 enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId); 631 enforceCallerCanManageCompanionDevice( 632 getContext(), "removeOnAssociationsChangedListener"); 633 634 mListeners.unregister(listener); 635 } 636 637 @Override addOnTransportsChangedListener(IOnTransportsChangedListener listener)638 public void addOnTransportsChangedListener(IOnTransportsChangedListener listener) { 639 mTransportManager.addListener(listener); 640 } 641 642 @Override removeOnTransportsChangedListener(IOnTransportsChangedListener listener)643 public void removeOnTransportsChangedListener(IOnTransportsChangedListener listener) { 644 mTransportManager.removeListener(listener); 645 } 646 647 @Override sendMessage(int messageType, byte[] data, int[] associationIds)648 public void sendMessage(int messageType, byte[] data, int[] associationIds) { 649 mTransportManager.sendMessage(messageType, data, associationIds); 650 } 651 652 @Override addOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener)653 public void addOnMessageReceivedListener(int messageType, 654 IOnMessageReceivedListener listener) { 655 mTransportManager.addListener(messageType, listener); 656 } 657 658 @Override removeOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener)659 public void removeOnMessageReceivedListener(int messageType, 660 IOnMessageReceivedListener listener) { 661 mTransportManager.removeListener(messageType, listener); 662 } 663 664 @Override legacyDisassociate(String deviceMacAddress, String packageName, int userId)665 public void legacyDisassociate(String deviceMacAddress, String packageName, int userId) { 666 Log.i(TAG, "legacyDisassociate() pkg=u" + userId + "/" + packageName 667 + ", macAddress=" + deviceMacAddress); 668 669 requireNonNull(deviceMacAddress); 670 requireNonNull(packageName); 671 672 final AssociationInfo association = 673 getAssociationWithCallerChecks(userId, packageName, deviceMacAddress); 674 disassociateInternal(association.getId()); 675 } 676 677 @Override disassociate(int associationId)678 public void disassociate(int associationId) { 679 Log.i(TAG, "disassociate() associationId=" + associationId); 680 681 final AssociationInfo association = 682 getAssociationWithCallerChecks(associationId); 683 disassociateInternal(association.getId()); 684 } 685 686 @Override requestNotificationAccess(ComponentName component, int userId)687 public PendingIntent requestNotificationAccess(ComponentName component, int userId) 688 throws RemoteException { 689 String callingPackage = component.getPackageName(); 690 checkCanCallNotificationApi(callingPackage, userId); 691 if (component.flattenToString().length() > MAX_CN_LENGTH) { 692 throw new IllegalArgumentException("Component name is too long."); 693 } 694 final long identity = Binder.clearCallingIdentity(); 695 try { 696 return PendingIntent.getActivityAsUser(getContext(), 697 0 /* request code */, 698 NotificationAccessConfirmationActivityContract.launcherIntent( 699 getContext(), userId, component), 700 PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT 701 | PendingIntent.FLAG_CANCEL_CURRENT, 702 null /* options */, 703 new UserHandle(userId)); 704 } finally { 705 Binder.restoreCallingIdentity(identity); 706 } 707 } 708 709 /** 710 * @deprecated Use 711 * {@link NotificationManager#isNotificationListenerAccessGranted(ComponentName)} instead. 712 */ 713 @Deprecated 714 @Override hasNotificationAccess(ComponentName component)715 public boolean hasNotificationAccess(ComponentName component) throws RemoteException { 716 checkCanCallNotificationApi(component.getPackageName(), getCallingUserId()); 717 NotificationManager nm = getContext().getSystemService(NotificationManager.class); 718 return nm.isNotificationListenerAccessGranted(component); 719 } 720 721 @Override isDeviceAssociatedForWifiConnection(String packageName, String macAddress, int userId)722 public boolean isDeviceAssociatedForWifiConnection(String packageName, String macAddress, 723 int userId) { 724 getContext().enforceCallingOrSelfPermission( 725 MANAGE_COMPANION_DEVICES, "isDeviceAssociated"); 726 727 boolean bypassMacPermission = getContext().getPackageManager().checkPermission( 728 android.Manifest.permission.COMPANION_APPROVE_WIFI_CONNECTIONS, packageName) 729 == PERMISSION_GRANTED; 730 if (bypassMacPermission) { 731 return true; 732 } 733 734 return any(mAssociationStore.getAssociationsForPackage(userId, packageName), 735 a -> a.isLinkedTo(macAddress)); 736 } 737 738 @Override registerDevicePresenceListenerService(String deviceAddress, String callingPackage, int userId)739 public void registerDevicePresenceListenerService(String deviceAddress, 740 String callingPackage, int userId) throws RemoteException { 741 // TODO: take the userId into account. 742 registerDevicePresenceListenerActive(callingPackage, deviceAddress, true); 743 } 744 745 @Override unregisterDevicePresenceListenerService(String deviceAddress, String callingPackage, int userId)746 public void unregisterDevicePresenceListenerService(String deviceAddress, 747 String callingPackage, int userId) throws RemoteException { 748 // TODO: take the userId into account. 749 registerDevicePresenceListenerActive(callingPackage, deviceAddress, false); 750 } 751 752 @Override buildPermissionTransferUserConsentIntent(String packageName, int userId, int associationId)753 public PendingIntent buildPermissionTransferUserConsentIntent(String packageName, 754 int userId, int associationId) { 755 if (!FeatureUtils.isPermSyncEnabled()) { 756 throw new UnsupportedOperationException("Calling" 757 + " buildPermissionTransferUserConsentIntent, but this API is disabled by" 758 + " the system."); 759 } 760 return mSystemDataTransferProcessor.buildPermissionTransferUserConsentIntent( 761 packageName, userId, associationId); 762 } 763 764 @Override startSystemDataTransfer(String packageName, int userId, int associationId, ISystemDataTransferCallback callback)765 public void startSystemDataTransfer(String packageName, int userId, int associationId, 766 ISystemDataTransferCallback callback) { 767 if (!FeatureUtils.isPermSyncEnabled()) { 768 throw new UnsupportedOperationException("Calling startSystemDataTransfer, but this" 769 + " API is disabled by the system."); 770 } 771 mSystemDataTransferProcessor.startSystemDataTransfer(packageName, userId, 772 associationId, callback); 773 } 774 775 @Override attachSystemDataTransport(String packageName, int userId, int associationId, ParcelFileDescriptor fd)776 public void attachSystemDataTransport(String packageName, int userId, int associationId, 777 ParcelFileDescriptor fd) { 778 getAssociationWithCallerChecks(associationId); 779 mTransportManager.attachSystemDataTransport(packageName, userId, associationId, fd); 780 } 781 782 @Override detachSystemDataTransport(String packageName, int userId, int associationId)783 public void detachSystemDataTransport(String packageName, int userId, int associationId) { 784 getAssociationWithCallerChecks(associationId); 785 mTransportManager.detachSystemDataTransport(packageName, userId, associationId); 786 } 787 788 @Override enableSystemDataSync(int associationId, int flags)789 public void enableSystemDataSync(int associationId, int flags) { 790 getAssociationWithCallerChecks(associationId); 791 mAssociationRequestsProcessor.enableSystemDataSync(associationId, flags); 792 } 793 794 @Override disableSystemDataSync(int associationId, int flags)795 public void disableSystemDataSync(int associationId, int flags) { 796 getAssociationWithCallerChecks(associationId); 797 mAssociationRequestsProcessor.disableSystemDataSync(associationId, flags); 798 } 799 800 @Override enablePermissionsSync(int associationId)801 public void enablePermissionsSync(int associationId) { 802 getAssociationWithCallerChecks(associationId); 803 mSystemDataTransferProcessor.enablePermissionsSync(associationId); 804 } 805 806 @Override disablePermissionsSync(int associationId)807 public void disablePermissionsSync(int associationId) { 808 getAssociationWithCallerChecks(associationId); 809 mSystemDataTransferProcessor.disablePermissionsSync(associationId); 810 } 811 812 @Override getPermissionSyncRequest(int associationId)813 public PermissionSyncRequest getPermissionSyncRequest(int associationId) { 814 getAssociationWithCallerChecks(associationId); 815 return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId); 816 } 817 818 @Override enableSecureTransport(boolean enabled)819 public void enableSecureTransport(boolean enabled) { 820 mTransportManager.enableSecureTransport(enabled); 821 } 822 823 @Override notifyDeviceAppeared(int associationId)824 public void notifyDeviceAppeared(int associationId) { 825 if (DEBUG) Log.i(TAG, "notifyDevice_Appeared() id=" + associationId); 826 827 AssociationInfo association = getAssociationWithCallerChecks(associationId); 828 if (!association.isSelfManaged()) { 829 throw new IllegalArgumentException("Association with ID " + associationId 830 + " is not self-managed. notifyDeviceAppeared(int) can only be called for" 831 + " self-managed associations."); 832 } 833 // AssociationInfo class is immutable: create a new AssociationInfo object with updated 834 // timestamp. 835 association = AssociationInfo.builder(association) 836 .setLastTimeConnected(System.currentTimeMillis()) 837 .build(); 838 mAssociationStore.updateAssociation(association); 839 840 mDevicePresenceMonitor.onSelfManagedDeviceConnected(associationId); 841 } 842 843 @Override notifyDeviceDisappeared(int associationId)844 public void notifyDeviceDisappeared(int associationId) { 845 if (DEBUG) Log.i(TAG, "notifyDevice_Disappeared() id=" + associationId); 846 847 final AssociationInfo association = getAssociationWithCallerChecks(associationId); 848 if (!association.isSelfManaged()) { 849 throw new IllegalArgumentException("Association with ID " + associationId 850 + " is not self-managed. notifyDeviceAppeared(int) can only be called for" 851 + " self-managed associations."); 852 } 853 854 mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId); 855 } 856 857 @Override isCompanionApplicationBound(String packageName, int userId)858 public boolean isCompanionApplicationBound(String packageName, int userId) { 859 return mCompanionAppController.isCompanionApplicationBound(userId, packageName); 860 } 861 registerDevicePresenceListenerActive(String packageName, String deviceAddress, boolean active)862 private void registerDevicePresenceListenerActive(String packageName, String deviceAddress, 863 boolean active) throws RemoteException { 864 if (DEBUG) { 865 Log.i(TAG, "registerDevicePresenceListenerActive()" 866 + " active=" + active 867 + " deviceAddress=" + deviceAddress); 868 } 869 870 getContext().enforceCallingOrSelfPermission( 871 android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE, 872 "[un]registerDevicePresenceListenerService"); 873 final int userId = getCallingUserId(); 874 enforceCallerIsSystemOr(userId, packageName); 875 876 AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress( 877 userId, packageName, deviceAddress); 878 879 if (association == null) { 880 throw new RemoteException(new DeviceNotAssociatedException("App " + packageName 881 + " is not associated with device " + deviceAddress 882 + " for user " + userId)); 883 } 884 885 // If already at specified state, then no-op. 886 if (active == association.isNotifyOnDeviceNearby()) { 887 if (DEBUG) Log.d(TAG, "Device presence listener is already at desired state."); 888 return; 889 } 890 891 // AssociationInfo class is immutable: create a new AssociationInfo object with updated 892 // flag. 893 association = AssociationInfo.builder(association) 894 .setNotifyOnDeviceNearby(active) 895 .build(); 896 // Do not need to call {@link BleCompanionDeviceScanner#restartScan()} since it will 897 // trigger {@link BleCompanionDeviceScanner#restartScan(int, AssociationInfo)} when 898 // an application sets/unsets the mNotifyOnDeviceNearby flag. 899 mAssociationStore.updateAssociation(association); 900 901 // If device is already present, then trigger callback. 902 if (active && mDevicePresenceMonitor.isDevicePresent(association.getId())) { 903 if (DEBUG) Log.d(TAG, "Device is already present. Triggering callback."); 904 onDeviceAppearedInternal(association.getId()); 905 } 906 907 // If last listener is unregistered, then unbind application. 908 if (!active && !shouldBindPackage(userId, packageName)) { 909 if (DEBUG) Log.d(TAG, "Last listener unregistered. Unbinding application."); 910 mCompanionAppController.unbindCompanionApplication(userId, packageName); 911 } 912 } 913 914 @Override createAssociation(String packageName, String macAddress, int userId, byte[] certificate)915 public void createAssociation(String packageName, String macAddress, int userId, 916 byte[] certificate) { 917 if (!getContext().getPackageManager().hasSigningCertificate( 918 packageName, certificate, CERT_INPUT_SHA256)) { 919 Slog.e(TAG, "Given certificate doesn't match the package certificate."); 920 return; 921 } 922 923 getContext().enforceCallingOrSelfPermission( 924 android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES, "createAssociation"); 925 926 final MacAddress macAddressObj = MacAddress.fromString(macAddress); 927 createNewAssociation(userId, packageName, macAddressObj, null, null, false); 928 } 929 checkCanCallNotificationApi(String callingPackage, int userId)930 private void checkCanCallNotificationApi(String callingPackage, int userId) { 931 enforceCallerIsSystemOr(userId, callingPackage); 932 933 if (getCallingUid() == SYSTEM_UID) return; 934 935 enforceUsesCompanionDeviceFeature(getContext(), userId, callingPackage); 936 checkState(!ArrayUtils.isEmpty( 937 mAssociationStore.getAssociationsForPackage(userId, callingPackage)), 938 "App must have an association before calling this API"); 939 } 940 941 @Override canPairWithoutPrompt(String packageName, String macAddress, int userId)942 public boolean canPairWithoutPrompt(String packageName, String macAddress, int userId) { 943 final AssociationInfo association = 944 mAssociationStore.getAssociationsForPackageWithAddress( 945 userId, packageName, macAddress); 946 if (association == null) { 947 return false; 948 } 949 return System.currentTimeMillis() - association.getTimeApprovedMs() 950 < PAIR_WITHOUT_PROMPT_WINDOW_MS; 951 } 952 953 @Override onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)954 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 955 String[] args, ShellCallback callback, ResultReceiver resultReceiver) 956 throws RemoteException { 957 new CompanionDeviceShellCommand(CompanionDeviceManagerService.this, mAssociationStore, 958 mDevicePresenceMonitor, mTransportManager, mSystemDataTransferProcessor, 959 mAssociationRequestsProcessor) 960 .exec(this, in, out, err, args, callback, resultReceiver); 961 } 962 963 @Override dump(@onNull FileDescriptor fd, @NonNull PrintWriter out, @Nullable String[] args)964 public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter out, 965 @Nullable String[] args) { 966 if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, out)) { 967 return; 968 } 969 970 mAssociationStore.dump(out); 971 mDevicePresenceMonitor.dump(out); 972 mCompanionAppController.dump(out); 973 } 974 } 975 createNewAssociation(@serIdInt int userId, @NonNull String packageName, @Nullable MacAddress macAddress, @Nullable CharSequence displayName, @Nullable String deviceProfile, boolean isSelfManaged)976 void createNewAssociation(@UserIdInt int userId, @NonNull String packageName, 977 @Nullable MacAddress macAddress, @Nullable CharSequence displayName, 978 @Nullable String deviceProfile, boolean isSelfManaged) { 979 mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress, 980 displayName, deviceProfile, /* associatedDevice */ null, isSelfManaged, 981 /* callback */ null, /* resultReceiver */ null); 982 } 983 984 @NonNull getPreviouslyUsedIdsForUser(@serIdInt int userId)985 private Map<String, Set<Integer>> getPreviouslyUsedIdsForUser(@UserIdInt int userId) { 986 synchronized (mPreviouslyUsedIds) { 987 return getPreviouslyUsedIdsForUserLocked(userId); 988 } 989 } 990 991 @GuardedBy("mPreviouslyUsedIds") 992 @NonNull getPreviouslyUsedIdsForUserLocked(@serIdInt int userId)993 private Map<String, Set<Integer>> getPreviouslyUsedIdsForUserLocked(@UserIdInt int userId) { 994 final Map<String, Set<Integer>> usedIdsForUser = mPreviouslyUsedIds.get(userId); 995 if (usedIdsForUser == null) { 996 return Collections.emptyMap(); 997 } 998 return deepUnmodifiableCopy(usedIdsForUser); 999 } 1000 1001 @GuardedBy("mPreviouslyUsedIds") 1002 @NonNull getPreviouslyUsedIdsForPackageLocked( @serIdInt int userId, @NonNull String packageName)1003 private Set<Integer> getPreviouslyUsedIdsForPackageLocked( 1004 @UserIdInt int userId, @NonNull String packageName) { 1005 // "Deeply unmodifiable" map: the map itself and the Set<Integer> values it contains are all 1006 // unmodifiable. 1007 final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUserLocked(userId); 1008 final Set<Integer> usedIdsForPackage = usedIdsForUser.get(packageName); 1009 1010 if (usedIdsForPackage == null) { 1011 return Collections.emptySet(); 1012 } 1013 1014 //The set is already unmodifiable. 1015 return usedIdsForPackage; 1016 } 1017 getNewAssociationIdForPackage(@serIdInt int userId, @NonNull String packageName)1018 int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) { 1019 synchronized (mPreviouslyUsedIds) { 1020 // First: collect all IDs currently in use for this user's Associations. 1021 final SparseBooleanArray usedIds = new SparseBooleanArray(); 1022 1023 // We should really only be checking associations for the given user (i.e.: 1024 // mAssociationStore.getAssociationsForUser(userId)), BUT in the past we've got in a 1025 // state where association IDs were not assigned correctly in regard to 1026 // user-to-association-ids-range (e.g. associations with IDs from 1 to 100,000 should 1027 // always belong to u0), so let's check all the associations. 1028 for (AssociationInfo it : mAssociationStore.getAssociations()) { 1029 usedIds.put(it.getId(), true); 1030 } 1031 1032 // Second: collect all IDs that have been previously used for this package (and user). 1033 final Set<Integer> previouslyUsedIds = 1034 getPreviouslyUsedIdsForPackageLocked(userId, packageName); 1035 1036 int id = getFirstAssociationIdForUser(userId); 1037 final int lastAvailableIdForUser = getLastAssociationIdForUser(userId); 1038 1039 // Find first ID that isn't used now AND has never been used for the given package. 1040 while (usedIds.get(id) || previouslyUsedIds.contains(id)) { 1041 // Increment and try again 1042 id++; 1043 // ... but first check if the ID is valid (within the range allocated to the user). 1044 if (id > lastAvailableIdForUser) { 1045 throw new RuntimeException("Cannot create a new Association ID for " 1046 + packageName + " for user " + userId); 1047 } 1048 } 1049 1050 return id; 1051 } 1052 } 1053 1054 // TODO: also revoke notification access disassociateInternal(int associationId)1055 void disassociateInternal(int associationId) { 1056 final AssociationInfo association = mAssociationStore.getAssociationById(associationId); 1057 final int userId = association.getUserId(); 1058 final String packageName = association.getPackageName(); 1059 final String deviceProfile = association.getDeviceProfile(); 1060 1061 if (!maybeRemoveRoleHolderForAssociation(association)) { 1062 // Need to remove the app from list of the role holders, but will have to do it later 1063 // (the app is in foreground at the moment). 1064 addToPendingRoleHolderRemoval(association); 1065 } 1066 1067 // Need to check if device still present now because CompanionDevicePresenceMonitor will 1068 // remove current connected device after mAssociationStore.removeAssociation 1069 final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(associationId); 1070 1071 // Removing the association. 1072 mAssociationStore.removeAssociation(associationId); 1073 // Do not need to persistUserState since CompanionDeviceManagerService will get callback 1074 // from #onAssociationChanged, and it will handle the persistUserState which including 1075 // active and revoked association. 1076 logRemoveAssociation(deviceProfile); 1077 1078 // Remove all the system data transfer requests for the association. 1079 mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId); 1080 1081 if (!wasPresent || !association.isNotifyOnDeviceNearby()) return; 1082 // The device was connected and the app was notified: check if we need to unbind the app 1083 // now. 1084 final boolean shouldStayBound = any( 1085 mAssociationStore.getAssociationsForPackage(userId, packageName), 1086 it -> it.isNotifyOnDeviceNearby() 1087 && mDevicePresenceMonitor.isDevicePresent(it.getId())); 1088 if (shouldStayBound) return; 1089 mCompanionAppController.unbindCompanionApplication(userId, packageName); 1090 } 1091 1092 /** 1093 * First, checks if the companion application should be removed from the list role holders when 1094 * upon association's removal, i.e.: association's profile (matches the role) is not null, 1095 * the application does not have other associations with the same profile, etc. 1096 * 1097 * <p> 1098 * Then, if establishes that the application indeed has to be removed from the list of the role 1099 * holders, checks if it could be done right now - 1100 * {@link android.app.role.RoleManager#removeRoleHolderAsUser(String, String, int, UserHandle, java.util.concurrent.Executor, java.util.function.Consumer) RoleManager#removeRoleHolderAsUser()} 1101 * will kill the application's process, which leads poor user experience if the application was 1102 * in foreground when this happened, to avoid this CDMS delays invoking 1103 * {@code RoleManager.removeRoleHolderAsUser()} until the app is no longer in foreground. 1104 * 1105 * @return {@code true} if the application does NOT need be removed from the list of the role 1106 * holders OR if the application was successfully removed from the list of role holders. 1107 * I.e.: from the role-management perspective the association is done with. 1108 * {@code false} if the application needs to be removed from the list of role the role 1109 * holders, BUT it CDMS would prefer to do it later. 1110 * I.e.: application is in the foreground at the moment, but invoking 1111 * {@code RoleManager.removeRoleHolderAsUser()} will kill the application's process, 1112 * which would lead to the poor UX, hence need to try later. 1113 */ 1114 maybeRemoveRoleHolderForAssociation(@onNull AssociationInfo association)1115 private boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) { 1116 if (DEBUG) Log.d(TAG, "maybeRemoveRoleHolderForAssociation() association=" + association); 1117 1118 final String deviceProfile = association.getDeviceProfile(); 1119 if (deviceProfile == null) { 1120 // No role was granted to for this association, there is nothing else we need to here. 1121 return true; 1122 } 1123 // Do not need to remove the system role since it was pre-granted by the system. 1124 if (deviceProfile.equals(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)) { 1125 return true; 1126 } 1127 1128 // Check if the applications is associated with another devices with the profile. If so, 1129 // it should remain the role holder. 1130 final int id = association.getId(); 1131 final int userId = association.getUserId(); 1132 final String packageName = association.getPackageName(); 1133 final boolean roleStillInUse = any( 1134 mAssociationStore.getAssociationsForPackage(userId, packageName), 1135 it -> deviceProfile.equals(it.getDeviceProfile()) && id != it.getId()); 1136 if (roleStillInUse) { 1137 // Application should remain a role holder, there is nothing else we need to here. 1138 return true; 1139 } 1140 1141 final int packageProcessImportance = getPackageProcessImportance(userId, packageName); 1142 if (packageProcessImportance <= IMPORTANCE_VISIBLE) { 1143 // Need to remove the app from the list of role holders, but the process is visible to 1144 // the user at the moment, so we'll need to it later: log and return false. 1145 Slog.i(TAG, "Cannot remove role holder for the removed association id=" + id 1146 + " now - process is visible."); 1147 return false; 1148 } 1149 1150 removeRoleHolderForAssociation(getContext(), association); 1151 return true; 1152 } 1153 getPackageProcessImportance(@serIdInt int userId, @NonNull String packageName)1154 private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) { 1155 return Binder.withCleanCallingIdentity(() -> { 1156 final int uid = 1157 mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId); 1158 return mActivityManager.getUidImportance(uid); 1159 }); 1160 } 1161 1162 /** 1163 * Set revoked flag for active association and add the revoked association and the uid into 1164 * the caches. 1165 * 1166 * @see #mRevokedAssociationsPendingRoleHolderRemoval 1167 * @see #mUidsPendingRoleHolderRemoval 1168 * @see OnPackageVisibilityChangeListener 1169 */ addToPendingRoleHolderRemoval(@onNull AssociationInfo association)1170 private void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) { 1171 // First: set revoked flag. 1172 association = AssociationInfo.builder(association) 1173 .setRevoked(true) 1174 .build(); 1175 1176 final String packageName = association.getPackageName(); 1177 final int userId = association.getUserId(); 1178 final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId); 1179 1180 // Second: add to the set. 1181 synchronized (mRevokedAssociationsPendingRoleHolderRemoval) { 1182 mRevokedAssociationsPendingRoleHolderRemoval.forUser(association.getUserId()) 1183 .add(association); 1184 if (!mUidsPendingRoleHolderRemoval.containsKey(uid)) { 1185 mUidsPendingRoleHolderRemoval.put(uid, packageName); 1186 1187 if (mUidsPendingRoleHolderRemoval.size() == 1) { 1188 // Just added first uid: start the listener 1189 mOnPackageVisibilityChangeListener.startListening(); 1190 } 1191 } 1192 } 1193 } 1194 1195 /** 1196 * Remove the revoked association from the cache and also remove the uid from the map if 1197 * there are other associations with the same package still pending for role holder removal. 1198 * 1199 * @see #mRevokedAssociationsPendingRoleHolderRemoval 1200 * @see #mUidsPendingRoleHolderRemoval 1201 * @see OnPackageVisibilityChangeListener 1202 */ removeFromPendingRoleHolderRemoval(@onNull AssociationInfo association)1203 private void removeFromPendingRoleHolderRemoval(@NonNull AssociationInfo association) { 1204 final String packageName = association.getPackageName(); 1205 final int userId = association.getUserId(); 1206 final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId); 1207 1208 synchronized (mRevokedAssociationsPendingRoleHolderRemoval) { 1209 mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId) 1210 .remove(association); 1211 1212 final boolean shouldKeepUidForRemoval = any( 1213 getPendingRoleHolderRemovalAssociationsForUser(userId), 1214 ai -> packageName.equals(ai.getPackageName())); 1215 // Do not remove the uid from the map since other associations with 1216 // the same packageName still pending for role holder removal. 1217 if (!shouldKeepUidForRemoval) { 1218 mUidsPendingRoleHolderRemoval.remove(uid); 1219 } 1220 1221 if (mUidsPendingRoleHolderRemoval.isEmpty()) { 1222 // The set is empty now - can "turn off" the listener. 1223 mOnPackageVisibilityChangeListener.stopListening(); 1224 } 1225 } 1226 } 1227 1228 /** 1229 * @return a copy of the revoked associations set (safeguarding against 1230 * {@code ConcurrentModificationException}-s). 1231 */ getPendingRoleHolderRemovalAssociationsForUser( @serIdInt int userId)1232 private @NonNull Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser( 1233 @UserIdInt int userId) { 1234 synchronized (mRevokedAssociationsPendingRoleHolderRemoval) { 1235 // Return a copy. 1236 return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId)); 1237 } 1238 } 1239 getPackageNameByUid(int uid)1240 private String getPackageNameByUid(int uid) { 1241 synchronized (mRevokedAssociationsPendingRoleHolderRemoval) { 1242 return mUidsPendingRoleHolderRemoval.get(uid); 1243 } 1244 } 1245 updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association)1246 void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) { 1247 final PackageInfo packageInfo = 1248 getPackageInfo(getContext(), association.getUserId(), association.getPackageName()); 1249 1250 Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo)); 1251 } 1252 updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo)1253 private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) { 1254 if (packageInfo == null) { 1255 return; 1256 } 1257 if (containsEither(packageInfo.requestedPermissions, 1258 android.Manifest.permission.RUN_IN_BACKGROUND, 1259 android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) { 1260 mPowerWhitelistManager.addToWhitelist(packageInfo.packageName); 1261 } else { 1262 try { 1263 mPowerWhitelistManager.removeFromWhitelist(packageInfo.packageName); 1264 } catch (UnsupportedOperationException e) { 1265 Slog.w(TAG, packageInfo.packageName + " can't be removed from power save" 1266 + " whitelist. It might due to the package is whitelisted by the system."); 1267 } 1268 } 1269 1270 NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(getContext()); 1271 try { 1272 if (containsEither(packageInfo.requestedPermissions, 1273 android.Manifest.permission.USE_DATA_IN_BACKGROUND, 1274 android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)) { 1275 networkPolicyManager.addUidPolicy( 1276 packageInfo.applicationInfo.uid, 1277 NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND); 1278 } else { 1279 networkPolicyManager.removeUidPolicy( 1280 packageInfo.applicationInfo.uid, 1281 NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND); 1282 } 1283 } catch (IllegalArgumentException e) { 1284 Slog.e(TAG, e.getMessage()); 1285 } 1286 1287 exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid); 1288 } 1289 exemptFromAutoRevoke(String packageName, int uid)1290 private void exemptFromAutoRevoke(String packageName, int uid) { 1291 try { 1292 mAppOpsManager.setMode( 1293 AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, 1294 uid, 1295 packageName, 1296 AppOpsManager.MODE_IGNORED); 1297 } catch (RemoteException e) { 1298 Slog.w(TAG, "Error while granting auto revoke exemption for " + packageName, e); 1299 } 1300 } 1301 updateAtm(int userId, List<AssociationInfo> associations)1302 private void updateAtm(int userId, List<AssociationInfo> associations) { 1303 final Set<Integer> companionAppUids = new ArraySet<>(); 1304 for (AssociationInfo association : associations) { 1305 final int uid = mPackageManagerInternal.getPackageUid(association.getPackageName(), 1306 0, userId); 1307 if (uid >= 0) { 1308 companionAppUids.add(uid); 1309 } 1310 } 1311 if (mAtmInternal != null) { 1312 mAtmInternal.setCompanionAppUids(userId, companionAppUids); 1313 } 1314 if (mAmInternal != null) { 1315 // Make a copy of the set and send it to ActivityManager. 1316 mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids)); 1317 } 1318 } 1319 maybeGrantAutoRevokeExemptions()1320 private void maybeGrantAutoRevokeExemptions() { 1321 Slog.d(TAG, "maybeGrantAutoRevokeExemptions()"); 1322 1323 PackageManager pm = getContext().getPackageManager(); 1324 for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) { 1325 SharedPreferences pref = getContext().getSharedPreferences( 1326 new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME), 1327 Context.MODE_PRIVATE); 1328 if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) { 1329 continue; 1330 } 1331 1332 try { 1333 final List<AssociationInfo> associations = 1334 mAssociationStore.getAssociationsForUser(userId); 1335 for (AssociationInfo a : associations) { 1336 try { 1337 int uid = pm.getPackageUidAsUser(a.getPackageName(), userId); 1338 exemptFromAutoRevoke(a.getPackageName(), uid); 1339 } catch (PackageManager.NameNotFoundException e) { 1340 Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e); 1341 } 1342 } 1343 } finally { 1344 pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply(); 1345 } 1346 } 1347 } 1348 1349 private final AssociationStore.OnChangeListener mAssociationStoreChangeListener = 1350 new AssociationStore.OnChangeListener() { 1351 @Override 1352 public void onAssociationChanged(int changeType, AssociationInfo association) { 1353 onAssociationChangedInternal(changeType, association); 1354 } 1355 }; 1356 1357 private final CompanionDevicePresenceMonitor.Callback mDevicePresenceCallback = 1358 new CompanionDevicePresenceMonitor.Callback() { 1359 @Override 1360 public void onDeviceAppeared(int associationId) { 1361 onDeviceAppearedInternal(associationId); 1362 } 1363 1364 @Override 1365 public void onDeviceDisappeared(int associationId) { 1366 onDeviceDisappearedInternal(associationId); 1367 } 1368 }; 1369 1370 private final PackageMonitor mPackageMonitor = new PackageMonitor() { 1371 @Override 1372 public void onPackageRemoved(String packageName, int uid) { 1373 onPackageRemoveOrDataClearedInternal(getChangingUserId(), packageName); 1374 } 1375 1376 @Override 1377 public void onPackageDataCleared(String packageName, int uid) { 1378 onPackageRemoveOrDataClearedInternal(getChangingUserId(), packageName); 1379 } 1380 1381 @Override 1382 public void onPackageModified(String packageName) { 1383 onPackageModifiedInternal(getChangingUserId(), packageName); 1384 } 1385 }; 1386 getFirstAssociationIdForUser(@serIdInt int userId)1387 static int getFirstAssociationIdForUser(@UserIdInt int userId) { 1388 // We want the IDs to start from 1, not 0. 1389 return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1; 1390 } 1391 getLastAssociationIdForUser(@serIdInt int userId)1392 static int getLastAssociationIdForUser(@UserIdInt int userId) { 1393 return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE; 1394 } 1395 deepUnmodifiableCopy(Map<String, Set<Integer>> orig)1396 private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) { 1397 final Map<String, Set<Integer>> copy = new HashMap<>(); 1398 1399 for (Map.Entry<String, Set<Integer>> entry : orig.entrySet()) { 1400 final Set<Integer> valueCopy = new HashSet<>(entry.getValue()); 1401 copy.put(entry.getKey(), Collections.unmodifiableSet(valueCopy)); 1402 } 1403 1404 return Collections.unmodifiableMap(copy); 1405 } 1406 containsEither(T[] array, T a, T b)1407 private static <T> boolean containsEither(T[] array, T a, T b) { 1408 return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b); 1409 } 1410 1411 private class LocalService implements CompanionDeviceManagerServiceInternal { 1412 @Override removeInactiveSelfManagedAssociations()1413 public void removeInactiveSelfManagedAssociations() { 1414 CompanionDeviceManagerService.this.removeInactiveSelfManagedAssociations(); 1415 } 1416 1417 @Override registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback, @CrossDeviceSyncControllerCallback.Type int type)1418 public void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback, 1419 @CrossDeviceSyncControllerCallback.Type int type) { 1420 if (CompanionDeviceConfig.isEnabled( 1421 CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) { 1422 mCrossDeviceSyncController.registerCallMetadataSyncCallback(callback, type); 1423 } 1424 } 1425 1426 @Override crossDeviceSync(int userId, Collection<CrossDeviceCall> calls)1427 public void crossDeviceSync(int userId, Collection<CrossDeviceCall> calls) { 1428 if (CompanionDeviceConfig.isEnabled( 1429 CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) { 1430 mCrossDeviceSyncController.syncToAllDevicesForUserId(userId, calls); 1431 } 1432 } 1433 1434 @Override crossDeviceSync(AssociationInfo associationInfo, Collection<CrossDeviceCall> calls)1435 public void crossDeviceSync(AssociationInfo associationInfo, 1436 Collection<CrossDeviceCall> calls) { 1437 if (CompanionDeviceConfig.isEnabled( 1438 CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) { 1439 mCrossDeviceSyncController.syncToSingleDevice(associationInfo, calls); 1440 } 1441 } 1442 1443 @Override sendCrossDeviceSyncMessage(int associationId, byte[] message)1444 public void sendCrossDeviceSyncMessage(int associationId, byte[] message) { 1445 if (CompanionDeviceConfig.isEnabled( 1446 CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) { 1447 mCrossDeviceSyncController.syncMessageToDevice(associationId, message); 1448 } 1449 } 1450 1451 @Override sendCrossDeviceSyncMessageToAllDevices(int userId, byte[] message)1452 public void sendCrossDeviceSyncMessageToAllDevices(int userId, byte[] message) { 1453 if (CompanionDeviceConfig.isEnabled( 1454 CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) { 1455 mCrossDeviceSyncController.syncMessageToAllDevicesForUserId(userId, message); 1456 } 1457 } 1458 1459 @Override addSelfOwnedCallId(String callId)1460 public void addSelfOwnedCallId(String callId) { 1461 if (CompanionDeviceConfig.isEnabled( 1462 CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) { 1463 mCrossDeviceSyncController.addSelfOwnedCallId(callId); 1464 } 1465 } 1466 1467 @Override removeSelfOwnedCallId(String callId)1468 public void removeSelfOwnedCallId(String callId) { 1469 if (CompanionDeviceConfig.isEnabled( 1470 CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) { 1471 mCrossDeviceSyncController.removeSelfOwnedCallId(callId); 1472 } 1473 } 1474 } 1475 1476 /** 1477 * This method must only be called from {@link CompanionDeviceShellCommand} for testing 1478 * purposes only! 1479 */ persistState()1480 void persistState() { 1481 mUserPersistenceHandler.clearMessages(); 1482 for (UserInfo user : mUserManager.getAliveUsers()) { 1483 persistStateForUser(user.id); 1484 } 1485 } 1486 1487 /** 1488 * This class is dedicated to handling requests to persist user state. 1489 */ 1490 @SuppressLint("HandlerLeak") 1491 private class PersistUserStateHandler extends Handler { PersistUserStateHandler()1492 PersistUserStateHandler() { 1493 super(BackgroundThread.get().getLooper()); 1494 } 1495 1496 /** 1497 * Persists user state unless there is already an outstanding request for the given user. 1498 */ postPersistUserState(@serIdInt int userId)1499 synchronized void postPersistUserState(@UserIdInt int userId) { 1500 if (!hasMessages(userId)) { 1501 sendMessage(obtainMessage(userId)); 1502 } 1503 } 1504 1505 /** 1506 * Clears *ALL* outstanding persist requests for *ALL* users. 1507 */ clearMessages()1508 synchronized void clearMessages() { 1509 removeCallbacksAndMessages(null); 1510 } 1511 1512 @Override handleMessage(@onNull Message msg)1513 public void handleMessage(@NonNull Message msg) { 1514 final int userId = msg.what; 1515 persistStateForUser(userId); 1516 } 1517 } 1518 1519 /** 1520 * An OnUidImportanceListener class which watches the importance of the packages. 1521 * In this class, we ONLY interested in the importance of the running process is greater than 1522 * {@link RunningAppProcessInfo.IMPORTANCE_VISIBLE} for the uids have been added into the 1523 * {@link mUidsPendingRoleHolderRemoval}. Lastly remove the role holder for the revoked 1524 * associations for the same packages. 1525 * 1526 * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo) 1527 * @see #removeFromPendingRoleHolderRemoval(AssociationInfo) 1528 * @see #getPendingRoleHolderRemovalAssociationsForUser(int) 1529 */ 1530 private class OnPackageVisibilityChangeListener implements 1531 ActivityManager.OnUidImportanceListener { 1532 final @NonNull ActivityManager mAm; 1533 OnPackageVisibilityChangeListener(@onNull ActivityManager am)1534 OnPackageVisibilityChangeListener(@NonNull ActivityManager am) { 1535 this.mAm = am; 1536 } 1537 startListening()1538 void startListening() { 1539 Binder.withCleanCallingIdentity( 1540 () -> mAm.addOnUidImportanceListener( 1541 /* listener */ OnPackageVisibilityChangeListener.this, 1542 RunningAppProcessInfo.IMPORTANCE_VISIBLE)); 1543 } 1544 stopListening()1545 void stopListening() { 1546 Binder.withCleanCallingIdentity( 1547 () -> mAm.removeOnUidImportanceListener( 1548 /* listener */ OnPackageVisibilityChangeListener.this)); 1549 } 1550 1551 @Override onUidImportance(int uid, int importance)1552 public void onUidImportance(int uid, int importance) { 1553 if (importance <= RunningAppProcessInfo.IMPORTANCE_VISIBLE) { 1554 // The lower the importance value the more "important" the process is. 1555 // We are only interested when the process ceases to be visible. 1556 return; 1557 } 1558 1559 final String packageName = getPackageNameByUid(uid); 1560 if (packageName == null) { 1561 // Not interested in this uid. 1562 return; 1563 } 1564 1565 final int userId = UserHandle.getUserId(uid); 1566 1567 boolean needToPersistStateForUser = false; 1568 1569 for (AssociationInfo association : 1570 getPendingRoleHolderRemovalAssociationsForUser(userId)) { 1571 if (!packageName.equals(association.getPackageName())) continue; 1572 1573 if (!maybeRemoveRoleHolderForAssociation(association)) { 1574 // Did not remove the role holder, will have to try again later. 1575 continue; 1576 } 1577 1578 removeFromPendingRoleHolderRemoval(association); 1579 needToPersistStateForUser = true; 1580 } 1581 1582 if (needToPersistStateForUser) { 1583 mUserPersistenceHandler.postPersistUserState(userId); 1584 } 1585 } 1586 } 1587 1588 private static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> { 1589 @Override create(int userId)1590 protected @NonNull Set<AssociationInfo> create(int userId) { 1591 return new ArraySet<>(); 1592 } 1593 } 1594 } 1595