1 /* 2 * Copyright (C) 2024 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.car.settings.privacy; 18 19 import android.Manifest; 20 import android.content.Context; 21 import android.content.pm.PackageInfo; 22 import android.content.pm.PackageManager; 23 import android.content.pm.PackageManager.NameNotFoundException; 24 import android.content.pm.PermissionInfo; 25 import android.location.LocationManager; 26 import android.os.Build; 27 import android.os.UserHandle; 28 import android.util.ArrayMap; 29 30 import androidx.annotation.Nullable; 31 import androidx.core.util.Preconditions; 32 33 import com.android.car.settings.R; 34 import com.android.car.settings.common.Logger; 35 import com.android.net.module.util.CollectionUtils; 36 37 import com.google.common.collect.ImmutableListMultimap; 38 39 import java.util.ArrayList; 40 import java.util.List; 41 42 /** 43 * Utility class for applications permissions. This logic is extracted from {@link 44 * com.android.permissioncontroller.permission.data.AppPermGroupUiInfoLiveData}. 45 */ 46 public final class PermissionUtils { 47 private static final Logger LOG = new Logger(PermissionUtils.class); 48 private static final ImmutableListMultimap<String, String> PERMISSION_GROUP_TO_PERMISSIONS = 49 ImmutableListMultimap.<String, String>builder() 50 .putAll(Manifest.permission_group.CAMERA, 51 Manifest.permission.CAMERA) 52 .putAll(Manifest.permission_group.LOCATION, 53 Manifest.permission.ACCESS_FINE_LOCATION, 54 Manifest.permission.ACCESS_COARSE_LOCATION, 55 Manifest.permission.ACCESS_BACKGROUND_LOCATION) 56 .putAll(Manifest.permission_group.MICROPHONE, 57 Manifest.permission.RECORD_AUDIO) 58 .build(); 59 PermissionUtils()60 private PermissionUtils() {} 61 62 private static class PermissionState { 63 public int mPermissionFlags; 64 public PermissionInfo mPermissionInfo; 65 public boolean mGranted; 66 PermissionState(int permissionFlags, PermissionInfo permInfo, boolean granted)67 PermissionState(int permissionFlags, PermissionInfo permInfo, boolean granted) { 68 mPermissionFlags = permissionFlags; 69 mPermissionInfo = permInfo; 70 mGranted = granted; 71 } 72 } 73 74 private static final Object sLock = new Object(); 75 private static final ArrayMap<UserHandle, Context> sUserContexts = new ArrayMap<>(); 76 77 /** 78 * TODO(b/339236027): Remove extra logic in Android W 79 * 80 * Creates and caches a PackageContext for the requested user, or returns the previously cached 81 * value. The package of the PackageContext is the application's package. 82 * 83 * Note: All this logic is directly from PermissionController. Ideally, we would've exposed a 84 * SystemApi to be used in CarSettings instead, but the API deadline passed. This logic is used 85 * below in {@link #getSpecialLocationState(Context, String, String, UserHandle)}. It appears 86 * there's a scenario where the locationManager from user context and app context may not align. 87 * 88 * @param context The context of the currently running application 89 * @param user The desired user for the context 90 * 91 * @return The generated or cached Context for the requested user 92 * 93 * @throws RuntimeException If the app has no package name attached, which should never happen 94 */ getUserContext(Context context, UserHandle user)95 private static Context getUserContext(Context context, UserHandle user) { 96 synchronized (sLock) { 97 if (!sUserContexts.containsKey(user)) { 98 sUserContexts.put(user, context.getApplicationContext() 99 .createContextAsUser(user, 0)); 100 } 101 return Preconditions.checkNotNull(sUserContexts.get(user)); 102 } 103 } 104 105 /** 106 * Returns a list of the packages holding a permission from the specified permission group. 107 * 108 * @param context current context. 109 * @param permissionGroup permission group for filtering the packages. 110 * @param userHandle current user handle. 111 * @param showSystem whether to return system packages. 112 * 113 * @return List of packages. 114 */ getPackagesWithPermissionGroup(Context context, String permissionGroup, UserHandle userHandle, boolean showSystem)115 public static List<PackageInfo> getPackagesWithPermissionGroup(Context context, 116 String permissionGroup, UserHandle userHandle, boolean showSystem) { 117 ArrayMap<String, PackageInfo> packageNameToInfo = new ArrayMap<>(); 118 List<String> permissionNames = PERMISSION_GROUP_TO_PERMISSIONS.get(permissionGroup); 119 for (String permissionName: permissionNames) { 120 List<PackageInfo> packages = context.getPackageManager().getPackagesHoldingPermissions( 121 new String[]{permissionName}, PackageManager.GET_PERMISSIONS); 122 123 for (PackageInfo packageInfo : packages) { 124 // Ignore duplicate packages 125 if (packageNameToInfo.containsKey(packageInfo.packageName)) { 126 continue; 127 } 128 PermissionState permState = 129 getPermissionState(context, packageInfo, permissionName, userHandle); 130 if (permState == null) { 131 continue; 132 } 133 134 boolean isUserSensitive = ((permState.mGranted && (permState.mPermissionFlags 135 & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0) 136 || (!permState.mGranted && (permState.mPermissionFlags 137 & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) != 0)); 138 // If an app is not user sensitive, then it is considered a system app, 139 // and hidden in the UI by default. 140 if (!showSystem && !isUserSensitive) { 141 continue; 142 } 143 packageNameToInfo.put(packageInfo.packageName, packageInfo); 144 } 145 } 146 return new ArrayList<>(packageNameToInfo.values()); 147 } 148 getPermissionState(Context context, PackageInfo packageInfo, String permissionName, UserHandle userHandle)149 private static PermissionState getPermissionState(Context context, PackageInfo packageInfo, 150 String permissionName, UserHandle userHandle) { 151 int permissionFlags = context.getPackageManager().getPermissionFlags(permissionName, 152 packageInfo.packageName, userHandle); 153 String[] requestedPermissions = packageInfo.requestedPermissions; 154 if (requestedPermissions == null) { 155 return null; 156 } 157 int requestedPermissionsIndex = CollectionUtils.indexOf(requestedPermissions, 158 permissionName); 159 int[] requestedPermissionsFlags = packageInfo.requestedPermissionsFlags; 160 PermissionInfo permInfo = null; 161 try { 162 permInfo = context.getPackageManager().getPermissionInfo(permissionName, 0); 163 } catch (NameNotFoundException ex) { 164 LOG.e("Failed to get application info for " + packageInfo.packageName); 165 return null; 166 } 167 168 if (requestedPermissionsIndex == -1) { 169 return null; 170 } 171 172 boolean hasPreRuntime = false; 173 boolean hasInstantPerm = false; 174 175 if ((permInfo.getProtectionFlags() & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) == 0) { 176 hasPreRuntime = true; 177 } 178 179 if ((permInfo.getProtectionFlags() & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0) { 180 hasInstantPerm = true; 181 } 182 183 boolean supportsRuntime = packageInfo.applicationInfo.targetSdkVersion 184 >= Build.VERSION_CODES.M; 185 boolean isGrantingAllowed = (!packageInfo.applicationInfo.isInstantApp() || hasInstantPerm) 186 && (supportsRuntime || hasPreRuntime); 187 if (!isGrantingAllowed) { 188 return null; 189 } 190 191 boolean granted = (((requestedPermissionsFlags[requestedPermissionsIndex] 192 & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) 193 && ((permissionFlags & PackageManager.FLAG_PERMISSION_REVOKED_COMPAT) == 0)); 194 195 return new PermissionState(permissionFlags, permInfo, granted); 196 } 197 198 /** 199 * Returns the grant status of all permissions in a group for a specific package. 200 * 201 * @param context current context. 202 * @param packageInfo package for which to check the grant status 203 * @param permissionGroup permission group for filtering the packages. 204 * @param userHandle current user handle. 205 * 206 * @return resource for permission grant status. 207 */ getPermissionGroupGrantStatus(Context context, PackageInfo packageInfo, String permissionGroup, UserHandle userHandle)208 public static int getPermissionGroupGrantStatus(Context context, PackageInfo packageInfo, 209 String permissionGroup, UserHandle userHandle) { 210 ArrayMap<String, PermissionState> permissionNameToState = new ArrayMap<>(); 211 List<String> permissionNames = PERMISSION_GROUP_TO_PERMISSIONS.get(permissionGroup); 212 for (String permissionName : permissionNames) { 213 PermissionState permState = 214 getPermissionState(context, packageInfo, permissionName, userHandle); 215 if (permState != null) { 216 permissionNameToState.put(permissionName, permState); 217 } 218 } 219 220 Boolean specialLocationState = getSpecialLocationState(context, packageInfo.packageName, 221 permissionGroup, userHandle); 222 boolean supportsRuntime = packageInfo.applicationInfo.targetSdkVersion 223 >= Build.VERSION_CODES.M; 224 boolean hasPermWithBackground = false; 225 boolean anyAllowed = false; 226 boolean isUserFixed = false; 227 boolean containsOneTimePerm = false; 228 boolean containsGrantedNonOneTimePerm = false; 229 for (int i = 0; i < permissionNameToState.size(); i++) { 230 PermissionState permState = permissionNameToState.valueAt(i); 231 if ((permState.mPermissionInfo.backgroundPermission != null)) { 232 hasPermWithBackground = true; 233 PermissionState backgroundPermState = permissionNameToState.get( 234 permState.mPermissionInfo.backgroundPermission); 235 if (backgroundPermState != null && backgroundPermState.mGranted 236 && (backgroundPermState.mPermissionFlags 237 & PackageManager.FLAG_PERMISSION_ONE_TIME) == 0 238 && !Boolean.FALSE.equals(specialLocationState)) { 239 return R.string.permission_grant_always; 240 } 241 } 242 243 if ((permState.mPermissionFlags & PackageManager.FLAG_PERMISSION_ONE_TIME) != 0) { 244 containsOneTimePerm = true; 245 } 246 247 if ((permState.mPermissionFlags & PackageManager.FLAG_PERMISSION_ONE_TIME) == 0 248 && permState.mGranted) { 249 containsGrantedNonOneTimePerm = true; 250 } 251 252 if (specialLocationState != null) { 253 anyAllowed = specialLocationState; 254 } else if (permState.mGranted 255 || (supportsRuntime 256 && (permState.mPermissionFlags 257 & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0)) { 258 anyAllowed = true; 259 } 260 261 isUserFixed = isUserFixed 262 || (permState.mPermissionFlags 263 & PackageManager.FLAG_PERMISSION_USER_FIXED) != 0; 264 } 265 266 // isOneTime indicates whether all granted permissions in permission states are one-time 267 // permissions 268 boolean isOneTime = containsOneTimePerm && !containsGrantedNonOneTimePerm; 269 boolean shouldShowAsForegroundGroup = 270 Manifest.permission_group.CAMERA.equals(permissionGroup) 271 || Manifest.permission_group.MICROPHONE.equals(permissionGroup); 272 273 if (anyAllowed) { 274 if (isOneTime) { 275 return R.string.permission_grant_ask; 276 } else if (hasPermWithBackground || shouldShowAsForegroundGroup) { 277 return R.string.permission_grant_in_use; 278 } else { 279 return R.string.permission_grant_allowed; 280 } 281 } 282 if (isUserFixed) { 283 return R.string.permission_grant_never; 284 } 285 if (isOneTime) { 286 return R.string.permission_grant_ask; 287 } 288 return R.string.permission_grant_never; 289 } 290 291 @Nullable getSpecialLocationState(Context appContext, String packageName, String permissionGroup, UserHandle userHandle)292 private static Boolean getSpecialLocationState(Context appContext, String packageName, 293 String permissionGroup, UserHandle userHandle) { 294 if (!Manifest.permission_group.LOCATION.equals(permissionGroup)) { 295 return null; 296 } 297 298 LocationManager appLocationManager = appContext.getSystemService(LocationManager.class); 299 if (!appLocationManager.isProviderPackage(packageName) 300 && !packageName.equals(appLocationManager.getExtraLocationControllerPackage())) { 301 return null; 302 } 303 304 Context userContext = getUserContext(appContext, userHandle); 305 LocationManager userLocationManager = userContext.getSystemService(LocationManager.class); 306 if (userLocationManager.isProviderPackage(packageName)) { 307 return userLocationManager.isLocationEnabled(); 308 } 309 310 // The permission of the extra location controller package is determined by the 311 // status of the controller package itself. 312 if (packageName.equals(userLocationManager.getExtraLocationControllerPackage())) { 313 try { 314 return userLocationManager.isExtraLocationControllerPackageEnabled(); 315 } catch (Exception e) { 316 return false; 317 } 318 } 319 320 return null; 321 } 322 } 323