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