1 /* 2 * Copyright (C) 2022 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.server.healthconnect.permission; 18 19 import static com.android.server.healthconnect.permission.FirstGrantTimeDatastore.DATA_TYPE_CURRENT; 20 import static com.android.server.healthconnect.permission.FirstGrantTimeDatastore.DATA_TYPE_STAGED; 21 22 import android.annotation.Nullable; 23 import android.annotation.WorkerThread; 24 import android.content.Context; 25 import android.content.pm.PackageInfo; 26 import android.content.pm.PackageManager; 27 import android.health.connect.Constants; 28 import android.os.UserHandle; 29 import android.os.UserManager; 30 import android.util.ArrayMap; 31 import android.util.ArraySet; 32 import android.util.Log; 33 34 import com.android.internal.annotations.GuardedBy; 35 import com.android.server.healthconnect.HealthConnectThreadScheduler; 36 import com.android.server.healthconnect.migration.MigrationStateManager; 37 import com.android.server.healthconnect.storage.datatypehelpers.HealthDataCategoryPriorityHelper; 38 39 import java.io.File; 40 import java.time.Instant; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.Optional; 44 import java.util.Set; 45 import java.util.concurrent.RejectedExecutionException; 46 import java.util.concurrent.locks.ReentrantReadWriteLock; 47 48 /** 49 * Manager class of the health permissions first grant time. 50 * 51 * @hide 52 */ 53 public final class FirstGrantTimeManager implements PackageManager.OnPermissionsChangedListener { 54 private static final String TAG = "HealthFirstGrantTimeMan"; 55 private static final int CURRENT_VERSION = 1; 56 57 private final PackageManager mPackageManager; 58 private final UserManager mUserManager; 59 private final HealthPermissionIntentAppsTracker mTracker; 60 61 private final ReentrantReadWriteLock mGrantTimeLock = new ReentrantReadWriteLock(); 62 63 @GuardedBy("mGrantTimeLock") 64 private final FirstGrantTimeDatastore mDatastore; 65 66 @GuardedBy("mGrantTimeLock") 67 private final UidToGrantTimeCache mUidToGrantTimeCache; 68 69 @GuardedBy("mGrantTimeLock") 70 private final Set<Integer> mRestoredAndValidatedUsers = new ArraySet<>(); 71 72 private final PackageInfoUtils mPackageInfoHelper; 73 private final Context mContext; 74 75 private final HealthDataCategoryPriorityHelper mHealthDataCategoryPriorityHelper; 76 private final MigrationStateManager mMigrationStateManager; 77 private final HealthConnectThreadScheduler mThreadScheduler; 78 FirstGrantTimeManager( Context context, HealthPermissionIntentAppsTracker tracker, FirstGrantTimeDatastore datastore, PackageInfoUtils packageInfoUtils, HealthDataCategoryPriorityHelper healthDataCategoryPriorityHelper, MigrationStateManager migrationStateManager, HealthConnectThreadScheduler threadScheduler)79 public FirstGrantTimeManager( 80 Context context, 81 HealthPermissionIntentAppsTracker tracker, 82 FirstGrantTimeDatastore datastore, 83 PackageInfoUtils packageInfoUtils, 84 HealthDataCategoryPriorityHelper healthDataCategoryPriorityHelper, 85 MigrationStateManager migrationStateManager, 86 HealthConnectThreadScheduler threadScheduler) { 87 mContext = context; 88 mTracker = tracker; 89 mDatastore = datastore; 90 mPackageInfoHelper = packageInfoUtils; 91 mHealthDataCategoryPriorityHelper = healthDataCategoryPriorityHelper; 92 mMigrationStateManager = migrationStateManager; 93 94 mUidToGrantTimeCache = new UidToGrantTimeCache(); 95 mUserManager = context.getSystemService(UserManager.class); 96 mPackageManager = context.getPackageManager(); 97 mPackageManager.addOnPermissionsChangeListener(this); 98 mThreadScheduler = threadScheduler; 99 } 100 101 /** 102 * Gets the {@link Instant} when the first health permission was granted for a given {@code 103 * packageName} by a given {@code user}. Returns {@link Optional#empty} if there's no health 104 * permission granted for the package by the user. 105 * 106 * <p>This method also initiates first grant time to the current time if there's any permission 107 * granted but there's no grant time recorded. This mitigates the case where some health 108 * permissions got granted/revoked without onPermissionsChanged callback. 109 */ getFirstGrantTime(String packageName, UserHandle user)110 public Optional<Instant> getFirstGrantTime(String packageName, UserHandle user) 111 throws IllegalArgumentException { 112 113 Integer uid = mPackageInfoHelper.getPackageUid(packageName, user, mContext); 114 if (uid == null) { 115 throw new IllegalArgumentException( 116 "Package name " 117 + packageName 118 + " of user " 119 + user.getIdentifier() 120 + " not found."); 121 } 122 initAndValidateUserStateIfNeedLocked(user); 123 124 Optional<Instant> firstGrantTime = getGrantTimeReadLocked(uid); 125 if (firstGrantTime.isPresent()) { 126 return firstGrantTime; 127 } 128 129 // Check and update the state in case health permission has been granted before 130 // onPermissionsChanged callback was propagated. 131 updateFirstGrantTimesFromPermissionState(user, uid, true); 132 return getGrantTimeReadLocked(uid); 133 } 134 135 /** Sets the provided first grant time for the given {@code packageName}. */ setFirstGrantTime(String packageName, Instant time, UserHandle user)136 public void setFirstGrantTime(String packageName, Instant time, UserHandle user) { 137 final Integer uid = mPackageInfoHelper.getPackageUid(packageName, user, mContext); 138 if (uid == null) { 139 throw new IllegalArgumentException( 140 "Package name " 141 + packageName 142 + " of user " 143 + user.getIdentifier() 144 + " not found."); 145 } 146 initAndValidateUserStateIfNeedLocked(user); 147 148 mGrantTimeLock.writeLock().lock(); 149 try { 150 mUidToGrantTimeCache.put(uid, time); 151 mDatastore.writeForUser( 152 mUidToGrantTimeCache.extractUserGrantTimeStateUseSharedNames(user), 153 user, 154 DATA_TYPE_CURRENT); 155 } finally { 156 mGrantTimeLock.writeLock().unlock(); 157 } 158 } 159 160 @Override onPermissionsChanged(int uid)161 public void onPermissionsChanged(int uid) { 162 updateFirstGrantTimesFromPermissionState(UserHandle.getUserHandleForUid(uid), uid, false); 163 } 164 165 /** 166 * Checks whether the {@code uid} is mapped to valid package names of valid health apps before 167 * updating first grant times from the current permission state. The update can be perform in 168 * the same thread where this method is called if {@code sync} is set to {@code true}, another 169 * background thread otherwise. 170 */ updateFirstGrantTimesFromPermissionState(UserHandle user, int uid, boolean sync)171 private void updateFirstGrantTimesFromPermissionState(UserHandle user, int uid, boolean sync) { 172 if (!mUserManager.isUserUnlocked(user)) { 173 // this method is called in onPermissionsChanged(uid) which is called as soon as the 174 // system boots up, even before the user has unlock the device for the first time. 175 // Side note: onPermissionsChanged() is also called on both primary user and work 176 // profile user. 177 return; 178 } 179 180 final String[] packageNames = mPackageInfoHelper.getPackagesForUid(mContext, user, uid); 181 if (packageNames == null) { 182 Log.w(TAG, "onPermissionsChanged: no known packages for UID: " + uid); 183 return; 184 } 185 186 if (!checkSupportPermissionsUsageIntent(packageNames, user)) { 187 logIfInDebugMode("Cannot find health intent declaration in ", packageNames[0]); 188 return; 189 } 190 191 if (sync) { 192 updateFirstGrantTimesFromPermissionState(uid, user, packageNames); 193 } else { 194 try { 195 mThreadScheduler.scheduleInternalTask( 196 () -> updateFirstGrantTimesFromPermissionState(uid, user, packageNames)); 197 } catch (RejectedExecutionException executionException) { 198 Log.e( 199 TAG, 200 "Can't queue internal task in #onPermissionsChanged for uid=" + uid, 201 executionException); 202 } 203 } 204 } 205 206 /** 207 * Checks permission states for {@code uid} and updates first grant times accordingly. 208 * 209 * <p><b>Note:</b>This method must only be called from a non-main thread. 210 */ 211 @WorkerThread updateFirstGrantTimesFromPermissionState( int uid, UserHandle user, String[] packageNames)212 private void updateFirstGrantTimesFromPermissionState( 213 int uid, UserHandle user, String[] packageNames) { 214 // call this method after `checkSupportPermissionsUsageIntent` so we are sure that we are 215 // not initializing user state when onPermissionsChanged(uid) is called for non HC client 216 // apps. 217 initAndValidateUserStateIfNeedLocked(user); 218 219 mGrantTimeLock.writeLock().lock(); 220 try { 221 boolean anyHealthPermissionGranted = 222 mPackageInfoHelper.hasGrantedHealthPermissions(packageNames, user, mContext); 223 224 boolean grantTimeRecorded = getGrantTimeReadLocked(uid).isPresent(); 225 if (grantTimeRecorded != anyHealthPermissionGranted) { 226 if (grantTimeRecorded) { 227 // An app doesn't have health permissions anymore, reset its grant time. 228 mUidToGrantTimeCache.remove(uid); 229 // Update priority table only if migration is not in progress as it should 230 // already take care of merging permissions. 231 if (!mMigrationStateManager.isMigrationInProgress()) { 232 mThreadScheduler.scheduleInternalTask( 233 () -> removeAppsFromPriorityList(packageNames)); 234 } 235 } else { 236 // An app got new health permission, set current time as it's first grant 237 // time if we can't update state from the staged data. 238 if (!tryUpdateGrantTimeFromStagedDataLocked(user, uid)) { 239 mUidToGrantTimeCache.put(uid, Instant.now()); 240 } 241 } 242 243 UserGrantTimeState updatedState = 244 mUidToGrantTimeCache.extractUserGrantTimeStateUseSharedNames(user); 245 logIfInDebugMode("State after onPermissionsChanged :", updatedState); 246 mDatastore.writeForUser(updatedState, user, DATA_TYPE_CURRENT); 247 } else { 248 // Update priority table only if migration is not in progress as it should already 249 // take care of merging permissions 250 if (!mMigrationStateManager.isMigrationInProgress()) { 251 mThreadScheduler.scheduleInternalTask( 252 () -> { 253 for (String packageName : packageNames) { 254 mHealthDataCategoryPriorityHelper 255 .maybeRemoveAppFromPriorityList(packageName, user); 256 } 257 }); 258 } 259 } 260 } finally { 261 mGrantTimeLock.writeLock().unlock(); 262 } 263 } 264 265 /** Returns the grant time state for this user. */ getGrantTimeStateForUser(UserHandle user)266 public UserGrantTimeState getGrantTimeStateForUser(UserHandle user) { 267 initAndValidateUserStateIfNeedLocked(user); 268 return mUidToGrantTimeCache.extractUserGrantTimeStateDoNotUseSharedNames(user); 269 } 270 271 /** 272 * Callback which should be called when backup grant time data is available. Triggers merge of 273 * current and backup grant time data. All grant times from backup state which are not merged 274 * with the current state (e.g. because an app is not installed) will be staged until app gets 275 * health permission. 276 * 277 * @param userId user for which the data is available. 278 * @param state backup state to apply. 279 */ applyAndStageGrantTimeStateForUser(UserHandle userId, UserGrantTimeState state)280 public void applyAndStageGrantTimeStateForUser(UserHandle userId, UserGrantTimeState state) { 281 initAndValidateUserStateIfNeedLocked(userId); 282 283 mGrantTimeLock.writeLock().lock(); 284 try { 285 // Write the state into the disk as staged data so that it can be merged. 286 mDatastore.writeForUser(state, userId, DATA_TYPE_STAGED); 287 updateGrantTimesWithStagedDataLocked(userId); 288 } finally { 289 mGrantTimeLock.writeLock().unlock(); 290 } 291 } 292 293 /** Returns file with grant times data. */ getFile(UserHandle userHandle)294 public File getFile(UserHandle userHandle) { 295 return mDatastore.getFile(userHandle, DATA_TYPE_CURRENT); 296 } 297 onPackageRemoved(String packageName, int removedPackageUid, UserHandle userHandle)298 void onPackageRemoved(String packageName, int removedPackageUid, UserHandle userHandle) { 299 String[] leftSharedUidPackages = 300 mPackageInfoHelper.getPackagesForUid(mContext, userHandle, removedPackageUid); 301 if (leftSharedUidPackages != null && leftSharedUidPackages.length > 0) { 302 // There are installed packages left with given UID, 303 // don't need to update grant time state. 304 return; 305 } 306 307 initAndValidateUserStateIfNeedLocked(userHandle); 308 309 if (getGrantTimeReadLocked(removedPackageUid).isPresent()) { 310 mGrantTimeLock.writeLock().lock(); 311 try { 312 mUidToGrantTimeCache.remove(removedPackageUid); 313 UserGrantTimeState updatedState = 314 mUidToGrantTimeCache.extractUserGrantTimeStateUseSharedNames(userHandle); 315 logIfInDebugMode("State after package " + packageName + " removed: ", updatedState); 316 mDatastore.writeForUser(updatedState, userHandle, DATA_TYPE_CURRENT); 317 } finally { 318 mGrantTimeLock.writeLock().unlock(); 319 } 320 } 321 } 322 323 @GuardedBy("mGrantTimeLock") getGrantTimeReadLocked(Integer uid)324 private Optional<Instant> getGrantTimeReadLocked(Integer uid) { 325 mGrantTimeLock.readLock().lock(); 326 try { 327 return mUidToGrantTimeCache.get(uid); 328 } finally { 329 mGrantTimeLock.readLock().unlock(); 330 } 331 } 332 333 @GuardedBy("mGrantTimeLock") updateGrantTimesWithStagedDataLocked(UserHandle user)334 private void updateGrantTimesWithStagedDataLocked(UserHandle user) { 335 boolean stateChanged = false; 336 for (Integer uid : mUidToGrantTimeCache.mUidToGrantTime.keySet()) { 337 if (!UserHandle.getUserHandleForUid(uid).equals(user)) { 338 continue; 339 } 340 341 stateChanged |= tryUpdateGrantTimeFromStagedDataLocked(user, uid); 342 } 343 344 if (stateChanged) { 345 mDatastore.writeForUser( 346 mUidToGrantTimeCache.extractUserGrantTimeStateUseSharedNames(user), 347 user, 348 DATA_TYPE_CURRENT); 349 } 350 } 351 352 @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression 353 @GuardedBy("mGrantTimeLock") tryUpdateGrantTimeFromStagedDataLocked(UserHandle user, Integer uid)354 private boolean tryUpdateGrantTimeFromStagedDataLocked(UserHandle user, Integer uid) { 355 UserGrantTimeState backupState = mDatastore.readForUser(user, DATA_TYPE_STAGED); 356 if (backupState == null) { 357 return false; 358 } 359 360 Instant stagedTime = null; 361 for (String packageName : 362 mPackageInfoHelper.getPackagesForUidNonNull(mContext, user, uid)) { 363 stagedTime = backupState.getPackageGrantTimes().get(packageName); 364 if (stagedTime != null) { 365 break; 366 } 367 } 368 369 if (stagedTime == null) { 370 return false; 371 } 372 Optional<Instant> firstGrantTime = mUidToGrantTimeCache.get(uid); 373 if (firstGrantTime.isPresent() && firstGrantTime.get().isBefore(stagedTime)) { 374 Log.w( 375 TAG, 376 "Backup grant time is later than currently stored grant time, " 377 + "skip restoring grant time for uid " 378 + uid); 379 return false; 380 } 381 382 mUidToGrantTimeCache.put(uid, stagedTime); 383 for (String packageName : 384 mPackageInfoHelper.getPackagesForUidNonNull(mContext, user, uid)) { 385 backupState.getPackageGrantTimes().remove(packageName); 386 } 387 mDatastore.writeForUser(backupState, user, DATA_TYPE_STAGED); 388 return true; 389 } 390 391 /** Initialize first grant time state for given user. */ initAndValidateUserStateIfNeedLocked(UserHandle user)392 private void initAndValidateUserStateIfNeedLocked(UserHandle user) { 393 if (!mUserManager.isUserUnlocked(user)) { 394 // only init first grant time state when device is unlocked, because before that, we 395 // cannot access any files, which leads to `mUidToGrantTimeCache` being empty and never 396 // get re-initialized. 397 return; 398 } 399 400 if (userStateIsInitializedReadLocked(user)) { 401 // This user state is already inited and validated 402 return; 403 } 404 405 mGrantTimeLock.writeLock().lock(); 406 try { 407 Log.i( 408 TAG, 409 "State for user: " 410 + user.getIdentifier() 411 + " has not been restored and validated."); 412 UserGrantTimeState restoredState = restoreCurrentUserStateLocked(user); 413 414 List<PackageInfo> validHealthApps = 415 mPackageInfoHelper.getPackagesHoldingHealthPermissions(user, mContext); 416 417 logIfInDebugMode( 418 "Packages holding health perms of user " + user + " :", validHealthApps); 419 420 validateAndCorrectRecordedStateForUser(restoredState, validHealthApps, user); 421 422 // TODO(b/260691599): consider removing mapping when getUidForSharedUser is 423 Map<String, Set<Integer>> sharedUserNamesToUid = 424 collectSharedUserNameToUidsMappingForUser(validHealthApps); 425 426 mUidToGrantTimeCache.populateFromUserGrantTimeState( 427 restoredState, sharedUserNamesToUid, user); 428 429 mRestoredAndValidatedUsers.add(user.getIdentifier()); 430 logIfInDebugMode("State after init: ", restoredState); 431 logIfInDebugMode("Cache after init: ", mUidToGrantTimeCache); 432 } finally { 433 mGrantTimeLock.writeLock().unlock(); 434 } 435 } 436 collectSharedUserNameToUidsMappingForUser( List<PackageInfo> packageInfos)437 private static Map<String, Set<Integer>> collectSharedUserNameToUidsMappingForUser( 438 List<PackageInfo> packageInfos) { 439 Map<String, Set<Integer>> sharedUserNameToUids = new ArrayMap<>(); 440 for (PackageInfo info : packageInfos) { 441 if (info.sharedUserId != null) { 442 if (sharedUserNameToUids.get(info.sharedUserId) == null) { 443 sharedUserNameToUids.put(info.sharedUserId, new ArraySet<>()); 444 } 445 sharedUserNameToUids.get(info.sharedUserId).add(info.applicationInfo.uid); 446 } 447 } 448 return sharedUserNameToUids; 449 } 450 userStateIsInitializedReadLocked(UserHandle user)451 private boolean userStateIsInitializedReadLocked(UserHandle user) { 452 mGrantTimeLock.readLock().lock(); 453 try { 454 return mRestoredAndValidatedUsers.contains(user.getIdentifier()); 455 } finally { 456 mGrantTimeLock.readLock().unlock(); 457 } 458 } 459 460 @GuardedBy("mGrantTimeLock") restoreCurrentUserStateLocked(UserHandle userHandle)461 private UserGrantTimeState restoreCurrentUserStateLocked(UserHandle userHandle) { 462 try { 463 UserGrantTimeState restoredState = 464 mDatastore.readForUser(userHandle, DATA_TYPE_CURRENT); 465 if (restoredState == null) { 466 restoredState = new UserGrantTimeState(CURRENT_VERSION); 467 } 468 return restoredState; 469 } catch (Exception e) { 470 Log.e(TAG, "Error while reading from datastore: " + e); 471 return new UserGrantTimeState(CURRENT_VERSION); 472 } 473 } 474 475 /** 476 * Validate current state and remove apps which are not present / hold health permissions, set 477 * new grant time to apps which doesn't have grant time but installed and hold health 478 * permissions. It should mitigate situation e.g. when permission mainline module did roll-back 479 * and some health permissions got granted/revoked without onPermissionsChanged callback. 480 * 481 * @param recordedState restored state 482 * @param healthPackagesInfos packageInfos of apps which currently hold health permissions 483 * @param user UserHandle for whom to perform validation 484 */ 485 @GuardedBy("mGrantTimeLock") validateAndCorrectRecordedStateForUser( UserGrantTimeState recordedState, List<PackageInfo> healthPackagesInfos, UserHandle user)486 private void validateAndCorrectRecordedStateForUser( 487 UserGrantTimeState recordedState, 488 List<PackageInfo> healthPackagesInfos, 489 UserHandle user) { 490 Set<String> validPackagesPerUser = new ArraySet<>(); 491 Set<String> validSharedUsersPerUser = new ArraySet<>(); 492 493 boolean stateChanged = false; 494 logIfInDebugMode("Valid apps for " + user + ": ", healthPackagesInfos); 495 496 // If package holds health permissions and supports health permission intent 497 // but doesn't have recorded grant time (e.g. because of permissions rollback), 498 // set current time as the first grant time. 499 for (PackageInfo info : healthPackagesInfos) { 500 if (!mTracker.supportsPermissionUsageIntent(info.packageName, user)) { 501 continue; 502 } 503 504 if (info.sharedUserId == null) { 505 stateChanged |= setPackageGrantTimeIfNotRecorded(recordedState, info.packageName); 506 validPackagesPerUser.add(info.packageName); 507 } else { 508 stateChanged |= 509 setSharedUserGrantTimeIfNotRecorded(recordedState, info.sharedUserId); 510 validSharedUsersPerUser.add(info.sharedUserId); 511 } 512 } 513 514 // If package is not installed / doesn't hold health permissions 515 // but has recorded first grant time, remove it from grant time state. 516 stateChanged |= 517 removeInvalidPackagesFromGrantTimeStateForUser(recordedState, validPackagesPerUser); 518 519 stateChanged |= 520 removeInvalidSharedUsersFromGrantTimeStateForUser( 521 recordedState, validSharedUsersPerUser); 522 523 if (stateChanged) { 524 logIfInDebugMode("Changed state after validation for " + user + ": ", recordedState); 525 mDatastore.writeForUser(recordedState, user, DATA_TYPE_CURRENT); 526 } 527 } 528 529 @GuardedBy("mGrantTimeLock") setPackageGrantTimeIfNotRecorded( UserGrantTimeState grantTimeState, String packageName)530 private boolean setPackageGrantTimeIfNotRecorded( 531 UserGrantTimeState grantTimeState, String packageName) { 532 if (!grantTimeState.containsPackageGrantTime(packageName)) { 533 Log.w( 534 TAG, 535 "No recorded grant time for package:" 536 + packageName 537 + ". Assigning current time as the first grant time."); 538 grantTimeState.setPackageGrantTime(packageName, Instant.now()); 539 return true; 540 } 541 return false; 542 } 543 544 @GuardedBy("mGrantTimeLock") setSharedUserGrantTimeIfNotRecorded( UserGrantTimeState grantTimeState, String sharedUserIdName)545 private boolean setSharedUserGrantTimeIfNotRecorded( 546 UserGrantTimeState grantTimeState, String sharedUserIdName) { 547 if (!grantTimeState.containsSharedUserGrantTime(sharedUserIdName)) { 548 Log.w( 549 TAG, 550 "No recorded grant time for shared user:" 551 + sharedUserIdName 552 + ". Assigning current time as first grant time."); 553 grantTimeState.setSharedUserGrantTime(sharedUserIdName, Instant.now()); 554 return true; 555 } 556 return false; 557 } 558 559 @GuardedBy("mGrantTimeLock") removeInvalidPackagesFromGrantTimeStateForUser( UserGrantTimeState recordedState, Set<String> validApps)560 private boolean removeInvalidPackagesFromGrantTimeStateForUser( 561 UserGrantTimeState recordedState, Set<String> validApps) { 562 Set<String> recordedButNotValid = 563 new ArraySet<>(recordedState.getPackageGrantTimes().keySet()); 564 recordedButNotValid.removeAll(validApps); 565 566 if (!recordedButNotValid.isEmpty()) { 567 Log.w( 568 TAG, 569 "Packages " 570 + recordedButNotValid 571 + " have recorded grant times, but not installed or hold health " 572 + "permissions anymore. Removing them from the grant time state."); 573 recordedState.getPackageGrantTimes().keySet().removeAll(recordedButNotValid); 574 return true; 575 } 576 return false; 577 } 578 579 @GuardedBy("mGrantTimeLock") removeInvalidSharedUsersFromGrantTimeStateForUser( UserGrantTimeState recordedState, Set<String> validSharedUsers)580 private boolean removeInvalidSharedUsersFromGrantTimeStateForUser( 581 UserGrantTimeState recordedState, Set<String> validSharedUsers) { 582 Set<String> recordedButNotValid = 583 new ArraySet<>(recordedState.getSharedUserGrantTimes().keySet()); 584 recordedButNotValid.removeAll(validSharedUsers); 585 586 if (!recordedButNotValid.isEmpty()) { 587 Log.w( 588 TAG, 589 "Shared users " 590 + recordedButNotValid 591 + " have recorded grant times, but not installed or hold health " 592 + "permissions anymore. Removing them from the grant time state."); 593 recordedState.getSharedUserGrantTimes().keySet().removeAll(recordedButNotValid); 594 return true; 595 } 596 return false; 597 } 598 checkSupportPermissionsUsageIntent(String[] names, UserHandle user)599 private boolean checkSupportPermissionsUsageIntent(String[] names, UserHandle user) { 600 for (String packageName : names) { 601 if (mTracker.supportsPermissionUsageIntent(packageName, user)) { 602 return true; 603 } 604 } 605 return false; 606 } 607 logIfInDebugMode(String prefixMessage, Object objectToLog)608 private void logIfInDebugMode(String prefixMessage, Object objectToLog) { 609 if (Constants.DEBUG) { 610 Log.d(TAG, prefixMessage + objectToLog); 611 } 612 } 613 614 private class UidToGrantTimeCache { 615 private final Map<Integer, Instant> mUidToGrantTime; 616 UidToGrantTimeCache()617 UidToGrantTimeCache() { 618 mUidToGrantTime = new ArrayMap<>(); 619 } 620 621 @Override toString()622 public String toString() { 623 return mUidToGrantTime.toString(); 624 } 625 626 @Nullable remove(@ullable Integer uid)627 Instant remove(@Nullable Integer uid) { 628 if (uid == null) { 629 return null; 630 } 631 return mUidToGrantTime.remove(uid); 632 } 633 get(Integer uid)634 Optional<Instant> get(Integer uid) { 635 Instant cachedGrantTime = mUidToGrantTime.get(uid); 636 return cachedGrantTime == null ? Optional.empty() : Optional.of(cachedGrantTime); 637 } 638 639 @Nullable put(Integer uid, Instant time)640 Instant put(Integer uid, Instant time) { 641 return mUidToGrantTime.put(uid, time); 642 } 643 644 /** 645 * Get the grant time state for the user. 646 * 647 * <p>Prefer using shared user names for apps where present. 648 */ extractUserGrantTimeStateUseSharedNames(UserHandle user)649 UserGrantTimeState extractUserGrantTimeStateUseSharedNames(UserHandle user) { 650 Map<String, Instant> sharedUserToGrantTime = new ArrayMap<>(); 651 Map<String, Instant> packageNameToGrantTime = new ArrayMap<>(); 652 653 for (Map.Entry<Integer, Instant> entry : mUidToGrantTime.entrySet()) { 654 Integer uid = entry.getKey(); 655 Instant time = entry.getValue(); 656 657 if (!UserHandle.getUserHandleForUid(uid).equals(user)) { 658 continue; 659 } 660 661 String sharedUserName = mPackageInfoHelper.getSharedUserNameFromUid(uid, mContext); 662 if (sharedUserName != null) { 663 sharedUserToGrantTime.put(sharedUserName, time); 664 } else { 665 String[] packageNames = 666 mPackageInfoHelper.getPackagesForUid(mContext, user, uid); 667 if (packageNames != null && packageNames.length == 1) { 668 packageNameToGrantTime.put(packageNames[0], time); 669 } 670 } 671 } 672 673 return new UserGrantTimeState( 674 packageNameToGrantTime, sharedUserToGrantTime, CURRENT_VERSION); 675 } 676 677 /** 678 * Get the grant time state for the user. 679 * 680 * <p>Always uses package names, even if shared user names for an app is present. 681 */ 682 @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression extractUserGrantTimeStateDoNotUseSharedNames(UserHandle user)683 UserGrantTimeState extractUserGrantTimeStateDoNotUseSharedNames(UserHandle user) { 684 Map<String, Instant> sharedUserToGrantTime = new ArrayMap<>(); 685 Map<String, Instant> packageNameToGrantTime = new ArrayMap<>(); 686 687 for (Map.Entry<Integer, Instant> entry : mUidToGrantTime.entrySet()) { 688 Integer uid = entry.getKey(); 689 Instant time = entry.getValue(); 690 691 if (!UserHandle.getUserHandleForUid(uid).equals(user)) { 692 continue; 693 } 694 695 for (String packageName : 696 mPackageInfoHelper.getPackagesForUidNonNull(mContext, user, uid)) { 697 packageNameToGrantTime.put(packageName, time); 698 } 699 } 700 701 return new UserGrantTimeState( 702 packageNameToGrantTime, sharedUserToGrantTime, CURRENT_VERSION); 703 } 704 populateFromUserGrantTimeState( @ullable UserGrantTimeState grantTimeState, Map<String, Set<Integer>> sharedUserNameToUids, UserHandle user)705 void populateFromUserGrantTimeState( 706 @Nullable UserGrantTimeState grantTimeState, 707 Map<String, Set<Integer>> sharedUserNameToUids, 708 UserHandle user) { 709 if (grantTimeState == null) { 710 return; 711 } 712 713 for (Map.Entry<String, Instant> entry : 714 grantTimeState.getSharedUserGrantTimes().entrySet()) { 715 String sharedUserName = entry.getKey(); 716 Instant time = entry.getValue(); 717 718 if (sharedUserNameToUids.get(sharedUserName) == null) { 719 continue; 720 } 721 722 for (Integer uid : sharedUserNameToUids.get(sharedUserName)) { 723 put(uid, time); 724 } 725 } 726 727 for (Map.Entry<String, Instant> entry : 728 grantTimeState.getPackageGrantTimes().entrySet()) { 729 String packageName = entry.getKey(); 730 Instant time = entry.getValue(); 731 732 Integer uid = mPackageInfoHelper.getPackageUid(packageName, user, mContext); 733 if (uid != null) { 734 put(uid, time); 735 } 736 } 737 } 738 } 739 removeAppsFromPriorityList(String[] packageNames)740 private void removeAppsFromPriorityList(String[] packageNames) { 741 for (String packageName : packageNames) { 742 mHealthDataCategoryPriorityHelper.maybeRemoveAppWithoutWritePermissionsFromPriorityList( 743 packageName); 744 } 745 } 746 } 747