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.Intent.EXTRA_REASON; 39 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; 40 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; 41 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; 42 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED; 43 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED; 44 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; 45 import static android.health.connect.HealthConnectManager.ACTION_MANAGE_HEALTH_PERMISSIONS; 46 import static android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP; 47 import static android.os.UserHandle.myUserId; 48 49 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID; 50 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID; 51 52 import static java.lang.annotation.RetentionPolicy.SOURCE; 53 54 import android.Manifest; 55 import android.app.AppOpsManager; 56 import android.app.Application; 57 import android.app.admin.DevicePolicyManager; 58 import android.app.ecm.EnhancedConfirmationManager; 59 import android.app.role.RoleManager; 60 import android.content.ActivityNotFoundException; 61 import android.content.ComponentName; 62 import android.content.Context; 63 import android.content.Intent; 64 import android.content.SharedPreferences; 65 import android.content.pm.ApplicationInfo; 66 import android.content.pm.PackageInfo; 67 import android.content.pm.PackageItemInfo; 68 import android.content.pm.PackageManager; 69 import android.content.pm.PackageManager.NameNotFoundException; 70 import android.content.pm.PermissionGroupInfo; 71 import android.content.pm.PermissionInfo; 72 import android.content.pm.ResolveInfo; 73 import android.content.pm.UserProperties; 74 import android.content.res.Resources; 75 import android.content.res.Resources.Theme; 76 import android.graphics.Bitmap; 77 import android.graphics.drawable.BitmapDrawable; 78 import android.graphics.drawable.Drawable; 79 import android.hardware.SensorPrivacyManager; 80 import android.health.connect.HealthConnectManager; 81 import android.health.connect.HealthPermissions; 82 import android.os.Binder; 83 import android.os.Build; 84 import android.os.Parcelable; 85 import android.os.UserHandle; 86 import android.os.UserManager; 87 import android.permission.flags.Flags; 88 import android.provider.DeviceConfig; 89 import android.provider.Settings; 90 import android.text.Html; 91 import android.text.TextUtils; 92 import android.text.format.DateFormat; 93 import android.util.ArrayMap; 94 import android.util.Log; 95 import android.util.TypedValue; 96 import android.view.Menu; 97 import android.view.MenuItem; 98 99 import androidx.annotation.ChecksSdkIntAtLeast; 100 import androidx.annotation.ColorRes; 101 import androidx.annotation.IntDef; 102 import androidx.annotation.NonNull; 103 import androidx.annotation.Nullable; 104 import androidx.annotation.RequiresApi; 105 import androidx.annotation.StringRes; 106 import androidx.core.text.BidiFormatter; 107 import androidx.core.util.Preconditions; 108 109 import com.android.launcher3.icons.IconFactory; 110 import com.android.modules.utils.build.SdkLevel; 111 import com.android.permissioncontroller.Constants; 112 import com.android.permissioncontroller.DeviceUtils; 113 import com.android.permissioncontroller.PermissionControllerApplication; 114 import com.android.permissioncontroller.R; 115 import com.android.permissioncontroller.permission.model.AppPermissionGroup; 116 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup; 117 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo; 118 import com.android.settingslib.widget.SettingsThemeHelper; 119 120 import kotlin.Triple; 121 122 import java.lang.annotation.Retention; 123 import java.time.ZonedDateTime; 124 import java.time.temporal.ChronoUnit; 125 import java.util.ArrayList; 126 import java.util.Calendar; 127 import java.util.HashSet; 128 import java.util.List; 129 import java.util.Locale; 130 import java.util.Random; 131 import java.util.Set; 132 import java.util.function.Supplier; 133 134 public final class Utils { 135 136 @Retention(SOURCE) 137 @IntDef(value = {LAST_24H_SENSOR_TODAY, LAST_24H_SENSOR_YESTERDAY, 138 LAST_24H_CONTENT_PROVIDER, NOT_IN_LAST_7D}) 139 public @interface AppPermsLastAccessType {} 140 public static final int LAST_24H_SENSOR_TODAY = 1; 141 public static final int LAST_24H_SENSOR_YESTERDAY = 2; 142 public static final int LAST_24H_CONTENT_PROVIDER = 3; 143 public static final int LAST_7D_SENSOR = 4; 144 public static final int LAST_7D_CONTENT_PROVIDER = 5; 145 public static final int NOT_IN_LAST_7D = 6; 146 147 private static final String LOG_TAG = "Utils"; 148 149 public static final String OS_PKG = "android"; 150 151 public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f; 152 153 /** The time an app needs to be unused in order to be hibernated */ 154 public static final String PROPERTY_HIBERNATION_UNUSED_THRESHOLD_MILLIS = 155 "auto_revoke_unused_threshold_millis2"; 156 157 /** The frequency of running the job for hibernating apps */ 158 public static final String PROPERTY_HIBERNATION_CHECK_FREQUENCY_MILLIS = 159 "auto_revoke_check_frequency_millis"; 160 161 /** Whether hibernation targets apps that target a pre-S SDK */ 162 public static final String PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS = 163 "app_hibernation_targets_pre_s_apps"; 164 165 /** Whether or not app hibernation is enabled on the device **/ 166 public static final String PROPERTY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled"; 167 168 /** Whether the system exempt from hibernation is enabled on the device **/ 169 public static final String PROPERTY_SYSTEM_EXEMPT_HIBERNATION_ENABLED = 170 "system_exempt_hibernation_enabled"; 171 172 /** The timeout for one-time permissions */ 173 private static final String PROPERTY_ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS = 174 "one_time_permissions_timeout_millis"; 175 176 /** The delay before ending a one-time permission session when all processes are dead */ 177 private static final String PROPERTY_ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS = 178 "one_time_permissions_killed_delay_millis"; 179 180 /** Whether to show health permission in various permission controller UIs. */ 181 private static final String PROPERTY_HEALTH_PERMISSION_UI_ENABLED = 182 "health_permission_ui_enabled"; 183 184 185 /** How frequently to check permission event store to scrub old data */ 186 public static final String PROPERTY_PERMISSION_EVENTS_CHECK_OLD_FREQUENCY_MILLIS = 187 "permission_events_check_old_frequency_millis"; 188 189 /** 190 * Whether to store the exact time for permission changes. Only for use in tests and should 191 * not be modified in prod. 192 */ 193 public static final String PROPERTY_PERMISSION_CHANGES_STORE_EXACT_TIME = 194 "permission_changes_store_exact_time"; 195 196 /** The max amount of time permission data can stay in the storage before being scrubbed */ 197 public static final String PROPERTY_PERMISSION_DECISIONS_MAX_DATA_AGE_MILLIS = 198 "permission_decisions_max_data_age_millis"; 199 200 /** All permission whitelists. */ 201 public static final int FLAGS_PERMISSION_WHITELIST_ALL = 202 PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM 203 | PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE 204 | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER; 205 206 /** All permission restriction exemptions. */ 207 public static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT = 208 FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT 209 | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT 210 | FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; 211 212 /** 213 * The default length of the timeout for one-time permissions 214 */ 215 public static final long ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS = 1 * 60 * 1000; // 1 minute 216 217 /** 218 * The default length to wait before ending a one-time permission session after all processes 219 * are dead. 220 */ 221 public static final long ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS = 5 * 1000; 222 223 private static final ArrayMap<String, Integer> PERM_GROUP_REQUEST_RES; 224 private static final ArrayMap<String, Integer> PERM_GROUP_REQUEST_DEVICE_AWARE_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_DEVICE_AWARE_RES; 228 private static final ArrayMap<String, Integer> PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES; 229 private static final ArrayMap<String, Integer> PERM_GROUP_UPGRADE_REQUEST_RES; 230 private static final ArrayMap<String, Integer> PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES; 231 private static final ArrayMap<String, Integer> PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES; 232 233 /** Permission -> Sensor codes */ 234 private static final ArrayMap<String, Integer> PERM_SENSOR_CODES; 235 /** Permission -> Icon res id */ 236 private static final ArrayMap<String, Integer> PERM_BLOCKED_ICON; 237 /** Permission -> Title res id */ 238 private static final ArrayMap<String, Integer> PERM_BLOCKED_TITLE; 239 /** Permission -> Title res id */ 240 private static final ArrayMap<String, Integer> PERM_BLOCKED_TITLE_AUTOMOTIVE; 241 242 public static final int FLAGS_ALWAYS_USER_SENSITIVE = 243 FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED 244 | FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED; 245 246 private static final String SYSTEM_AMBIENT_AUDIO_INTELLIGENCE = 247 "android.app.role.SYSTEM_AMBIENT_AUDIO_INTELLIGENCE"; 248 private static final String SYSTEM_UI_INTELLIGENCE = 249 "android.app.role.SYSTEM_UI_INTELLIGENCE"; 250 private static final String SYSTEM_AUDIO_INTELLIGENCE = 251 "android.app.role.SYSTEM_AUDIO_INTELLIGENCE"; 252 private static final String SYSTEM_NOTIFICATION_INTELLIGENCE = 253 "android.app.role.SYSTEM_NOTIFICATION_INTELLIGENCE"; 254 private static final String SYSTEM_TEXT_INTELLIGENCE = 255 "android.app.role.SYSTEM_TEXT_INTELLIGENCE"; 256 private static final String SYSTEM_VISUAL_INTELLIGENCE = 257 "android.app.role.SYSTEM_VISUAL_INTELLIGENCE"; 258 259 // TODO: theianchen Using hardcoded values here as a WIP solution for now. 260 private static final String[] EXEMPTED_ROLES = { 261 SYSTEM_AMBIENT_AUDIO_INTELLIGENCE, 262 SYSTEM_UI_INTELLIGENCE, 263 SYSTEM_AUDIO_INTELLIGENCE, 264 SYSTEM_NOTIFICATION_INTELLIGENCE, 265 SYSTEM_TEXT_INTELLIGENCE, 266 SYSTEM_VISUAL_INTELLIGENCE, 267 }; 268 269 static { 270 271 PERM_GROUP_REQUEST_RES = new ArrayMap<>(); PERM_GROUP_REQUEST_RES.put(CONTACTS, R.string.permgrouprequest_contacts)272 PERM_GROUP_REQUEST_RES.put(CONTACTS, R.string.permgrouprequest_contacts); PERM_GROUP_REQUEST_RES.put(LOCATION, R.string.permgrouprequest_location)273 PERM_GROUP_REQUEST_RES.put(LOCATION, R.string.permgrouprequest_location); PERM_GROUP_REQUEST_RES.put(NEARBY_DEVICES, R.string.permgrouprequest_nearby_devices)274 PERM_GROUP_REQUEST_RES.put(NEARBY_DEVICES, R.string.permgrouprequest_nearby_devices); PERM_GROUP_REQUEST_RES.put(CALENDAR, R.string.permgrouprequest_calendar)275 PERM_GROUP_REQUEST_RES.put(CALENDAR, R.string.permgrouprequest_calendar); PERM_GROUP_REQUEST_RES.put(SMS, R.string.permgrouprequest_sms)276 PERM_GROUP_REQUEST_RES.put(SMS, R.string.permgrouprequest_sms); PERM_GROUP_REQUEST_RES.put(STORAGE, R.string.permgrouprequest_storage)277 PERM_GROUP_REQUEST_RES.put(STORAGE, R.string.permgrouprequest_storage); PERM_GROUP_REQUEST_RES.put(READ_MEDIA_AURAL, R.string.permgrouprequest_read_media_aural)278 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)279 PERM_GROUP_REQUEST_RES.put(READ_MEDIA_VISUAL, R.string.permgrouprequest_read_media_visual); PERM_GROUP_REQUEST_RES.put(MICROPHONE, R.string.permgrouprequest_microphone)280 PERM_GROUP_REQUEST_RES.put(MICROPHONE, R.string.permgrouprequest_microphone); 281 PERM_GROUP_REQUEST_RES put(ACTIVITY_RECOGNITION, R.string.permgrouprequest_activityRecognition)282 .put(ACTIVITY_RECOGNITION, R.string.permgrouprequest_activityRecognition); PERM_GROUP_REQUEST_RES.put(CAMERA, R.string.permgrouprequest_camera)283 PERM_GROUP_REQUEST_RES.put(CAMERA, R.string.permgrouprequest_camera); PERM_GROUP_REQUEST_RES.put(CALL_LOG, R.string.permgrouprequest_calllog)284 PERM_GROUP_REQUEST_RES.put(CALL_LOG, R.string.permgrouprequest_calllog); PERM_GROUP_REQUEST_RES.put(PHONE, R.string.permgrouprequest_phone)285 PERM_GROUP_REQUEST_RES.put(PHONE, R.string.permgrouprequest_phone); PERM_GROUP_REQUEST_RES.put(SENSORS, R.string.permgrouprequest_sensors)286 PERM_GROUP_REQUEST_RES.put(SENSORS, R.string.permgrouprequest_sensors); PERM_GROUP_REQUEST_RES.put(NOTIFICATIONS, R.string.permgrouprequest_notifications)287 PERM_GROUP_REQUEST_RES.put(NOTIFICATIONS, R.string.permgrouprequest_notifications); 288 289 PERM_GROUP_REQUEST_DEVICE_AWARE_RES = new ArrayMap<>(); PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(CONTACTS, R.string.permgrouprequest_device_aware_contacts)290 PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(CONTACTS, 291 R.string.permgrouprequest_device_aware_contacts); PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(LOCATION, R.string.permgrouprequest_device_aware_location)292 PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(LOCATION, 293 R.string.permgrouprequest_device_aware_location); PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(NEARBY_DEVICES, R.string.permgrouprequest_device_aware_nearby_devices)294 PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(NEARBY_DEVICES, 295 R.string.permgrouprequest_device_aware_nearby_devices); PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(CALENDAR, R.string.permgrouprequest_device_aware_calendar)296 PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(CALENDAR, 297 R.string.permgrouprequest_device_aware_calendar); PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(SMS, R.string.permgrouprequest_device_aware_sms)298 PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(SMS, R.string.permgrouprequest_device_aware_sms); PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(STORAGE, R.string.permgrouprequest_device_aware_storage)299 PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(STORAGE, 300 R.string.permgrouprequest_device_aware_storage); PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(READ_MEDIA_AURAL, R.string.permgrouprequest_device_aware_read_media_aural)301 PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(READ_MEDIA_AURAL, 302 R.string.permgrouprequest_device_aware_read_media_aural); PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(READ_MEDIA_VISUAL, R.string.permgrouprequest_device_aware_read_media_visual)303 PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(READ_MEDIA_VISUAL, 304 R.string.permgrouprequest_device_aware_read_media_visual); PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(MICROPHONE, R.string.permgrouprequest_device_aware_microphone)305 PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(MICROPHONE, 306 R.string.permgrouprequest_device_aware_microphone); 307 PERM_GROUP_REQUEST_DEVICE_AWARE_RES put(ACTIVITY_RECOGNITION, R.string.permgrouprequest_device_aware_activityRecognition)308 .put(ACTIVITY_RECOGNITION, 309 R.string.permgrouprequest_device_aware_activityRecognition); PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(CAMERA, R.string.permgrouprequest_device_aware_camera)310 PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(CAMERA, 311 R.string.permgrouprequest_device_aware_camera); PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(CALL_LOG, R.string.permgrouprequest_device_aware_calllog)312 PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(CALL_LOG, 313 R.string.permgrouprequest_device_aware_calllog); PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(PHONE, R.string.permgrouprequest_device_aware_phone)314 PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(PHONE, 315 R.string.permgrouprequest_device_aware_phone); PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(SENSORS, R.string.permgrouprequest_device_aware_sensors)316 PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(SENSORS, 317 R.string.permgrouprequest_device_aware_sensors); PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(NOTIFICATIONS, R.string.permgrouprequest_device_aware_notifications)318 PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(NOTIFICATIONS, 319 R.string.permgrouprequest_device_aware_notifications); 320 321 PERM_GROUP_REQUEST_DETAIL_RES = new ArrayMap<>(); PERM_GROUP_REQUEST_DETAIL_RES.put(LOCATION, R.string.permgrouprequestdetail_location)322 PERM_GROUP_REQUEST_DETAIL_RES.put(LOCATION, R.string.permgrouprequestdetail_location); PERM_GROUP_REQUEST_DETAIL_RES.put(MICROPHONE, R.string.permgrouprequestdetail_microphone)323 PERM_GROUP_REQUEST_DETAIL_RES.put(MICROPHONE, R.string.permgrouprequestdetail_microphone); PERM_GROUP_REQUEST_DETAIL_RES.put(CAMERA, R.string.permgrouprequestdetail_camera)324 PERM_GROUP_REQUEST_DETAIL_RES.put(CAMERA, R.string.permgrouprequestdetail_camera); 325 326 PERM_GROUP_BACKGROUND_REQUEST_RES = new ArrayMap<>(); 327 PERM_GROUP_BACKGROUND_REQUEST_RES put(LOCATION, R.string.permgroupbackgroundrequest_location)328 .put(LOCATION, R.string.permgroupbackgroundrequest_location); 329 PERM_GROUP_BACKGROUND_REQUEST_RES put(MICROPHONE, R.string.permgroupbackgroundrequest_microphone)330 .put(MICROPHONE, R.string.permgroupbackgroundrequest_microphone); 331 PERM_GROUP_BACKGROUND_REQUEST_RES put(CAMERA, R.string.permgroupbackgroundrequest_camera)332 .put(CAMERA, R.string.permgroupbackgroundrequest_camera); 333 PERM_GROUP_BACKGROUND_REQUEST_RES put(SENSORS, R.string.permgroupbackgroundrequest_sensors)334 .put(SENSORS, R.string.permgroupbackgroundrequest_sensors); 335 336 PERM_GROUP_BACKGROUND_REQUEST_DEVICE_AWARE_RES = new ArrayMap<>(); 337 PERM_GROUP_BACKGROUND_REQUEST_DEVICE_AWARE_RES put(LOCATION, R.string.permgroupbackgroundrequest_device_aware_location)338 .put(LOCATION, R.string.permgroupbackgroundrequest_device_aware_location); 339 PERM_GROUP_BACKGROUND_REQUEST_DEVICE_AWARE_RES put(MICROPHONE, R.string.permgroupbackgroundrequest_device_aware_microphone)340 .put(MICROPHONE, R.string.permgroupbackgroundrequest_device_aware_microphone); 341 PERM_GROUP_BACKGROUND_REQUEST_DEVICE_AWARE_RES put(CAMERA, R.string.permgroupbackgroundrequest_device_aware_camera)342 .put(CAMERA, R.string.permgroupbackgroundrequest_device_aware_camera); 343 PERM_GROUP_BACKGROUND_REQUEST_DEVICE_AWARE_RES put(SENSORS, R.string.permgroupbackgroundrequest_device_aware_sensors)344 .put(SENSORS, R.string.permgroupbackgroundrequest_device_aware_sensors); 345 346 PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES = new ArrayMap<>(); 347 PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES put(LOCATION, R.string.permgroupbackgroundrequestdetail_location)348 .put(LOCATION, R.string.permgroupbackgroundrequestdetail_location); 349 PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES put(MICROPHONE, R.string.permgroupbackgroundrequestdetail_microphone)350 .put(MICROPHONE, R.string.permgroupbackgroundrequestdetail_microphone); 351 PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES put(CAMERA, R.string.permgroupbackgroundrequestdetail_camera)352 .put(CAMERA, R.string.permgroupbackgroundrequestdetail_camera); 353 PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES put(SENSORS, R.string.permgroupbackgroundrequestdetail_sensors)354 .put(SENSORS, R.string.permgroupbackgroundrequestdetail_sensors); 355 356 PERM_GROUP_UPGRADE_REQUEST_RES = new ArrayMap<>(); PERM_GROUP_UPGRADE_REQUEST_RES.put(LOCATION, R.string.permgroupupgraderequest_location)357 PERM_GROUP_UPGRADE_REQUEST_RES.put(LOCATION, R.string.permgroupupgraderequest_location); PERM_GROUP_UPGRADE_REQUEST_RES.put(MICROPHONE, R.string.permgroupupgraderequest_microphone)358 PERM_GROUP_UPGRADE_REQUEST_RES.put(MICROPHONE, R.string.permgroupupgraderequest_microphone); PERM_GROUP_UPGRADE_REQUEST_RES.put(CAMERA, R.string.permgroupupgraderequest_camera)359 PERM_GROUP_UPGRADE_REQUEST_RES.put(CAMERA, R.string.permgroupupgraderequest_camera); PERM_GROUP_UPGRADE_REQUEST_RES.put(SENSORS, R.string.permgroupupgraderequest_sensors)360 PERM_GROUP_UPGRADE_REQUEST_RES.put(SENSORS, R.string.permgroupupgraderequest_sensors); 361 362 PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES = new ArrayMap<>(); PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.put(LOCATION, R.string.permgroupupgraderequest_device_aware_location)363 PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.put(LOCATION, 364 R.string.permgroupupgraderequest_device_aware_location); PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.put(MICROPHONE, R.string.permgroupupgraderequest_device_aware_microphone)365 PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.put(MICROPHONE, 366 R.string.permgroupupgraderequest_device_aware_microphone); PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.put(CAMERA, R.string.permgroupupgraderequest_device_aware_camera)367 PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.put(CAMERA, 368 R.string.permgroupupgraderequest_device_aware_camera); PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.put(SENSORS, R.string.permgroupupgraderequest_device_aware_sensors)369 PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.put(SENSORS, 370 R.string.permgroupupgraderequest_device_aware_sensors); 371 372 PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES = new ArrayMap<>(); 373 PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES put(LOCATION, R.string.permgroupupgraderequestdetail_location)374 .put(LOCATION, R.string.permgroupupgraderequestdetail_location); 375 PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES put(MICROPHONE, R.string.permgroupupgraderequestdetail_microphone)376 .put(MICROPHONE, R.string.permgroupupgraderequestdetail_microphone); 377 PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES put(CAMERA, R.string.permgroupupgraderequestdetail_camera)378 .put(CAMERA, R.string.permgroupupgraderequestdetail_camera); 379 PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES put(SENSORS, R.string.permgroupupgraderequestdetail_sensors)380 .put(SENSORS, R.string.permgroupupgraderequestdetail_sensors); 381 382 PERM_SENSOR_CODES = new ArrayMap<>(); 383 if (SdkLevel.isAtLeastS()) { PERM_SENSOR_CODES.put(CAMERA, SensorPrivacyManager.Sensors.CAMERA)384 PERM_SENSOR_CODES.put(CAMERA, SensorPrivacyManager.Sensors.CAMERA); PERM_SENSOR_CODES.put(MICROPHONE, SensorPrivacyManager.Sensors.MICROPHONE)385 PERM_SENSOR_CODES.put(MICROPHONE, SensorPrivacyManager.Sensors.MICROPHONE); 386 } 387 388 PERM_BLOCKED_ICON = new ArrayMap<>(); PERM_BLOCKED_ICON.put(CAMERA, R.drawable.ic_camera_blocked)389 PERM_BLOCKED_ICON.put(CAMERA, R.drawable.ic_camera_blocked); PERM_BLOCKED_ICON.put(MICROPHONE, R.drawable.ic_mic_blocked)390 PERM_BLOCKED_ICON.put(MICROPHONE, R.drawable.ic_mic_blocked); PERM_BLOCKED_ICON.put(LOCATION, R.drawable.ic_location_blocked)391 PERM_BLOCKED_ICON.put(LOCATION, R.drawable.ic_location_blocked); 392 393 PERM_BLOCKED_TITLE = new ArrayMap<>(); PERM_BLOCKED_TITLE.put(CAMERA, R.string.blocked_camera_title)394 PERM_BLOCKED_TITLE.put(CAMERA, R.string.blocked_camera_title); PERM_BLOCKED_TITLE.put(MICROPHONE, R.string.blocked_microphone_title)395 PERM_BLOCKED_TITLE.put(MICROPHONE, R.string.blocked_microphone_title); PERM_BLOCKED_TITLE.put(LOCATION, R.string.blocked_location_title)396 PERM_BLOCKED_TITLE.put(LOCATION, R.string.blocked_location_title); 397 398 PERM_BLOCKED_TITLE_AUTOMOTIVE = new ArrayMap<>(); PERM_BLOCKED_TITLE_AUTOMOTIVE.put(CAMERA, R.string.automotive_blocked_camera_title)399 PERM_BLOCKED_TITLE_AUTOMOTIVE.put(CAMERA, R.string.automotive_blocked_camera_title); PERM_BLOCKED_TITLE_AUTOMOTIVE.put(MICROPHONE, R.string.automotive_blocked_microphone_title)400 PERM_BLOCKED_TITLE_AUTOMOTIVE.put(MICROPHONE, R.string.automotive_blocked_microphone_title); PERM_BLOCKED_TITLE_AUTOMOTIVE.put(LOCATION, R.string.automotive_blocked_location_title)401 PERM_BLOCKED_TITLE_AUTOMOTIVE.put(LOCATION, R.string.automotive_blocked_location_title); 402 } 403 Utils()404 private Utils() { 405 /* do nothing - hide constructor */ 406 } 407 408 private static Object sLock = new Object(); 409 410 private static ArrayMap<UserHandle, Context> sUserContexts = new ArrayMap<>(); 411 412 /** 413 * Creates and caches a PackageContext for the requested user, or returns the previously cached 414 * value. The package of the PackageContext is the application's package. 415 * 416 * @param context The context of the currently running application 417 * @param user The desired user for the context 418 * 419 * @return The generated or cached Context for the requested user 420 * 421 * @throws RuntimeException If the app has no package name attached, which should never happen 422 */ getUserContext(Context context, UserHandle user)423 public static @NonNull Context getUserContext(Context context, UserHandle user) { 424 synchronized (sLock) { 425 if (!sUserContexts.containsKey(user)) { 426 sUserContexts.put(user, context.getApplicationContext() 427 .createContextAsUser(user, 0)); 428 } 429 return Preconditions.checkNotNull(sUserContexts.get(user)); 430 } 431 } 432 433 /** 434 * {@code @NonNull} version of {@link Context#getSystemService(Class)} 435 */ getSystemServiceSafe(@onNull Context context, Class<M> clazz)436 public static @NonNull <M> M getSystemServiceSafe(@NonNull Context context, Class<M> clazz) { 437 return Preconditions.checkNotNull(context.getSystemService(clazz), 438 "Could not resolve " + clazz.getSimpleName()); 439 } 440 441 /** 442 * {@code @NonNull} version of {@link Context#getSystemService(Class)} 443 */ getSystemServiceSafe(@onNull Context context, Class<M> clazz, @NonNull UserHandle user)444 public static @NonNull <M> M getSystemServiceSafe(@NonNull Context context, Class<M> clazz, 445 @NonNull UserHandle user) { 446 try { 447 return Preconditions.checkNotNull(context.createPackageContextAsUser( 448 context.getPackageName(), 0, user).getSystemService(clazz), 449 "Could not resolve " + clazz.getSimpleName()); 450 } catch (PackageManager.NameNotFoundException neverHappens) { 451 throw new IllegalStateException(); 452 } 453 } 454 455 /** 456 * {@code @NonNull} version of {@link Intent#getParcelableExtra(String)} 457 */ getParcelableExtraSafe(@onNull Intent intent, @NonNull String name)458 public static @NonNull <T extends Parcelable> T getParcelableExtraSafe(@NonNull Intent intent, 459 @NonNull String name) { 460 return Preconditions.checkNotNull(intent.getParcelableExtra(name), 461 "Could not get parcelable extra for " + name); 462 } 463 464 /** 465 * {@code @NonNull} version of {@link Intent#getStringExtra(String)} 466 */ getStringExtraSafe(@onNull Intent intent, @NonNull String name)467 public static @NonNull String getStringExtraSafe(@NonNull Intent intent, 468 @NonNull String name) { 469 return Preconditions.checkNotNull(intent.getStringExtra(name), 470 "Could not get string extra for " + name); 471 } 472 473 /** 474 * Returns true if a permission is dangerous, installed, and not removed 475 * @param permissionInfo The permission we wish to check 476 * @return If all of the conditions are met 477 */ isPermissionDangerousInstalledNotRemoved(PermissionInfo permissionInfo)478 public static boolean isPermissionDangerousInstalledNotRemoved(PermissionInfo permissionInfo) { 479 return permissionInfo != null 480 && permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS 481 && (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0 482 && (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) == 0; 483 } 484 485 /** 486 * Get the {@link PermissionInfo infos} for all permission infos belonging to a group. 487 * 488 * @param pm Package manager to use to resolve permission infos 489 * @param group the group 490 * 491 * @return The infos of permissions belonging to the group or an empty list if the group 492 * does not have runtime permissions 493 */ getPermissionInfosForGroup( @onNull PackageManager pm, @NonNull String group)494 public static @NonNull List<PermissionInfo> getPermissionInfosForGroup( 495 @NonNull PackageManager pm, @NonNull String group) 496 throws PackageManager.NameNotFoundException { 497 List<PermissionInfo> permissions = pm.queryPermissionsByGroup(group, 0); 498 permissions.addAll(PermissionMapping.getPlatformPermissionsOfGroup(pm, group)); 499 500 /* 501 * If the undefined group is requested, the package manager will return all platform 502 * permissions, since they are marked as Undefined in the manifest. Do not return these 503 * permissions. 504 */ 505 if (group.equals(Manifest.permission_group.UNDEFINED)) { 506 List<PermissionInfo> undefinedPerms = new ArrayList<>(); 507 for (PermissionInfo permissionInfo : permissions) { 508 String permGroup = 509 PermissionMapping.getGroupOfPlatformPermission(permissionInfo.name); 510 if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) { 511 undefinedPerms.add(permissionInfo); 512 } 513 } 514 return undefinedPerms; 515 } 516 517 return permissions; 518 } 519 520 /** 521 * Get the {@link PermissionInfo infos} for all runtime installed permission infos belonging to 522 * a group. 523 * 524 * @param pm Package manager to use to resolve permission infos 525 * @param group the group 526 * 527 * @return The infos of installed runtime permissions belonging to the group or an empty list 528 * if the group does not have runtime permissions 529 */ getInstalledRuntimePermissionInfosForGroup( @onNull PackageManager pm, @NonNull String group)530 public static @NonNull List<PermissionInfo> getInstalledRuntimePermissionInfosForGroup( 531 @NonNull PackageManager pm, @NonNull String group) 532 throws PackageManager.NameNotFoundException { 533 List<PermissionInfo> permissions = pm.queryPermissionsByGroup(group, 0); 534 permissions.addAll(PermissionMapping.getPlatformPermissionsOfGroup(pm, group)); 535 536 List<PermissionInfo> installedRuntime = new ArrayList<>(); 537 for (PermissionInfo permissionInfo: permissions) { 538 if (permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS 539 && (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0 540 && (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) == 0) { 541 installedRuntime.add(permissionInfo); 542 } 543 } 544 545 /* 546 * If the undefined group is requested, the package manager will return all platform 547 * permissions, since they are marked as Undefined in the manifest. Do not return these 548 * permissions. 549 */ 550 if (group.equals(Manifest.permission_group.UNDEFINED)) { 551 List<PermissionInfo> undefinedPerms = new ArrayList<>(); 552 for (PermissionInfo permissionInfo : installedRuntime) { 553 if (Flags.replaceBodySensorPermissionEnabled() 554 && (permissionInfo.name.equals(Manifest.permission.BODY_SENSORS) || 555 permissionInfo.name.equals(Manifest.permission.BODY_SENSORS_BACKGROUND))) { 556 continue; 557 } 558 559 String permGroup = 560 PermissionMapping.getGroupOfPlatformPermission(permissionInfo.name); 561 if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) { 562 undefinedPerms.add(permissionInfo); 563 } 564 } 565 return undefinedPerms; 566 } 567 568 return installedRuntime; 569 } 570 571 /** 572 * Get the {@link PackageItemInfo infos} for the given permission group. 573 * 574 * @param groupName the group 575 * @param context the {@code Context} to retrieve {@code PackageManager} 576 * 577 * @return The info of permission group or null if the group does not have runtime permissions. 578 */ getGroupInfo(@onNull String groupName, @NonNull Context context)579 public static @Nullable PackageItemInfo getGroupInfo(@NonNull String groupName, 580 @NonNull Context context) { 581 try { 582 return context.getPackageManager().getPermissionGroupInfo(groupName, 0); 583 } catch (NameNotFoundException e) { 584 /* ignore */ 585 } 586 try { 587 return context.getPackageManager().getPermissionInfo(groupName, 0); 588 } catch (NameNotFoundException e) { 589 /* ignore */ 590 } 591 return null; 592 } 593 594 /** 595 * Get the {@link PermissionInfo infos} for all permission infos belonging to a group. 596 * 597 * @param groupName the group 598 * @param context the {@code Context} to retrieve {@code PackageManager} 599 * 600 * @return The infos of permissions belonging to the group or null if the group does not have 601 * runtime permissions. 602 */ getGroupPermissionInfos(@onNull String groupName, @NonNull Context context)603 public static @Nullable List<PermissionInfo> getGroupPermissionInfos(@NonNull String groupName, 604 @NonNull Context context) { 605 try { 606 return Utils.getPermissionInfosForGroup(context.getPackageManager(), groupName); 607 } catch (NameNotFoundException e) { 608 /* ignore */ 609 } 610 try { 611 PermissionInfo permissionInfo = context.getPackageManager() 612 .getPermissionInfo(groupName, 0); 613 List<PermissionInfo> permissions = new ArrayList<>(); 614 permissions.add(permissionInfo); 615 return permissions; 616 } catch (NameNotFoundException e) { 617 /* ignore */ 618 } 619 return null; 620 } 621 622 /** 623 * Get the label for an application, truncating if it is too long. 624 * 625 * @param applicationInfo the {@link ApplicationInfo} of the application 626 * @param context the {@code Context} to retrieve {@code PackageManager} 627 * 628 * @return the label for the application 629 */ 630 @NonNull getAppLabel(@onNull ApplicationInfo applicationInfo, @NonNull Context context)631 public static String getAppLabel(@NonNull ApplicationInfo applicationInfo, 632 @NonNull Context context) { 633 return getAppLabel(applicationInfo, DEFAULT_MAX_LABEL_SIZE_PX, context); 634 } 635 636 /** 637 * Get the full label for an application without truncation. 638 * 639 * @param applicationInfo the {@link ApplicationInfo} of the application 640 * @param context the {@code Context} to retrieve {@code PackageManager} 641 * 642 * @return the label for the application 643 */ 644 @NonNull getFullAppLabel(@onNull ApplicationInfo applicationInfo, @NonNull Context context)645 public static String getFullAppLabel(@NonNull ApplicationInfo applicationInfo, 646 @NonNull Context context) { 647 return getAppLabel(applicationInfo, 0, context); 648 } 649 650 /** 651 * Get the label for an application with the ability to control truncating. 652 * 653 * @param applicationInfo the {@link ApplicationInfo} of the application 654 * @param ellipsizeDip see {@link TextUtils#makeSafeForPresentation}. 655 * @param context the {@code Context} to retrieve {@code PackageManager} 656 * 657 * @return the label for the application 658 */ 659 @NonNull getAppLabel(@onNull ApplicationInfo applicationInfo, float ellipsizeDip, @NonNull Context context)660 private static String getAppLabel(@NonNull ApplicationInfo applicationInfo, float ellipsizeDip, 661 @NonNull Context context) { 662 return BidiFormatter.getInstance().unicodeWrap(applicationInfo.loadSafeLabel( 663 context.getPackageManager(), ellipsizeDip, 664 TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE) 665 .toString()); 666 } 667 loadDrawable(PackageManager pm, String pkg, int resId)668 public static Drawable loadDrawable(PackageManager pm, String pkg, int resId) { 669 try { 670 return pm.getResourcesForApplication(pkg).getDrawable(resId, null); 671 } catch (Resources.NotFoundException | PackageManager.NameNotFoundException e) { 672 Log.d(LOG_TAG, "Couldn't get resource", e); 673 return null; 674 } 675 } 676 677 /** 678 * Should UI show this permission. 679 * 680 * <p>If the user cannot change the group, it should not be shown. 681 * 682 * @param group The group that might need to be shown to the user 683 * 684 * @return 685 */ shouldShowPermission(Context context, AppPermissionGroup group)686 public static boolean shouldShowPermission(Context context, AppPermissionGroup group) { 687 if (!group.isGrantingAllowed()) { 688 return false; 689 } 690 691 final boolean isPlatformPermission = group.getDeclaringPackage().equals(OS_PKG); 692 // Show legacy permissions only if the user chose that. 693 if (isPlatformPermission 694 && !PermissionMapping.isPlatformPermissionGroup(group.getName())) { 695 return false; 696 } 697 return true; 698 } 699 applyTint(Context context, Drawable icon, int attr)700 public static Drawable applyTint(Context context, Drawable icon, int attr) { 701 Theme theme = context.getTheme(); 702 TypedValue typedValue = new TypedValue(); 703 theme.resolveAttribute(attr, typedValue, true); 704 icon = icon.mutate(); 705 icon.setTint(context.getColor(typedValue.resourceId)); 706 return icon; 707 } 708 applyTint(Context context, int iconResId, int attr)709 public static Drawable applyTint(Context context, int iconResId, int attr) { 710 return applyTint(context, context.getDrawable(iconResId), attr); 711 } 712 713 /** 714 * Get the color resource id based on the attribute 715 * 716 * @return Resource id for the color 717 */ 718 @ColorRes getColorResId(Context context, int attr)719 public static int getColorResId(Context context, int attr) { 720 Theme theme = context.getTheme(); 721 TypedValue typedValue = new TypedValue(); 722 theme.resolveAttribute(attr, typedValue, true); 723 return typedValue.resourceId; 724 } 725 726 /** 727 * Is the group or background group user sensitive? 728 * 729 * @param group The group that might be user sensitive 730 * 731 * @return {@code true} if the group (or it's subgroup) is user sensitive. 732 */ isGroupOrBgGroupUserSensitive(AppPermissionGroup group)733 public static boolean isGroupOrBgGroupUserSensitive(AppPermissionGroup group) { 734 return group.isUserSensitive() || (group.getBackgroundPermissions() != null 735 && group.getBackgroundPermissions().isUserSensitive()); 736 } 737 areGroupPermissionsIndividuallyControlled(Context context, String group)738 public static boolean areGroupPermissionsIndividuallyControlled(Context context, String group) { 739 if (!context.getPackageManager().arePermissionsIndividuallyControlled()) { 740 return false; 741 } 742 return Manifest.permission_group.SMS.equals(group) 743 || Manifest.permission_group.PHONE.equals(group) 744 || Manifest.permission_group.CONTACTS.equals(group) 745 || Manifest.permission_group.CALL_LOG.equals(group); 746 } 747 isPermissionIndividuallyControlled(Context context, String permission)748 public static boolean isPermissionIndividuallyControlled(Context context, String permission) { 749 if (!context.getPackageManager().arePermissionsIndividuallyControlled()) { 750 return false; 751 } 752 return Manifest.permission.READ_CONTACTS.equals(permission) 753 || Manifest.permission.WRITE_CONTACTS.equals(permission) 754 || Manifest.permission.SEND_SMS.equals(permission) 755 || Manifest.permission.RECEIVE_SMS.equals(permission) 756 || Manifest.permission.READ_SMS.equals(permission) 757 || Manifest.permission.RECEIVE_MMS.equals(permission) 758 || Manifest.permission.CALL_PHONE.equals(permission) 759 || Manifest.permission.READ_CALL_LOG.equals(permission) 760 || Manifest.permission.WRITE_CALL_LOG.equals(permission); 761 } 762 763 /** 764 * Get the message shown to grant a permission group to an app. 765 * 766 * @param appLabel The label of the app 767 * @param packageName The package name of the app 768 * @param groupName The name of the permission group 769 * @param context A context to resolve resources 770 * @param requestRes The resource id of the grant request message 771 * @return The formatted message to be used as title when granting permissions 772 */ 773 @NonNull getRequestMessage( @onNull String appLabel, @NonNull String packageName, @NonNull String groupName, @NonNull Context context, @StringRes int requestRes)774 public static CharSequence getRequestMessage( 775 @NonNull String appLabel, 776 @NonNull String packageName, 777 @NonNull String groupName, 778 @NonNull Context context, 779 @StringRes int requestRes) { 780 String escapedAppLabel = Html.escapeHtml(appLabel); 781 782 boolean isIsolatedStorage; 783 try { 784 isIsolatedStorage = !isNonIsolatedStorage(context, packageName); 785 } catch (NameNotFoundException e) { 786 isIsolatedStorage = false; 787 } 788 if (groupName.equals(STORAGE) && isIsolatedStorage) { 789 return Html.fromHtml( 790 String.format( 791 context.getResources().getConfiguration().getLocales().get(0), 792 context.getString(R.string.permgrouprequest_storage_isolated), 793 escapedAppLabel), 794 0); 795 } else if (requestRes != 0) { 796 return Html.fromHtml(context.getResources().getString(requestRes, escapedAppLabel), 0); 797 } 798 799 return Html.fromHtml( 800 context.getString( 801 R.string.permission_warning_template, 802 escapedAppLabel, 803 loadGroupDescription(context, groupName, context.getPackageManager())), 804 0); 805 } 806 807 /** 808 * Get the message shown to grant a permission group to an app. 809 * 810 * @param appLabel The label of the app 811 * @param packageName The package name of the app 812 * @param groupName The name of the permission group 813 * @param context A context to resolve resources 814 * @param requestRes The resource id of the grant request message 815 * @return The formatted message to be used as title when granting permissions 816 */ 817 @NonNull getRequestMessage( @onNull String appLabel, @NonNull String packageName, @NonNull String groupName, @NonNull String deviceLabel, @NonNull Context context, Boolean isDeviceAwareMessage, @StringRes int requestRes)818 public static CharSequence getRequestMessage( 819 @NonNull String appLabel, 820 @NonNull String packageName, 821 @NonNull String groupName, 822 @NonNull String deviceLabel, 823 @NonNull Context context, 824 Boolean isDeviceAwareMessage, 825 @StringRes int requestRes) { 826 if (!isDeviceAwareMessage) { 827 return getRequestMessage(appLabel, packageName, groupName, context, requestRes); 828 } 829 String escapedAppLabel = Html.escapeHtml(appLabel); 830 831 boolean isIsolatedStorage; 832 try { 833 isIsolatedStorage = !isNonIsolatedStorage(context, packageName); 834 } catch (NameNotFoundException e) { 835 isIsolatedStorage = false; 836 } 837 if (groupName.equals(STORAGE) && isIsolatedStorage) { 838 String escapedDeviceLabel = Html.escapeHtml(deviceLabel); 839 return Html.fromHtml( 840 String.format( 841 context.getResources().getConfiguration().getLocales().get(0), 842 context.getString( 843 R.string.permgrouprequest_device_aware_storage_isolated), 844 escapedAppLabel, 845 escapedDeviceLabel), 846 0); 847 848 } else if (requestRes != 0) { 849 String escapedDeviceLabel = Html.escapeHtml(deviceLabel); 850 return Html.fromHtml(context.getResources().getString(requestRes, escapedAppLabel, 851 escapedDeviceLabel), 0); 852 } 853 854 return Html.fromHtml( 855 context.getString( 856 R.string.permission_warning_template, 857 escapedAppLabel, 858 loadGroupDescription(context, groupName, context.getPackageManager())), 859 0); 860 } 861 loadGroupDescription(Context context, String groupName, @NonNull PackageManager packageManager)862 private static CharSequence loadGroupDescription(Context context, String groupName, 863 @NonNull PackageManager packageManager) { 864 PackageItemInfo groupInfo = getGroupInfo(groupName, context); 865 CharSequence description = null; 866 if (groupInfo instanceof PermissionGroupInfo) { 867 description = ((PermissionGroupInfo) groupInfo).loadDescription(packageManager); 868 } else if (groupInfo instanceof PermissionInfo) { 869 description = ((PermissionInfo) groupInfo).loadDescription(packageManager); 870 } 871 872 if (description == null || description.length() <= 0) { 873 description = context.getString(R.string.default_permission_description); 874 } 875 876 return description; 877 } 878 879 /** 880 * Whether or not the given package has non-isolated storage permissions 881 * @param context The current context 882 * @param packageName The package name to check 883 * @return True if the package has access to non-isolated storage, false otherwise 884 * @throws NameNotFoundException 885 */ isNonIsolatedStorage(@onNull Context context, @NonNull String packageName)886 public static boolean isNonIsolatedStorage(@NonNull Context context, 887 @NonNull String packageName) throws NameNotFoundException { 888 PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0); 889 AppOpsManager manager = context.getSystemService(AppOpsManager.class); 890 891 892 return packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.P 893 || (packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.R 894 && manager.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE, 895 packageInfo.applicationInfo.uid, packageInfo.packageName) == MODE_ALLOWED); 896 } 897 898 /** 899 * Gets whether the STORAGE group should be hidden from the UI for this package. This is true 900 * when the platform is T+, and the package has legacy storage access (i.e., either the package 901 * has a targetSdk less than Q, or has a targetSdk equal to Q and has OPSTR_LEGACY_STORAGE). 902 * 903 * TODO jaysullivan: This is always calling AppOpsManager; not taking advantage of LiveData 904 * 905 * @param pkg The package to check 906 */ shouldShowStorage(LightPackageInfo pkg)907 public static boolean shouldShowStorage(LightPackageInfo pkg) { 908 if (!SdkLevel.isAtLeastT()) { 909 return true; 910 } 911 int targetSdkVersion = pkg.getTargetSdkVersion(); 912 PermissionControllerApplication app = PermissionControllerApplication.get(); 913 Context context = Utils.getUserContext(app, UserHandle.getUserHandleForUid(pkg.getUid())); 914 AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); 915 if (appOpsManager == null) { 916 return true; 917 } 918 919 return targetSdkVersion < Build.VERSION_CODES.Q 920 || (targetSdkVersion == Build.VERSION_CODES.Q 921 && appOpsManager.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE, pkg.getUid(), 922 pkg.getPackageName()) == MODE_ALLOWED); 923 } 924 925 /** 926 * Build a string representing the given time if it happened on the current day and the date 927 * otherwise. 928 * 929 * @param context the context. 930 * @param lastAccessTime the time in milliseconds. 931 * 932 * @return a string representing the time or date of the given time or null if the time is 0. 933 */ getAbsoluteTimeString(@onNull Context context, long lastAccessTime)934 public static @Nullable String getAbsoluteTimeString(@NonNull Context context, 935 long lastAccessTime) { 936 if (lastAccessTime == 0) { 937 return null; 938 } 939 if (isToday(lastAccessTime)) { 940 return DateFormat.getTimeFormat(context).format(lastAccessTime); 941 } else { 942 return DateFormat.getMediumDateFormat(context).format(lastAccessTime); 943 } 944 } 945 946 /** 947 * Check whether the given time (in milliseconds) is in the current day. 948 * 949 * @param time the time in milliseconds 950 * 951 * @return whether the given time is in the current day. 952 */ isToday(long time)953 private static boolean isToday(long time) { 954 Calendar today = Calendar.getInstance(Locale.getDefault()); 955 today.setTimeInMillis(System.currentTimeMillis()); 956 today.set(Calendar.HOUR_OF_DAY, 0); 957 today.set(Calendar.MINUTE, 0); 958 today.set(Calendar.SECOND, 0); 959 today.set(Calendar.MILLISECOND, 0); 960 961 Calendar date = Calendar.getInstance(Locale.getDefault()); 962 date.setTimeInMillis(time); 963 return !date.before(today); 964 } 965 966 /** 967 * Add a menu item for searching Settings, if there is an activity handling the action. 968 * 969 * @param menu the menu to add the menu item into 970 * @param context the context for checking whether there is an activity handling the action 971 */ prepareSearchMenuItem(@onNull Menu menu, @NonNull Context context)972 public static void prepareSearchMenuItem(@NonNull Menu menu, @NonNull Context context) { 973 Intent intent = new Intent(Settings.ACTION_APP_SEARCH_SETTINGS); 974 if (context.getPackageManager().resolveActivity(intent, 0) == null) { 975 return; 976 } 977 MenuItem searchItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, 978 com.android.settingslib.search.widget.R.string.search_menu); 979 searchItem.setIcon(com.android.settingslib.search.widget.R.drawable.ic_search_24dp); 980 searchItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); 981 searchItem.setOnMenuItemClickListener(item -> { 982 try { 983 context.startActivity(intent); 984 } catch (ActivityNotFoundException e) { 985 Log.e(LOG_TAG, "Cannot start activity to search settings", e); 986 } 987 return true; 988 }); 989 } 990 991 /** 992 * Get badged app icon if necessary, similar as used in the Settings UI. 993 * 994 * @param context The context to use 995 * @param appInfo The app the icon belong to 996 * 997 * @return The icon to use 998 */ getBadgedIcon(@onNull Context context, @NonNull ApplicationInfo appInfo)999 public static @NonNull Drawable getBadgedIcon(@NonNull Context context, 1000 @NonNull ApplicationInfo appInfo) { 1001 UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid); 1002 try (IconFactory iconFactory = IconFactory.obtain(context)) { 1003 Bitmap iconBmp = iconFactory.createBadgedIconBitmap( 1004 appInfo.loadUnbadgedIcon(context.getPackageManager()), user, false).icon; 1005 return new BitmapDrawable(context.getResources(), iconBmp); 1006 } 1007 } 1008 1009 /** 1010 * Get a string saying what apps with the given permission group can do. 1011 * 1012 * @param context The context to use 1013 * @param groupName The name of the permission group 1014 * @param description The description of the permission group 1015 * 1016 * @return a string saying what apps with the given permission group can do. 1017 */ getPermissionGroupDescriptionString(@onNull Context context, @NonNull String groupName, @NonNull CharSequence description)1018 public static @NonNull String getPermissionGroupDescriptionString(@NonNull Context context, 1019 @NonNull String groupName, @NonNull CharSequence description) { 1020 switch (groupName) { 1021 case ACTIVITY_RECOGNITION: 1022 return context.getString( 1023 R.string.permission_description_summary_activity_recognition); 1024 case CALENDAR: 1025 return context.getString(R.string.permission_description_summary_calendar); 1026 case CALL_LOG: 1027 return context.getString(R.string.permission_description_summary_call_log); 1028 case CAMERA: 1029 return context.getString(R.string.permission_description_summary_camera); 1030 case CONTACTS: 1031 return context.getString(R.string.permission_description_summary_contacts); 1032 case LOCATION: 1033 return context.getString(R.string.permission_description_summary_location); 1034 case MICROPHONE: 1035 return context.getString(R.string.permission_description_summary_microphone); 1036 case NEARBY_DEVICES: 1037 return context.getString(R.string.permission_description_summary_nearby_devices); 1038 case PHONE: 1039 return context.getString(R.string.permission_description_summary_phone); 1040 case READ_MEDIA_AURAL: 1041 return context.getString(R.string.permission_description_summary_read_media_aural); 1042 case READ_MEDIA_VISUAL: 1043 return context.getString(R.string.permission_description_summary_read_media_visual); 1044 case SENSORS: 1045 return context.getString(R.string.permission_description_summary_sensors); 1046 case SMS: 1047 return context.getString(R.string.permission_description_summary_sms); 1048 case STORAGE: 1049 return context.getString(R.string.permission_description_summary_storage); 1050 default: 1051 return context.getString(R.string.permission_description_summary_generic, 1052 description); 1053 } 1054 } 1055 1056 /** 1057 * Whether we should show health permissions as platform permissions in the various 1058 * permission controller UI. 1059 */ 1060 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake") isHealthPermissionUiEnabled()1061 public static boolean isHealthPermissionUiEnabled() { 1062 final long token = Binder.clearCallingIdentity(); 1063 try { 1064 return SdkLevel.isAtLeastU() 1065 && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, 1066 PROPERTY_HEALTH_PERMISSION_UI_ENABLED, true); 1067 } finally { 1068 Binder.restoreCallingIdentity(token); 1069 } 1070 } 1071 1072 /** 1073 * Whether Expressive Design is enabled on this device. 1074 */ isExpressiveDesignEnabled(@onNull Context context)1075 public static boolean isExpressiveDesignEnabled(@NonNull Context context) { 1076 return SdkLevel.isAtLeastB() && DeviceUtils.isHandheld() 1077 && SettingsThemeHelper.isExpressiveTheme(context); 1078 } 1079 1080 /** 1081 * Returns true if the group name passed is that of the Platform health group. 1082 * @param permGroupName name of the group that needs to be checked. 1083 */ 1084 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) isHealthPermissionGroup(String permGroupName)1085 public static Boolean isHealthPermissionGroup(String permGroupName) { 1086 return SdkLevel.isAtLeastU() && HEALTH_PERMISSION_GROUP.equals(permGroupName); 1087 } 1088 1089 /** 1090 * Return whether health permission setting entry should be shown or not 1091 * 1092 * Should not show Health permissions preference if the package doesn't handle 1093 * VIEW_PERMISSION_USAGE_INTENT. 1094 * 1095 * Will show if above is true AND permission is already granted. 1096 * 1097 * @param packageInfo the {@link PackageInfo} app which uses the permission 1098 * @param permGroupName the health permission group name to show 1099 * @return {@code TRUE} iff health permission should be shown 1100 */ 1101 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) shouldShowHealthPermission(LightPackageInfo packageInfo, String permGroupName)1102 public static Boolean shouldShowHealthPermission(LightPackageInfo packageInfo, 1103 String permGroupName) { 1104 if (!isHealthPermissionGroup(permGroupName)) { 1105 return false; 1106 } 1107 1108 PermissionControllerApplication app = PermissionControllerApplication.get(); 1109 PackageManager pm = app.getPackageManager(); 1110 Context context = getUserContext(app, UserHandle.getUserHandleForUid(packageInfo.getUid())); 1111 1112 List<PermissionInfo> permissions = new ArrayList<>(); 1113 try { 1114 permissions.addAll(getPermissionInfosForGroup(pm, permGroupName)); 1115 } catch (NameNotFoundException e) { 1116 Log.e(LOG_TAG, "No permissions found for permission group " + permGroupName); 1117 return false; 1118 } 1119 1120 // Only show Fitness&Wellness chip on Wear if the app is requesting system permissions. 1121 if (Flags.replaceBodySensorPermissionEnabled() 1122 && pm.hasSystemFeature(PackageManager.FEATURE_WATCH)) { 1123 Set<String> requestedPermissions = new HashSet<>(packageInfo.getRequestedPermissions()); 1124 for (PermissionInfo permission : permissions) { 1125 if (!requestedPermissions.contains(permission.name)) { 1126 continue; 1127 } 1128 String appOpStr = AppOpsManager.permissionToOp(permission.name); 1129 if (appOpStr != null 1130 && !appOpStr.equals(AppOpsManager.OPSTR_READ_WRITE_HEALTH_DATA)) { 1131 // Found system health permission. Show the chip. 1132 return true; 1133 } 1134 } 1135 // No valid system permissions are requested. 1136 return false; 1137 } 1138 1139 // Check in permission is already granted as we should not hide it in the UX at that point. 1140 List<String> grantedPermissions = packageInfo.getGrantedPermissions(); 1141 for (PermissionInfo permission : permissions) { 1142 boolean isCurrentlyGranted = grantedPermissions.contains(permission.name); 1143 if (isCurrentlyGranted) { 1144 Log.d( 1145 LOG_TAG, 1146 "At least one Health permission group permission is granted, " 1147 + "show permission group entry"); 1148 return true; 1149 } 1150 } 1151 1152 // When none health permission is granted, exempt health permission view usage intent filter 1153 // if all the requested health permissions are from permission splits. 1154 if (isRequestFromSplitHealthPermission(packageInfo)) { 1155 return true; 1156 } 1157 1158 Intent viewUsageIntent = new Intent(Intent.ACTION_VIEW_PERMISSION_USAGE); 1159 viewUsageIntent.addCategory(HealthConnectManager.CATEGORY_HEALTH_PERMISSIONS); 1160 viewUsageIntent.setPackage(packageInfo.getPackageName()); 1161 1162 ResolveInfo resolveInfo = pm.resolveActivity(viewUsageIntent, PackageManager.MATCH_ALL); 1163 if (resolveInfo == null) { 1164 Log.e(LOG_TAG, "Package that asks for Health permission must also handle " 1165 + "VIEW_PERMISSION_USAGE_INTENT."); 1166 return false; 1167 } 1168 return true; 1169 } 1170 1171 /** 1172 * Returns true if the request is being made as the result of a split health permission from 1173 * BODY_SENSORS call. 1174 */ 1175 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) isRequestFromSplitHealthPermission(LightPackageInfo packageInfo)1176 private static boolean isRequestFromSplitHealthPermission(LightPackageInfo packageInfo) { 1177 // Sdk check to make sure HealthConnectManager.isHealthPermission() is supported. 1178 if (!SdkLevel.isAtLeastU() || !Flags.replaceBodySensorPermissionEnabled()) { 1179 return false; 1180 } 1181 1182 PermissionControllerApplication app = PermissionControllerApplication.get(); 1183 PackageManager pm = app.getPackageManager(); 1184 String packageName = packageInfo.getPackageName(); 1185 UserHandle user = UserHandle.getUserHandleForUid(packageInfo.getUid()); 1186 Context context = Utils.getUserContext(app, user); 1187 1188 List<String> requestedHealthPermissions = new ArrayList<>(); 1189 for (String permission : packageInfo.getRequestedPermissions()) { 1190 if (HealthConnectManager.isHealthPermission(context, permission)) { 1191 requestedHealthPermissions.add(permission); 1192 } 1193 } 1194 1195 if (!isValidSplitHealthPermissions(requestedHealthPermissions)) { 1196 return false; 1197 } 1198 1199 int targetSdk = packageInfo.getTargetSdkVersion(); 1200 for (String perm : requestedHealthPermissions) { 1201 if (!isFromSplitPermission(pm.getPermissionFlags(perm, packageName, user), targetSdk)) { 1202 return false; 1203 } 1204 } 1205 return true; 1206 } 1207 isValidSplitHealthPermissions(List<String> permissions)1208 private static boolean isValidSplitHealthPermissions(List<String> permissions) { 1209 return (permissions.size() == 1 && permissions.contains(HealthPermissions.READ_HEART_RATE)) 1210 || (permissions.size() == 2 && permissions.contains(HealthPermissions.READ_HEART_RATE) 1211 && permissions.contains(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND)); 1212 } 1213 isFromSplitPermission(int permissionFlag, int targetSdk)1214 private static boolean isFromSplitPermission(int permissionFlag, int targetSdk) { 1215 return (targetSdk >= Build.VERSION_CODES.M) 1216 ? (permissionFlag & PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0 1217 : (permissionFlag & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0; 1218 } 1219 1220 /** 1221 * Get a device protected storage based shared preferences. Avoid storing sensitive data in it. 1222 * 1223 * @param context the context to get the shared preferences 1224 * @return a device protected storage based shared preferences 1225 */ 1226 @NonNull getDeviceProtectedSharedPreferences(@onNull Context context)1227 public static SharedPreferences getDeviceProtectedSharedPreferences(@NonNull Context context) { 1228 if (!context.isDeviceProtectedStorage()) { 1229 context = context.createDeviceProtectedStorageContext(); 1230 } 1231 return context.getSharedPreferences(Constants.PREFERENCES_FILE, MODE_PRIVATE); 1232 } 1233 getOneTimePermissionsTimeout()1234 public static long getOneTimePermissionsTimeout() { 1235 return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS, 1236 PROPERTY_ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS, ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS); 1237 } 1238 1239 /** 1240 * Returns the delay in milliseconds before revoking permissions at the end of a one-time 1241 * permission session if all processes have been killed. 1242 * If the session was triggered by a self-revocation, then revocation should happen 1243 * immediately. For a regular one-time permission session, a grace period allows a quick 1244 * app restart without losing the permission. 1245 * @param isSelfRevoked If true, return the delay for a self-revocation session. Otherwise, 1246 * return delay for a regular one-time permission session. 1247 */ getOneTimePermissionsKilledDelay(boolean isSelfRevoked)1248 public static long getOneTimePermissionsKilledDelay(boolean isSelfRevoked) { 1249 if (isSelfRevoked) { 1250 // For a self-revoked session, we revoke immediately when the process dies. 1251 return 0; 1252 } 1253 return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS, 1254 PROPERTY_ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS, 1255 ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS); 1256 } 1257 1258 /** 1259 * Get context of the parent user of the profile group (i.e. usually the 'personal' profile, 1260 * not the 'work' profile). 1261 * 1262 * @param context The context of a user of the profile user group. 1263 * 1264 * @return The context of the parent user 1265 */ getParentUserContext(@onNull Context context)1266 public static Context getParentUserContext(@NonNull Context context) { 1267 UserHandle parentUser = getSystemServiceSafe(context, UserManager.class) 1268 .getProfileParent(UserHandle.of(myUserId())); 1269 1270 if (parentUser == null) { 1271 return context; 1272 } 1273 1274 // In a multi profile environment perform all operations as the parent user of the 1275 // current profile 1276 try { 1277 return context.createPackageContextAsUser(context.getPackageName(), 0, 1278 parentUser); 1279 } catch (PackageManager.NameNotFoundException e) { 1280 // cannot happen 1281 throw new IllegalStateException("Could not switch to parent user " + parentUser, e); 1282 } 1283 } 1284 1285 /** 1286 * The resource id for the request message for a permission group 1287 * @param groupName Permission group name 1288 * @return The id or 0 if the permission group doesn't exist or have a message 1289 */ getRequest(String groupName)1290 public static int getRequest(String groupName) { 1291 return getRequest(groupName, false); 1292 } 1293 1294 /** 1295 * The resource id for the request message for a permission group for a specific device 1296 * 1297 * @param groupName Permission group name 1298 * @return The id or 0 if the permission group doesn't exist or have a message 1299 */ getRequest(String groupName, Boolean isDeviceAwareMessage)1300 public static int getRequest(String groupName, Boolean isDeviceAwareMessage) { 1301 if (isDeviceAwareMessage) { 1302 return PERM_GROUP_REQUEST_DEVICE_AWARE_RES.getOrDefault(groupName, 0); 1303 } else { 1304 return PERM_GROUP_REQUEST_RES.getOrDefault(groupName, 0); 1305 } 1306 } 1307 1308 /** 1309 * The resource id for the request detail message for a permission group 1310 * @param groupName Permission group name 1311 * @return The id or 0 if the permission group doesn't exist or have a message 1312 */ getRequestDetail(String groupName)1313 public static int getRequestDetail(String groupName) { 1314 return PERM_GROUP_REQUEST_DETAIL_RES.getOrDefault(groupName, 0); 1315 } 1316 1317 /** 1318 * The resource id for the background request message for a permission group 1319 * @param groupName Permission group name 1320 * @return The id or 0 if the permission group doesn't exist or have a message 1321 */ getBackgroundRequest(String groupName)1322 public static int getBackgroundRequest(String groupName) { 1323 return getBackgroundRequest(groupName, false); 1324 } 1325 1326 /** 1327 * The resource id for the background request message for a permission group for a specific 1328 * device 1329 * 1330 * @param groupName Permission group name 1331 * @return The id or 0 if the permission group doesn't exist or have a message 1332 */ getBackgroundRequest(String groupName, Boolean isDeviceAwareMessage)1333 public static int getBackgroundRequest(String groupName, Boolean isDeviceAwareMessage) { 1334 if (isDeviceAwareMessage) { 1335 return PERM_GROUP_BACKGROUND_REQUEST_DEVICE_AWARE_RES.getOrDefault(groupName, 0); 1336 } else { 1337 return PERM_GROUP_BACKGROUND_REQUEST_RES.getOrDefault(groupName, 0); 1338 } 1339 } 1340 1341 /** 1342 * The resource id for the background request detail message for a permission group 1343 * @param groupName Permission group name 1344 * @return The id or 0 if the permission group doesn't exist or have a message 1345 */ getBackgroundRequestDetail(String groupName)1346 public static int getBackgroundRequestDetail(String groupName) { 1347 return PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES.getOrDefault(groupName, 0); 1348 } 1349 1350 /** 1351 * The resource id for the upgrade request message for a permission group 1352 * @param groupName Permission group name 1353 * @return The id or 0 if the permission group doesn't exist or have a message 1354 */ getUpgradeRequest(String groupName)1355 public static int getUpgradeRequest(String groupName) { 1356 return getUpgradeRequest(groupName, false); 1357 } 1358 1359 /** 1360 * The resource id for the upgrade request message for a permission group for a specific device. 1361 * 1362 * @param groupName Permission group name 1363 * @return The id or 0 if the permission group doesn't exist or have a message 1364 */ getUpgradeRequest(String groupName, Boolean isDeviceAwareMessage)1365 public static int getUpgradeRequest(String groupName, Boolean isDeviceAwareMessage) { 1366 if (isDeviceAwareMessage) { 1367 return PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.getOrDefault(groupName, 0); 1368 } else { 1369 return PERM_GROUP_UPGRADE_REQUEST_RES.getOrDefault(groupName, 0); 1370 } 1371 } 1372 1373 /** 1374 * The resource id for the upgrade request detail message for a permission group 1375 * @param groupName Permission group name 1376 * @return The id or 0 if the permission group doesn't exist or have a message 1377 */ getUpgradeRequestDetail(String groupName)1378 public static int getUpgradeRequestDetail(String groupName) { 1379 return PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES.getOrDefault(groupName, 0); 1380 } 1381 1382 /** 1383 * The resource id for the fine location request message for a specific device 1384 * 1385 * @return The id 1386 */ getFineLocationRequest(Boolean isDeviceAwareMessage)1387 public static int getFineLocationRequest(Boolean isDeviceAwareMessage) { 1388 if (isDeviceAwareMessage) { 1389 return R.string.permgrouprequest_device_aware_fineupgrade; 1390 } else { 1391 return R.string.permgrouprequest_fineupgrade; 1392 } 1393 } 1394 1395 /** 1396 * The resource id for the coarse location request message for a specific device 1397 * 1398 * @return The id 1399 */ getCoarseLocationRequest(Boolean isDeviceAwareMessage)1400 public static int getCoarseLocationRequest(Boolean isDeviceAwareMessage) { 1401 if (isDeviceAwareMessage) { 1402 return R.string.permgrouprequest_device_aware_coarselocation; 1403 } else { 1404 return R.string.permgrouprequest_coarselocation; 1405 } 1406 } 1407 1408 /** 1409 * The resource id for the get more photos request message for a specific device 1410 * 1411 * @return The id 1412 */ getMorePhotosRequest(Boolean isDeviceAwareMessage)1413 public static int getMorePhotosRequest(Boolean isDeviceAwareMessage) { 1414 if (isDeviceAwareMessage) { 1415 return R.string.permgrouprequest_device_aware_more_photos; 1416 } else { 1417 return R.string.permgrouprequest_more_photos; 1418 } 1419 } 1420 1421 /** 1422 * Returns a random session ID value that's guaranteed to not be {@code INVALID_SESSION_ID}. 1423 * 1424 * @return A valid session ID. 1425 */ getValidSessionId()1426 public static long getValidSessionId() { 1427 long sessionId = INVALID_SESSION_ID; 1428 while (sessionId == INVALID_SESSION_ID) { 1429 sessionId = new Random().nextLong(); 1430 } 1431 return sessionId; 1432 } 1433 1434 /** 1435 * Retrieves an existing session ID from the given intent or generates a new one if none is 1436 * present. 1437 * 1438 * @return A valid session ID. 1439 */ getOrGenerateSessionId(Intent intent)1440 public static long getOrGenerateSessionId(Intent intent) { 1441 long sessionId = intent.getLongExtra(EXTRA_SESSION_ID, INVALID_SESSION_ID); 1442 if (sessionId == INVALID_SESSION_ID) { 1443 sessionId = getValidSessionId(); 1444 } 1445 return sessionId; 1446 } 1447 1448 /** 1449 * Gets the label of the Settings application 1450 * 1451 * @param pm The packageManager used to get the activity resolution 1452 * 1453 * @return The CharSequence title of the settings app 1454 */ 1455 @Nullable getSettingsLabelForNotifications(PackageManager pm)1456 public static CharSequence getSettingsLabelForNotifications(PackageManager pm) { 1457 // We pretend we're the Settings app sending the notification, so figure out its name. 1458 Intent openSettingsIntent = new Intent(Settings.ACTION_SETTINGS); 1459 ResolveInfo resolveInfo = pm.resolveActivity(openSettingsIntent, MATCH_SYSTEM_ONLY); 1460 if (resolveInfo == null) { 1461 return null; 1462 } 1463 return pm.getApplicationLabel(resolveInfo.activityInfo.applicationInfo); 1464 } 1465 1466 /** 1467 * Determines if a given user is disabled, or is a work profile. 1468 * @param user The user to check 1469 * @return true if the user is disabled, or the user is a work profile 1470 */ isUserDisabledOrWorkProfile(UserHandle user)1471 public static boolean isUserDisabledOrWorkProfile(UserHandle user) { 1472 Application app = PermissionControllerApplication.get(); 1473 UserManager userManager = app.getSystemService(UserManager.class); 1474 // In android TV, parental control accounts are managed profiles 1475 return !userManager.getEnabledProfiles().contains(user) 1476 || (userManager.isManagedProfile(user.getIdentifier()) 1477 && !DeviceUtils.isTelevision(app)); 1478 } 1479 1480 /** 1481 * Determines if a given user ID belongs to a managed profile user. 1482 * @param userId The user ID to check 1483 * @return true if the user is a managed profile 1484 */ isUserManagedProfile(int userId)1485 public static boolean isUserManagedProfile(int userId) { 1486 return PermissionControllerApplication.get() 1487 .getSystemService(UserManager.class) 1488 .isManagedProfile(userId); 1489 } 1490 1491 /** 1492 * Get all the exempted packages. 1493 */ getExemptedPackages(@onNull RoleManager roleManager)1494 public static Set<String> getExemptedPackages(@NonNull RoleManager roleManager) { 1495 Set<String> exemptedPackages = new HashSet<>(); 1496 1497 exemptedPackages.add(OS_PKG); 1498 for (int i = 0; i < EXEMPTED_ROLES.length; i++) { 1499 exemptedPackages.addAll(roleManager.getRoleHolders(EXEMPTED_ROLES[i])); 1500 } 1501 1502 return exemptedPackages; 1503 } 1504 1505 /** 1506 * Get the timestamp and lastAccessType for the summary text 1507 * in app permission groups and permission apps screens 1508 * @return Triple<String, Integer, String> with the first being the formatted time 1509 * the second being lastAccessType and the third being the formatted date. 1510 */ getPermissionLastAccessSummaryTimestamp( Long lastAccessTime, Context context, String groupName)1511 public static Triple<String, Integer, String> getPermissionLastAccessSummaryTimestamp( 1512 Long lastAccessTime, Context context, String groupName) { 1513 long midnightToday = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).toEpochSecond() 1514 * 1000L; 1515 long midnightYesterday = ZonedDateTime.now().minusDays(1).truncatedTo(ChronoUnit.DAYS) 1516 .toEpochSecond() * 1000L; 1517 long yesterdayAtThisTime = ZonedDateTime.now().minusDays(1).toEpochSecond() * 1000L; 1518 1519 boolean isLastAccessToday = lastAccessTime != null 1520 && midnightToday <= lastAccessTime; 1521 boolean isLastAccessWithinPast24h = lastAccessTime != null 1522 && yesterdayAtThisTime <= lastAccessTime; 1523 boolean isLastAccessTodayOrYesterday = lastAccessTime != null 1524 && midnightYesterday <= lastAccessTime; 1525 1526 String lastAccessTimeFormatted = ""; 1527 String lastAccessDateFormatted = ""; 1528 @AppPermsLastAccessType int lastAccessType = NOT_IN_LAST_7D; 1529 1530 if (lastAccessTime != null) { 1531 lastAccessTimeFormatted = DateFormat.getTimeFormat(context) 1532 .format(lastAccessTime); 1533 lastAccessDateFormatted = DateFormat.getDateFormat(context) 1534 .format(lastAccessTime); 1535 1536 if (!PermissionMapping.SENSOR_DATA_PERMISSIONS.contains(groupName)) { 1537 // For content providers we show either the last access is within 1538 // past 24 hours or past 7 days 1539 lastAccessType = isLastAccessWithinPast24h 1540 ? LAST_24H_CONTENT_PROVIDER : LAST_7D_CONTENT_PROVIDER; 1541 } else { 1542 // For sensor data permissions we show if the last access 1543 // is today, yesterday or older than yesterday 1544 lastAccessType = isLastAccessToday 1545 ? LAST_24H_SENSOR_TODAY : isLastAccessTodayOrYesterday 1546 ? LAST_24H_SENSOR_YESTERDAY : LAST_7D_SENSOR; 1547 } 1548 } 1549 1550 return new Triple<>(lastAccessTimeFormatted, lastAccessType, lastAccessDateFormatted); 1551 } 1552 1553 /** 1554 * Returns if the permission group is Camera or Microphone (status bar indicators). 1555 **/ isStatusBarIndicatorPermission(@onNull String permissionGroupName)1556 public static boolean isStatusBarIndicatorPermission(@NonNull String permissionGroupName) { 1557 return CAMERA.equals(permissionGroupName) || MICROPHONE.equals(permissionGroupName); 1558 } 1559 1560 /** 1561 * Navigate to notification settings for all apps 1562 * @param context The current Context 1563 */ navigateToNotificationSettings(@onNull Context context)1564 public static void navigateToNotificationSettings(@NonNull Context context) { 1565 Intent notificationIntent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS); 1566 context.startActivity(notificationIntent); 1567 } 1568 1569 /** 1570 * Navigate to notification settings for an app 1571 * @param context The current Context 1572 * @param packageName The package to navigate to 1573 * @param user Specifies the user of the package which should be navigated to. If null, the 1574 * current user is used. 1575 */ navigateToAppNotificationSettings(@onNull Context context, @NonNull String packageName, @NonNull UserHandle user)1576 public static void navigateToAppNotificationSettings(@NonNull Context context, 1577 @NonNull String packageName, @NonNull UserHandle user) { 1578 Intent notificationIntent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); 1579 notificationIntent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName); 1580 context.startActivityAsUser(notificationIntent, user); 1581 } 1582 1583 /** 1584 * Navigate to health connect settings for all apps 1585 * @param context The current Context 1586 */ navigateToHealthConnectSettings(@onNull Context context)1587 public static void navigateToHealthConnectSettings(@NonNull Context context) { 1588 Intent healthConnectIntent = new Intent(ACTION_MANAGE_HEALTH_PERMISSIONS); 1589 context.startActivity(healthConnectIntent); 1590 } 1591 1592 1593 /** 1594 * Navigate to health connect settings Wear privacy dashboard. 1595 * 1596 * @param context The current Context 1597 */ navigateToWearHealthConnectSettingsPrivacyDashboard( @onNull Context context)1598 public static void navigateToWearHealthConnectSettingsPrivacyDashboard( 1599 @NonNull Context context) { 1600 Intent privacyDashboardHealthConnectIntent = new Intent(ACTION_MANAGE_HEALTH_PERMISSIONS); 1601 privacyDashboardHealthConnectIntent.putExtra(EXTRA_REASON, "privacy_dashboard"); 1602 context.startActivity(privacyDashboardHealthConnectIntent); 1603 } 1604 1605 /** 1606 * Navigate to health connect settings for an app 1607 * @param context The current Context 1608 * @param packageName The package's health connect settings to navigate to 1609 */ navigateToAppHealthConnectSettings(@onNull Context context, @NonNull String packageName, @NonNull UserHandle user)1610 public static void navigateToAppHealthConnectSettings(@NonNull Context context, 1611 @NonNull String packageName, @NonNull UserHandle user) { 1612 Intent appHealthConnectIntent = new Intent(ACTION_MANAGE_HEALTH_PERMISSIONS); 1613 appHealthConnectIntent.putExtra(EXTRA_PACKAGE_NAME, packageName); 1614 appHealthConnectIntent.putExtra(Intent.EXTRA_USER, user); 1615 context.startActivity(appHealthConnectIntent); 1616 } 1617 1618 /** 1619 * Returns if a card should be shown if the sensor is blocked 1620 **/ shouldDisplayCardIfBlocked(@onNull String permissionGroupName)1621 public static boolean shouldDisplayCardIfBlocked(@NonNull String permissionGroupName) { 1622 return CAMERA.equals(permissionGroupName) || MICROPHONE.equals(permissionGroupName) 1623 || LOCATION.equals(permissionGroupName); 1624 } 1625 1626 /** 1627 * Returns the sensor code for a permission 1628 **/ 1629 @RequiresApi(Build.VERSION_CODES.S) getSensorCode(@onNull String permissionGroupName)1630 public static int getSensorCode(@NonNull String permissionGroupName) { 1631 return PERM_SENSOR_CODES.getOrDefault(permissionGroupName, -1); 1632 } 1633 1634 /** 1635 * Returns the blocked icon code for a permission 1636 **/ getBlockedIcon(@onNull String permissionGroupName)1637 public static int getBlockedIcon(@NonNull String permissionGroupName) { 1638 return PERM_BLOCKED_ICON.getOrDefault(permissionGroupName, -1); 1639 } 1640 1641 /** 1642 * Returns the blocked title code for a permission 1643 **/ getBlockedTitle(@onNull String permissionGroupName)1644 public static int getBlockedTitle(@NonNull String permissionGroupName) { 1645 return PERM_BLOCKED_TITLE.getOrDefault(permissionGroupName, -1); 1646 } 1647 1648 /** 1649 * Returns the blocked title code on automotive for a permission 1650 **/ getBlockedTitleAutomotive(@onNull String permissionGroupName)1651 public static int getBlockedTitleAutomotive(@NonNull String permissionGroupName) { 1652 return PERM_BLOCKED_TITLE_AUTOMOTIVE.getOrDefault(permissionGroupName, -1); 1653 } 1654 1655 /** 1656 * Returns if the permission group has a background mode, even if the background mode is 1657 * introduced in a platform version after the one currently running 1658 **/ hasPermWithBackgroundModeCompat(LightAppPermGroup group)1659 public static boolean hasPermWithBackgroundModeCompat(LightAppPermGroup group) { 1660 if (SdkLevel.isAtLeastS()) { 1661 return group.getHasPermWithBackgroundMode(); 1662 } 1663 String groupName = group.getPermGroupName(); 1664 return group.getHasPermWithBackgroundMode() 1665 || Manifest.permission_group.CAMERA.equals(groupName) 1666 || Manifest.permission_group.MICROPHONE.equals(groupName); 1667 } 1668 1669 /** 1670 * Returns the appropriate enterprise string for the provided IDs 1671 */ 1672 @NonNull getEnterpriseString(@onNull Context context, @NonNull String updatableStringId, int defaultStringId, @NonNull Object... formatArgs)1673 public static String getEnterpriseString(@NonNull Context context, 1674 @NonNull String updatableStringId, int defaultStringId, @NonNull Object... formatArgs) { 1675 return SdkLevel.isAtLeastT() 1676 ? getUpdatableEnterpriseString(context, updatableStringId, 1677 () -> context.getString(defaultStringId, formatArgs), formatArgs) 1678 : context.getString(defaultStringId, formatArgs); 1679 } 1680 1681 /** 1682 * Selects the appropriate enterprise string for the provided resource ID and a fallback string 1683 */ 1684 @NonNull getEnterpriseString(@onNull Context context, @NonNull String updatableStringId, @NonNull String defaultString)1685 public static String getEnterpriseString(@NonNull Context context, 1686 @NonNull String updatableStringId, @NonNull String defaultString) { 1687 return SdkLevel.isAtLeastT() 1688 ? getUpdatableEnterpriseString(context, updatableStringId, () -> defaultString) 1689 : defaultString; 1690 } 1691 1692 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 1693 @NonNull getUpdatableEnterpriseString(@onNull Context context, @NonNull String updatableStringId, @NonNull Supplier<String> defaultStringLoader, @NonNull Object... formatArgs)1694 private static String getUpdatableEnterpriseString(@NonNull Context context, 1695 @NonNull String updatableStringId, @NonNull Supplier<String> defaultStringLoader, 1696 @NonNull Object... formatArgs) { 1697 DevicePolicyManager dpm = getSystemServiceSafe(context, DevicePolicyManager.class); 1698 return dpm.getResources().getString(updatableStringId, defaultStringLoader, formatArgs); 1699 } 1700 1701 /** 1702 * Returns the profile label from the {@link UserManager} for the provided profile 1703 */ 1704 @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) 1705 @NonNull getProfileLabel(@onNull UserHandle profile, @NonNull Context context)1706 public static String getProfileLabel(@NonNull UserHandle profile, @NonNull Context context) { 1707 Context profileContext = context.createContextAsUser(profile, 0); 1708 UserManager profileUserManager = profileContext.getSystemService(UserManager.class); 1709 return profileUserManager.getProfileLabel(); 1710 } 1711 1712 /** 1713 * Get {@link PackageInfo} for this ComponentName. 1714 * 1715 * @param context The current Context 1716 * @param component component to get package info for 1717 * @return The package info 1718 * 1719 * @throws PackageManager.NameNotFoundException if package does not exist 1720 */ 1721 @NonNull getPackageInfoForComponentName(@onNull Context context, @NonNull ComponentName component)1722 public static PackageInfo getPackageInfoForComponentName(@NonNull Context context, 1723 @NonNull ComponentName component) throws PackageManager.NameNotFoundException { 1724 return context.getPackageManager().getPackageInfo(component.getPackageName(), 0); 1725 } 1726 1727 /** 1728 * Return the label to use for this application. 1729 * 1730 * @param context The current Context 1731 * @param applicationInfo The {@link ApplicationInfo} of the application to get the label of. 1732 * @return the label associated with this application, or its name if there is no label. 1733 */ 1734 @NonNull getApplicationLabel(@onNull Context context, @NonNull ApplicationInfo applicationInfo)1735 public static String getApplicationLabel(@NonNull Context context, 1736 @NonNull ApplicationInfo applicationInfo) { 1737 return context.getPackageManager().getApplicationLabel(applicationInfo).toString(); 1738 } 1739 1740 /** 1741 * Returns whether the given user should be shown in the Settings UI in SdkLevel V+. This method 1742 * will always return true for SdkLevels below V. 1743 * 1744 * @param userHandle The user for which to check whether it should be shown or not. 1745 * @return true if it should be shown, false otherwise. 1746 */ shouldShowInSettings(UserHandle userHandle, UserManager userManager)1747 public static boolean shouldShowInSettings(UserHandle userHandle, UserManager userManager) { 1748 return !SdkLevel.isAtLeastV() || shouldShowInSettingsInternal(userHandle, userManager); 1749 } 1750 1751 /** 1752 * Returns whether the given user should be shown in the Settings UI. 1753 * 1754 * @param userHandle The user for which to check whether it should be shown or not. 1755 * @return true if it should be shown, false otherwise. 1756 */ 1757 @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) 1758 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.VANILLA_ICE_CREAM) shouldShowInSettingsInternal( UserHandle userHandle, UserManager userManager)1759 private static boolean shouldShowInSettingsInternal( 1760 UserHandle userHandle, UserManager userManager) { 1761 var userProperties = userManager.getUserProperties(userHandle); 1762 return !userManager.isQuietModeEnabled(userHandle) 1763 || userProperties.getShowInQuietMode() != UserProperties.SHOW_IN_QUIET_MODE_HIDDEN; 1764 } 1765 1766 /** 1767 * Check whether an application is restricted for this setting identifier and return the 1768 * {@code Intent} for the restriction if it is. 1769 * 1770 * @param user the user to check for 1771 * @param context the {@code Context} to retrieve system services 1772 * 1773 * @return the {@code Intent} for the restriction if the application is restricted for this 1774 * setting identifier, or {@code null} otherwise. 1775 */ 1776 @Nullable getApplicationEnhancedConfirmationRestrictedIntentAsUser( @onNull UserHandle user, @NonNull Context context, @Nullable String packageName, @Nullable String settingIdentifier)1777 public static Intent getApplicationEnhancedConfirmationRestrictedIntentAsUser( 1778 @NonNull UserHandle user, 1779 @NonNull Context context, 1780 @Nullable String packageName, 1781 @Nullable String settingIdentifier) { 1782 if (SdkLevel.isAtLeastV() && Flags.enhancedConfirmationModeApisEnabled()) { 1783 Context userContext = Utils.getUserContext(context, user); 1784 EnhancedConfirmationManager userEnhancedConfirmationManager = 1785 userContext.getSystemService(EnhancedConfirmationManager.class); 1786 if (packageName == null || settingIdentifier == null) return null; 1787 try { 1788 boolean isRestricted = userEnhancedConfirmationManager.isRestricted(packageName, 1789 settingIdentifier); 1790 if (isRestricted) { 1791 return userEnhancedConfirmationManager.createRestrictedSettingDialogIntent( 1792 packageName, settingIdentifier); 1793 } 1794 1795 } catch (PackageManager.NameNotFoundException e) { 1796 Log.w(LOG_TAG, "Cannot check enhanced confirmation restriction for package: " 1797 + packageName, e); 1798 } 1799 } 1800 return null; 1801 } 1802 } 1803