1 /* 2 * Copyright (C) 2015 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.packageinstaller.permission.utils; 18 19 import static android.Manifest.permission.RECORD_AUDIO; 20 import static android.Manifest.permission_group.ACTIVITY_RECOGNITION; 21 import static android.Manifest.permission_group.CALENDAR; 22 import static android.Manifest.permission_group.CALL_LOG; 23 import static android.Manifest.permission_group.CAMERA; 24 import static android.Manifest.permission_group.CONTACTS; 25 import static android.Manifest.permission_group.LOCATION; 26 import static android.Manifest.permission_group.MICROPHONE; 27 import static android.Manifest.permission_group.PHONE; 28 import static android.Manifest.permission_group.SENSORS; 29 import static android.Manifest.permission_group.SMS; 30 import static android.Manifest.permission_group.STORAGE; 31 import static android.app.role.RoleManager.ROLE_ASSISTANT; 32 import static android.content.Context.MODE_PRIVATE; 33 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED; 34 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED; 35 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; 36 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; 37 import static android.os.UserHandle.myUserId; 38 39 import static com.android.packageinstaller.Constants.ASSISTANT_RECORD_AUDIO_IS_USER_SENSITIVE_KEY; 40 import static com.android.packageinstaller.Constants.FORCED_USER_SENSITIVE_UIDS_KEY; 41 import static com.android.packageinstaller.Constants.PREFERENCES_FILE; 42 43 import android.Manifest; 44 import android.app.Application; 45 import android.app.role.RoleManager; 46 import android.content.ActivityNotFoundException; 47 import android.content.Context; 48 import android.content.Intent; 49 import android.content.SharedPreferences; 50 import android.content.pm.ApplicationInfo; 51 import android.content.pm.PackageItemInfo; 52 import android.content.pm.PackageManager; 53 import android.content.pm.PackageManager.NameNotFoundException; 54 import android.content.pm.PermissionInfo; 55 import android.content.pm.ResolveInfo; 56 import android.content.res.Resources; 57 import android.content.res.Resources.Theme; 58 import android.graphics.Bitmap; 59 import android.graphics.drawable.BitmapDrawable; 60 import android.graphics.drawable.Drawable; 61 import android.os.Parcelable; 62 import android.os.UserHandle; 63 import android.os.UserManager; 64 import android.provider.DeviceConfig; 65 import android.provider.Settings; 66 import android.text.Html; 67 import android.text.TextUtils; 68 import android.text.format.DateFormat; 69 import android.util.ArrayMap; 70 import android.util.ArraySet; 71 import android.util.Log; 72 import android.util.SparseArray; 73 import android.util.TypedValue; 74 import android.view.Menu; 75 import android.view.MenuItem; 76 77 import androidx.annotation.NonNull; 78 import androidx.annotation.Nullable; 79 import androidx.annotation.StringRes; 80 import androidx.core.text.BidiFormatter; 81 import androidx.core.util.Preconditions; 82 83 import com.android.launcher3.icons.IconFactory; 84 import com.android.packageinstaller.Constants; 85 import com.android.packageinstaller.permission.data.PerUserUidToSensitivityLiveData; 86 import com.android.packageinstaller.permission.model.AppPermissionGroup; 87 import com.android.permissioncontroller.R; 88 89 import java.util.ArrayList; 90 import java.util.Calendar; 91 import java.util.Collections; 92 import java.util.List; 93 import java.util.Locale; 94 import java.util.Set; 95 96 public final class Utils { 97 98 private static final String LOG_TAG = "Utils"; 99 100 public static final String OS_PKG = "android"; 101 102 public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f; 103 104 /** Whether to show location access check notifications. */ 105 private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED = 106 "location_access_check_enabled"; 107 108 /** All permission whitelists. */ 109 public static final int FLAGS_PERMISSION_WHITELIST_ALL = 110 PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM 111 | PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE 112 | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER; 113 114 /** Mapping permission -> group for all dangerous platform permissions */ 115 private static final ArrayMap<String, String> PLATFORM_PERMISSIONS; 116 117 /** Mapping group -> permissions for all dangerous platform permissions */ 118 private static final ArrayMap<String, ArrayList<String>> PLATFORM_PERMISSION_GROUPS; 119 120 private static final Intent LAUNCHER_INTENT = new Intent(Intent.ACTION_MAIN, null) 121 .addCategory(Intent.CATEGORY_LAUNCHER); 122 123 public static final int FLAGS_ALWAYS_USER_SENSITIVE = 124 FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED 125 | FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED; 126 127 static { 128 PLATFORM_PERMISSIONS = new ArrayMap<>(); 129 PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CONTACTS, CONTACTS)130 PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CONTACTS, CONTACTS); PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CONTACTS, CONTACTS)131 PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CONTACTS, CONTACTS); PLATFORM_PERMISSIONS.put(Manifest.permission.GET_ACCOUNTS, CONTACTS)132 PLATFORM_PERMISSIONS.put(Manifest.permission.GET_ACCOUNTS, CONTACTS); 133 PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALENDAR, CALENDAR)134 PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALENDAR, CALENDAR); PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALENDAR, CALENDAR)135 PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALENDAR, CALENDAR); 136 PLATFORM_PERMISSIONS.put(Manifest.permission.SEND_SMS, SMS)137 PLATFORM_PERMISSIONS.put(Manifest.permission.SEND_SMS, SMS); PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_SMS, SMS)138 PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_SMS, SMS); PLATFORM_PERMISSIONS.put(Manifest.permission.READ_SMS, SMS)139 PLATFORM_PERMISSIONS.put(Manifest.permission.READ_SMS, SMS); PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_MMS, SMS)140 PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_MMS, SMS); PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_WAP_PUSH, SMS)141 PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_WAP_PUSH, SMS); PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CELL_BROADCASTS, SMS)142 PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CELL_BROADCASTS, SMS); 143 PLATFORM_PERMISSIONS.put(Manifest.permission.READ_EXTERNAL_STORAGE, STORAGE)144 PLATFORM_PERMISSIONS.put(Manifest.permission.READ_EXTERNAL_STORAGE, STORAGE); PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, STORAGE)145 PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, STORAGE); PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_MEDIA_LOCATION, STORAGE)146 PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_MEDIA_LOCATION, STORAGE); 147 PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_FINE_LOCATION, LOCATION)148 PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_FINE_LOCATION, LOCATION); PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_COARSE_LOCATION, LOCATION)149 PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_COARSE_LOCATION, LOCATION); PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_BACKGROUND_LOCATION, LOCATION)150 PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_BACKGROUND_LOCATION, LOCATION); 151 PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALL_LOG, CALL_LOG)152 PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALL_LOG, CALL_LOG); PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALL_LOG, CALL_LOG)153 PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALL_LOG, CALL_LOG); PLATFORM_PERMISSIONS.put(Manifest.permission.PROCESS_OUTGOING_CALLS, CALL_LOG)154 PLATFORM_PERMISSIONS.put(Manifest.permission.PROCESS_OUTGOING_CALLS, CALL_LOG); 155 PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_STATE, PHONE)156 PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_STATE, PHONE); PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_NUMBERS, PHONE)157 PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_NUMBERS, PHONE); PLATFORM_PERMISSIONS.put(Manifest.permission.CALL_PHONE, PHONE)158 PLATFORM_PERMISSIONS.put(Manifest.permission.CALL_PHONE, PHONE); PLATFORM_PERMISSIONS.put(Manifest.permission.ADD_VOICEMAIL, PHONE)159 PLATFORM_PERMISSIONS.put(Manifest.permission.ADD_VOICEMAIL, PHONE); PLATFORM_PERMISSIONS.put(Manifest.permission.USE_SIP, PHONE)160 PLATFORM_PERMISSIONS.put(Manifest.permission.USE_SIP, PHONE); PLATFORM_PERMISSIONS.put(Manifest.permission.ANSWER_PHONE_CALLS, PHONE)161 PLATFORM_PERMISSIONS.put(Manifest.permission.ANSWER_PHONE_CALLS, PHONE); PLATFORM_PERMISSIONS.put(Manifest.permission.ACCEPT_HANDOVER, PHONE)162 PLATFORM_PERMISSIONS.put(Manifest.permission.ACCEPT_HANDOVER, PHONE); 163 PLATFORM_PERMISSIONS.put(Manifest.permission.RECORD_AUDIO, MICROPHONE)164 PLATFORM_PERMISSIONS.put(Manifest.permission.RECORD_AUDIO, MICROPHONE); 165 PLATFORM_PERMISSIONS.put(Manifest.permission.ACTIVITY_RECOGNITION, ACTIVITY_RECOGNITION)166 PLATFORM_PERMISSIONS.put(Manifest.permission.ACTIVITY_RECOGNITION, ACTIVITY_RECOGNITION); 167 PLATFORM_PERMISSIONS.put(Manifest.permission.CAMERA, CAMERA)168 PLATFORM_PERMISSIONS.put(Manifest.permission.CAMERA, CAMERA); 169 PLATFORM_PERMISSIONS.put(Manifest.permission.BODY_SENSORS, SENSORS)170 PLATFORM_PERMISSIONS.put(Manifest.permission.BODY_SENSORS, SENSORS); 171 172 PLATFORM_PERMISSION_GROUPS = new ArrayMap<>(); 173 int numPlatformPermissions = PLATFORM_PERMISSIONS.size(); 174 for (int i = 0; i < numPlatformPermissions; i++) { 175 String permission = PLATFORM_PERMISSIONS.keyAt(i); 176 String permissionGroup = PLATFORM_PERMISSIONS.valueAt(i); 177 178 ArrayList<String> permissionsOfThisGroup = PLATFORM_PERMISSION_GROUPS.get( 179 permissionGroup); 180 if (permissionsOfThisGroup == null) { 181 permissionsOfThisGroup = new ArrayList<>(); PLATFORM_PERMISSION_GROUPS.put(permissionGroup, permissionsOfThisGroup)182 PLATFORM_PERMISSION_GROUPS.put(permissionGroup, permissionsOfThisGroup); 183 } 184 185 permissionsOfThisGroup.add(permission); 186 } 187 } 188 Utils()189 private Utils() { 190 /* do nothing - hide constructor */ 191 } 192 193 /** 194 * {@code @NonNull} version of {@link Context#getSystemService(Class)} 195 */ getSystemServiceSafe(@onNull Context context, Class<M> clazz)196 public static @NonNull <M> M getSystemServiceSafe(@NonNull Context context, Class<M> clazz) { 197 return Preconditions.checkNotNull(context.getSystemService(clazz), 198 "Could not resolve " + clazz.getSimpleName()); 199 } 200 201 /** 202 * {@code @NonNull} version of {@link Context#getSystemService(Class)} 203 */ getSystemServiceSafe(@onNull Context context, Class<M> clazz, @NonNull UserHandle user)204 public static @NonNull <M> M getSystemServiceSafe(@NonNull Context context, Class<M> clazz, 205 @NonNull UserHandle user) { 206 try { 207 return Preconditions.checkNotNull(context.createPackageContextAsUser( 208 context.getPackageName(), 0, user).getSystemService(clazz), 209 "Could not resolve " + clazz.getSimpleName()); 210 } catch (PackageManager.NameNotFoundException neverHappens) { 211 throw new IllegalStateException(); 212 } 213 } 214 215 /** 216 * {@code @NonNull} version of {@link Intent#getParcelableExtra(String)} 217 */ getParcelableExtraSafe(@onNull Intent intent, @NonNull String name)218 public static @NonNull <T extends Parcelable> T getParcelableExtraSafe(@NonNull Intent intent, 219 @NonNull String name) { 220 return Preconditions.checkNotNull(intent.getParcelableExtra(name), 221 "Could not get parcelable extra for " + name); 222 } 223 224 /** 225 * {@code @NonNull} version of {@link Intent#getStringExtra(String)} 226 */ getStringExtraSafe(@onNull Intent intent, @NonNull String name)227 public static @NonNull String getStringExtraSafe(@NonNull Intent intent, 228 @NonNull String name) { 229 return Preconditions.checkNotNull(intent.getStringExtra(name), 230 "Could not get string extra for " + name); 231 } 232 233 /** 234 * Get permission group a platform permission belongs to. 235 * 236 * @param permission the permission to resolve 237 * 238 * @return The group the permission belongs to 239 */ getGroupOfPlatformPermission(@onNull String permission)240 public static @Nullable String getGroupOfPlatformPermission(@NonNull String permission) { 241 return PLATFORM_PERMISSIONS.get(permission); 242 } 243 244 /** 245 * Get name of the permission group a permission belongs to. 246 * 247 * @param permission the {@link PermissionInfo info} of the permission to resolve 248 * 249 * @return The group the permission belongs to 250 */ getGroupOfPermission(@onNull PermissionInfo permission)251 public static @Nullable String getGroupOfPermission(@NonNull PermissionInfo permission) { 252 String groupName = Utils.getGroupOfPlatformPermission(permission.name); 253 if (groupName == null) { 254 groupName = permission.group; 255 } 256 257 return groupName; 258 } 259 260 /** 261 * Get the names for all platform permissions belonging to a group. 262 * 263 * @param group the group 264 * 265 * @return The permission names or an empty list if the 266 * group is not does not have platform runtime permissions 267 */ getPlatformPermissionNamesOfGroup(@onNull String group)268 public static @NonNull List<String> getPlatformPermissionNamesOfGroup(@NonNull String group) { 269 final ArrayList<String> permissions = PLATFORM_PERMISSION_GROUPS.get(group); 270 return (permissions != null) ? permissions : Collections.emptyList(); 271 } 272 273 /** 274 * Get the {@link PermissionInfo infos} for all platform permissions belonging to a group. 275 * 276 * @param pm Package manager to use to resolve permission infos 277 * @param group the group 278 * 279 * @return The infos for platform permissions belonging to the group or an empty list if the 280 * group is not does not have platform runtime permissions 281 */ getPlatformPermissionsOfGroup( @onNull PackageManager pm, @NonNull String group)282 public static @NonNull List<PermissionInfo> getPlatformPermissionsOfGroup( 283 @NonNull PackageManager pm, @NonNull String group) { 284 ArrayList<PermissionInfo> permInfos = new ArrayList<>(); 285 286 ArrayList<String> permissions = PLATFORM_PERMISSION_GROUPS.get(group); 287 if (permissions == null) { 288 return Collections.emptyList(); 289 } 290 291 int numPermissions = permissions.size(); 292 for (int i = 0; i < numPermissions; i++) { 293 String permName = permissions.get(i); 294 PermissionInfo permInfo; 295 try { 296 permInfo = pm.getPermissionInfo(permName, 0); 297 } catch (PackageManager.NameNotFoundException e) { 298 throw new IllegalStateException(permName + " not defined by platform", e); 299 } 300 301 permInfos.add(permInfo); 302 } 303 304 return permInfos; 305 } 306 307 /** 308 * Get the {@link PermissionInfo infos} for all permission infos belonging to a group. 309 * 310 * @param pm Package manager to use to resolve permission infos 311 * @param group the group 312 * 313 * @return The infos of permissions belonging to the group or an empty list if the group 314 * does not have runtime permissions 315 */ getPermissionInfosForGroup( @onNull PackageManager pm, @NonNull String group)316 public static @NonNull List<PermissionInfo> getPermissionInfosForGroup( 317 @NonNull PackageManager pm, @NonNull String group) 318 throws PackageManager.NameNotFoundException { 319 List<PermissionInfo> permissions = new ArrayList<>(); 320 for (PermissionInfo permission : pm.queryPermissionsByGroup(group, 0)) { 321 // PermissionController's mapping takes precedence 322 if (getGroupOfPermission(permission).equals(group)) { 323 permissions.add(permission); 324 } 325 } 326 permissions.addAll(getPlatformPermissionsOfGroup(pm, group)); 327 328 return permissions; 329 } 330 331 /** 332 * Get the {@link PackageItemInfo infos} for the given permission group. 333 * 334 * @param groupName the group 335 * @param context the {@code Context} to retrieve {@code PackageManager} 336 * 337 * @return The info of permission group or null if the group does not have runtime permissions. 338 */ getGroupInfo(@onNull String groupName, @NonNull Context context)339 public static @Nullable PackageItemInfo getGroupInfo(@NonNull String groupName, 340 @NonNull Context context) { 341 try { 342 return context.getPackageManager().getPermissionGroupInfo(groupName, 0); 343 } catch (NameNotFoundException e) { 344 /* ignore */ 345 } 346 try { 347 return context.getPackageManager().getPermissionInfo(groupName, 0); 348 } catch (NameNotFoundException e) { 349 /* ignore */ 350 } 351 return null; 352 } 353 354 /** 355 * Get the {@link PermissionInfo infos} for all permission infos belonging to a group. 356 * 357 * @param groupName the group 358 * @param context the {@code Context} to retrieve {@code PackageManager} 359 * 360 * @return The infos of permissions belonging to the group or null if the group does not have 361 * runtime permissions. 362 */ getGroupPermissionInfos(@onNull String groupName, @NonNull Context context)363 public static @Nullable List<PermissionInfo> getGroupPermissionInfos(@NonNull String groupName, 364 @NonNull Context context) { 365 try { 366 return Utils.getPermissionInfosForGroup(context.getPackageManager(), groupName); 367 } catch (NameNotFoundException e) { 368 /* ignore */ 369 } 370 try { 371 PermissionInfo permissionInfo = context.getPackageManager() 372 .getPermissionInfo(groupName, 0); 373 List<PermissionInfo> permissions = new ArrayList<>(); 374 permissions.add(permissionInfo); 375 return permissions; 376 } catch (NameNotFoundException e) { 377 /* ignore */ 378 } 379 return null; 380 } 381 382 /** 383 * Get the label for an application, truncating if it is too long. 384 * 385 * @param applicationInfo the {@link ApplicationInfo} of the application 386 * @param context the {@code Context} to retrieve {@code PackageManager} 387 * 388 * @return the label for the application 389 */ 390 @NonNull getAppLabel(@onNull ApplicationInfo applicationInfo, @NonNull Context context)391 public static String getAppLabel(@NonNull ApplicationInfo applicationInfo, 392 @NonNull Context context) { 393 return getAppLabel(applicationInfo, DEFAULT_MAX_LABEL_SIZE_PX, context); 394 } 395 396 /** 397 * Get the full label for an application without truncation. 398 * 399 * @param applicationInfo the {@link ApplicationInfo} of the application 400 * @param context the {@code Context} to retrieve {@code PackageManager} 401 * 402 * @return the label for the application 403 */ 404 @NonNull getFullAppLabel(@onNull ApplicationInfo applicationInfo, @NonNull Context context)405 public static String getFullAppLabel(@NonNull ApplicationInfo applicationInfo, 406 @NonNull Context context) { 407 return getAppLabel(applicationInfo, 0, context); 408 } 409 410 /** 411 * Get the label for an application with the ability to control truncating. 412 * 413 * @param applicationInfo the {@link ApplicationInfo} of the application 414 * @param ellipsizeDip see {@link TextUtils#makeSafeForPresentation}. 415 * @param context the {@code Context} to retrieve {@code PackageManager} 416 * 417 * @return the label for the application 418 */ 419 @NonNull getAppLabel(@onNull ApplicationInfo applicationInfo, float ellipsizeDip, @NonNull Context context)420 private static String getAppLabel(@NonNull ApplicationInfo applicationInfo, float ellipsizeDip, 421 @NonNull Context context) { 422 return BidiFormatter.getInstance().unicodeWrap(applicationInfo.loadSafeLabel( 423 context.getPackageManager(), ellipsizeDip, 424 TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE) 425 .toString()); 426 } 427 loadDrawable(PackageManager pm, String pkg, int resId)428 public static Drawable loadDrawable(PackageManager pm, String pkg, int resId) { 429 try { 430 return pm.getResourcesForApplication(pkg).getDrawable(resId, null); 431 } catch (Resources.NotFoundException | PackageManager.NameNotFoundException e) { 432 Log.d(LOG_TAG, "Couldn't get resource", e); 433 return null; 434 } 435 } 436 isModernPermissionGroup(String name)437 public static boolean isModernPermissionGroup(String name) { 438 return PLATFORM_PERMISSION_GROUPS.containsKey(name); 439 } 440 441 /** 442 * Get the names of the platform permission groups. 443 * 444 * @return the names of the platform permission groups. 445 */ getPlatformPermissionGroups()446 public static List<String> getPlatformPermissionGroups() { 447 return new ArrayList<>(PLATFORM_PERMISSION_GROUPS.keySet()); 448 } 449 450 /** 451 * Get the names of the platform permissions. 452 * 453 * @return the names of the platform permissions. 454 */ getPlatformPermissions()455 public static Set<String> getPlatformPermissions() { 456 return PLATFORM_PERMISSIONS.keySet(); 457 } 458 459 /** 460 * Should UI show this permission. 461 * 462 * <p>If the user cannot change the group, it should not be shown. 463 * 464 * @param group The group that might need to be shown to the user 465 * 466 * @return 467 */ shouldShowPermission(Context context, AppPermissionGroup group)468 public static boolean shouldShowPermission(Context context, AppPermissionGroup group) { 469 if (!group.isGrantingAllowed()) { 470 return false; 471 } 472 473 final boolean isPlatformPermission = group.getDeclaringPackage().equals(OS_PKG); 474 // Show legacy permissions only if the user chose that. 475 if (isPlatformPermission 476 && !Utils.isModernPermissionGroup(group.getName())) { 477 return false; 478 } 479 return true; 480 } 481 applyTint(Context context, Drawable icon, int attr)482 public static Drawable applyTint(Context context, Drawable icon, int attr) { 483 Theme theme = context.getTheme(); 484 TypedValue typedValue = new TypedValue(); 485 theme.resolveAttribute(attr, typedValue, true); 486 icon = icon.mutate(); 487 icon.setTint(context.getColor(typedValue.resourceId)); 488 return icon; 489 } 490 applyTint(Context context, int iconResId, int attr)491 public static Drawable applyTint(Context context, int iconResId, int attr) { 492 return applyTint(context, context.getDrawable(iconResId), attr); 493 } 494 getLauncherPackages(Context context)495 public static ArraySet<String> getLauncherPackages(Context context) { 496 ArraySet<String> launcherPkgs = new ArraySet<>(); 497 for (ResolveInfo info : context.getPackageManager().queryIntentActivities(LAUNCHER_INTENT, 498 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE)) { 499 launcherPkgs.add(info.activityInfo.packageName); 500 } 501 502 return launcherPkgs; 503 } 504 getAllInstalledApplications(Context context)505 public static List<ApplicationInfo> getAllInstalledApplications(Context context) { 506 return context.getPackageManager().getInstalledApplications(0); 507 } 508 509 /** 510 * Is the group or background group user sensitive? 511 * 512 * @param group The group that might be user sensitive 513 * 514 * @return {@code true} if the group (or it's subgroup) is user sensitive. 515 */ isGroupOrBgGroupUserSensitive(AppPermissionGroup group)516 public static boolean isGroupOrBgGroupUserSensitive(AppPermissionGroup group) { 517 return group.isUserSensitive() || (group.getBackgroundPermissions() != null 518 && group.getBackgroundPermissions().isUserSensitive()); 519 } 520 areGroupPermissionsIndividuallyControlled(Context context, String group)521 public static boolean areGroupPermissionsIndividuallyControlled(Context context, String group) { 522 if (!context.getPackageManager().arePermissionsIndividuallyControlled()) { 523 return false; 524 } 525 return Manifest.permission_group.SMS.equals(group) 526 || Manifest.permission_group.PHONE.equals(group) 527 || Manifest.permission_group.CONTACTS.equals(group); 528 } 529 isPermissionIndividuallyControlled(Context context, String permission)530 public static boolean isPermissionIndividuallyControlled(Context context, String permission) { 531 if (!context.getPackageManager().arePermissionsIndividuallyControlled()) { 532 return false; 533 } 534 return Manifest.permission.READ_CONTACTS.equals(permission) 535 || Manifest.permission.WRITE_CONTACTS.equals(permission) 536 || Manifest.permission.SEND_SMS.equals(permission) 537 || Manifest.permission.RECEIVE_SMS.equals(permission) 538 || Manifest.permission.READ_SMS.equals(permission) 539 || Manifest.permission.RECEIVE_MMS.equals(permission) 540 || Manifest.permission.CALL_PHONE.equals(permission) 541 || Manifest.permission.READ_CALL_LOG.equals(permission) 542 || Manifest.permission.WRITE_CALL_LOG.equals(permission); 543 } 544 545 /** 546 * Get the message shown to grant a permission group to an app. 547 * 548 * @param appLabel The label of the app 549 * @param group the group to be granted 550 * @param context A context to resolve resources 551 * @param requestRes The resource id of the grant request message 552 * 553 * @return The formatted message to be used as title when granting permissions 554 */ getRequestMessage(CharSequence appLabel, AppPermissionGroup group, Context context, @StringRes int requestRes)555 public static CharSequence getRequestMessage(CharSequence appLabel, AppPermissionGroup group, 556 Context context, @StringRes int requestRes) { 557 if (group.getName().equals(STORAGE) && !group.isNonIsolatedStorage()) { 558 return Html.fromHtml( 559 String.format(context.getResources().getConfiguration().getLocales().get(0), 560 context.getString(R.string.permgrouprequest_storage_isolated), 561 appLabel), 0); 562 } else if (requestRes != 0) { 563 try { 564 return Html.fromHtml(context.getPackageManager().getResourcesForApplication( 565 group.getDeclaringPackage()).getString(requestRes, appLabel), 0); 566 } catch (PackageManager.NameNotFoundException ignored) { 567 } 568 } 569 570 return Html.fromHtml(context.getString(R.string.permission_warning_template, appLabel, 571 group.getDescription()), 0); 572 } 573 574 /** 575 * Build a string representing the given time if it happened on the current day and the date 576 * otherwise. 577 * 578 * @param context the context. 579 * @param lastAccessTime the time in milliseconds. 580 * 581 * @return a string representing the time or date of the given time or null if the time is 0. 582 */ getAbsoluteTimeString(@onNull Context context, long lastAccessTime)583 public static @Nullable String getAbsoluteTimeString(@NonNull Context context, 584 long lastAccessTime) { 585 if (lastAccessTime == 0) { 586 return null; 587 } 588 if (isToday(lastAccessTime)) { 589 return DateFormat.getTimeFormat(context).format(lastAccessTime); 590 } else { 591 return DateFormat.getMediumDateFormat(context).format(lastAccessTime); 592 } 593 } 594 595 /** 596 * Build a string representing the number of milliseconds passed in. It rounds to the nearest 597 * unit. For example, given a duration of 3500 and an English locale, this can return 598 * "3 seconds". 599 * @param context The context. 600 * @param duration The number of milliseconds. 601 * @return a string representing the given number of milliseconds. 602 */ getTimeDiffStr(@onNull Context context, long duration)603 public static @NonNull String getTimeDiffStr(@NonNull Context context, long duration) { 604 long seconds = Math.max(1, duration / 1000); 605 if (seconds < 60) { 606 return context.getResources().getQuantityString(R.plurals.seconds, (int) seconds, 607 seconds); 608 } 609 long minutes = seconds / 60; 610 if (minutes < 60) { 611 return context.getResources().getQuantityString(R.plurals.minutes, (int) minutes, 612 minutes); 613 } 614 long hours = minutes / 60; 615 if (hours < 24) { 616 return context.getResources().getQuantityString(R.plurals.hours, (int) hours, hours); 617 } 618 long days = hours / 24; 619 return context.getResources().getQuantityString(R.plurals.days, (int) days, days); 620 } 621 622 /** 623 * Check whether the given time (in milliseconds) is in the current day. 624 * 625 * @param time the time in milliseconds 626 * 627 * @return whether the given time is in the current day. 628 */ isToday(long time)629 private static boolean isToday(long time) { 630 Calendar today = Calendar.getInstance(Locale.getDefault()); 631 today.setTimeInMillis(System.currentTimeMillis()); 632 today.set(Calendar.HOUR_OF_DAY, 0); 633 today.set(Calendar.MINUTE, 0); 634 today.set(Calendar.SECOND, 0); 635 today.set(Calendar.MILLISECOND, 0); 636 637 Calendar date = Calendar.getInstance(Locale.getDefault()); 638 date.setTimeInMillis(time); 639 return !date.before(today); 640 } 641 642 /** 643 * Add a menu item for searching Settings, if there is an activity handling the action. 644 * 645 * @param menu the menu to add the menu item into 646 * @param context the context for checking whether there is an activity handling the action 647 */ prepareSearchMenuItem(@onNull Menu menu, @NonNull Context context)648 public static void prepareSearchMenuItem(@NonNull Menu menu, @NonNull Context context) { 649 Intent intent = new Intent(Settings.ACTION_APP_SEARCH_SETTINGS); 650 if (context.getPackageManager().resolveActivity(intent, 0) == null) { 651 return; 652 } 653 MenuItem searchItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, R.string.search_menu); 654 searchItem.setIcon(R.drawable.ic_search_24dp); 655 searchItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); 656 searchItem.setOnMenuItemClickListener(item -> { 657 try { 658 context.startActivity(intent); 659 } catch (ActivityNotFoundException e) { 660 Log.e(LOG_TAG, "Cannot start activity to search settings", e); 661 } 662 return true; 663 }); 664 } 665 666 /** 667 * Get badged app icon if necessary, similar as used in the Settings UI. 668 * 669 * @param context The context to use 670 * @param appInfo The app the icon belong to 671 * 672 * @return The icon to use 673 */ getBadgedIcon(@onNull Context context, @NonNull ApplicationInfo appInfo)674 public static @NonNull Drawable getBadgedIcon(@NonNull Context context, 675 @NonNull ApplicationInfo appInfo) { 676 UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid); 677 try (IconFactory iconFactory = IconFactory.obtain(context)) { 678 Bitmap iconBmp = iconFactory.createBadgedIconBitmap( 679 appInfo.loadUnbadgedIcon(context.getPackageManager()), user, false).icon; 680 return new BitmapDrawable(context.getResources(), iconBmp); 681 } 682 } 683 684 /** 685 * Get a string saying what apps with the given permission group can do. 686 * 687 * @param context The context to use 688 * @param groupName The name of the permission group 689 * @param description The description of the permission group 690 * 691 * @return a string saying what apps with the given permission group can do. 692 */ getPermissionGroupDescriptionString(@onNull Context context, @NonNull String groupName, @NonNull CharSequence description)693 public static @NonNull String getPermissionGroupDescriptionString(@NonNull Context context, 694 @NonNull String groupName, @NonNull CharSequence description) { 695 switch (groupName) { 696 case ACTIVITY_RECOGNITION: 697 return context.getString( 698 R.string.permission_description_summary_activity_recognition); 699 case CALENDAR: 700 return context.getString(R.string.permission_description_summary_calendar); 701 case CALL_LOG: 702 return context.getString(R.string.permission_description_summary_call_log); 703 case CAMERA: 704 return context.getString(R.string.permission_description_summary_camera); 705 case CONTACTS: 706 return context.getString(R.string.permission_description_summary_contacts); 707 case LOCATION: 708 return context.getString(R.string.permission_description_summary_location); 709 case MICROPHONE: 710 return context.getString(R.string.permission_description_summary_microphone); 711 case PHONE: 712 return context.getString(R.string.permission_description_summary_phone); 713 case SENSORS: 714 return context.getString(R.string.permission_description_summary_sensors); 715 case SMS: 716 return context.getString(R.string.permission_description_summary_sms); 717 case STORAGE: 718 return context.getString(R.string.permission_description_summary_storage); 719 default: 720 return context.getString(R.string.permission_description_summary_generic, 721 description); 722 } 723 } 724 725 /** 726 * Whether the Location Access Check is enabled. 727 * 728 * @return {@code true} iff the Location Access Check is enabled. 729 */ isLocationAccessCheckEnabled()730 public static boolean isLocationAccessCheckEnabled() { 731 return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, 732 PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, true); 733 } 734 735 /** 736 * Get a device protected storage based shared preferences. Avoid storing sensitive data in it. 737 * 738 * @param context the context to get the shared preferences 739 * @return a device protected storage based shared preferences 740 */ 741 @NonNull getDeviceProtectedSharedPreferences(@onNull Context context)742 public static SharedPreferences getDeviceProtectedSharedPreferences(@NonNull Context context) { 743 if (!context.isDeviceProtectedStorage()) { 744 context = context.createDeviceProtectedStorageContext(); 745 } 746 return context.getSharedPreferences(Constants.PREFERENCES_FILE, MODE_PRIVATE); 747 } 748 749 /** 750 * Update the {@link PackageManager#FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED} and 751 * {@link PackageManager#FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED} for all apps of this user. 752 * 753 * @see PerUserUidToSensitivityLiveData#loadValueInBackground() 754 */ updateUserSensitive(@onNull Application application, @NonNull UserHandle user)755 public static void updateUserSensitive(@NonNull Application application, 756 @NonNull UserHandle user) { 757 Context userContext = getParentUserContext(application); 758 PackageManager pm = userContext.getPackageManager(); 759 RoleManager rm = userContext.getSystemService(RoleManager.class); 760 SharedPreferences prefs = userContext.getSharedPreferences(PREFERENCES_FILE, MODE_PRIVATE); 761 762 boolean showAssistantRecordAudio = prefs.getBoolean( 763 ASSISTANT_RECORD_AUDIO_IS_USER_SENSITIVE_KEY, false); 764 Set<String> overriddenUids = prefs.getStringSet(FORCED_USER_SENSITIVE_UIDS_KEY, 765 Collections.emptySet()); 766 767 List<String> assistants = rm.getRoleHolders(ROLE_ASSISTANT); 768 String assistant = null; 769 if (!assistants.isEmpty()) { 770 if (assistants.size() > 1) { 771 Log.wtf(LOG_TAG, "Assistant role is not exclusive"); 772 } 773 774 // Assistant is an exclusive role 775 assistant = assistants.get(0); 776 } 777 778 PerUserUidToSensitivityLiveData appUserSensitivityLiveData = 779 PerUserUidToSensitivityLiveData.get(user, application); 780 781 // uid -> permission -> flags 782 SparseArray<ArrayMap<String, Integer>> uidUserSensitivity = 783 appUserSensitivityLiveData.loadValueInBackground(); 784 785 // Apply the update 786 int numUids = uidUserSensitivity.size(); 787 for (int uidNum = 0; uidNum < numUids; uidNum++) { 788 int uid = uidUserSensitivity.keyAt(uidNum); 789 790 String[] uidPkgs = pm.getPackagesForUid(uid); 791 if (uidPkgs == null) { 792 continue; 793 } 794 795 boolean isOverridden = overriddenUids.contains(String.valueOf(uid)); 796 boolean isAssistantUid = ArrayUtils.contains(uidPkgs, assistant); 797 798 ArrayMap<String, Integer> uidPermissions = uidUserSensitivity.valueAt(uidNum); 799 800 int numPerms = uidPermissions.size(); 801 for (int permNum = 0; permNum < numPerms; permNum++) { 802 String perm = uidPermissions.keyAt(permNum); 803 804 for (String uidPkg : uidPkgs) { 805 int flags = isOverridden ? FLAGS_ALWAYS_USER_SENSITIVE : uidPermissions.valueAt( 806 permNum); 807 808 if (isAssistantUid && perm.equals(RECORD_AUDIO)) { 809 flags = showAssistantRecordAudio ? FLAGS_ALWAYS_USER_SENSITIVE : 0; 810 } 811 812 try { 813 pm.updatePermissionFlags(perm, uidPkg, FLAGS_ALWAYS_USER_SENSITIVE, flags, 814 user); 815 break; 816 } catch (IllegalArgumentException e) { 817 Log.e(LOG_TAG, "Unexpected exception while updating flags for " 818 + uidPkg + " permission " + perm, e); 819 } 820 } 821 } 822 } 823 } 824 825 /** 826 * Get context of the parent user of the profile group (i.e. usually the 'personal' profile, 827 * not the 'work' profile). 828 * 829 * @param context The context of a user of the profile user group. 830 * 831 * @return The context of the parent user 832 */ getParentUserContext(@onNull Context context)833 public static Context getParentUserContext(@NonNull Context context) { 834 UserHandle parentUser = getSystemServiceSafe(context, UserManager.class) 835 .getProfileParent(UserHandle.of(myUserId())); 836 837 if (parentUser == null) { 838 return context; 839 } 840 841 // In a multi profile environment perform all operations as the parent user of the 842 // current profile 843 try { 844 return context.createPackageContextAsUser(context.getPackageName(), 0, 845 parentUser); 846 } catch (PackageManager.NameNotFoundException e) { 847 // cannot happen 848 throw new IllegalStateException("Could not switch to parent user " + parentUser, e); 849 } 850 } 851 } 852