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 = pm.queryPermissionsByGroup(group, 0); 320 permissions.addAll(getPlatformPermissionsOfGroup(pm, group)); 321 322 return permissions; 323 } 324 325 /** 326 * Get the {@link PackageItemInfo infos} for the given permission group. 327 * 328 * @param groupName the group 329 * @param context the {@code Context} to retrieve {@code PackageManager} 330 * 331 * @return The info of permission group or null if the group does not have runtime permissions. 332 */ getGroupInfo(@onNull String groupName, @NonNull Context context)333 public static @Nullable PackageItemInfo getGroupInfo(@NonNull String groupName, 334 @NonNull Context context) { 335 try { 336 return context.getPackageManager().getPermissionGroupInfo(groupName, 0); 337 } catch (NameNotFoundException e) { 338 /* ignore */ 339 } 340 try { 341 return context.getPackageManager().getPermissionInfo(groupName, 0); 342 } catch (NameNotFoundException e) { 343 /* ignore */ 344 } 345 return null; 346 } 347 348 /** 349 * Get the {@link PermissionInfo infos} for all permission infos belonging to a group. 350 * 351 * @param groupName the group 352 * @param context the {@code Context} to retrieve {@code PackageManager} 353 * 354 * @return The infos of permissions belonging to the group or null if the group does not have 355 * runtime permissions. 356 */ getGroupPermissionInfos(@onNull String groupName, @NonNull Context context)357 public static @Nullable List<PermissionInfo> getGroupPermissionInfos(@NonNull String groupName, 358 @NonNull Context context) { 359 try { 360 return Utils.getPermissionInfosForGroup(context.getPackageManager(), groupName); 361 } catch (NameNotFoundException e) { 362 /* ignore */ 363 } 364 try { 365 PermissionInfo permissionInfo = context.getPackageManager() 366 .getPermissionInfo(groupName, 0); 367 List<PermissionInfo> permissions = new ArrayList<>(); 368 permissions.add(permissionInfo); 369 return permissions; 370 } catch (NameNotFoundException e) { 371 /* ignore */ 372 } 373 return null; 374 } 375 376 /** 377 * Get the label for an application, truncating if it is too long. 378 * 379 * @param applicationInfo the {@link ApplicationInfo} of the application 380 * @param context the {@code Context} to retrieve {@code PackageManager} 381 * 382 * @return the label for the application 383 */ 384 @NonNull getAppLabel(@onNull ApplicationInfo applicationInfo, @NonNull Context context)385 public static String getAppLabel(@NonNull ApplicationInfo applicationInfo, 386 @NonNull Context context) { 387 return getAppLabel(applicationInfo, DEFAULT_MAX_LABEL_SIZE_PX, context); 388 } 389 390 /** 391 * Get the full label for an application without truncation. 392 * 393 * @param applicationInfo the {@link ApplicationInfo} of the application 394 * @param context the {@code Context} to retrieve {@code PackageManager} 395 * 396 * @return the label for the application 397 */ 398 @NonNull getFullAppLabel(@onNull ApplicationInfo applicationInfo, @NonNull Context context)399 public static String getFullAppLabel(@NonNull ApplicationInfo applicationInfo, 400 @NonNull Context context) { 401 return getAppLabel(applicationInfo, 0, context); 402 } 403 404 /** 405 * Get the label for an application with the ability to control truncating. 406 * 407 * @param applicationInfo the {@link ApplicationInfo} of the application 408 * @param ellipsizeDip see {@link TextUtils#makeSafeForPresentation}. 409 * @param context the {@code Context} to retrieve {@code PackageManager} 410 * 411 * @return the label for the application 412 */ 413 @NonNull getAppLabel(@onNull ApplicationInfo applicationInfo, float ellipsizeDip, @NonNull Context context)414 private static String getAppLabel(@NonNull ApplicationInfo applicationInfo, float ellipsizeDip, 415 @NonNull Context context) { 416 return BidiFormatter.getInstance().unicodeWrap(applicationInfo.loadSafeLabel( 417 context.getPackageManager(), ellipsizeDip, 418 TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE) 419 .toString()); 420 } 421 loadDrawable(PackageManager pm, String pkg, int resId)422 public static Drawable loadDrawable(PackageManager pm, String pkg, int resId) { 423 try { 424 return pm.getResourcesForApplication(pkg).getDrawable(resId, null); 425 } catch (Resources.NotFoundException | PackageManager.NameNotFoundException e) { 426 Log.d(LOG_TAG, "Couldn't get resource", e); 427 return null; 428 } 429 } 430 isModernPermissionGroup(String name)431 public static boolean isModernPermissionGroup(String name) { 432 return PLATFORM_PERMISSION_GROUPS.containsKey(name); 433 } 434 435 /** 436 * Get the names of the platform permission groups. 437 * 438 * @return the names of the platform permission groups. 439 */ getPlatformPermissionGroups()440 public static List<String> getPlatformPermissionGroups() { 441 return new ArrayList<>(PLATFORM_PERMISSION_GROUPS.keySet()); 442 } 443 444 /** 445 * Get the names of the platform permissions. 446 * 447 * @return the names of the platform permissions. 448 */ getPlatformPermissions()449 public static Set<String> getPlatformPermissions() { 450 return PLATFORM_PERMISSIONS.keySet(); 451 } 452 453 /** 454 * Should UI show this permission. 455 * 456 * <p>If the user cannot change the group, it should not be shown. 457 * 458 * @param group The group that might need to be shown to the user 459 * 460 * @return 461 */ shouldShowPermission(Context context, AppPermissionGroup group)462 public static boolean shouldShowPermission(Context context, AppPermissionGroup group) { 463 if (!group.isGrantingAllowed()) { 464 return false; 465 } 466 467 final boolean isPlatformPermission = group.getDeclaringPackage().equals(OS_PKG); 468 // Show legacy permissions only if the user chose that. 469 if (isPlatformPermission 470 && !Utils.isModernPermissionGroup(group.getName())) { 471 return false; 472 } 473 return true; 474 } 475 applyTint(Context context, Drawable icon, int attr)476 public static Drawable applyTint(Context context, Drawable icon, int attr) { 477 Theme theme = context.getTheme(); 478 TypedValue typedValue = new TypedValue(); 479 theme.resolveAttribute(attr, typedValue, true); 480 icon = icon.mutate(); 481 icon.setTint(context.getColor(typedValue.resourceId)); 482 return icon; 483 } 484 applyTint(Context context, int iconResId, int attr)485 public static Drawable applyTint(Context context, int iconResId, int attr) { 486 return applyTint(context, context.getDrawable(iconResId), attr); 487 } 488 getLauncherPackages(Context context)489 public static ArraySet<String> getLauncherPackages(Context context) { 490 ArraySet<String> launcherPkgs = new ArraySet<>(); 491 for (ResolveInfo info : context.getPackageManager().queryIntentActivities(LAUNCHER_INTENT, 492 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE)) { 493 launcherPkgs.add(info.activityInfo.packageName); 494 } 495 496 return launcherPkgs; 497 } 498 getAllInstalledApplications(Context context)499 public static List<ApplicationInfo> getAllInstalledApplications(Context context) { 500 return context.getPackageManager().getInstalledApplications(0); 501 } 502 503 /** 504 * Is the group or background group user sensitive? 505 * 506 * @param group The group that might be user sensitive 507 * 508 * @return {@code true} if the group (or it's subgroup) is user sensitive. 509 */ isGroupOrBgGroupUserSensitive(AppPermissionGroup group)510 public static boolean isGroupOrBgGroupUserSensitive(AppPermissionGroup group) { 511 return group.isUserSensitive() || (group.getBackgroundPermissions() != null 512 && group.getBackgroundPermissions().isUserSensitive()); 513 } 514 areGroupPermissionsIndividuallyControlled(Context context, String group)515 public static boolean areGroupPermissionsIndividuallyControlled(Context context, String group) { 516 if (!context.getPackageManager().arePermissionsIndividuallyControlled()) { 517 return false; 518 } 519 return Manifest.permission_group.SMS.equals(group) 520 || Manifest.permission_group.PHONE.equals(group) 521 || Manifest.permission_group.CONTACTS.equals(group); 522 } 523 isPermissionIndividuallyControlled(Context context, String permission)524 public static boolean isPermissionIndividuallyControlled(Context context, String permission) { 525 if (!context.getPackageManager().arePermissionsIndividuallyControlled()) { 526 return false; 527 } 528 return Manifest.permission.READ_CONTACTS.equals(permission) 529 || Manifest.permission.WRITE_CONTACTS.equals(permission) 530 || Manifest.permission.SEND_SMS.equals(permission) 531 || Manifest.permission.RECEIVE_SMS.equals(permission) 532 || Manifest.permission.READ_SMS.equals(permission) 533 || Manifest.permission.RECEIVE_MMS.equals(permission) 534 || Manifest.permission.CALL_PHONE.equals(permission) 535 || Manifest.permission.READ_CALL_LOG.equals(permission) 536 || Manifest.permission.WRITE_CALL_LOG.equals(permission); 537 } 538 539 /** 540 * Get the message shown to grant a permission group to an app. 541 * 542 * @param appLabel The label of the app 543 * @param group the group to be granted 544 * @param context A context to resolve resources 545 * @param requestRes The resource id of the grant request message 546 * 547 * @return The formatted message to be used as title when granting permissions 548 */ getRequestMessage(CharSequence appLabel, AppPermissionGroup group, Context context, @StringRes int requestRes)549 public static CharSequence getRequestMessage(CharSequence appLabel, AppPermissionGroup group, 550 Context context, @StringRes int requestRes) { 551 if (group.getName().equals(STORAGE) && !group.isNonIsolatedStorage()) { 552 return Html.fromHtml( 553 String.format(context.getResources().getConfiguration().getLocales().get(0), 554 context.getString(R.string.permgrouprequest_storage_isolated), 555 appLabel), 0); 556 } else if (requestRes != 0) { 557 try { 558 return Html.fromHtml(context.getPackageManager().getResourcesForApplication( 559 group.getDeclaringPackage()).getString(requestRes, appLabel), 0); 560 } catch (PackageManager.NameNotFoundException ignored) { 561 } 562 } 563 564 return Html.fromHtml(context.getString(R.string.permission_warning_template, appLabel, 565 group.getDescription()), 0); 566 } 567 568 /** 569 * Build a string representing the given time if it happened on the current day and the date 570 * otherwise. 571 * 572 * @param context the context. 573 * @param lastAccessTime the time in milliseconds. 574 * 575 * @return a string representing the time or date of the given time or null if the time is 0. 576 */ getAbsoluteTimeString(@onNull Context context, long lastAccessTime)577 public static @Nullable String getAbsoluteTimeString(@NonNull Context context, 578 long lastAccessTime) { 579 if (lastAccessTime == 0) { 580 return null; 581 } 582 if (isToday(lastAccessTime)) { 583 return DateFormat.getTimeFormat(context).format(lastAccessTime); 584 } else { 585 return DateFormat.getMediumDateFormat(context).format(lastAccessTime); 586 } 587 } 588 589 /** 590 * Build a string representing the number of milliseconds passed in. It rounds to the nearest 591 * unit. For example, given a duration of 3500 and an English locale, this can return 592 * "3 seconds". 593 * @param context The context. 594 * @param duration The number of milliseconds. 595 * @return a string representing the given number of milliseconds. 596 */ getTimeDiffStr(@onNull Context context, long duration)597 public static @NonNull String getTimeDiffStr(@NonNull Context context, long duration) { 598 long seconds = Math.max(1, duration / 1000); 599 if (seconds < 60) { 600 return context.getResources().getQuantityString(R.plurals.seconds, (int) seconds, 601 seconds); 602 } 603 long minutes = seconds / 60; 604 if (minutes < 60) { 605 return context.getResources().getQuantityString(R.plurals.minutes, (int) minutes, 606 minutes); 607 } 608 long hours = minutes / 60; 609 if (hours < 24) { 610 return context.getResources().getQuantityString(R.plurals.hours, (int) hours, hours); 611 } 612 long days = hours / 24; 613 return context.getResources().getQuantityString(R.plurals.days, (int) days, days); 614 } 615 616 /** 617 * Check whether the given time (in milliseconds) is in the current day. 618 * 619 * @param time the time in milliseconds 620 * 621 * @return whether the given time is in the current day. 622 */ isToday(long time)623 private static boolean isToday(long time) { 624 Calendar today = Calendar.getInstance(Locale.getDefault()); 625 today.setTimeInMillis(System.currentTimeMillis()); 626 today.set(Calendar.HOUR_OF_DAY, 0); 627 today.set(Calendar.MINUTE, 0); 628 today.set(Calendar.SECOND, 0); 629 today.set(Calendar.MILLISECOND, 0); 630 631 Calendar date = Calendar.getInstance(Locale.getDefault()); 632 date.setTimeInMillis(time); 633 return !date.before(today); 634 } 635 636 /** 637 * Add a menu item for searching Settings, if there is an activity handling the action. 638 * 639 * @param menu the menu to add the menu item into 640 * @param context the context for checking whether there is an activity handling the action 641 */ prepareSearchMenuItem(@onNull Menu menu, @NonNull Context context)642 public static void prepareSearchMenuItem(@NonNull Menu menu, @NonNull Context context) { 643 Intent intent = new Intent(Settings.ACTION_APP_SEARCH_SETTINGS); 644 if (context.getPackageManager().resolveActivity(intent, 0) == null) { 645 return; 646 } 647 MenuItem searchItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, R.string.search_menu); 648 searchItem.setIcon(R.drawable.ic_search_24dp); 649 searchItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); 650 searchItem.setOnMenuItemClickListener(item -> { 651 try { 652 context.startActivity(intent); 653 } catch (ActivityNotFoundException e) { 654 Log.e(LOG_TAG, "Cannot start activity to search settings", e); 655 } 656 return true; 657 }); 658 } 659 660 /** 661 * Get badged app icon if necessary, similar as used in the Settings UI. 662 * 663 * @param context The context to use 664 * @param appInfo The app the icon belong to 665 * 666 * @return The icon to use 667 */ getBadgedIcon(@onNull Context context, @NonNull ApplicationInfo appInfo)668 public static @NonNull Drawable getBadgedIcon(@NonNull Context context, 669 @NonNull ApplicationInfo appInfo) { 670 UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid); 671 try (IconFactory iconFactory = IconFactory.obtain(context)) { 672 Bitmap iconBmp = iconFactory.createBadgedIconBitmap( 673 appInfo.loadUnbadgedIcon(context.getPackageManager()), user, false).icon; 674 return new BitmapDrawable(context.getResources(), iconBmp); 675 } 676 } 677 678 /** 679 * Get a string saying what apps with the given permission group can do. 680 * 681 * @param context The context to use 682 * @param groupName The name of the permission group 683 * @param description The description of the permission group 684 * 685 * @return a string saying what apps with the given permission group can do. 686 */ getPermissionGroupDescriptionString(@onNull Context context, @NonNull String groupName, @NonNull CharSequence description)687 public static @NonNull String getPermissionGroupDescriptionString(@NonNull Context context, 688 @NonNull String groupName, @NonNull CharSequence description) { 689 switch (groupName) { 690 case ACTIVITY_RECOGNITION: 691 return context.getString( 692 R.string.permission_description_summary_activity_recognition); 693 case CALENDAR: 694 return context.getString(R.string.permission_description_summary_calendar); 695 case CALL_LOG: 696 return context.getString(R.string.permission_description_summary_call_log); 697 case CAMERA: 698 return context.getString(R.string.permission_description_summary_camera); 699 case CONTACTS: 700 return context.getString(R.string.permission_description_summary_contacts); 701 case LOCATION: 702 return context.getString(R.string.permission_description_summary_location); 703 case MICROPHONE: 704 return context.getString(R.string.permission_description_summary_microphone); 705 case PHONE: 706 return context.getString(R.string.permission_description_summary_phone); 707 case SENSORS: 708 return context.getString(R.string.permission_description_summary_sensors); 709 case SMS: 710 return context.getString(R.string.permission_description_summary_sms); 711 case STORAGE: 712 return context.getString(R.string.permission_description_summary_storage); 713 default: 714 return context.getString(R.string.permission_description_summary_generic, 715 description); 716 } 717 } 718 719 /** 720 * Whether the Location Access Check is enabled. 721 * 722 * @return {@code true} iff the Location Access Check is enabled. 723 */ isLocationAccessCheckEnabled()724 public static boolean isLocationAccessCheckEnabled() { 725 return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, 726 PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, true); 727 } 728 729 /** 730 * Get a device protected storage based shared preferences. Avoid storing sensitive data in it. 731 * 732 * @param context the context to get the shared preferences 733 * @return a device protected storage based shared preferences 734 */ 735 @NonNull getDeviceProtectedSharedPreferences(@onNull Context context)736 public static SharedPreferences getDeviceProtectedSharedPreferences(@NonNull Context context) { 737 if (!context.isDeviceProtectedStorage()) { 738 context = context.createDeviceProtectedStorageContext(); 739 } 740 return context.getSharedPreferences(Constants.PREFERENCES_FILE, MODE_PRIVATE); 741 } 742 743 /** 744 * Update the {@link PackageManager#FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED} and 745 * {@link PackageManager#FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED} for all apps of this user. 746 * 747 * @see PerUserUidToSensitivityLiveData#loadValueInBackground() 748 */ updateUserSensitive(@onNull Application application, @NonNull UserHandle user)749 public static void updateUserSensitive(@NonNull Application application, 750 @NonNull UserHandle user) { 751 Context userContext = getParentUserContext(application); 752 PackageManager pm = userContext.getPackageManager(); 753 RoleManager rm = userContext.getSystemService(RoleManager.class); 754 SharedPreferences prefs = userContext.getSharedPreferences(PREFERENCES_FILE, MODE_PRIVATE); 755 756 boolean showAssistantRecordAudio = prefs.getBoolean( 757 ASSISTANT_RECORD_AUDIO_IS_USER_SENSITIVE_KEY, false); 758 Set<String> overriddenUids = prefs.getStringSet(FORCED_USER_SENSITIVE_UIDS_KEY, 759 Collections.emptySet()); 760 761 List<String> assistants = rm.getRoleHolders(ROLE_ASSISTANT); 762 String assistant = null; 763 if (!assistants.isEmpty()) { 764 if (assistants.size() > 1) { 765 Log.wtf(LOG_TAG, "Assistant role is not exclusive"); 766 } 767 768 // Assistant is an exclusive role 769 assistant = assistants.get(0); 770 } 771 772 PerUserUidToSensitivityLiveData appUserSensitivityLiveData = 773 PerUserUidToSensitivityLiveData.get(user, application); 774 775 // uid -> permission -> flags 776 SparseArray<ArrayMap<String, Integer>> uidUserSensitivity = 777 appUserSensitivityLiveData.loadValueInBackground(); 778 779 // Apply the update 780 int numUids = uidUserSensitivity.size(); 781 for (int uidNum = 0; uidNum < numUids; uidNum++) { 782 int uid = uidUserSensitivity.keyAt(uidNum); 783 784 String[] uidPkgs = pm.getPackagesForUid(uid); 785 if (uidPkgs == null) { 786 continue; 787 } 788 789 boolean isOverridden = overriddenUids.contains(String.valueOf(uid)); 790 boolean isAssistantUid = ArrayUtils.contains(uidPkgs, assistant); 791 792 ArrayMap<String, Integer> uidPermissions = uidUserSensitivity.valueAt(uidNum); 793 794 int numPerms = uidPermissions.size(); 795 for (int permNum = 0; permNum < numPerms; permNum++) { 796 String perm = uidPermissions.keyAt(permNum); 797 798 for (String uidPkg : uidPkgs) { 799 int flags = isOverridden ? FLAGS_ALWAYS_USER_SENSITIVE : uidPermissions.valueAt( 800 permNum); 801 802 if (isAssistantUid && perm.equals(RECORD_AUDIO)) { 803 flags = showAssistantRecordAudio ? FLAGS_ALWAYS_USER_SENSITIVE : 0; 804 } 805 806 try { 807 pm.updatePermissionFlags(perm, uidPkg, FLAGS_ALWAYS_USER_SENSITIVE, flags, 808 user); 809 break; 810 } catch (IllegalArgumentException e) { 811 Log.e(LOG_TAG, "Unexpected exception while updating flags for " 812 + uidPkg + " permission " + perm, e); 813 } 814 } 815 } 816 } 817 } 818 819 /** 820 * Get context of the parent user of the profile group (i.e. usually the 'personal' profile, 821 * not the 'work' profile). 822 * 823 * @param context The context of a user of the profile user group. 824 * 825 * @return The context of the parent user 826 */ getParentUserContext(@onNull Context context)827 public static Context getParentUserContext(@NonNull Context context) { 828 UserHandle parentUser = getSystemServiceSafe(context, UserManager.class) 829 .getProfileParent(UserHandle.of(myUserId())); 830 831 if (parentUser == null) { 832 return context; 833 } 834 835 // In a multi profile environment perform all operations as the parent user of the 836 // current profile 837 try { 838 return context.createPackageContextAsUser(context.getPackageName(), 0, 839 parentUser); 840 } catch (PackageManager.NameNotFoundException e) { 841 // cannot happen 842 throw new IllegalStateException("Could not switch to parent user " + parentUser, e); 843 } 844 } 845 } 846