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