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.permissioncontroller.permission.utils; 18 19 import static android.Manifest.permission_group.ACTIVITY_RECOGNITION; 20 import static android.Manifest.permission_group.CALENDAR; 21 import static android.Manifest.permission_group.CALL_LOG; 22 import static android.Manifest.permission_group.CAMERA; 23 import static android.Manifest.permission_group.CONTACTS; 24 import static android.Manifest.permission_group.LOCATION; 25 import static android.Manifest.permission_group.MICROPHONE; 26 import static android.Manifest.permission_group.NEARBY_DEVICES; 27 import static android.Manifest.permission_group.NOTIFICATIONS; 28 import static android.Manifest.permission_group.PHONE; 29 import static android.Manifest.permission_group.READ_MEDIA_AURAL; 30 import static android.Manifest.permission_group.READ_MEDIA_VISUAL; 31 import static android.Manifest.permission_group.SENSORS; 32 import static android.Manifest.permission_group.SMS; 33 import static android.Manifest.permission_group.STORAGE; 34 import static android.app.AppOpsManager.MODE_ALLOWED; 35 import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE; 36 import static android.content.Context.MODE_PRIVATE; 37 import static android.content.Intent.EXTRA_PACKAGE_NAME; 38 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; 39 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; 40 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; 41 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED; 42 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED; 43 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; 44 import static android.health.connect.HealthConnectManager.ACTION_MANAGE_HEALTH_PERMISSIONS; 45 import static android.os.UserHandle.myUserId; 46 47 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID; 48 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID; 49 50 import static java.lang.annotation.RetentionPolicy.SOURCE; 51 52 import android.Manifest; 53 import android.app.AppOpsManager; 54 import android.app.Application; 55 import android.app.admin.DevicePolicyManager; 56 import android.app.role.RoleManager; 57 import android.content.ActivityNotFoundException; 58 import android.content.ComponentName; 59 import android.content.Context; 60 import android.content.Intent; 61 import android.content.SharedPreferences; 62 import android.content.pm.ApplicationInfo; 63 import android.content.pm.PackageInfo; 64 import android.content.pm.PackageItemInfo; 65 import android.content.pm.PackageManager; 66 import android.content.pm.PackageManager.NameNotFoundException; 67 import android.content.pm.PermissionGroupInfo; 68 import android.content.pm.PermissionInfo; 69 import android.content.pm.ResolveInfo; 70 import android.content.res.Resources; 71 import android.content.res.Resources.Theme; 72 import android.graphics.Bitmap; 73 import android.graphics.drawable.BitmapDrawable; 74 import android.graphics.drawable.Drawable; 75 import android.hardware.SensorPrivacyManager; 76 import android.os.Binder; 77 import android.os.Build; 78 import android.os.Parcelable; 79 import android.os.UserHandle; 80 import android.os.UserManager; 81 import android.provider.DeviceConfig; 82 import android.provider.Settings; 83 import android.text.Html; 84 import android.text.TextUtils; 85 import android.text.format.DateFormat; 86 import android.util.ArrayMap; 87 import android.util.Log; 88 import android.util.TypedValue; 89 import android.view.Menu; 90 import android.view.MenuItem; 91 92 import androidx.annotation.ChecksSdkIntAtLeast; 93 import androidx.annotation.ColorRes; 94 import androidx.annotation.IntDef; 95 import androidx.annotation.NonNull; 96 import androidx.annotation.Nullable; 97 import androidx.annotation.RequiresApi; 98 import androidx.annotation.StringRes; 99 import androidx.core.text.BidiFormatter; 100 import androidx.core.util.Preconditions; 101 102 import com.android.launcher3.icons.IconFactory; 103 import com.android.modules.utils.build.SdkLevel; 104 import com.android.permissioncontroller.Constants; 105 import com.android.permissioncontroller.DeviceUtils; 106 import com.android.permissioncontroller.PermissionControllerApplication; 107 import com.android.permissioncontroller.R; 108 import com.android.permissioncontroller.permission.model.AppPermissionGroup; 109 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup; 110 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo; 111 112 import java.lang.annotation.Retention; 113 import java.time.ZonedDateTime; 114 import java.time.temporal.ChronoUnit; 115 import java.util.ArrayList; 116 import java.util.Calendar; 117 import java.util.HashSet; 118 import java.util.List; 119 import java.util.Locale; 120 import java.util.Random; 121 import java.util.Set; 122 123 import kotlin.Triple; 124 125 public final class Utils { 126 127 @Retention(SOURCE) 128 @IntDef(value = {LAST_24H_SENSOR_TODAY, LAST_24H_SENSOR_YESTERDAY, 129 LAST_24H_CONTENT_PROVIDER, NOT_IN_LAST_7D}) 130 public @interface AppPermsLastAccessType {} 131 public static final int LAST_24H_SENSOR_TODAY = 1; 132 public static final int LAST_24H_SENSOR_YESTERDAY = 2; 133 public static final int LAST_24H_CONTENT_PROVIDER = 3; 134 public static final int LAST_7D_SENSOR = 4; 135 public static final int LAST_7D_CONTENT_PROVIDER = 5; 136 public static final int NOT_IN_LAST_7D = 6; 137 138 private static final String LOG_TAG = "Utils"; 139 140 public static final String OS_PKG = "android"; 141 142 public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f; 143 144 /** The time an app needs to be unused in order to be hibernated */ 145 public static final String PROPERTY_HIBERNATION_UNUSED_THRESHOLD_MILLIS = 146 "auto_revoke_unused_threshold_millis2"; 147 148 /** The frequency of running the job for hibernating apps */ 149 public static final String PROPERTY_HIBERNATION_CHECK_FREQUENCY_MILLIS = 150 "auto_revoke_check_frequency_millis"; 151 152 /** Whether hibernation targets apps that target a pre-S SDK */ 153 public static final String PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS = 154 "app_hibernation_targets_pre_s_apps"; 155 156 /** Whether or not app hibernation is enabled on the device **/ 157 public static final String PROPERTY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled"; 158 159 /** Whether the system exempt from hibernation is enabled on the device **/ 160 public static final String PROPERTY_SYSTEM_EXEMPT_HIBERNATION_ENABLED = 161 "system_exempt_hibernation_enabled"; 162 163 /** Whether to show the Permissions Hub. */ 164 private static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled"; 165 166 /** The timeout for one-time permissions */ 167 private static final String PROPERTY_ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS = 168 "one_time_permissions_timeout_millis"; 169 170 /** The delay before ending a one-time permission session when all processes are dead */ 171 private static final String PROPERTY_ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS = 172 "one_time_permissions_killed_delay_millis"; 173 174 /** Whether to show location access check notifications. */ 175 private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED = 176 "location_access_check_enabled"; 177 178 /** Whether to show health permission in various permission controller UIs. */ 179 private static final String PROPERTY_HEALTH_PERMISSION_UI_ENABLED = 180 "health_permission_ui_enabled"; 181 182 183 /** How frequently to check permission event store to scrub old data */ 184 public static final String PROPERTY_PERMISSION_EVENTS_CHECK_OLD_FREQUENCY_MILLIS = 185 "permission_events_check_old_frequency_millis"; 186 187 /** 188 * Whether to store the exact time for permission changes. Only for use in tests and should 189 * not be modified in prod. 190 */ 191 public static final String PROPERTY_PERMISSION_CHANGES_STORE_EXACT_TIME = 192 "permission_changes_store_exact_time"; 193 194 /** The max amount of time permission data can stay in the storage before being scrubbed */ 195 public static final String PROPERTY_PERMISSION_DECISIONS_MAX_DATA_AGE_MILLIS = 196 "permission_decisions_max_data_age_millis"; 197 198 /** Whether or not warning banner is displayed when device sensors are off **/ 199 public static final String PROPERTY_WARNING_BANNER_DISPLAY_ENABLED = "warning_banner_enabled"; 200 201 /** All permission whitelists. */ 202 public static final int FLAGS_PERMISSION_WHITELIST_ALL = 203 PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM 204 | PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE 205 | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER; 206 207 /** All permission restriction exemptions. */ 208 public static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT = 209 FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT 210 | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT 211 | FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; 212 213 /** 214 * The default length of the timeout for one-time permissions 215 */ 216 public static final long ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS = 1 * 60 * 1000; // 1 minute 217 218 /** 219 * The default length to wait before ending a one-time permission session after all processes 220 * are dead. 221 */ 222 public static final long ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS = 5 * 1000; 223 224 private static final ArrayMap<String, Integer> PERM_GROUP_REQUEST_RES; 225 private static final ArrayMap<String, Integer> PERM_GROUP_REQUEST_DETAIL_RES; 226 private static final ArrayMap<String, Integer> PERM_GROUP_BACKGROUND_REQUEST_RES; 227 private static final ArrayMap<String, Integer> PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES; 228 private static final ArrayMap<String, Integer> PERM_GROUP_UPGRADE_REQUEST_RES; 229 private static final ArrayMap<String, Integer> PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES; 230 231 /** Permission -> Sensor codes */ 232 private static final ArrayMap<String, Integer> PERM_SENSOR_CODES; 233 /** Permission -> Icon res id */ 234 private static final ArrayMap<String, Integer> PERM_BLOCKED_ICON; 235 /** Permission -> Title res id */ 236 private static final ArrayMap<String, Integer> PERM_BLOCKED_TITLE; 237 238 public static final int FLAGS_ALWAYS_USER_SENSITIVE = 239 FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED 240 | FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED; 241 242 private static final String SYSTEM_AMBIENT_AUDIO_INTELLIGENCE = 243 "android.app.role.SYSTEM_AMBIENT_AUDIO_INTELLIGENCE"; 244 private static final String SYSTEM_UI_INTELLIGENCE = 245 "android.app.role.SYSTEM_UI_INTELLIGENCE"; 246 private static final String SYSTEM_AUDIO_INTELLIGENCE = 247 "android.app.role.SYSTEM_AUDIO_INTELLIGENCE"; 248 private static final String SYSTEM_NOTIFICATION_INTELLIGENCE = 249 "android.app.role.SYSTEM_NOTIFICATION_INTELLIGENCE"; 250 private static final String SYSTEM_TEXT_INTELLIGENCE = 251 "android.app.role.SYSTEM_TEXT_INTELLIGENCE"; 252 private static final String SYSTEM_VISUAL_INTELLIGENCE = 253 "android.app.role.SYSTEM_VISUAL_INTELLIGENCE"; 254 255 // TODO: theianchen Using hardcoded values here as a WIP solution for now. 256 private static final String[] EXEMPTED_ROLES = { 257 SYSTEM_AMBIENT_AUDIO_INTELLIGENCE, 258 SYSTEM_UI_INTELLIGENCE, 259 SYSTEM_AUDIO_INTELLIGENCE, 260 SYSTEM_NOTIFICATION_INTELLIGENCE, 261 SYSTEM_TEXT_INTELLIGENCE, 262 SYSTEM_VISUAL_INTELLIGENCE, 263 }; 264 265 static { 266 267 PERM_GROUP_REQUEST_RES = new ArrayMap<>(); PERM_GROUP_REQUEST_RES.put(CONTACTS, R.string.permgrouprequest_contacts)268 PERM_GROUP_REQUEST_RES.put(CONTACTS, R.string.permgrouprequest_contacts); PERM_GROUP_REQUEST_RES.put(LOCATION, R.string.permgrouprequest_location)269 PERM_GROUP_REQUEST_RES.put(LOCATION, R.string.permgrouprequest_location); PERM_GROUP_REQUEST_RES.put(NEARBY_DEVICES, R.string.permgrouprequest_nearby_devices)270 PERM_GROUP_REQUEST_RES.put(NEARBY_DEVICES, R.string.permgrouprequest_nearby_devices); PERM_GROUP_REQUEST_RES.put(CALENDAR, R.string.permgrouprequest_calendar)271 PERM_GROUP_REQUEST_RES.put(CALENDAR, R.string.permgrouprequest_calendar); PERM_GROUP_REQUEST_RES.put(SMS, R.string.permgrouprequest_sms)272 PERM_GROUP_REQUEST_RES.put(SMS, R.string.permgrouprequest_sms); PERM_GROUP_REQUEST_RES.put(STORAGE, R.string.permgrouprequest_storage)273 PERM_GROUP_REQUEST_RES.put(STORAGE, R.string.permgrouprequest_storage); PERM_GROUP_REQUEST_RES.put(READ_MEDIA_AURAL, R.string.permgrouprequest_read_media_aural)274 PERM_GROUP_REQUEST_RES.put(READ_MEDIA_AURAL, R.string.permgrouprequest_read_media_aural); PERM_GROUP_REQUEST_RES.put(READ_MEDIA_VISUAL, R.string.permgrouprequest_read_media_visual)275 PERM_GROUP_REQUEST_RES.put(READ_MEDIA_VISUAL, R.string.permgrouprequest_read_media_visual); PERM_GROUP_REQUEST_RES.put(MICROPHONE, R.string.permgrouprequest_microphone)276 PERM_GROUP_REQUEST_RES.put(MICROPHONE, R.string.permgrouprequest_microphone); 277 PERM_GROUP_REQUEST_RES put(ACTIVITY_RECOGNITION, R.string.permgrouprequest_activityRecognition)278 .put(ACTIVITY_RECOGNITION, R.string.permgrouprequest_activityRecognition); PERM_GROUP_REQUEST_RES.put(CAMERA, R.string.permgrouprequest_camera)279 PERM_GROUP_REQUEST_RES.put(CAMERA, R.string.permgrouprequest_camera); PERM_GROUP_REQUEST_RES.put(CALL_LOG, R.string.permgrouprequest_calllog)280 PERM_GROUP_REQUEST_RES.put(CALL_LOG, R.string.permgrouprequest_calllog); PERM_GROUP_REQUEST_RES.put(PHONE, R.string.permgrouprequest_phone)281 PERM_GROUP_REQUEST_RES.put(PHONE, R.string.permgrouprequest_phone); PERM_GROUP_REQUEST_RES.put(SENSORS, R.string.permgrouprequest_sensors)282 PERM_GROUP_REQUEST_RES.put(SENSORS, R.string.permgrouprequest_sensors); PERM_GROUP_REQUEST_RES.put(NOTIFICATIONS, R.string.permgrouprequest_notifications)283 PERM_GROUP_REQUEST_RES.put(NOTIFICATIONS, R.string.permgrouprequest_notifications); 284 285 PERM_GROUP_REQUEST_DETAIL_RES = new ArrayMap<>(); PERM_GROUP_REQUEST_DETAIL_RES.put(LOCATION, R.string.permgrouprequestdetail_location)286 PERM_GROUP_REQUEST_DETAIL_RES.put(LOCATION, R.string.permgrouprequestdetail_location); PERM_GROUP_REQUEST_DETAIL_RES.put(MICROPHONE, R.string.permgrouprequestdetail_microphone)287 PERM_GROUP_REQUEST_DETAIL_RES.put(MICROPHONE, R.string.permgrouprequestdetail_microphone); PERM_GROUP_REQUEST_DETAIL_RES.put(CAMERA, R.string.permgrouprequestdetail_camera)288 PERM_GROUP_REQUEST_DETAIL_RES.put(CAMERA, R.string.permgrouprequestdetail_camera); 289 290 PERM_GROUP_BACKGROUND_REQUEST_RES = new ArrayMap<>(); 291 PERM_GROUP_BACKGROUND_REQUEST_RES put(LOCATION, R.string.permgroupbackgroundrequest_location)292 .put(LOCATION, R.string.permgroupbackgroundrequest_location); 293 PERM_GROUP_BACKGROUND_REQUEST_RES put(MICROPHONE, R.string.permgroupbackgroundrequest_microphone)294 .put(MICROPHONE, R.string.permgroupbackgroundrequest_microphone); 295 PERM_GROUP_BACKGROUND_REQUEST_RES put(CAMERA, R.string.permgroupbackgroundrequest_camera)296 .put(CAMERA, R.string.permgroupbackgroundrequest_camera); 297 PERM_GROUP_BACKGROUND_REQUEST_RES put(SENSORS, R.string.permgroupbackgroundrequest_sensors)298 .put(SENSORS, R.string.permgroupbackgroundrequest_sensors); 299 300 PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES = new ArrayMap<>(); 301 PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES put(LOCATION, R.string.permgroupbackgroundrequestdetail_location)302 .put(LOCATION, R.string.permgroupbackgroundrequestdetail_location); 303 PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES put(MICROPHONE, R.string.permgroupbackgroundrequestdetail_microphone)304 .put(MICROPHONE, R.string.permgroupbackgroundrequestdetail_microphone); 305 PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES put(CAMERA, R.string.permgroupbackgroundrequestdetail_camera)306 .put(CAMERA, R.string.permgroupbackgroundrequestdetail_camera); 307 PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES put(SENSORS, R.string.permgroupbackgroundrequestdetail_sensors)308 .put(SENSORS, R.string.permgroupbackgroundrequestdetail_sensors); 309 310 PERM_GROUP_UPGRADE_REQUEST_RES = new ArrayMap<>(); PERM_GROUP_UPGRADE_REQUEST_RES.put(LOCATION, R.string.permgroupupgraderequest_location)311 PERM_GROUP_UPGRADE_REQUEST_RES.put(LOCATION, R.string.permgroupupgraderequest_location); PERM_GROUP_UPGRADE_REQUEST_RES.put(MICROPHONE, R.string.permgroupupgraderequest_microphone)312 PERM_GROUP_UPGRADE_REQUEST_RES.put(MICROPHONE, R.string.permgroupupgraderequest_microphone); PERM_GROUP_UPGRADE_REQUEST_RES.put(CAMERA, R.string.permgroupupgraderequest_camera)313 PERM_GROUP_UPGRADE_REQUEST_RES.put(CAMERA, R.string.permgroupupgraderequest_camera); PERM_GROUP_UPGRADE_REQUEST_RES.put(SENSORS, R.string.permgroupupgraderequest_sensors)314 PERM_GROUP_UPGRADE_REQUEST_RES.put(SENSORS, R.string.permgroupupgraderequest_sensors); 315 316 PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES = new ArrayMap<>(); 317 PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES put(LOCATION, R.string.permgroupupgraderequestdetail_location)318 .put(LOCATION, R.string.permgroupupgraderequestdetail_location); 319 PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES put(MICROPHONE, R.string.permgroupupgraderequestdetail_microphone)320 .put(MICROPHONE, R.string.permgroupupgraderequestdetail_microphone); 321 PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES put(CAMERA, R.string.permgroupupgraderequestdetail_camera)322 .put(CAMERA, R.string.permgroupupgraderequestdetail_camera); 323 PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES put(SENSORS, R.string.permgroupupgraderequestdetail_sensors)324 .put(SENSORS, R.string.permgroupupgraderequestdetail_sensors); 325 326 PERM_SENSOR_CODES = new ArrayMap<>(); 327 if (SdkLevel.isAtLeastS()) { PERM_SENSOR_CODES.put(CAMERA, SensorPrivacyManager.Sensors.CAMERA)328 PERM_SENSOR_CODES.put(CAMERA, SensorPrivacyManager.Sensors.CAMERA); PERM_SENSOR_CODES.put(MICROPHONE, SensorPrivacyManager.Sensors.MICROPHONE)329 PERM_SENSOR_CODES.put(MICROPHONE, SensorPrivacyManager.Sensors.MICROPHONE); 330 } 331 332 PERM_BLOCKED_ICON = new ArrayMap<>(); PERM_BLOCKED_ICON.put(CAMERA, R.drawable.ic_camera_blocked)333 PERM_BLOCKED_ICON.put(CAMERA, R.drawable.ic_camera_blocked); PERM_BLOCKED_ICON.put(MICROPHONE, R.drawable.ic_mic_blocked)334 PERM_BLOCKED_ICON.put(MICROPHONE, R.drawable.ic_mic_blocked); PERM_BLOCKED_ICON.put(LOCATION, R.drawable.ic_location_blocked)335 PERM_BLOCKED_ICON.put(LOCATION, R.drawable.ic_location_blocked); 336 337 PERM_BLOCKED_TITLE = new ArrayMap<>(); PERM_BLOCKED_TITLE.put(CAMERA, R.string.blocked_camera_title)338 PERM_BLOCKED_TITLE.put(CAMERA, R.string.blocked_camera_title); PERM_BLOCKED_TITLE.put(MICROPHONE, R.string.blocked_microphone_title)339 PERM_BLOCKED_TITLE.put(MICROPHONE, R.string.blocked_microphone_title); PERM_BLOCKED_TITLE.put(LOCATION, R.string.blocked_location_title)340 PERM_BLOCKED_TITLE.put(LOCATION, R.string.blocked_location_title); 341 342 } 343 Utils()344 private Utils() { 345 /* do nothing - hide constructor */ 346 } 347 348 private static ArrayMap<UserHandle, Context> sUserContexts = new ArrayMap<>(); 349 350 /** 351 * Creates and caches a PackageContext for the requested user, or returns the previously cached 352 * value. The package of the PackageContext is the application's package. 353 * 354 * @param context The context of the currently running application 355 * @param user The desired user for the context 356 * 357 * @return The generated or cached Context for the requested user 358 * 359 * @throws RuntimeException If the app has no package name attached, which should never happen 360 */ getUserContext(Context context, UserHandle user)361 public static @NonNull Context getUserContext(Context context, UserHandle user) { 362 if (!sUserContexts.containsKey(user)) { 363 sUserContexts.put(user, context.getApplicationContext() 364 .createContextAsUser(user, 0)); 365 } 366 return Preconditions.checkNotNull(sUserContexts.get(user)); 367 } 368 369 /** 370 * {@code @NonNull} version of {@link Context#getSystemService(Class)} 371 */ getSystemServiceSafe(@onNull Context context, Class<M> clazz)372 public static @NonNull <M> M getSystemServiceSafe(@NonNull Context context, Class<M> clazz) { 373 return Preconditions.checkNotNull(context.getSystemService(clazz), 374 "Could not resolve " + clazz.getSimpleName()); 375 } 376 377 /** 378 * {@code @NonNull} version of {@link Context#getSystemService(Class)} 379 */ getSystemServiceSafe(@onNull Context context, Class<M> clazz, @NonNull UserHandle user)380 public static @NonNull <M> M getSystemServiceSafe(@NonNull Context context, Class<M> clazz, 381 @NonNull UserHandle user) { 382 try { 383 return Preconditions.checkNotNull(context.createPackageContextAsUser( 384 context.getPackageName(), 0, user).getSystemService(clazz), 385 "Could not resolve " + clazz.getSimpleName()); 386 } catch (PackageManager.NameNotFoundException neverHappens) { 387 throw new IllegalStateException(); 388 } 389 } 390 391 /** 392 * {@code @NonNull} version of {@link Intent#getParcelableExtra(String)} 393 */ getParcelableExtraSafe(@onNull Intent intent, @NonNull String name)394 public static @NonNull <T extends Parcelable> T getParcelableExtraSafe(@NonNull Intent intent, 395 @NonNull String name) { 396 return Preconditions.checkNotNull(intent.getParcelableExtra(name), 397 "Could not get parcelable extra for " + name); 398 } 399 400 /** 401 * {@code @NonNull} version of {@link Intent#getStringExtra(String)} 402 */ getStringExtraSafe(@onNull Intent intent, @NonNull String name)403 public static @NonNull String getStringExtraSafe(@NonNull Intent intent, 404 @NonNull String name) { 405 return Preconditions.checkNotNull(intent.getStringExtra(name), 406 "Could not get string extra for " + name); 407 } 408 409 /** 410 * Returns true if a permission is dangerous, installed, and not removed 411 * @param permissionInfo The permission we wish to check 412 * @return If all of the conditions are met 413 */ isPermissionDangerousInstalledNotRemoved(PermissionInfo permissionInfo)414 public static boolean isPermissionDangerousInstalledNotRemoved(PermissionInfo permissionInfo) { 415 return permissionInfo != null 416 && permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS 417 && (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0 418 && (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) == 0; 419 } 420 421 /** 422 * Get the {@link PermissionInfo infos} for all permission infos belonging to a group. 423 * 424 * @param pm Package manager to use to resolve permission infos 425 * @param group the group 426 * 427 * @return The infos of permissions belonging to the group or an empty list if the group 428 * does not have runtime permissions 429 */ getPermissionInfosForGroup( @onNull PackageManager pm, @NonNull String group)430 public static @NonNull List<PermissionInfo> getPermissionInfosForGroup( 431 @NonNull PackageManager pm, @NonNull String group) 432 throws PackageManager.NameNotFoundException { 433 List<PermissionInfo> permissions = pm.queryPermissionsByGroup(group, 0); 434 permissions.addAll(PermissionMapping.getPlatformPermissionsOfGroup(pm, group)); 435 436 /* 437 * If the undefined group is requested, the package manager will return all platform 438 * permissions, since they are marked as Undefined in the manifest. Do not return these 439 * permissions. 440 */ 441 if (group.equals(Manifest.permission_group.UNDEFINED)) { 442 List<PermissionInfo> undefinedPerms = new ArrayList<>(); 443 for (PermissionInfo permissionInfo : permissions) { 444 String permGroup = 445 PermissionMapping.getGroupOfPlatformPermission(permissionInfo.name); 446 if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) { 447 undefinedPerms.add(permissionInfo); 448 } 449 } 450 return undefinedPerms; 451 } 452 453 return permissions; 454 } 455 456 /** 457 * Get the {@link PermissionInfo infos} for all runtime installed permission infos belonging to 458 * a group. 459 * 460 * @param pm Package manager to use to resolve permission infos 461 * @param group the group 462 * 463 * @return The infos of installed runtime permissions belonging to the group or an empty list 464 * if the group does not have runtime permissions 465 */ getInstalledRuntimePermissionInfosForGroup( @onNull PackageManager pm, @NonNull String group)466 public static @NonNull List<PermissionInfo> getInstalledRuntimePermissionInfosForGroup( 467 @NonNull PackageManager pm, @NonNull String group) 468 throws PackageManager.NameNotFoundException { 469 List<PermissionInfo> permissions = pm.queryPermissionsByGroup(group, 0); 470 permissions.addAll(PermissionMapping.getPlatformPermissionsOfGroup(pm, group)); 471 472 List<PermissionInfo> installedRuntime = new ArrayList<>(); 473 for (PermissionInfo permissionInfo: permissions) { 474 if (permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS 475 && (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0 476 && (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) == 0) { 477 installedRuntime.add(permissionInfo); 478 } 479 } 480 481 /* 482 * If the undefined group is requested, the package manager will return all platform 483 * permissions, since they are marked as Undefined in the manifest. Do not return these 484 * permissions. 485 */ 486 if (group.equals(Manifest.permission_group.UNDEFINED)) { 487 List<PermissionInfo> undefinedPerms = new ArrayList<>(); 488 for (PermissionInfo permissionInfo : installedRuntime) { 489 String permGroup = 490 PermissionMapping.getGroupOfPlatformPermission(permissionInfo.name); 491 if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) { 492 undefinedPerms.add(permissionInfo); 493 } 494 } 495 return undefinedPerms; 496 } 497 498 return installedRuntime; 499 } 500 501 /** 502 * Get the {@link PackageItemInfo infos} for the given permission group. 503 * 504 * @param groupName the group 505 * @param context the {@code Context} to retrieve {@code PackageManager} 506 * 507 * @return The info of permission group or null if the group does not have runtime permissions. 508 */ getGroupInfo(@onNull String groupName, @NonNull Context context)509 public static @Nullable PackageItemInfo getGroupInfo(@NonNull String groupName, 510 @NonNull Context context) { 511 try { 512 return context.getPackageManager().getPermissionGroupInfo(groupName, 0); 513 } catch (NameNotFoundException e) { 514 /* ignore */ 515 } 516 try { 517 return context.getPackageManager().getPermissionInfo(groupName, 0); 518 } catch (NameNotFoundException e) { 519 /* ignore */ 520 } 521 return null; 522 } 523 524 /** 525 * Get the {@link PermissionInfo infos} for all permission infos belonging to a group. 526 * 527 * @param groupName the group 528 * @param context the {@code Context} to retrieve {@code PackageManager} 529 * 530 * @return The infos of permissions belonging to the group or null if the group does not have 531 * runtime permissions. 532 */ getGroupPermissionInfos(@onNull String groupName, @NonNull Context context)533 public static @Nullable List<PermissionInfo> getGroupPermissionInfos(@NonNull String groupName, 534 @NonNull Context context) { 535 try { 536 return Utils.getPermissionInfosForGroup(context.getPackageManager(), groupName); 537 } catch (NameNotFoundException e) { 538 /* ignore */ 539 } 540 try { 541 PermissionInfo permissionInfo = context.getPackageManager() 542 .getPermissionInfo(groupName, 0); 543 List<PermissionInfo> permissions = new ArrayList<>(); 544 permissions.add(permissionInfo); 545 return permissions; 546 } catch (NameNotFoundException e) { 547 /* ignore */ 548 } 549 return null; 550 } 551 552 /** 553 * Get the label for an application, truncating if it is too long. 554 * 555 * @param applicationInfo the {@link ApplicationInfo} of the application 556 * @param context the {@code Context} to retrieve {@code PackageManager} 557 * 558 * @return the label for the application 559 */ 560 @NonNull getAppLabel(@onNull ApplicationInfo applicationInfo, @NonNull Context context)561 public static String getAppLabel(@NonNull ApplicationInfo applicationInfo, 562 @NonNull Context context) { 563 return getAppLabel(applicationInfo, DEFAULT_MAX_LABEL_SIZE_PX, context); 564 } 565 566 /** 567 * Get the full label for an application without truncation. 568 * 569 * @param applicationInfo the {@link ApplicationInfo} of the application 570 * @param context the {@code Context} to retrieve {@code PackageManager} 571 * 572 * @return the label for the application 573 */ 574 @NonNull getFullAppLabel(@onNull ApplicationInfo applicationInfo, @NonNull Context context)575 public static String getFullAppLabel(@NonNull ApplicationInfo applicationInfo, 576 @NonNull Context context) { 577 return getAppLabel(applicationInfo, 0, context); 578 } 579 580 /** 581 * Get the label for an application with the ability to control truncating. 582 * 583 * @param applicationInfo the {@link ApplicationInfo} of the application 584 * @param ellipsizeDip see {@link TextUtils#makeSafeForPresentation}. 585 * @param context the {@code Context} to retrieve {@code PackageManager} 586 * 587 * @return the label for the application 588 */ 589 @NonNull getAppLabel(@onNull ApplicationInfo applicationInfo, float ellipsizeDip, @NonNull Context context)590 private static String getAppLabel(@NonNull ApplicationInfo applicationInfo, float ellipsizeDip, 591 @NonNull Context context) { 592 return BidiFormatter.getInstance().unicodeWrap(applicationInfo.loadSafeLabel( 593 context.getPackageManager(), ellipsizeDip, 594 TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE) 595 .toString()); 596 } 597 loadDrawable(PackageManager pm, String pkg, int resId)598 public static Drawable loadDrawable(PackageManager pm, String pkg, int resId) { 599 try { 600 return pm.getResourcesForApplication(pkg).getDrawable(resId, null); 601 } catch (Resources.NotFoundException | PackageManager.NameNotFoundException e) { 602 Log.d(LOG_TAG, "Couldn't get resource", e); 603 return null; 604 } 605 } 606 607 /** 608 * Should UI show this permission. 609 * 610 * <p>If the user cannot change the group, it should not be shown. 611 * 612 * @param group The group that might need to be shown to the user 613 * 614 * @return 615 */ shouldShowPermission(Context context, AppPermissionGroup group)616 public static boolean shouldShowPermission(Context context, AppPermissionGroup group) { 617 if (!group.isGrantingAllowed()) { 618 return false; 619 } 620 621 final boolean isPlatformPermission = group.getDeclaringPackage().equals(OS_PKG); 622 // Show legacy permissions only if the user chose that. 623 if (isPlatformPermission 624 && !PermissionMapping.isPlatformPermissionGroup(group.getName())) { 625 return false; 626 } 627 return true; 628 } 629 applyTint(Context context, Drawable icon, int attr)630 public static Drawable applyTint(Context context, Drawable icon, int attr) { 631 Theme theme = context.getTheme(); 632 TypedValue typedValue = new TypedValue(); 633 theme.resolveAttribute(attr, typedValue, true); 634 icon = icon.mutate(); 635 icon.setTint(context.getColor(typedValue.resourceId)); 636 return icon; 637 } 638 applyTint(Context context, int iconResId, int attr)639 public static Drawable applyTint(Context context, int iconResId, int attr) { 640 return applyTint(context, context.getDrawable(iconResId), attr); 641 } 642 643 /** 644 * Get the color resource id based on the attribute 645 * 646 * @return Resource id for the color 647 */ 648 @ColorRes getColorResId(Context context, int attr)649 public static int getColorResId(Context context, int attr) { 650 Theme theme = context.getTheme(); 651 TypedValue typedValue = new TypedValue(); 652 theme.resolveAttribute(attr, typedValue, true); 653 return typedValue.resourceId; 654 } 655 656 /** 657 * Is the group or background group user sensitive? 658 * 659 * @param group The group that might be user sensitive 660 * 661 * @return {@code true} if the group (or it's subgroup) is user sensitive. 662 */ isGroupOrBgGroupUserSensitive(AppPermissionGroup group)663 public static boolean isGroupOrBgGroupUserSensitive(AppPermissionGroup group) { 664 return group.isUserSensitive() || (group.getBackgroundPermissions() != null 665 && group.getBackgroundPermissions().isUserSensitive()); 666 } 667 areGroupPermissionsIndividuallyControlled(Context context, String group)668 public static boolean areGroupPermissionsIndividuallyControlled(Context context, String group) { 669 if (!context.getPackageManager().arePermissionsIndividuallyControlled()) { 670 return false; 671 } 672 return Manifest.permission_group.SMS.equals(group) 673 || Manifest.permission_group.PHONE.equals(group) 674 || Manifest.permission_group.CONTACTS.equals(group) 675 || Manifest.permission_group.CALL_LOG.equals(group); 676 } 677 isPermissionIndividuallyControlled(Context context, String permission)678 public static boolean isPermissionIndividuallyControlled(Context context, String permission) { 679 if (!context.getPackageManager().arePermissionsIndividuallyControlled()) { 680 return false; 681 } 682 return Manifest.permission.READ_CONTACTS.equals(permission) 683 || Manifest.permission.WRITE_CONTACTS.equals(permission) 684 || Manifest.permission.SEND_SMS.equals(permission) 685 || Manifest.permission.RECEIVE_SMS.equals(permission) 686 || Manifest.permission.READ_SMS.equals(permission) 687 || Manifest.permission.RECEIVE_MMS.equals(permission) 688 || Manifest.permission.CALL_PHONE.equals(permission) 689 || Manifest.permission.READ_CALL_LOG.equals(permission) 690 || Manifest.permission.WRITE_CALL_LOG.equals(permission); 691 } 692 693 /** 694 * Get the message shown to grant a permission group to an app. 695 * 696 * @param appLabel The label of the app 697 * @param packageName The package name of the app 698 * @param groupName The name of the permission group 699 * @param context A context to resolve resources 700 * @param requestRes The resource id of the grant request message 701 * 702 * @return The formatted message to be used as title when granting permissions 703 */ getRequestMessage(CharSequence appLabel, String packageName, String groupName, Context context, @StringRes int requestRes)704 public static CharSequence getRequestMessage(CharSequence appLabel, String packageName, 705 String groupName, Context context, @StringRes int requestRes) { 706 707 boolean isIsolatedStorage; 708 try { 709 isIsolatedStorage = !isNonIsolatedStorage(context, packageName); 710 } catch (NameNotFoundException e) { 711 isIsolatedStorage = false; 712 } 713 if (groupName.equals(STORAGE) && isIsolatedStorage) { 714 return Html.fromHtml( 715 String.format(context.getResources().getConfiguration().getLocales().get(0), 716 context.getString(R.string.permgrouprequest_storage_isolated), 717 appLabel), 0); 718 } else if (requestRes != 0) { 719 return Html.fromHtml(context.getResources().getString(requestRes, appLabel), 0); 720 } 721 722 return Html.fromHtml(context.getString(R.string.permission_warning_template, appLabel, 723 loadGroupDescription(context, groupName, context.getPackageManager())), 0); 724 } 725 loadGroupDescription(Context context, String groupName, @NonNull PackageManager packageManager)726 private static CharSequence loadGroupDescription(Context context, String groupName, 727 @NonNull PackageManager packageManager) { 728 PackageItemInfo groupInfo = getGroupInfo(groupName, context); 729 CharSequence description = null; 730 if (groupInfo instanceof PermissionGroupInfo) { 731 description = ((PermissionGroupInfo) groupInfo).loadDescription(packageManager); 732 } else if (groupInfo instanceof PermissionInfo) { 733 description = ((PermissionInfo) groupInfo).loadDescription(packageManager); 734 } 735 736 if (description == null || description.length() <= 0) { 737 description = context.getString(R.string.default_permission_description); 738 } 739 740 return description; 741 } 742 743 /** 744 * Whether or not the given package has non-isolated storage permissions 745 * @param context The current context 746 * @param packageName The package name to check 747 * @return True if the package has access to non-isolated storage, false otherwise 748 * @throws NameNotFoundException 749 */ isNonIsolatedStorage(@onNull Context context, @NonNull String packageName)750 public static boolean isNonIsolatedStorage(@NonNull Context context, 751 @NonNull String packageName) throws NameNotFoundException { 752 PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0); 753 AppOpsManager manager = context.getSystemService(AppOpsManager.class); 754 755 756 return packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.P 757 || (packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.R 758 && manager.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE, 759 packageInfo.applicationInfo.uid, packageInfo.packageName) == MODE_ALLOWED); 760 } 761 762 /** 763 * Gets whether the STORAGE group should be hidden from the UI for this package. This is true 764 * when the platform is T+, and the package has legacy storage access (i.e., either the package 765 * has a targetSdk less than Q, or has a targetSdk equal to Q and has OPSTR_LEGACY_STORAGE). 766 * 767 * TODO jaysullivan: This is always calling AppOpsManager; not taking advantage of LiveData 768 * 769 * @param pkg The package to check 770 */ shouldShowStorage(LightPackageInfo pkg)771 public static boolean shouldShowStorage(LightPackageInfo pkg) { 772 if (!SdkLevel.isAtLeastT()) { 773 return true; 774 } 775 int targetSdkVersion = pkg.getTargetSdkVersion(); 776 PermissionControllerApplication app = PermissionControllerApplication.get(); 777 Context context = Utils.getUserContext(app, UserHandle.getUserHandleForUid(pkg.getUid())); 778 AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); 779 if (appOpsManager == null) { 780 return true; 781 } 782 783 return targetSdkVersion < Build.VERSION_CODES.Q 784 || (targetSdkVersion == Build.VERSION_CODES.Q 785 && appOpsManager.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE, pkg.getUid(), 786 pkg.getPackageName()) == MODE_ALLOWED); 787 } 788 789 /** 790 * Build a string representing the given time if it happened on the current day and the date 791 * otherwise. 792 * 793 * @param context the context. 794 * @param lastAccessTime the time in milliseconds. 795 * 796 * @return a string representing the time or date of the given time or null if the time is 0. 797 */ getAbsoluteTimeString(@onNull Context context, long lastAccessTime)798 public static @Nullable String getAbsoluteTimeString(@NonNull Context context, 799 long lastAccessTime) { 800 if (lastAccessTime == 0) { 801 return null; 802 } 803 if (isToday(lastAccessTime)) { 804 return DateFormat.getTimeFormat(context).format(lastAccessTime); 805 } else { 806 return DateFormat.getMediumDateFormat(context).format(lastAccessTime); 807 } 808 } 809 810 /** 811 * Check whether the given time (in milliseconds) is in the current day. 812 * 813 * @param time the time in milliseconds 814 * 815 * @return whether the given time is in the current day. 816 */ isToday(long time)817 private static boolean isToday(long time) { 818 Calendar today = Calendar.getInstance(Locale.getDefault()); 819 today.setTimeInMillis(System.currentTimeMillis()); 820 today.set(Calendar.HOUR_OF_DAY, 0); 821 today.set(Calendar.MINUTE, 0); 822 today.set(Calendar.SECOND, 0); 823 today.set(Calendar.MILLISECOND, 0); 824 825 Calendar date = Calendar.getInstance(Locale.getDefault()); 826 date.setTimeInMillis(time); 827 return !date.before(today); 828 } 829 830 /** 831 * Add a menu item for searching Settings, if there is an activity handling the action. 832 * 833 * @param menu the menu to add the menu item into 834 * @param context the context for checking whether there is an activity handling the action 835 */ prepareSearchMenuItem(@onNull Menu menu, @NonNull Context context)836 public static void prepareSearchMenuItem(@NonNull Menu menu, @NonNull Context context) { 837 Intent intent = new Intent(Settings.ACTION_APP_SEARCH_SETTINGS); 838 if (context.getPackageManager().resolveActivity(intent, 0) == null) { 839 return; 840 } 841 MenuItem searchItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, R.string.search_menu); 842 searchItem.setIcon(R.drawable.ic_search_24dp); 843 searchItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); 844 searchItem.setOnMenuItemClickListener(item -> { 845 try { 846 context.startActivity(intent); 847 } catch (ActivityNotFoundException e) { 848 Log.e(LOG_TAG, "Cannot start activity to search settings", e); 849 } 850 return true; 851 }); 852 } 853 854 /** 855 * Get badged app icon if necessary, similar as used in the Settings UI. 856 * 857 * @param context The context to use 858 * @param appInfo The app the icon belong to 859 * 860 * @return The icon to use 861 */ getBadgedIcon(@onNull Context context, @NonNull ApplicationInfo appInfo)862 public static @NonNull Drawable getBadgedIcon(@NonNull Context context, 863 @NonNull ApplicationInfo appInfo) { 864 UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid); 865 try (IconFactory iconFactory = IconFactory.obtain(context)) { 866 Bitmap iconBmp = iconFactory.createBadgedIconBitmap( 867 appInfo.loadUnbadgedIcon(context.getPackageManager()), user, false).icon; 868 return new BitmapDrawable(context.getResources(), iconBmp); 869 } 870 } 871 872 /** 873 * Get a string saying what apps with the given permission group can do. 874 * 875 * @param context The context to use 876 * @param groupName The name of the permission group 877 * @param description The description of the permission group 878 * 879 * @return a string saying what apps with the given permission group can do. 880 */ getPermissionGroupDescriptionString(@onNull Context context, @NonNull String groupName, @NonNull CharSequence description)881 public static @NonNull String getPermissionGroupDescriptionString(@NonNull Context context, 882 @NonNull String groupName, @NonNull CharSequence description) { 883 switch (groupName) { 884 case ACTIVITY_RECOGNITION: 885 return context.getString( 886 R.string.permission_description_summary_activity_recognition); 887 case CALENDAR: 888 return context.getString(R.string.permission_description_summary_calendar); 889 case CALL_LOG: 890 return context.getString(R.string.permission_description_summary_call_log); 891 case CAMERA: 892 return context.getString(R.string.permission_description_summary_camera); 893 case CONTACTS: 894 return context.getString(R.string.permission_description_summary_contacts); 895 case LOCATION: 896 return context.getString(R.string.permission_description_summary_location); 897 case MICROPHONE: 898 return context.getString(R.string.permission_description_summary_microphone); 899 case NEARBY_DEVICES: 900 return context.getString(R.string.permission_description_summary_nearby_devices); 901 case PHONE: 902 return context.getString(R.string.permission_description_summary_phone); 903 case READ_MEDIA_AURAL: 904 return context.getString(R.string.permission_description_summary_read_media_aural); 905 case READ_MEDIA_VISUAL: 906 return context.getString(R.string.permission_description_summary_read_media_visual); 907 case SENSORS: 908 return context.getString(R.string.permission_description_summary_sensors); 909 case SMS: 910 return context.getString(R.string.permission_description_summary_sms); 911 case STORAGE: 912 return context.getString(R.string.permission_description_summary_storage); 913 default: 914 return context.getString(R.string.permission_description_summary_generic, 915 description); 916 } 917 } 918 919 /** 920 * Whether the Location Access Check is enabled. 921 * 922 * @return {@code true} iff the Location Access Check is enabled. 923 */ isLocationAccessCheckEnabled()924 public static boolean isLocationAccessCheckEnabled() { 925 return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, 926 PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, true); 927 } 928 929 /** 930 * Whether we should show health permissions as platform permissions in the various 931 * permission controller UI. 932 */ 933 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake") isHealthPermissionUiEnabled()934 public static boolean isHealthPermissionUiEnabled() { 935 final long token = Binder.clearCallingIdentity(); 936 try { 937 return SdkLevel.isAtLeastU() 938 && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, 939 PROPERTY_HEALTH_PERMISSION_UI_ENABLED, true); 940 } finally { 941 Binder.restoreCallingIdentity(token); 942 } 943 } 944 945 /** 946 * Get a device protected storage based shared preferences. Avoid storing sensitive data in it. 947 * 948 * @param context the context to get the shared preferences 949 * @return a device protected storage based shared preferences 950 */ 951 @NonNull getDeviceProtectedSharedPreferences(@onNull Context context)952 public static SharedPreferences getDeviceProtectedSharedPreferences(@NonNull Context context) { 953 if (!context.isDeviceProtectedStorage()) { 954 context = context.createDeviceProtectedStorageContext(); 955 } 956 return context.getSharedPreferences(Constants.PREFERENCES_FILE, MODE_PRIVATE); 957 } 958 getOneTimePermissionsTimeout()959 public static long getOneTimePermissionsTimeout() { 960 return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS, 961 PROPERTY_ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS, ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS); 962 } 963 964 /** 965 * Returns the delay in milliseconds before revoking permissions at the end of a one-time 966 * permission session if all processes have been killed. 967 * If the session was triggered by a self-revocation, then revocation should happen 968 * immediately. For a regular one-time permission session, a grace period allows a quick 969 * app restart without losing the permission. 970 * @param isSelfRevoked If true, return the delay for a self-revocation session. Otherwise, 971 * return delay for a regular one-time permission session. 972 */ getOneTimePermissionsKilledDelay(boolean isSelfRevoked)973 public static long getOneTimePermissionsKilledDelay(boolean isSelfRevoked) { 974 if (isSelfRevoked) { 975 // For a self-revoked session, we revoke immediately when the process dies. 976 return 0; 977 } 978 return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS, 979 PROPERTY_ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS, 980 ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS); 981 } 982 983 /** 984 * Get context of the parent user of the profile group (i.e. usually the 'personal' profile, 985 * not the 'work' profile). 986 * 987 * @param context The context of a user of the profile user group. 988 * 989 * @return The context of the parent user 990 */ getParentUserContext(@onNull Context context)991 public static Context getParentUserContext(@NonNull Context context) { 992 UserHandle parentUser = getSystemServiceSafe(context, UserManager.class) 993 .getProfileParent(UserHandle.of(myUserId())); 994 995 if (parentUser == null) { 996 return context; 997 } 998 999 // In a multi profile environment perform all operations as the parent user of the 1000 // current profile 1001 try { 1002 return context.createPackageContextAsUser(context.getPackageName(), 0, 1003 parentUser); 1004 } catch (PackageManager.NameNotFoundException e) { 1005 // cannot happen 1006 throw new IllegalStateException("Could not switch to parent user " + parentUser, e); 1007 } 1008 } 1009 1010 /** 1011 * The resource id for the request message for a permission group 1012 * @param groupName Permission group name 1013 * @return The id or 0 if the permission group doesn't exist or have a message 1014 */ getRequest(String groupName)1015 public static int getRequest(String groupName) { 1016 return PERM_GROUP_REQUEST_RES.getOrDefault(groupName, 0); 1017 } 1018 1019 /** 1020 * The resource id for the request detail message for a permission group 1021 * @param groupName Permission group name 1022 * @return The id or 0 if the permission group doesn't exist or have a message 1023 */ getRequestDetail(String groupName)1024 public static int getRequestDetail(String groupName) { 1025 return PERM_GROUP_REQUEST_DETAIL_RES.getOrDefault(groupName, 0); 1026 } 1027 1028 /** 1029 * The resource id for the background request message for a permission group 1030 * @param groupName Permission group name 1031 * @return The id or 0 if the permission group doesn't exist or have a message 1032 */ getBackgroundRequest(String groupName)1033 public static int getBackgroundRequest(String groupName) { 1034 return PERM_GROUP_BACKGROUND_REQUEST_RES.getOrDefault(groupName, 0); 1035 } 1036 1037 /** 1038 * The resource id for the background request detail message for a permission group 1039 * @param groupName Permission group name 1040 * @return The id or 0 if the permission group doesn't exist or have a message 1041 */ getBackgroundRequestDetail(String groupName)1042 public static int getBackgroundRequestDetail(String groupName) { 1043 return PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES.getOrDefault(groupName, 0); 1044 } 1045 1046 /** 1047 * The resource id for the upgrade request message for a permission group 1048 * @param groupName Permission group name 1049 * @return The id or 0 if the permission group doesn't exist or have a message 1050 */ getUpgradeRequest(String groupName)1051 public static int getUpgradeRequest(String groupName) { 1052 return PERM_GROUP_UPGRADE_REQUEST_RES.getOrDefault(groupName, 0); 1053 } 1054 1055 /** 1056 * The resource id for the upgrade request detail message for a permission group 1057 * @param groupName Permission group name 1058 * @return The id or 0 if the permission group doesn't exist or have a message 1059 */ getUpgradeRequestDetail(String groupName)1060 public static int getUpgradeRequestDetail(String groupName) { 1061 return PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES.getOrDefault(groupName, 0); 1062 } 1063 1064 /** 1065 * Returns a random session ID value that's guaranteed to not be {@code INVALID_SESSION_ID}. 1066 * 1067 * @return A valid session ID. 1068 */ getValidSessionId()1069 public static long getValidSessionId() { 1070 long sessionId = INVALID_SESSION_ID; 1071 while (sessionId == INVALID_SESSION_ID) { 1072 sessionId = new Random().nextLong(); 1073 } 1074 return sessionId; 1075 } 1076 1077 /** 1078 * Retrieves an existing session ID from the given intent or generates a new one if none is 1079 * present. 1080 * 1081 * @return A valid session ID. 1082 */ getOrGenerateSessionId(Intent intent)1083 public static long getOrGenerateSessionId(Intent intent) { 1084 long sessionId = intent.getLongExtra(EXTRA_SESSION_ID, INVALID_SESSION_ID); 1085 if (sessionId == INVALID_SESSION_ID) { 1086 sessionId = getValidSessionId(); 1087 } 1088 return sessionId; 1089 } 1090 1091 /** 1092 * Gets the label of the Settings application 1093 * 1094 * @param pm The packageManager used to get the activity resolution 1095 * 1096 * @return The CharSequence title of the settings app 1097 */ 1098 @Nullable getSettingsLabelForNotifications(PackageManager pm)1099 public static CharSequence getSettingsLabelForNotifications(PackageManager pm) { 1100 // We pretend we're the Settings app sending the notification, so figure out its name. 1101 Intent openSettingsIntent = new Intent(Settings.ACTION_SETTINGS); 1102 ResolveInfo resolveInfo = pm.resolveActivity(openSettingsIntent, MATCH_SYSTEM_ONLY); 1103 if (resolveInfo == null) { 1104 return null; 1105 } 1106 return pm.getApplicationLabel(resolveInfo.activityInfo.applicationInfo); 1107 } 1108 1109 /** 1110 * Determines if a given user is disabled, or is a work profile. 1111 * @param user The user to check 1112 * @return true if the user is disabled, or the user is a work profile 1113 */ isUserDisabledOrWorkProfile(UserHandle user)1114 public static boolean isUserDisabledOrWorkProfile(UserHandle user) { 1115 Application app = PermissionControllerApplication.get(); 1116 UserManager userManager = app.getSystemService(UserManager.class); 1117 // In android TV, parental control accounts are managed profiles 1118 return !userManager.getEnabledProfiles().contains(user) 1119 || (userManager.isManagedProfile(user.getIdentifier()) 1120 && !DeviceUtils.isTelevision(app)); 1121 } 1122 1123 /** 1124 * Determines if a given user ID belongs to a managed profile user. 1125 * @param userId The user ID to check 1126 * @return true if the user is a managed profile 1127 */ isUserManagedProfile(int userId)1128 public static boolean isUserManagedProfile(int userId) { 1129 return PermissionControllerApplication.get() 1130 .getSystemService(UserManager.class) 1131 .isManagedProfile(userId); 1132 } 1133 1134 /** 1135 * Get all the exempted packages. 1136 */ getExemptedPackages(@onNull RoleManager roleManager)1137 public static Set<String> getExemptedPackages(@NonNull RoleManager roleManager) { 1138 Set<String> exemptedPackages = new HashSet<>(); 1139 1140 exemptedPackages.add(OS_PKG); 1141 for (int i = 0; i < EXEMPTED_ROLES.length; i++) { 1142 exemptedPackages.addAll(roleManager.getRoleHolders(EXEMPTED_ROLES[i])); 1143 } 1144 1145 return exemptedPackages; 1146 } 1147 1148 /** 1149 * Get the timestamp and lastAccessType for the summary text 1150 * in app permission groups and permission apps screens 1151 * @return Triple<String, Integer, String> with the first being the formatted time 1152 * the second being lastAccessType and the third being the formatted date. 1153 */ getPermissionLastAccessSummaryTimestamp( Long lastAccessTime, Context context, String groupName)1154 public static Triple<String, Integer, String> getPermissionLastAccessSummaryTimestamp( 1155 Long lastAccessTime, Context context, String groupName) { 1156 long midnightToday = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).toEpochSecond() 1157 * 1000L; 1158 long midnightYesterday = ZonedDateTime.now().minusDays(1).truncatedTo(ChronoUnit.DAYS) 1159 .toEpochSecond() * 1000L; 1160 long yesterdayAtThisTime = ZonedDateTime.now().minusDays(1).toEpochSecond() * 1000L; 1161 1162 boolean isLastAccessToday = lastAccessTime != null 1163 && midnightToday <= lastAccessTime; 1164 boolean isLastAccessWithinPast24h = lastAccessTime != null 1165 && yesterdayAtThisTime <= lastAccessTime; 1166 boolean isLastAccessTodayOrYesterday = lastAccessTime != null 1167 && midnightYesterday <= lastAccessTime; 1168 1169 String lastAccessTimeFormatted = ""; 1170 String lastAccessDateFormatted = ""; 1171 @AppPermsLastAccessType int lastAccessType = NOT_IN_LAST_7D; 1172 1173 if (lastAccessTime != null) { 1174 lastAccessTimeFormatted = DateFormat.getTimeFormat(context) 1175 .format(lastAccessTime); 1176 lastAccessDateFormatted = DateFormat.getDateFormat(context) 1177 .format(lastAccessTime); 1178 1179 if (!PermissionMapping.SENSOR_DATA_PERMISSIONS.contains(groupName)) { 1180 // For content providers we show either the last access is within 1181 // past 24 hours or past 7 days 1182 lastAccessType = isLastAccessWithinPast24h 1183 ? LAST_24H_CONTENT_PROVIDER : LAST_7D_CONTENT_PROVIDER; 1184 } else { 1185 // For sensor data permissions we show if the last access 1186 // is today, yesterday or older than yesterday 1187 lastAccessType = isLastAccessToday 1188 ? LAST_24H_SENSOR_TODAY : isLastAccessTodayOrYesterday 1189 ? LAST_24H_SENSOR_YESTERDAY : LAST_7D_SENSOR; 1190 } 1191 } 1192 1193 return new Triple<>(lastAccessTimeFormatted, lastAccessType, lastAccessDateFormatted); 1194 } 1195 1196 /** 1197 * Returns if the permission group is Camera or Microphone (status bar indicators). 1198 **/ isStatusBarIndicatorPermission(@onNull String permissionGroupName)1199 public static boolean isStatusBarIndicatorPermission(@NonNull String permissionGroupName) { 1200 return CAMERA.equals(permissionGroupName) || MICROPHONE.equals(permissionGroupName); 1201 } 1202 1203 /** 1204 * Navigate to notification settings for all apps 1205 * @param context The current Context 1206 */ navigateToNotificationSettings(@onNull Context context)1207 public static void navigateToNotificationSettings(@NonNull Context context) { 1208 Intent notificationIntent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS); 1209 context.startActivity(notificationIntent); 1210 } 1211 1212 /** 1213 * Navigate to notification settings for an app 1214 * @param context The current Context 1215 * @param packageName The package to navigate to 1216 * @param user Specifies the user of the package which should be navigated to. If null, the 1217 * current user is used. 1218 */ navigateToAppNotificationSettings(@onNull Context context, @NonNull String packageName, @NonNull UserHandle user)1219 public static void navigateToAppNotificationSettings(@NonNull Context context, 1220 @NonNull String packageName, @NonNull UserHandle user) { 1221 Intent notificationIntent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); 1222 notificationIntent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName); 1223 context.startActivityAsUser(notificationIntent, user); 1224 } 1225 1226 /** 1227 * Navigate to health connect settings for all apps 1228 * @param context The current Context 1229 */ navigateToHealthConnectSettings(@onNull Context context)1230 public static void navigateToHealthConnectSettings(@NonNull Context context) { 1231 Intent healthConnectIntent = new Intent(ACTION_MANAGE_HEALTH_PERMISSIONS); 1232 context.startActivity(healthConnectIntent); 1233 } 1234 1235 /** 1236 * Navigate to health connect settings for an app 1237 * @param context The current Context 1238 * @param packageName The package's health connect settings to navigate to 1239 */ navigateToAppHealthConnectSettings(@onNull Context context, @NonNull String packageName, @NonNull UserHandle user)1240 public static void navigateToAppHealthConnectSettings(@NonNull Context context, 1241 @NonNull String packageName, @NonNull UserHandle user) { 1242 Intent appHealthConnectIntent = new Intent(ACTION_MANAGE_HEALTH_PERMISSIONS); 1243 appHealthConnectIntent.putExtra(EXTRA_PACKAGE_NAME, packageName); 1244 appHealthConnectIntent.putExtra(Intent.EXTRA_USER, user); 1245 context.startActivity(appHealthConnectIntent); 1246 } 1247 1248 /** 1249 * Returns if a card should be shown if the sensor is blocked 1250 **/ shouldDisplayCardIfBlocked(@onNull String permissionGroupName)1251 public static boolean shouldDisplayCardIfBlocked(@NonNull String permissionGroupName) { 1252 return DeviceConfig.getBoolean( 1253 DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_WARNING_BANNER_DISPLAY_ENABLED, true) && ( 1254 CAMERA.equals(permissionGroupName) || MICROPHONE.equals(permissionGroupName) 1255 || LOCATION.equals(permissionGroupName)); 1256 } 1257 1258 /** 1259 * Returns the sensor code for a permission 1260 **/ 1261 @RequiresApi(Build.VERSION_CODES.S) getSensorCode(@onNull String permissionGroupName)1262 public static int getSensorCode(@NonNull String permissionGroupName) { 1263 return PERM_SENSOR_CODES.getOrDefault(permissionGroupName, -1); 1264 } 1265 1266 /** 1267 * Returns the blocked icon code for a permission 1268 **/ getBlockedIcon(@onNull String permissionGroupName)1269 public static int getBlockedIcon(@NonNull String permissionGroupName) { 1270 return PERM_BLOCKED_ICON.getOrDefault(permissionGroupName, -1); 1271 } 1272 1273 /** 1274 * Returns the blocked title code for a permission 1275 **/ getBlockedTitle(@onNull String permissionGroupName)1276 public static int getBlockedTitle(@NonNull String permissionGroupName) { 1277 return PERM_BLOCKED_TITLE.getOrDefault(permissionGroupName, -1); 1278 } 1279 1280 /** 1281 * Returns if the permission group has a background mode, even if the background mode is 1282 * introduced in a platform version after the one currently running 1283 **/ hasPermWithBackgroundModeCompat(LightAppPermGroup group)1284 public static boolean hasPermWithBackgroundModeCompat(LightAppPermGroup group) { 1285 if (SdkLevel.isAtLeastS()) { 1286 return group.getHasPermWithBackgroundMode(); 1287 } 1288 String groupName = group.getPermGroupName(); 1289 return group.getHasPermWithBackgroundMode() 1290 || Manifest.permission_group.CAMERA.equals(groupName) 1291 || Manifest.permission_group.MICROPHONE.equals(groupName); 1292 } 1293 1294 /** 1295 * Returns the appropriate enterprise string for the provided IDs 1296 */ 1297 @NonNull getEnterpriseString(@onNull Context context, @NonNull String updatableStringId, int defaultStringId, @NonNull Object... formatArgs)1298 public static String getEnterpriseString(@NonNull Context context, 1299 @NonNull String updatableStringId, int defaultStringId, @NonNull Object... formatArgs) { 1300 return SdkLevel.isAtLeastT() 1301 ? getUpdatableEnterpriseString( 1302 context, updatableStringId, defaultStringId, formatArgs) 1303 : context.getString(defaultStringId, formatArgs); 1304 } 1305 1306 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 1307 @NonNull getUpdatableEnterpriseString(@onNull Context context, @NonNull String updatableStringId, int defaultStringId, @NonNull Object... formatArgs)1308 private static String getUpdatableEnterpriseString(@NonNull Context context, 1309 @NonNull String updatableStringId, int defaultStringId, @NonNull Object... formatArgs) { 1310 DevicePolicyManager dpm = getSystemServiceSafe(context, DevicePolicyManager.class); 1311 return dpm.getResources().getString(updatableStringId, () -> context.getString( 1312 defaultStringId, formatArgs), formatArgs); 1313 } 1314 1315 /** 1316 * Get {@link PackageInfo} for this ComponentName. 1317 * 1318 * @param context The current Context 1319 * @param component component to get package info for 1320 * @return The package info 1321 * 1322 * @throws PackageManager.NameNotFoundException if package does not exist 1323 */ 1324 @NonNull getPackageInfoForComponentName(@onNull Context context, @NonNull ComponentName component)1325 public static PackageInfo getPackageInfoForComponentName(@NonNull Context context, 1326 @NonNull ComponentName component) throws PackageManager.NameNotFoundException { 1327 return context.getPackageManager().getPackageInfo(component.getPackageName(), 0); 1328 } 1329 1330 /** 1331 * Return the label to use for this application. 1332 * 1333 * @param context The current Context 1334 * @param applicationInfo The {@link ApplicationInfo} of the application to get the label of. 1335 * @return the label associated with this application, or its name if there is no label. 1336 */ 1337 @NonNull getApplicationLabel(@onNull Context context, @NonNull ApplicationInfo applicationInfo)1338 public static String getApplicationLabel(@NonNull Context context, 1339 @NonNull ApplicationInfo applicationInfo) { 1340 return context.getPackageManager().getApplicationLabel(applicationInfo).toString(); 1341 } 1342 } 1343