/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.permissioncontroller.permission.utils; import static android.Manifest.permission.BIND_SOUND_TRIGGER_DETECTION_SERVICE; import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; import static android.Manifest.permission_group.ACTIVITY_RECOGNITION; import static android.Manifest.permission_group.CALENDAR; import static android.Manifest.permission_group.CALL_LOG; import static android.Manifest.permission_group.CAMERA; import static android.Manifest.permission_group.CONTACTS; import static android.Manifest.permission_group.LOCATION; import static android.Manifest.permission_group.MICROPHONE; import static android.Manifest.permission_group.PHONE; import static android.Manifest.permission_group.SENSORS; import static android.Manifest.permission_group.SMS; import static android.Manifest.permission_group.STORAGE; import static android.content.Context.MODE_PRIVATE; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED; import static android.content.pm.PackageManager.GET_SERVICES; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.UserHandle.myUserId; import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID; import android.Manifest; import android.app.Application; import android.app.role.RoleManager; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PermissionInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Parcelable; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; import android.provider.Settings; import android.service.carrier.CarrierService; import android.telephony.TelephonyManager; import android.text.Html; import android.text.TextUtils; import android.text.format.DateFormat; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.TypedValue; import android.view.Menu; import android.view.MenuItem; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.text.BidiFormatter; import androidx.core.util.Preconditions; import com.android.launcher3.icons.IconFactory; import com.android.permissioncontroller.Constants; import com.android.permissioncontroller.DeviceUtils; import com.android.permissioncontroller.PermissionControllerApplication; import com.android.permissioncontroller.R; import com.android.permissioncontroller.permission.model.AppPermissionGroup; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Random; public final class Utils { private static final String LOG_TAG = "Utils"; public static final String OS_PKG = "android"; public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f; /** Whether to show the Permissions Hub. */ private static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled"; /** The timeout for one-time permissions */ private static final String PROPERTY_ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS = "one_time_permissions_timeout_millis"; /** The timeout for auto-revoke permissions */ public static final String PROPERTY_AUTO_REVOKE_UNUSED_THRESHOLD_MILLIS = "auto_revoke_unused_threshold_millis2"; /** The frequency of running the job for auto-revoke permissions */ public static final String PROPERTY_AUTO_REVOKE_CHECK_FREQUENCY_MILLIS = "auto_revoke_check_frequency_millis"; /** Whether to show location access check notifications. */ private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED = "location_access_check_enabled"; /** All permission whitelists. */ public static final int FLAGS_PERMISSION_WHITELIST_ALL = PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM | PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER; /** All permission restriction excemptions. */ public static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT = FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT | FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; /** * The default length of the timeout for one-time permissions */ public static final long ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS = 1 * 60 * 1000; // 1 minute /** Mapping permission -> group for all dangerous platform permissions */ private static final ArrayMap PLATFORM_PERMISSIONS; /** Mapping group -> permissions for all dangerous platform permissions */ private static final ArrayMap> PLATFORM_PERMISSION_GROUPS; /** Set of groups that will be able to receive one-time grant */ private static final ArraySet ONE_TIME_PERMISSION_GROUPS; private static final ArrayMap PERM_GROUP_REQUEST_RES; private static final ArrayMap PERM_GROUP_REQUEST_DETAIL_RES; private static final ArrayMap PERM_GROUP_BACKGROUND_REQUEST_RES; private static final ArrayMap PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES; private static final ArrayMap PERM_GROUP_UPGRADE_REQUEST_RES; private static final ArrayMap PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES; public static final int FLAGS_ALWAYS_USER_SENSITIVE = FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED | FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED; static { PLATFORM_PERMISSIONS = new ArrayMap<>(); PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CONTACTS, CONTACTS); PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CONTACTS, CONTACTS); PLATFORM_PERMISSIONS.put(Manifest.permission.GET_ACCOUNTS, CONTACTS); PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALENDAR, CALENDAR); PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALENDAR, CALENDAR); PLATFORM_PERMISSIONS.put(Manifest.permission.SEND_SMS, SMS); PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_SMS, SMS); PLATFORM_PERMISSIONS.put(Manifest.permission.READ_SMS, SMS); PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_MMS, SMS); PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_WAP_PUSH, SMS); PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CELL_BROADCASTS, SMS); PLATFORM_PERMISSIONS.put(Manifest.permission.READ_EXTERNAL_STORAGE, STORAGE); PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, STORAGE); PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_MEDIA_LOCATION, STORAGE); PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_FINE_LOCATION, LOCATION); PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_COARSE_LOCATION, LOCATION); PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_BACKGROUND_LOCATION, LOCATION); PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALL_LOG, CALL_LOG); PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALL_LOG, CALL_LOG); PLATFORM_PERMISSIONS.put(Manifest.permission.PROCESS_OUTGOING_CALLS, CALL_LOG); PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_STATE, PHONE); PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_NUMBERS, PHONE); PLATFORM_PERMISSIONS.put(Manifest.permission.CALL_PHONE, PHONE); PLATFORM_PERMISSIONS.put(Manifest.permission.ADD_VOICEMAIL, PHONE); PLATFORM_PERMISSIONS.put(Manifest.permission.USE_SIP, PHONE); PLATFORM_PERMISSIONS.put(Manifest.permission.ANSWER_PHONE_CALLS, PHONE); PLATFORM_PERMISSIONS.put(Manifest.permission.ACCEPT_HANDOVER, PHONE); PLATFORM_PERMISSIONS.put(Manifest.permission.RECORD_AUDIO, MICROPHONE); PLATFORM_PERMISSIONS.put(Manifest.permission.ACTIVITY_RECOGNITION, ACTIVITY_RECOGNITION); PLATFORM_PERMISSIONS.put(Manifest.permission.CAMERA, CAMERA); PLATFORM_PERMISSIONS.put(Manifest.permission.BODY_SENSORS, SENSORS); PLATFORM_PERMISSION_GROUPS = new ArrayMap<>(); int numPlatformPermissions = PLATFORM_PERMISSIONS.size(); for (int i = 0; i < numPlatformPermissions; i++) { String permission = PLATFORM_PERMISSIONS.keyAt(i); String permissionGroup = PLATFORM_PERMISSIONS.valueAt(i); ArrayList permissionsOfThisGroup = PLATFORM_PERMISSION_GROUPS.get( permissionGroup); if (permissionsOfThisGroup == null) { permissionsOfThisGroup = new ArrayList<>(); PLATFORM_PERMISSION_GROUPS.put(permissionGroup, permissionsOfThisGroup); } permissionsOfThisGroup.add(permission); } ONE_TIME_PERMISSION_GROUPS = new ArraySet<>(); ONE_TIME_PERMISSION_GROUPS.add(LOCATION); ONE_TIME_PERMISSION_GROUPS.add(CAMERA); ONE_TIME_PERMISSION_GROUPS.add(MICROPHONE); PERM_GROUP_REQUEST_RES = new ArrayMap<>(); PERM_GROUP_REQUEST_RES.put(CONTACTS, R.string.permgrouprequest_contacts); PERM_GROUP_REQUEST_RES.put(LOCATION, R.string.permgrouprequest_location); PERM_GROUP_REQUEST_RES.put(CALENDAR, R.string.permgrouprequest_calendar); PERM_GROUP_REQUEST_RES.put(SMS, R.string.permgrouprequest_sms); PERM_GROUP_REQUEST_RES.put(STORAGE, R.string.permgrouprequest_storage); PERM_GROUP_REQUEST_RES.put(MICROPHONE, R.string.permgrouprequest_microphone); PERM_GROUP_REQUEST_RES .put(ACTIVITY_RECOGNITION, R.string.permgrouprequest_activityRecognition); PERM_GROUP_REQUEST_RES.put(CAMERA, R.string.permgrouprequest_camera); PERM_GROUP_REQUEST_RES.put(CALL_LOG, R.string.permgrouprequest_calllog); PERM_GROUP_REQUEST_RES.put(PHONE, R.string.permgrouprequest_phone); PERM_GROUP_REQUEST_RES.put(SENSORS, R.string.permgrouprequest_sensors); PERM_GROUP_REQUEST_DETAIL_RES = new ArrayMap<>(); PERM_GROUP_REQUEST_DETAIL_RES.put(LOCATION, R.string.permgrouprequestdetail_location); PERM_GROUP_BACKGROUND_REQUEST_RES = new ArrayMap<>(); PERM_GROUP_BACKGROUND_REQUEST_RES .put(LOCATION, R.string.permgroupbackgroundrequest_location); PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES = new ArrayMap<>(); PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES .put(LOCATION, R.string.permgroupbackgroundrequestdetail_location); PERM_GROUP_UPGRADE_REQUEST_RES = new ArrayMap<>(); PERM_GROUP_UPGRADE_REQUEST_RES.put(LOCATION, R.string.permgroupupgraderequest_location); PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES = new ArrayMap<>(); PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES .put(LOCATION, R.string.permgroupupgraderequestdetail_location); } private Utils() { /* do nothing - hide constructor */ } private static ArrayMap sUserContexts = new ArrayMap<>(); public enum ForegroundCapableType { SOUND_TRIGGER, ASSISTANT, VOICE_INTERACTION, CARRIER_SERVICE, NONE } /** * Creates and caches a PackageContext for the requested user, or returns the previously cached * value. The package of the PackageContext is the application's package. * * @param app The currently running application * @param user The desired user for the context * * @return The generated or cached Context for the requested user * * @throws PackageManager.NameNotFoundException If the app has no package name attached */ public static @NonNull Context getUserContext(Application app, UserHandle user) throws PackageManager.NameNotFoundException { if (!sUserContexts.containsKey(user)) { sUserContexts.put(user, app.getApplicationContext() .createPackageContextAsUser(app.getPackageName(), 0, user)); } return sUserContexts.get(user); } /** * {@code @NonNull} version of {@link Context#getSystemService(Class)} */ public static @NonNull M getSystemServiceSafe(@NonNull Context context, Class clazz) { return Preconditions.checkNotNull(context.getSystemService(clazz), "Could not resolve " + clazz.getSimpleName()); } /** * {@code @NonNull} version of {@link Context#getSystemService(Class)} */ public static @NonNull M getSystemServiceSafe(@NonNull Context context, Class clazz, @NonNull UserHandle user) { try { return Preconditions.checkNotNull(context.createPackageContextAsUser( context.getPackageName(), 0, user).getSystemService(clazz), "Could not resolve " + clazz.getSimpleName()); } catch (PackageManager.NameNotFoundException neverHappens) { throw new IllegalStateException(); } } /** * {@code @NonNull} version of {@link Intent#getParcelableExtra(String)} */ public static @NonNull T getParcelableExtraSafe(@NonNull Intent intent, @NonNull String name) { return Preconditions.checkNotNull(intent.getParcelableExtra(name), "Could not get parcelable extra for " + name); } /** * {@code @NonNull} version of {@link Intent#getStringExtra(String)} */ public static @NonNull String getStringExtraSafe(@NonNull Intent intent, @NonNull String name) { return Preconditions.checkNotNull(intent.getStringExtra(name), "Could not get string extra for " + name); } /** * Returns true if a permission is dangerous, installed, and not removed * @param permissionInfo The permission we wish to check * @return If all of the conditions are met */ public static boolean isPermissionDangerousInstalledNotRemoved(PermissionInfo permissionInfo) { return permissionInfo != null && permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS && (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0 && (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) == 0; } /** * Get permission group a platform permission belongs to, or null if the permission is not a * platform permission. * * @param permission the permission to resolve * * @return The group the permission belongs to */ public static @Nullable String getGroupOfPlatformPermission(@NonNull String permission) { return PLATFORM_PERMISSIONS.get(permission); } /** * Get name of the permission group a permission belongs to. * * @param permission the {@link PermissionInfo info} of the permission to resolve * * @return The group the permission belongs to */ public static @Nullable String getGroupOfPermission(@NonNull PermissionInfo permission) { String groupName = Utils.getGroupOfPlatformPermission(permission.name); if (groupName == null) { groupName = permission.group; } return groupName; } /** * Get the names for all platform permissions belonging to a group. * * @param group the group * * @return The permission names or an empty list if the * group is not does not have platform runtime permissions */ public static @NonNull List getPlatformPermissionNamesOfGroup(@NonNull String group) { final ArrayList permissions = PLATFORM_PERMISSION_GROUPS.get(group); return (permissions != null) ? permissions : Collections.emptyList(); } /** * Get the {@link PermissionInfo infos} for all platform permissions belonging to a group. * * @param pm Package manager to use to resolve permission infos * @param group the group * * @return The infos for platform permissions belonging to the group or an empty list if the * group is not does not have platform runtime permissions */ public static @NonNull List getPlatformPermissionsOfGroup( @NonNull PackageManager pm, @NonNull String group) { ArrayList permInfos = new ArrayList<>(); ArrayList permissions = PLATFORM_PERMISSION_GROUPS.get(group); if (permissions == null) { return Collections.emptyList(); } int numPermissions = permissions.size(); for (int i = 0; i < numPermissions; i++) { String permName = permissions.get(i); PermissionInfo permInfo; try { permInfo = pm.getPermissionInfo(permName, 0); } catch (PackageManager.NameNotFoundException e) { throw new IllegalStateException(permName + " not defined by platform", e); } permInfos.add(permInfo); } return permInfos; } /** * Get the {@link PermissionInfo infos} for all permission infos belonging to a group. * * @param pm Package manager to use to resolve permission infos * @param group the group * * @return The infos of permissions belonging to the group or an empty list if the group * does not have runtime permissions */ public static @NonNull List getPermissionInfosForGroup( @NonNull PackageManager pm, @NonNull String group) throws PackageManager.NameNotFoundException { List permissions = pm.queryPermissionsByGroup(group, 0); permissions.addAll(getPlatformPermissionsOfGroup(pm, group)); /* * If the undefined group is requested, the package manager will return all platform * permissions, since they are marked as Undefined in the manifest. Do not return these * permissions. */ if (group.equals(Manifest.permission_group.UNDEFINED)) { List undefinedPerms = new ArrayList<>(); for (PermissionInfo permissionInfo : permissions) { String permGroup = getGroupOfPlatformPermission(permissionInfo.name); if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) { undefinedPerms.add(permissionInfo); } } return undefinedPerms; } return permissions; } /** * Get the {@link PermissionInfo infos} for all runtime installed permission infos belonging to * a group. * * @param pm Package manager to use to resolve permission infos * @param group the group * * @return The infos of installed runtime permissions belonging to the group or an empty list * if the group does not have runtime permissions */ public static @NonNull List getInstalledRuntimePermissionInfosForGroup( @NonNull PackageManager pm, @NonNull String group) throws PackageManager.NameNotFoundException { List permissions = pm.queryPermissionsByGroup(group, 0); permissions.addAll(getPlatformPermissionsOfGroup(pm, group)); List installedRuntime = new ArrayList<>(); for (PermissionInfo permissionInfo: permissions) { if (permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS && (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0 && (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) == 0) { installedRuntime.add(permissionInfo); } } /* * If the undefined group is requested, the package manager will return all platform * permissions, since they are marked as Undefined in the manifest. Do not return these * permissions. */ if (group.equals(Manifest.permission_group.UNDEFINED)) { List undefinedPerms = new ArrayList<>(); for (PermissionInfo permissionInfo : installedRuntime) { String permGroup = getGroupOfPlatformPermission(permissionInfo.name); if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) { undefinedPerms.add(permissionInfo); } } return undefinedPerms; } return installedRuntime; } /** * Get the {@link PackageItemInfo infos} for the given permission group. * * @param groupName the group * @param context the {@code Context} to retrieve {@code PackageManager} * * @return The info of permission group or null if the group does not have runtime permissions. */ public static @Nullable PackageItemInfo getGroupInfo(@NonNull String groupName, @NonNull Context context) { try { return context.getPackageManager().getPermissionGroupInfo(groupName, 0); } catch (NameNotFoundException e) { /* ignore */ } try { return context.getPackageManager().getPermissionInfo(groupName, 0); } catch (NameNotFoundException e) { /* ignore */ } return null; } /** * Get the {@link PermissionInfo infos} for all permission infos belonging to a group. * * @param groupName the group * @param context the {@code Context} to retrieve {@code PackageManager} * * @return The infos of permissions belonging to the group or null if the group does not have * runtime permissions. */ public static @Nullable List getGroupPermissionInfos(@NonNull String groupName, @NonNull Context context) { try { return Utils.getPermissionInfosForGroup(context.getPackageManager(), groupName); } catch (NameNotFoundException e) { /* ignore */ } try { PermissionInfo permissionInfo = context.getPackageManager() .getPermissionInfo(groupName, 0); List permissions = new ArrayList<>(); permissions.add(permissionInfo); return permissions; } catch (NameNotFoundException e) { /* ignore */ } return null; } /** * Get the label for an application, truncating if it is too long. * * @param applicationInfo the {@link ApplicationInfo} of the application * @param context the {@code Context} to retrieve {@code PackageManager} * * @return the label for the application */ @NonNull public static String getAppLabel(@NonNull ApplicationInfo applicationInfo, @NonNull Context context) { return getAppLabel(applicationInfo, DEFAULT_MAX_LABEL_SIZE_PX, context); } /** * Get the full label for an application without truncation. * * @param applicationInfo the {@link ApplicationInfo} of the application * @param context the {@code Context} to retrieve {@code PackageManager} * * @return the label for the application */ @NonNull public static String getFullAppLabel(@NonNull ApplicationInfo applicationInfo, @NonNull Context context) { return getAppLabel(applicationInfo, 0, context); } /** * Get the label for an application with the ability to control truncating. * * @param applicationInfo the {@link ApplicationInfo} of the application * @param ellipsizeDip see {@link TextUtils#makeSafeForPresentation}. * @param context the {@code Context} to retrieve {@code PackageManager} * * @return the label for the application */ @NonNull private static String getAppLabel(@NonNull ApplicationInfo applicationInfo, float ellipsizeDip, @NonNull Context context) { return BidiFormatter.getInstance().unicodeWrap(applicationInfo.loadSafeLabel( context.getPackageManager(), ellipsizeDip, TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE) .toString()); } public static Drawable loadDrawable(PackageManager pm, String pkg, int resId) { try { return pm.getResourcesForApplication(pkg).getDrawable(resId, null); } catch (Resources.NotFoundException | PackageManager.NameNotFoundException e) { Log.d(LOG_TAG, "Couldn't get resource", e); return null; } } public static boolean isModernPermissionGroup(String name) { return PLATFORM_PERMISSION_GROUPS.containsKey(name); } /** * Get the names of the platform permission groups. * * @return the names of the platform permission groups. */ public static List getPlatformPermissionGroups() { return new ArrayList<>(PLATFORM_PERMISSION_GROUPS.keySet()); } /** * Get the names of the runtime platform permissions * * @return the names of the runtime platform permissions. */ public static List getRuntimePlatformPermissionNames() { return new ArrayList<>(PLATFORM_PERMISSIONS.keySet()); } /** * Is the permissions a platform runtime permission * * @return the names of the runtime platform permissions. */ public static boolean isRuntimePlatformPermission(@NonNull String permission) { return PLATFORM_PERMISSIONS.containsKey(permission); } /** * Should UI show this permission. * *

If the user cannot change the group, it should not be shown. * * @param group The group that might need to be shown to the user * * @return */ public static boolean shouldShowPermission(Context context, AppPermissionGroup group) { if (!group.isGrantingAllowed()) { return false; } final boolean isPlatformPermission = group.getDeclaringPackage().equals(OS_PKG); // Show legacy permissions only if the user chose that. if (isPlatformPermission && !Utils.isModernPermissionGroup(group.getName())) { return false; } return true; } public static Drawable applyTint(Context context, Drawable icon, int attr) { Theme theme = context.getTheme(); TypedValue typedValue = new TypedValue(); theme.resolveAttribute(attr, typedValue, true); icon = icon.mutate(); icon.setTint(context.getColor(typedValue.resourceId)); return icon; } public static Drawable applyTint(Context context, int iconResId, int attr) { return applyTint(context, context.getDrawable(iconResId), attr); } public static List getAllInstalledApplications(Context context) { return context.getPackageManager().getInstalledApplications(0); } /** * Is the group or background group user sensitive? * * @param group The group that might be user sensitive * * @return {@code true} if the group (or it's subgroup) is user sensitive. */ public static boolean isGroupOrBgGroupUserSensitive(AppPermissionGroup group) { return group.isUserSensitive() || (group.getBackgroundPermissions() != null && group.getBackgroundPermissions().isUserSensitive()); } public static boolean areGroupPermissionsIndividuallyControlled(Context context, String group) { if (!context.getPackageManager().arePermissionsIndividuallyControlled()) { return false; } return Manifest.permission_group.SMS.equals(group) || Manifest.permission_group.PHONE.equals(group) || Manifest.permission_group.CONTACTS.equals(group); } public static boolean isPermissionIndividuallyControlled(Context context, String permission) { if (!context.getPackageManager().arePermissionsIndividuallyControlled()) { return false; } return Manifest.permission.READ_CONTACTS.equals(permission) || Manifest.permission.WRITE_CONTACTS.equals(permission) || Manifest.permission.SEND_SMS.equals(permission) || Manifest.permission.RECEIVE_SMS.equals(permission) || Manifest.permission.READ_SMS.equals(permission) || Manifest.permission.RECEIVE_MMS.equals(permission) || Manifest.permission.CALL_PHONE.equals(permission) || Manifest.permission.READ_CALL_LOG.equals(permission) || Manifest.permission.WRITE_CALL_LOG.equals(permission); } /** * Get the message shown to grant a permission group to an app. * * @param appLabel The label of the app * @param group the group to be granted * @param context A context to resolve resources * @param requestRes The resource id of the grant request message * * @return The formatted message to be used as title when granting permissions */ public static CharSequence getRequestMessage(CharSequence appLabel, AppPermissionGroup group, Context context, @StringRes int requestRes) { if (group.getName().equals(STORAGE) && !group.isNonIsolatedStorage()) { return Html.fromHtml( String.format(context.getResources().getConfiguration().getLocales().get(0), context.getString(R.string.permgrouprequest_storage_isolated), appLabel), 0); } else if (requestRes != 0) { return Html.fromHtml(context.getResources().getString(requestRes, appLabel), 0); } return Html.fromHtml(context.getString(R.string.permission_warning_template, appLabel, group.getDescription()), 0); } /** * Build a string representing the given time if it happened on the current day and the date * otherwise. * * @param context the context. * @param lastAccessTime the time in milliseconds. * * @return a string representing the time or date of the given time or null if the time is 0. */ public static @Nullable String getAbsoluteTimeString(@NonNull Context context, long lastAccessTime) { if (lastAccessTime == 0) { return null; } if (isToday(lastAccessTime)) { return DateFormat.getTimeFormat(context).format(lastAccessTime); } else { return DateFormat.getMediumDateFormat(context).format(lastAccessTime); } } /** * Check whether the given time (in milliseconds) is in the current day. * * @param time the time in milliseconds * * @return whether the given time is in the current day. */ private static boolean isToday(long time) { Calendar today = Calendar.getInstance(Locale.getDefault()); today.setTimeInMillis(System.currentTimeMillis()); today.set(Calendar.HOUR_OF_DAY, 0); today.set(Calendar.MINUTE, 0); today.set(Calendar.SECOND, 0); today.set(Calendar.MILLISECOND, 0); Calendar date = Calendar.getInstance(Locale.getDefault()); date.setTimeInMillis(time); return !date.before(today); } /** * Add a menu item for searching Settings, if there is an activity handling the action. * * @param menu the menu to add the menu item into * @param context the context for checking whether there is an activity handling the action */ public static void prepareSearchMenuItem(@NonNull Menu menu, @NonNull Context context) { Intent intent = new Intent(Settings.ACTION_APP_SEARCH_SETTINGS); if (context.getPackageManager().resolveActivity(intent, 0) == null) { return; } MenuItem searchItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, R.string.search_menu); searchItem.setIcon(R.drawable.ic_search_24dp); searchItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); searchItem.setOnMenuItemClickListener(item -> { try { context.startActivity(intent); } catch (ActivityNotFoundException e) { Log.e(LOG_TAG, "Cannot start activity to search settings", e); } return true; }); } /** * Get badged app icon if necessary, similar as used in the Settings UI. * * @param context The context to use * @param appInfo The app the icon belong to * * @return The icon to use */ public static @NonNull Drawable getBadgedIcon(@NonNull Context context, @NonNull ApplicationInfo appInfo) { UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid); try (IconFactory iconFactory = IconFactory.obtain(context)) { Bitmap iconBmp = iconFactory.createBadgedIconBitmap( appInfo.loadUnbadgedIcon(context.getPackageManager()), user, false).icon; return new BitmapDrawable(context.getResources(), iconBmp); } } /** * Get a string saying what apps with the given permission group can do. * * @param context The context to use * @param groupName The name of the permission group * @param description The description of the permission group * * @return a string saying what apps with the given permission group can do. */ public static @NonNull String getPermissionGroupDescriptionString(@NonNull Context context, @NonNull String groupName, @NonNull CharSequence description) { switch (groupName) { case ACTIVITY_RECOGNITION: return context.getString( R.string.permission_description_summary_activity_recognition); case CALENDAR: return context.getString(R.string.permission_description_summary_calendar); case CALL_LOG: return context.getString(R.string.permission_description_summary_call_log); case CAMERA: return context.getString(R.string.permission_description_summary_camera); case CONTACTS: return context.getString(R.string.permission_description_summary_contacts); case LOCATION: return context.getString(R.string.permission_description_summary_location); case MICROPHONE: return context.getString(R.string.permission_description_summary_microphone); case PHONE: return context.getString(R.string.permission_description_summary_phone); case SENSORS: return context.getString(R.string.permission_description_summary_sensors); case SMS: return context.getString(R.string.permission_description_summary_sms); case STORAGE: return context.getString(R.string.permission_description_summary_storage); default: return context.getString(R.string.permission_description_summary_generic, description); } } /** * Whether the Location Access Check is enabled. * * @return {@code true} iff the Location Access Check is enabled. */ public static boolean isLocationAccessCheckEnabled() { return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, true); } /** * Get a device protected storage based shared preferences. Avoid storing sensitive data in it. * * @param context the context to get the shared preferences * @return a device protected storage based shared preferences */ @NonNull public static SharedPreferences getDeviceProtectedSharedPreferences(@NonNull Context context) { if (!context.isDeviceProtectedStorage()) { context = context.createDeviceProtectedStorageContext(); } return context.getSharedPreferences(Constants.PREFERENCES_FILE, MODE_PRIVATE); } public static long getOneTimePermissionsTimeout() { return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS, ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS); } /** * Get context of the parent user of the profile group (i.e. usually the 'personal' profile, * not the 'work' profile). * * @param context The context of a user of the profile user group. * * @return The context of the parent user */ public static Context getParentUserContext(@NonNull Context context) { UserHandle parentUser = getSystemServiceSafe(context, UserManager.class) .getProfileParent(UserHandle.of(myUserId())); if (parentUser == null) { return context; } // In a multi profile environment perform all operations as the parent user of the // current profile try { return context.createPackageContextAsUser(context.getPackageName(), 0, parentUser); } catch (PackageManager.NameNotFoundException e) { // cannot happen throw new IllegalStateException("Could not switch to parent user " + parentUser, e); } } /** * Whether the permission group supports one-time * @param permissionGroup The permission group to check * @return {@code true} iff the group supports one-time */ public static boolean supportsOneTimeGrant(String permissionGroup) { return ONE_TIME_PERMISSION_GROUPS.contains(permissionGroup); } /** * The resource id for the request message for a permission group * @param groupName Permission group name * @return The id or 0 if the permission group doesn't exist or have a message */ public static int getRequest(String groupName) { return PERM_GROUP_REQUEST_RES.getOrDefault(groupName, 0); } /** * The resource id for the request detail message for a permission group * @param groupName Permission group name * @return The id or 0 if the permission group doesn't exist or have a message */ public static int getRequestDetail(String groupName) { return PERM_GROUP_REQUEST_DETAIL_RES.getOrDefault(groupName, 0); } /** * The resource id for the background request message for a permission group * @param groupName Permission group name * @return The id or 0 if the permission group doesn't exist or have a message */ public static int getBackgroundRequest(String groupName) { return PERM_GROUP_BACKGROUND_REQUEST_RES.getOrDefault(groupName, 0); } /** * The resource id for the background request detail message for a permission group * @param groupName Permission group name * @return The id or 0 if the permission group doesn't exist or have a message */ public static int getBackgroundRequestDetail(String groupName) { return PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES.getOrDefault(groupName, 0); } /** * The resource id for the upgrade request message for a permission group * @param groupName Permission group name * @return The id or 0 if the permission group doesn't exist or have a message */ public static int getUpgradeRequest(String groupName) { return PERM_GROUP_UPGRADE_REQUEST_RES.getOrDefault(groupName, 0); } /** * The resource id for the upgrade request detail message for a permission group * @param groupName Permission group name * @return The id or 0 if the permission group doesn't exist or have a message */ public static int getUpgradeRequestDetail(String groupName) { return PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES.getOrDefault(groupName, 0); } /** * Checks whether a package has an active one-time permission according to the system server's * flags * * @param context the {@code Context} to retrieve {@code PackageManager} * @param packageName The package to check for * @return Whether a package has an active one-time permission */ public static boolean hasOneTimePermissions(Context context, String packageName) { String[] permissions; PackageManager pm = context.getPackageManager(); try { permissions = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS) .requestedPermissions; } catch (NameNotFoundException e) { Log.w(LOG_TAG, "Checking for one-time permissions in nonexistent package"); return false; } if (permissions == null) { return false; } for (String permissionName : permissions) { if ((pm.getPermissionFlags(permissionName, packageName, Process.myUserHandle()) & PackageManager.FLAG_PERMISSION_ONE_TIME) != 0 && pm.checkPermission(permissionName, packageName) == PERMISSION_GRANTED) { return true; } } return false; } /** * Returns a random session ID value that's guaranteed to not be {@code INVALID_SESSION_ID}. * * @return A valid session ID. */ public static long getValidSessionId() { long sessionId = INVALID_SESSION_ID; while (sessionId == INVALID_SESSION_ID) { sessionId = new Random().nextLong(); } return sessionId; } /** * Gets the label of the Settings application * * @param pm The packageManager used to get the activity resolution * * @return The CharSequence title of the settings app */ @Nullable public static CharSequence getSettingsLabelForNotifications(PackageManager pm) { // We pretend we're the Settings app sending the notification, so figure out its name. Intent openSettingsIntent = new Intent(Settings.ACTION_SETTINGS); ResolveInfo resolveInfo = pm.resolveActivity(openSettingsIntent, MATCH_SYSTEM_ONLY); if (resolveInfo == null) { return null; } return pm.getApplicationLabel(resolveInfo.activityInfo.applicationInfo); } /** * If an app could have foreground capabilities it is because it meets some criteria. This * function returns which criteria it meets. * @param context The context as the user of interest. * @param packageName The package to check. * @return the type of foreground capable app. * @throws NameNotFoundException */ public static @NonNull ForegroundCapableType getForegroundCapableType(@NonNull Context context, @NonNull String packageName) throws NameNotFoundException { PackageManager pm = context.getPackageManager(); // Apps which can be bound by SoundTriggerService if (pm.checkPermission(CAPTURE_AUDIO_HOTWORD, packageName) == PERMISSION_GRANTED) { ServiceInfo[] services = pm.getPackageInfo(packageName, GET_SERVICES).services; if (services != null) { for (ServiceInfo service : services) { if (BIND_SOUND_TRIGGER_DETECTION_SERVICE.equals(service.permission)) { return ForegroundCapableType.SOUND_TRIGGER; } } } } // VoiceInteractionService if (context.getSystemService(RoleManager.class).getRoleHolders(RoleManager.ROLE_ASSISTANT) .contains(packageName)) { return ForegroundCapableType.ASSISTANT; } String voiceInteraction = Settings.Secure.getString(context.getContentResolver(), "voice_interaction_service"); if (!TextUtils.isEmpty(voiceInteraction)) { ComponentName component = ComponentName.unflattenFromString(voiceInteraction); if (component != null && TextUtils.equals(packageName, component.getPackageName())) { return ForegroundCapableType.VOICE_INTERACTION; } } // Carrier privileged apps implementing the carrier service final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); int numPhones = telephonyManager.getActiveModemCount(); for (int phoneId = 0; phoneId < numPhones; phoneId++) { List packages = telephonyManager.getCarrierPackageNamesForIntentAndPhone( new Intent(CarrierService.CARRIER_SERVICE_INTERFACE), phoneId); if (packages != null && packages.contains(packageName)) { return ForegroundCapableType.CARRIER_SERVICE; } } return ForegroundCapableType.NONE; } /** * This tells whether we should blame the app for potential background access. Intended to be * used for creating Ui. * @param context The context as the user of interest * @param packageName The package to check * @return true if the given package could possibly have foreground capabilities while in the * background, otherwise false. * @throws NameNotFoundException */ public static boolean couldHaveForegroundCapabilities(@NonNull Context context, @NonNull String packageName) throws NameNotFoundException { return getForegroundCapableType(context, packageName) != ForegroundCapableType.NONE; } /** * Determines if a given user is disabled, or is a work profile. * @param user The user to check * @return true if the user is disabled, or the user is a work profile */ public static boolean isUserDisabledOrWorkProfile(UserHandle user) { Application app = PermissionControllerApplication.get(); UserManager userManager = app.getSystemService(UserManager.class); // In android TV, parental control accounts are managed profiles return !userManager.getEnabledProfiles().contains(user) || (userManager.isManagedProfile(user.getIdentifier()) && !DeviceUtils.isTelevision(app)); } /** * @return Whether a package is an emergency app. */ public static boolean isEmergencyApp(@NonNull Context context, @NonNull String packageName) { try { return context.getSystemService(RoleManager.class) .getRoleHolders(RoleManager.ROLE_EMERGENCY).contains(packageName); } catch (Throwable t) { // Avoid crashing for any reason, this isn't very well tested Log.e(LOG_TAG, "Unable to check if " + packageName + " is an emergency app.", t); return false; } } }