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