• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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