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 android.content.Context; 20 import android.content.Intent; 21 import android.content.pm.PackageManager; 22 import android.content.pm.ResolveInfo; 23 import android.health.connect.Constants; 24 import android.health.connect.HealthConnectManager; 25 import android.os.UserHandle; 26 import android.os.UserManager; 27 import android.util.ArraySet; 28 import android.util.Log; 29 30 import com.android.internal.annotations.GuardedBy; 31 32 import java.util.HashMap; 33 import java.util.List; 34 import java.util.Map; 35 import java.util.Set; 36 37 /** 38 * Tracks apps which support {@link android.content.Intent#ACTION_VIEW_PERMISSION_USAGE} with {@link 39 * HealthConnectManager#CATEGORY_HEALTH_PERMISSIONS}. 40 * 41 * <p>This class stores a mapping for all UserHandles on the device, since this can be called for 42 * the non-foreground users. 43 * 44 * @hide 45 */ 46 public class HealthPermissionIntentAppsTracker { 47 private static final String TAG = "HealthPermIntentTracker"; 48 private static final Intent HEALTH_PERMISSIONS_USAGE_INTENT = getHealthPermissionsUsageIntent(); 49 50 private final PackageManager mPackageManager; 51 private final Object mLock = new Object(); 52 53 @GuardedBy("mLock") 54 private final Map<UserHandle, Set<String>> mUserToHealthPackageNamesMap; 55 HealthPermissionIntentAppsTracker(Context context)56 public HealthPermissionIntentAppsTracker(Context context) { 57 mPackageManager = context.getPackageManager(); 58 synchronized (mLock) { 59 mUserToHealthPackageNamesMap = new HashMap<>(); 60 } 61 } 62 63 /** Setup the for the new user. */ setupForUser(UserHandle userHandle)64 public void setupForUser(UserHandle userHandle) { 65 initPackageSetForUser(userHandle); 66 } 67 68 /** 69 * Checks if the given app supports {@link android.content.Intent#ACTION_VIEW_PERMISSION_USAGE} 70 * with {@link HealthConnectManager#CATEGORY_HEALTH_PERMISSIONS} 71 * 72 * @param packageName: name of the package to check 73 * @param userHandle: the user to query 74 */ supportsPermissionUsageIntent(String packageName, UserHandle userHandle)75 boolean supportsPermissionUsageIntent(String packageName, UserHandle userHandle) { 76 synchronized (mLock) { 77 if (!mUserToHealthPackageNamesMap.containsKey(userHandle)) { 78 mUserToHealthPackageNamesMap.put(userHandle, new ArraySet<>()); 79 } 80 81 if (mUserToHealthPackageNamesMap.get(userHandle).contains(packageName)) { 82 return true; 83 } 84 return updateAndGetSupportsPackageUsageIntent(packageName, userHandle); 85 } 86 } 87 88 /** 89 * Updates package state if needed, returns whether activity for {@link 90 * android.content.Intent#ACTION_VIEW_PERMISSION_USAGE} with {@link 91 * HealthConnectManager#CATEGORY_HEALTH_PERMISSIONS} support is currently disabled. 92 */ updateAndGetSupportsPackageUsageIntent(String packageName, UserHandle userHandle)93 boolean updateAndGetSupportsPackageUsageIntent(String packageName, UserHandle userHandle) { 94 synchronized (mLock) { 95 if (!mUserToHealthPackageNamesMap.containsKey(userHandle)) { 96 mUserToHealthPackageNamesMap.put(userHandle, new ArraySet<>()); 97 } 98 99 Intent permissionPackageUsageIntent = getHealthPermissionsUsageIntent(); 100 permissionPackageUsageIntent.setPackage(packageName); 101 if (mPackageManager 102 .queryIntentActivitiesAsUser( 103 permissionPackageUsageIntent, 104 PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL), 105 userHandle) 106 .isEmpty()) { 107 mUserToHealthPackageNamesMap.get(userHandle).remove(packageName); 108 return false; 109 } else { 110 mUserToHealthPackageNamesMap.get(userHandle).add(packageName); 111 return true; 112 } 113 } 114 } 115 getHealthPermissionsUsageIntent()116 private static Intent getHealthPermissionsUsageIntent() { 117 Intent healthIntent = new Intent(Intent.ACTION_VIEW_PERMISSION_USAGE); 118 healthIntent.addCategory(HealthConnectManager.CATEGORY_HEALTH_PERMISSIONS); 119 return healthIntent; 120 } 121 initPerUserMapping(Context context)122 private void initPerUserMapping(Context context) { 123 List<UserHandle> userHandles = 124 context.getSystemService(UserManager.class) 125 .getUserHandles(/* excludeDying= */ true); 126 for (UserHandle userHandle : userHandles) { 127 initPackageSetForUser(userHandle); 128 } 129 } 130 131 /** Update list of health apps for given user. */ initPackageSetForUser(UserHandle userHandle)132 private void initPackageSetForUser(UserHandle userHandle) { 133 List<ResolveInfo> healthAppInfos = getHealthIntentSupportiveAppsForUser(userHandle); 134 Set<String> healthApps = new ArraySet<String>(healthAppInfos.size()); 135 for (ResolveInfo info : healthAppInfos) { 136 String packageName = extractPackageName(info); 137 if (packageName != null) { 138 healthApps.add(packageName); 139 } 140 } 141 synchronized (mLock) { 142 mUserToHealthPackageNamesMap.put(userHandle, healthApps); 143 } 144 logStateIfDebugMode(userHandle); 145 } 146 getHealthIntentSupportiveAppsForUser(UserHandle userHandle)147 private List<ResolveInfo> getHealthIntentSupportiveAppsForUser(UserHandle userHandle) { 148 return mPackageManager.queryIntentActivitiesAsUser( 149 HEALTH_PERMISSIONS_USAGE_INTENT, 150 PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL), 151 userHandle); 152 } 153 154 @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression extractPackageName(ResolveInfo info)155 private String extractPackageName(ResolveInfo info) { 156 if (info == null 157 || info.activityInfo == null 158 || info.activityInfo.applicationInfo == null) { 159 Log.w(TAG, "Can't fetch application info from resolve info."); 160 return null; 161 } 162 return info.activityInfo.applicationInfo.packageName; 163 } 164 logStateIfDebugMode(UserHandle userHandle)165 private void logStateIfDebugMode(UserHandle userHandle) { 166 if (Constants.DEBUG) { 167 Log.d(TAG, "State for user: " + userHandle.getIdentifier()); 168 synchronized (mLock) { 169 Log.d(TAG, mUserToHealthPackageNamesMap.toString()); 170 } 171 } 172 } 173 } 174