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