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