• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.packageinstaller.permission.utils;
18 
19 import static android.Manifest.permission.RECORD_AUDIO;
20 import static android.Manifest.permission_group.ACTIVITY_RECOGNITION;
21 import static android.Manifest.permission_group.CALENDAR;
22 import static android.Manifest.permission_group.CALL_LOG;
23 import static android.Manifest.permission_group.CAMERA;
24 import static android.Manifest.permission_group.CONTACTS;
25 import static android.Manifest.permission_group.LOCATION;
26 import static android.Manifest.permission_group.MICROPHONE;
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.role.RoleManager.ROLE_ASSISTANT;
32 import static android.content.Context.MODE_PRIVATE;
33 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED;
34 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED;
35 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
36 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
37 import static android.os.UserHandle.myUserId;
38 
39 import static com.android.packageinstaller.Constants.ASSISTANT_RECORD_AUDIO_IS_USER_SENSITIVE_KEY;
40 import static com.android.packageinstaller.Constants.FORCED_USER_SENSITIVE_UIDS_KEY;
41 import static com.android.packageinstaller.Constants.PREFERENCES_FILE;
42 
43 import android.Manifest;
44 import android.app.Application;
45 import android.app.role.RoleManager;
46 import android.content.ActivityNotFoundException;
47 import android.content.Context;
48 import android.content.Intent;
49 import android.content.SharedPreferences;
50 import android.content.pm.ApplicationInfo;
51 import android.content.pm.PackageItemInfo;
52 import android.content.pm.PackageManager;
53 import android.content.pm.PackageManager.NameNotFoundException;
54 import android.content.pm.PermissionInfo;
55 import android.content.pm.ResolveInfo;
56 import android.content.res.Resources;
57 import android.content.res.Resources.Theme;
58 import android.graphics.Bitmap;
59 import android.graphics.drawable.BitmapDrawable;
60 import android.graphics.drawable.Drawable;
61 import android.os.Parcelable;
62 import android.os.UserHandle;
63 import android.os.UserManager;
64 import android.provider.DeviceConfig;
65 import android.provider.Settings;
66 import android.text.Html;
67 import android.text.TextUtils;
68 import android.text.format.DateFormat;
69 import android.util.ArrayMap;
70 import android.util.ArraySet;
71 import android.util.Log;
72 import android.util.SparseArray;
73 import android.util.TypedValue;
74 import android.view.Menu;
75 import android.view.MenuItem;
76 
77 import androidx.annotation.NonNull;
78 import androidx.annotation.Nullable;
79 import androidx.annotation.StringRes;
80 import androidx.core.text.BidiFormatter;
81 import androidx.core.util.Preconditions;
82 
83 import com.android.launcher3.icons.IconFactory;
84 import com.android.packageinstaller.Constants;
85 import com.android.packageinstaller.permission.data.PerUserUidToSensitivityLiveData;
86 import com.android.packageinstaller.permission.model.AppPermissionGroup;
87 import com.android.permissioncontroller.R;
88 
89 import java.util.ArrayList;
90 import java.util.Calendar;
91 import java.util.Collections;
92 import java.util.List;
93 import java.util.Locale;
94 import java.util.Set;
95 
96 public final class Utils {
97 
98     private static final String LOG_TAG = "Utils";
99 
100     public static final String OS_PKG = "android";
101 
102     public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f;
103 
104     /** Whether to show location access check notifications. */
105     private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED =
106             "location_access_check_enabled";
107 
108     /** All permission whitelists. */
109     public static final int FLAGS_PERMISSION_WHITELIST_ALL =
110             PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
111                     | PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
112                     | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER;
113 
114     /** Mapping permission -> group for all dangerous platform permissions */
115     private static final ArrayMap<String, String> PLATFORM_PERMISSIONS;
116 
117     /** Mapping group -> permissions for all dangerous platform permissions */
118     private static final ArrayMap<String, ArrayList<String>> PLATFORM_PERMISSION_GROUPS;
119 
120     private static final Intent LAUNCHER_INTENT = new Intent(Intent.ACTION_MAIN, null)
121             .addCategory(Intent.CATEGORY_LAUNCHER);
122 
123     public static final int FLAGS_ALWAYS_USER_SENSITIVE =
124             FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
125                     | FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED;
126 
127     static {
128         PLATFORM_PERMISSIONS = new ArrayMap<>();
129 
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CONTACTS, CONTACTS)130         PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CONTACTS, CONTACTS);
PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CONTACTS, CONTACTS)131         PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CONTACTS, CONTACTS);
PLATFORM_PERMISSIONS.put(Manifest.permission.GET_ACCOUNTS, CONTACTS)132         PLATFORM_PERMISSIONS.put(Manifest.permission.GET_ACCOUNTS, CONTACTS);
133 
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALENDAR, CALENDAR)134         PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALENDAR, CALENDAR);
PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALENDAR, CALENDAR)135         PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALENDAR, CALENDAR);
136 
PLATFORM_PERMISSIONS.put(Manifest.permission.SEND_SMS, SMS)137         PLATFORM_PERMISSIONS.put(Manifest.permission.SEND_SMS, SMS);
PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_SMS, SMS)138         PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_SMS, SMS);
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_SMS, SMS)139         PLATFORM_PERMISSIONS.put(Manifest.permission.READ_SMS, SMS);
PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_MMS, SMS)140         PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_MMS, SMS);
PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_WAP_PUSH, SMS)141         PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_WAP_PUSH, SMS);
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CELL_BROADCASTS, SMS)142         PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CELL_BROADCASTS, SMS);
143 
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_EXTERNAL_STORAGE, STORAGE)144         PLATFORM_PERMISSIONS.put(Manifest.permission.READ_EXTERNAL_STORAGE, STORAGE);
PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, STORAGE)145         PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, STORAGE);
PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_MEDIA_LOCATION, STORAGE)146         PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_MEDIA_LOCATION, STORAGE);
147 
PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_FINE_LOCATION, LOCATION)148         PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_FINE_LOCATION, LOCATION);
PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_COARSE_LOCATION, LOCATION)149         PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_COARSE_LOCATION, LOCATION);
PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_BACKGROUND_LOCATION, LOCATION)150         PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_BACKGROUND_LOCATION, LOCATION);
151 
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALL_LOG, CALL_LOG)152         PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALL_LOG, CALL_LOG);
PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALL_LOG, CALL_LOG)153         PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALL_LOG, CALL_LOG);
PLATFORM_PERMISSIONS.put(Manifest.permission.PROCESS_OUTGOING_CALLS, CALL_LOG)154         PLATFORM_PERMISSIONS.put(Manifest.permission.PROCESS_OUTGOING_CALLS, CALL_LOG);
155 
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_STATE, PHONE)156         PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_STATE, PHONE);
PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_NUMBERS, PHONE)157         PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_NUMBERS, PHONE);
PLATFORM_PERMISSIONS.put(Manifest.permission.CALL_PHONE, PHONE)158         PLATFORM_PERMISSIONS.put(Manifest.permission.CALL_PHONE, PHONE);
PLATFORM_PERMISSIONS.put(Manifest.permission.ADD_VOICEMAIL, PHONE)159         PLATFORM_PERMISSIONS.put(Manifest.permission.ADD_VOICEMAIL, PHONE);
PLATFORM_PERMISSIONS.put(Manifest.permission.USE_SIP, PHONE)160         PLATFORM_PERMISSIONS.put(Manifest.permission.USE_SIP, PHONE);
PLATFORM_PERMISSIONS.put(Manifest.permission.ANSWER_PHONE_CALLS, PHONE)161         PLATFORM_PERMISSIONS.put(Manifest.permission.ANSWER_PHONE_CALLS, PHONE);
PLATFORM_PERMISSIONS.put(Manifest.permission.ACCEPT_HANDOVER, PHONE)162         PLATFORM_PERMISSIONS.put(Manifest.permission.ACCEPT_HANDOVER, PHONE);
163 
PLATFORM_PERMISSIONS.put(Manifest.permission.RECORD_AUDIO, MICROPHONE)164         PLATFORM_PERMISSIONS.put(Manifest.permission.RECORD_AUDIO, MICROPHONE);
165 
PLATFORM_PERMISSIONS.put(Manifest.permission.ACTIVITY_RECOGNITION, ACTIVITY_RECOGNITION)166         PLATFORM_PERMISSIONS.put(Manifest.permission.ACTIVITY_RECOGNITION, ACTIVITY_RECOGNITION);
167 
PLATFORM_PERMISSIONS.put(Manifest.permission.CAMERA, CAMERA)168         PLATFORM_PERMISSIONS.put(Manifest.permission.CAMERA, CAMERA);
169 
PLATFORM_PERMISSIONS.put(Manifest.permission.BODY_SENSORS, SENSORS)170         PLATFORM_PERMISSIONS.put(Manifest.permission.BODY_SENSORS, SENSORS);
171 
172         PLATFORM_PERMISSION_GROUPS = new ArrayMap<>();
173         int numPlatformPermissions = PLATFORM_PERMISSIONS.size();
174         for (int i = 0; i < numPlatformPermissions; i++) {
175             String permission = PLATFORM_PERMISSIONS.keyAt(i);
176             String permissionGroup = PLATFORM_PERMISSIONS.valueAt(i);
177 
178             ArrayList<String> permissionsOfThisGroup = PLATFORM_PERMISSION_GROUPS.get(
179                     permissionGroup);
180             if (permissionsOfThisGroup == null) {
181                 permissionsOfThisGroup = new ArrayList<>();
PLATFORM_PERMISSION_GROUPS.put(permissionGroup, permissionsOfThisGroup)182                 PLATFORM_PERMISSION_GROUPS.put(permissionGroup, permissionsOfThisGroup);
183             }
184 
185             permissionsOfThisGroup.add(permission);
186         }
187     }
188 
Utils()189     private Utils() {
190         /* do nothing - hide constructor */
191     }
192 
193     /**
194      * {@code @NonNull} version of {@link Context#getSystemService(Class)}
195      */
getSystemServiceSafe(@onNull Context context, Class<M> clazz)196     public static @NonNull <M> M getSystemServiceSafe(@NonNull Context context, Class<M> clazz) {
197         return Preconditions.checkNotNull(context.getSystemService(clazz),
198                 "Could not resolve " + clazz.getSimpleName());
199     }
200 
201     /**
202      * {@code @NonNull} version of {@link Context#getSystemService(Class)}
203      */
getSystemServiceSafe(@onNull Context context, Class<M> clazz, @NonNull UserHandle user)204     public static @NonNull <M> M getSystemServiceSafe(@NonNull Context context, Class<M> clazz,
205             @NonNull UserHandle user) {
206         try {
207             return Preconditions.checkNotNull(context.createPackageContextAsUser(
208                     context.getPackageName(), 0, user).getSystemService(clazz),
209                     "Could not resolve " + clazz.getSimpleName());
210         } catch (PackageManager.NameNotFoundException neverHappens) {
211             throw new IllegalStateException();
212         }
213     }
214 
215     /**
216      * {@code @NonNull} version of {@link Intent#getParcelableExtra(String)}
217      */
getParcelableExtraSafe(@onNull Intent intent, @NonNull String name)218     public static @NonNull <T extends Parcelable> T getParcelableExtraSafe(@NonNull Intent intent,
219             @NonNull String name) {
220         return Preconditions.checkNotNull(intent.getParcelableExtra(name),
221                 "Could not get parcelable extra for " + name);
222     }
223 
224     /**
225      * {@code @NonNull} version of {@link Intent#getStringExtra(String)}
226      */
getStringExtraSafe(@onNull Intent intent, @NonNull String name)227     public static @NonNull String getStringExtraSafe(@NonNull Intent intent,
228             @NonNull String name) {
229         return Preconditions.checkNotNull(intent.getStringExtra(name),
230                 "Could not get string extra for " + name);
231     }
232 
233     /**
234      * Get permission group a platform permission belongs to.
235      *
236      * @param permission the permission to resolve
237      *
238      * @return The group the permission belongs to
239      */
getGroupOfPlatformPermission(@onNull String permission)240     public static @Nullable String getGroupOfPlatformPermission(@NonNull String permission) {
241         return PLATFORM_PERMISSIONS.get(permission);
242     }
243 
244     /**
245      * Get name of the permission group a permission belongs to.
246      *
247      * @param permission the {@link PermissionInfo info} of the permission to resolve
248      *
249      * @return The group the permission belongs to
250      */
getGroupOfPermission(@onNull PermissionInfo permission)251     public static @Nullable String getGroupOfPermission(@NonNull PermissionInfo permission) {
252         String groupName = Utils.getGroupOfPlatformPermission(permission.name);
253         if (groupName == null) {
254             groupName = permission.group;
255         }
256 
257         return groupName;
258     }
259 
260     /**
261      * Get the names for all platform permissions belonging to a group.
262      *
263      * @param group the group
264      *
265      * @return The permission names  or an empty list if the
266      *         group is not does not have platform runtime permissions
267      */
getPlatformPermissionNamesOfGroup(@onNull String group)268     public static @NonNull List<String> getPlatformPermissionNamesOfGroup(@NonNull String group) {
269         final ArrayList<String> permissions = PLATFORM_PERMISSION_GROUPS.get(group);
270         return (permissions != null) ? permissions : Collections.emptyList();
271     }
272 
273     /**
274      * Get the {@link PermissionInfo infos} for all platform permissions belonging to a group.
275      *
276      * @param pm    Package manager to use to resolve permission infos
277      * @param group the group
278      *
279      * @return The infos for platform permissions belonging to the group or an empty list if the
280      *         group is not does not have platform runtime permissions
281      */
getPlatformPermissionsOfGroup( @onNull PackageManager pm, @NonNull String group)282     public static @NonNull List<PermissionInfo> getPlatformPermissionsOfGroup(
283             @NonNull PackageManager pm, @NonNull String group) {
284         ArrayList<PermissionInfo> permInfos = new ArrayList<>();
285 
286         ArrayList<String> permissions = PLATFORM_PERMISSION_GROUPS.get(group);
287         if (permissions == null) {
288             return Collections.emptyList();
289         }
290 
291         int numPermissions = permissions.size();
292         for (int i = 0; i < numPermissions; i++) {
293             String permName = permissions.get(i);
294             PermissionInfo permInfo;
295             try {
296                 permInfo = pm.getPermissionInfo(permName, 0);
297             } catch (PackageManager.NameNotFoundException e) {
298                 throw new IllegalStateException(permName + " not defined by platform", e);
299             }
300 
301             permInfos.add(permInfo);
302         }
303 
304         return permInfos;
305     }
306 
307     /**
308      * Get the {@link PermissionInfo infos} for all permission infos belonging to a group.
309      *
310      * @param pm    Package manager to use to resolve permission infos
311      * @param group the group
312      *
313      * @return The infos of permissions belonging to the group or an empty list if the group
314      *         does not have runtime permissions
315      */
getPermissionInfosForGroup( @onNull PackageManager pm, @NonNull String group)316     public static @NonNull List<PermissionInfo> getPermissionInfosForGroup(
317             @NonNull PackageManager pm, @NonNull String group)
318             throws PackageManager.NameNotFoundException {
319         List<PermissionInfo> permissions = new ArrayList<>();
320         for (PermissionInfo permission : pm.queryPermissionsByGroup(group, 0)) {
321             // PermissionController's mapping takes precedence
322             if (getGroupOfPermission(permission).equals(group)) {
323                 permissions.add(permission);
324             }
325         }
326         permissions.addAll(getPlatformPermissionsOfGroup(pm, group));
327 
328         return permissions;
329     }
330 
331     /**
332      * Get the {@link PackageItemInfo infos} for the given permission group.
333      *
334      * @param groupName the group
335      * @param context the {@code Context} to retrieve {@code PackageManager}
336      *
337      * @return The info of permission group or null if the group does not have runtime permissions.
338      */
getGroupInfo(@onNull String groupName, @NonNull Context context)339     public static @Nullable PackageItemInfo getGroupInfo(@NonNull String groupName,
340             @NonNull Context context) {
341         try {
342             return context.getPackageManager().getPermissionGroupInfo(groupName, 0);
343         } catch (NameNotFoundException e) {
344             /* ignore */
345         }
346         try {
347             return context.getPackageManager().getPermissionInfo(groupName, 0);
348         } catch (NameNotFoundException e) {
349             /* ignore */
350         }
351         return null;
352     }
353 
354     /**
355      * Get the {@link PermissionInfo infos} for all permission infos belonging to a group.
356      *
357      * @param groupName the group
358      * @param context the {@code Context} to retrieve {@code PackageManager}
359      *
360      * @return The infos of permissions belonging to the group or null if the group does not have
361      *         runtime permissions.
362      */
getGroupPermissionInfos(@onNull String groupName, @NonNull Context context)363     public static @Nullable List<PermissionInfo> getGroupPermissionInfos(@NonNull String groupName,
364             @NonNull Context context) {
365         try {
366             return Utils.getPermissionInfosForGroup(context.getPackageManager(), groupName);
367         } catch (NameNotFoundException e) {
368             /* ignore */
369         }
370         try {
371             PermissionInfo permissionInfo = context.getPackageManager()
372                     .getPermissionInfo(groupName, 0);
373             List<PermissionInfo> permissions = new ArrayList<>();
374             permissions.add(permissionInfo);
375             return permissions;
376         } catch (NameNotFoundException e) {
377             /* ignore */
378         }
379         return null;
380     }
381 
382     /**
383      * Get the label for an application, truncating if it is too long.
384      *
385      * @param applicationInfo the {@link ApplicationInfo} of the application
386      * @param context the {@code Context} to retrieve {@code PackageManager}
387      *
388      * @return the label for the application
389      */
390     @NonNull
getAppLabel(@onNull ApplicationInfo applicationInfo, @NonNull Context context)391     public static String getAppLabel(@NonNull ApplicationInfo applicationInfo,
392             @NonNull Context context) {
393         return getAppLabel(applicationInfo, DEFAULT_MAX_LABEL_SIZE_PX, context);
394     }
395 
396     /**
397      * Get the full label for an application without truncation.
398      *
399      * @param applicationInfo the {@link ApplicationInfo} of the application
400      * @param context the {@code Context} to retrieve {@code PackageManager}
401      *
402      * @return the label for the application
403      */
404     @NonNull
getFullAppLabel(@onNull ApplicationInfo applicationInfo, @NonNull Context context)405     public static String getFullAppLabel(@NonNull ApplicationInfo applicationInfo,
406             @NonNull Context context) {
407         return getAppLabel(applicationInfo, 0, context);
408     }
409 
410     /**
411      * Get the label for an application with the ability to control truncating.
412      *
413      * @param applicationInfo the {@link ApplicationInfo} of the application
414      * @param ellipsizeDip see {@link TextUtils#makeSafeForPresentation}.
415      * @param context the {@code Context} to retrieve {@code PackageManager}
416      *
417      * @return the label for the application
418      */
419     @NonNull
getAppLabel(@onNull ApplicationInfo applicationInfo, float ellipsizeDip, @NonNull Context context)420     private static String getAppLabel(@NonNull ApplicationInfo applicationInfo, float ellipsizeDip,
421             @NonNull Context context) {
422         return BidiFormatter.getInstance().unicodeWrap(applicationInfo.loadSafeLabel(
423                 context.getPackageManager(), ellipsizeDip,
424                 TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE)
425                 .toString());
426     }
427 
loadDrawable(PackageManager pm, String pkg, int resId)428     public static Drawable loadDrawable(PackageManager pm, String pkg, int resId) {
429         try {
430             return pm.getResourcesForApplication(pkg).getDrawable(resId, null);
431         } catch (Resources.NotFoundException | PackageManager.NameNotFoundException e) {
432             Log.d(LOG_TAG, "Couldn't get resource", e);
433             return null;
434         }
435     }
436 
isModernPermissionGroup(String name)437     public static boolean isModernPermissionGroup(String name) {
438         return PLATFORM_PERMISSION_GROUPS.containsKey(name);
439     }
440 
441     /**
442      * Get the names of the platform permission groups.
443      *
444      * @return the names of the platform permission groups.
445      */
getPlatformPermissionGroups()446     public static List<String> getPlatformPermissionGroups() {
447         return new ArrayList<>(PLATFORM_PERMISSION_GROUPS.keySet());
448     }
449 
450     /**
451      * Get the names of the platform permissions.
452      *
453      * @return the names of the platform permissions.
454      */
getPlatformPermissions()455     public static Set<String> getPlatformPermissions() {
456         return PLATFORM_PERMISSIONS.keySet();
457     }
458 
459     /**
460      * Should UI show this permission.
461      *
462      * <p>If the user cannot change the group, it should not be shown.
463      *
464      * @param group The group that might need to be shown to the user
465      *
466      * @return
467      */
shouldShowPermission(Context context, AppPermissionGroup group)468     public static boolean shouldShowPermission(Context context, AppPermissionGroup group) {
469         if (!group.isGrantingAllowed()) {
470             return false;
471         }
472 
473         final boolean isPlatformPermission = group.getDeclaringPackage().equals(OS_PKG);
474         // Show legacy permissions only if the user chose that.
475         if (isPlatformPermission
476                 && !Utils.isModernPermissionGroup(group.getName())) {
477             return false;
478         }
479         return true;
480     }
481 
applyTint(Context context, Drawable icon, int attr)482     public static Drawable applyTint(Context context, Drawable icon, int attr) {
483         Theme theme = context.getTheme();
484         TypedValue typedValue = new TypedValue();
485         theme.resolveAttribute(attr, typedValue, true);
486         icon = icon.mutate();
487         icon.setTint(context.getColor(typedValue.resourceId));
488         return icon;
489     }
490 
applyTint(Context context, int iconResId, int attr)491     public static Drawable applyTint(Context context, int iconResId, int attr) {
492         return applyTint(context, context.getDrawable(iconResId), attr);
493     }
494 
getLauncherPackages(Context context)495     public static ArraySet<String> getLauncherPackages(Context context) {
496         ArraySet<String> launcherPkgs = new ArraySet<>();
497         for (ResolveInfo info : context.getPackageManager().queryIntentActivities(LAUNCHER_INTENT,
498                 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE)) {
499             launcherPkgs.add(info.activityInfo.packageName);
500         }
501 
502         return launcherPkgs;
503     }
504 
getAllInstalledApplications(Context context)505     public static List<ApplicationInfo> getAllInstalledApplications(Context context) {
506         return context.getPackageManager().getInstalledApplications(0);
507     }
508 
509     /**
510      * Is the group or background group user sensitive?
511      *
512      * @param group The group that might be user sensitive
513      *
514      * @return {@code true} if the group (or it's subgroup) is user sensitive.
515      */
isGroupOrBgGroupUserSensitive(AppPermissionGroup group)516     public static boolean isGroupOrBgGroupUserSensitive(AppPermissionGroup group) {
517         return group.isUserSensitive() || (group.getBackgroundPermissions() != null
518                 && group.getBackgroundPermissions().isUserSensitive());
519     }
520 
areGroupPermissionsIndividuallyControlled(Context context, String group)521     public static boolean areGroupPermissionsIndividuallyControlled(Context context, String group) {
522         if (!context.getPackageManager().arePermissionsIndividuallyControlled()) {
523             return false;
524         }
525         return Manifest.permission_group.SMS.equals(group)
526                 || Manifest.permission_group.PHONE.equals(group)
527                 || Manifest.permission_group.CONTACTS.equals(group);
528     }
529 
isPermissionIndividuallyControlled(Context context, String permission)530     public static boolean isPermissionIndividuallyControlled(Context context, String permission) {
531         if (!context.getPackageManager().arePermissionsIndividuallyControlled()) {
532             return false;
533         }
534         return Manifest.permission.READ_CONTACTS.equals(permission)
535                 || Manifest.permission.WRITE_CONTACTS.equals(permission)
536                 || Manifest.permission.SEND_SMS.equals(permission)
537                 || Manifest.permission.RECEIVE_SMS.equals(permission)
538                 || Manifest.permission.READ_SMS.equals(permission)
539                 || Manifest.permission.RECEIVE_MMS.equals(permission)
540                 || Manifest.permission.CALL_PHONE.equals(permission)
541                 || Manifest.permission.READ_CALL_LOG.equals(permission)
542                 || Manifest.permission.WRITE_CALL_LOG.equals(permission);
543     }
544 
545     /**
546      * Get the message shown to grant a permission group to an app.
547      *
548      * @param appLabel The label of the app
549      * @param group the group to be granted
550      * @param context A context to resolve resources
551      * @param requestRes The resource id of the grant request message
552      *
553      * @return The formatted message to be used as title when granting permissions
554      */
getRequestMessage(CharSequence appLabel, AppPermissionGroup group, Context context, @StringRes int requestRes)555     public static CharSequence getRequestMessage(CharSequence appLabel, AppPermissionGroup group,
556             Context context, @StringRes int requestRes) {
557         if (group.getName().equals(STORAGE) && !group.isNonIsolatedStorage()) {
558             return Html.fromHtml(
559                     String.format(context.getResources().getConfiguration().getLocales().get(0),
560                             context.getString(R.string.permgrouprequest_storage_isolated),
561                             appLabel), 0);
562         } else if (requestRes != 0) {
563             try {
564                 return Html.fromHtml(context.getPackageManager().getResourcesForApplication(
565                         group.getDeclaringPackage()).getString(requestRes, appLabel), 0);
566             } catch (PackageManager.NameNotFoundException ignored) {
567             }
568         }
569 
570         return Html.fromHtml(context.getString(R.string.permission_warning_template, appLabel,
571                 group.getDescription()), 0);
572     }
573 
574     /**
575      * Build a string representing the given time if it happened on the current day and the date
576      * otherwise.
577      *
578      * @param context the context.
579      * @param lastAccessTime the time in milliseconds.
580      *
581      * @return a string representing the time or date of the given time or null if the time is 0.
582      */
getAbsoluteTimeString(@onNull Context context, long lastAccessTime)583     public static @Nullable String getAbsoluteTimeString(@NonNull Context context,
584             long lastAccessTime) {
585         if (lastAccessTime == 0) {
586             return null;
587         }
588         if (isToday(lastAccessTime)) {
589             return DateFormat.getTimeFormat(context).format(lastAccessTime);
590         } else {
591             return DateFormat.getMediumDateFormat(context).format(lastAccessTime);
592         }
593     }
594 
595     /**
596      * Build a string representing the number of milliseconds passed in.  It rounds to the nearest
597      * unit.  For example, given a duration of 3500 and an English locale, this can return
598      * "3 seconds".
599      * @param context The context.
600      * @param duration The number of milliseconds.
601      * @return a string representing the given number of milliseconds.
602      */
getTimeDiffStr(@onNull Context context, long duration)603     public static @NonNull String getTimeDiffStr(@NonNull Context context, long duration) {
604         long seconds = Math.max(1, duration / 1000);
605         if (seconds < 60) {
606             return context.getResources().getQuantityString(R.plurals.seconds, (int) seconds,
607                     seconds);
608         }
609         long minutes = seconds / 60;
610         if (minutes < 60) {
611             return context.getResources().getQuantityString(R.plurals.minutes, (int) minutes,
612                     minutes);
613         }
614         long hours = minutes / 60;
615         if (hours < 24) {
616             return context.getResources().getQuantityString(R.plurals.hours, (int) hours, hours);
617         }
618         long days = hours / 24;
619         return context.getResources().getQuantityString(R.plurals.days, (int) days, days);
620     }
621 
622     /**
623      * Check whether the given time (in milliseconds) is in the current day.
624      *
625      * @param time the time in milliseconds
626      *
627      * @return whether the given time is in the current day.
628      */
isToday(long time)629     private static boolean isToday(long time) {
630         Calendar today = Calendar.getInstance(Locale.getDefault());
631         today.setTimeInMillis(System.currentTimeMillis());
632         today.set(Calendar.HOUR_OF_DAY, 0);
633         today.set(Calendar.MINUTE, 0);
634         today.set(Calendar.SECOND, 0);
635         today.set(Calendar.MILLISECOND, 0);
636 
637         Calendar date = Calendar.getInstance(Locale.getDefault());
638         date.setTimeInMillis(time);
639         return !date.before(today);
640     }
641 
642     /**
643      * Add a menu item for searching Settings, if there is an activity handling the action.
644      *
645      * @param menu the menu to add the menu item into
646      * @param context the context for checking whether there is an activity handling the action
647      */
prepareSearchMenuItem(@onNull Menu menu, @NonNull Context context)648     public static void prepareSearchMenuItem(@NonNull Menu menu, @NonNull Context context) {
649         Intent intent = new Intent(Settings.ACTION_APP_SEARCH_SETTINGS);
650         if (context.getPackageManager().resolveActivity(intent, 0) == null) {
651             return;
652         }
653         MenuItem searchItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, R.string.search_menu);
654         searchItem.setIcon(R.drawable.ic_search_24dp);
655         searchItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
656         searchItem.setOnMenuItemClickListener(item -> {
657             try {
658                 context.startActivity(intent);
659             } catch (ActivityNotFoundException e) {
660                 Log.e(LOG_TAG, "Cannot start activity to search settings", e);
661             }
662             return true;
663         });
664     }
665 
666     /**
667      * Get badged app icon if necessary, similar as used in the Settings UI.
668      *
669      * @param context The context to use
670      * @param appInfo The app the icon belong to
671      *
672      * @return The icon to use
673      */
getBadgedIcon(@onNull Context context, @NonNull ApplicationInfo appInfo)674     public static @NonNull Drawable getBadgedIcon(@NonNull Context context,
675             @NonNull ApplicationInfo appInfo) {
676         UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid);
677         try (IconFactory iconFactory = IconFactory.obtain(context)) {
678             Bitmap iconBmp = iconFactory.createBadgedIconBitmap(
679                     appInfo.loadUnbadgedIcon(context.getPackageManager()), user, false).icon;
680             return new BitmapDrawable(context.getResources(), iconBmp);
681         }
682     }
683 
684     /**
685      * Get a string saying what apps with the given permission group can do.
686      *
687      * @param context The context to use
688      * @param groupName The name of the permission group
689      * @param description The description of the permission group
690      *
691      * @return a string saying what apps with the given permission group can do.
692      */
getPermissionGroupDescriptionString(@onNull Context context, @NonNull String groupName, @NonNull CharSequence description)693     public static @NonNull String getPermissionGroupDescriptionString(@NonNull Context context,
694             @NonNull String groupName, @NonNull CharSequence description) {
695         switch (groupName) {
696             case ACTIVITY_RECOGNITION:
697                 return context.getString(
698                         R.string.permission_description_summary_activity_recognition);
699             case CALENDAR:
700                 return context.getString(R.string.permission_description_summary_calendar);
701             case CALL_LOG:
702                 return context.getString(R.string.permission_description_summary_call_log);
703             case CAMERA:
704                 return context.getString(R.string.permission_description_summary_camera);
705             case CONTACTS:
706                 return context.getString(R.string.permission_description_summary_contacts);
707             case LOCATION:
708                 return context.getString(R.string.permission_description_summary_location);
709             case MICROPHONE:
710                 return context.getString(R.string.permission_description_summary_microphone);
711             case PHONE:
712                 return context.getString(R.string.permission_description_summary_phone);
713             case SENSORS:
714                 return context.getString(R.string.permission_description_summary_sensors);
715             case SMS:
716                 return context.getString(R.string.permission_description_summary_sms);
717             case STORAGE:
718                 return context.getString(R.string.permission_description_summary_storage);
719             default:
720                 return context.getString(R.string.permission_description_summary_generic,
721                         description);
722         }
723     }
724 
725     /**
726      * Whether the Location Access Check is enabled.
727      *
728      * @return {@code true} iff the Location Access Check is enabled.
729      */
isLocationAccessCheckEnabled()730     public static boolean isLocationAccessCheckEnabled() {
731         return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
732                 PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, true);
733     }
734 
735     /**
736      * Get a device protected storage based shared preferences. Avoid storing sensitive data in it.
737      *
738      * @param context the context to get the shared preferences
739      * @return a device protected storage based shared preferences
740      */
741     @NonNull
getDeviceProtectedSharedPreferences(@onNull Context context)742     public static SharedPreferences getDeviceProtectedSharedPreferences(@NonNull Context context) {
743         if (!context.isDeviceProtectedStorage()) {
744             context = context.createDeviceProtectedStorageContext();
745         }
746         return context.getSharedPreferences(Constants.PREFERENCES_FILE, MODE_PRIVATE);
747     }
748 
749     /**
750      * Update the {@link PackageManager#FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED} and
751      * {@link PackageManager#FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED} for all apps of this user.
752      *
753      * @see PerUserUidToSensitivityLiveData#loadValueInBackground()
754      */
updateUserSensitive(@onNull Application application, @NonNull UserHandle user)755     public static void updateUserSensitive(@NonNull Application application,
756             @NonNull UserHandle user) {
757         Context userContext = getParentUserContext(application);
758         PackageManager pm = userContext.getPackageManager();
759         RoleManager rm = userContext.getSystemService(RoleManager.class);
760         SharedPreferences prefs = userContext.getSharedPreferences(PREFERENCES_FILE, MODE_PRIVATE);
761 
762         boolean showAssistantRecordAudio = prefs.getBoolean(
763                 ASSISTANT_RECORD_AUDIO_IS_USER_SENSITIVE_KEY, false);
764         Set<String> overriddenUids = prefs.getStringSet(FORCED_USER_SENSITIVE_UIDS_KEY,
765                 Collections.emptySet());
766 
767         List<String> assistants = rm.getRoleHolders(ROLE_ASSISTANT);
768         String assistant = null;
769         if (!assistants.isEmpty()) {
770             if (assistants.size() > 1) {
771                 Log.wtf(LOG_TAG, "Assistant role is not exclusive");
772             }
773 
774             // Assistant is an exclusive role
775             assistant = assistants.get(0);
776         }
777 
778         PerUserUidToSensitivityLiveData appUserSensitivityLiveData =
779                 PerUserUidToSensitivityLiveData.get(user, application);
780 
781         // uid -> permission -> flags
782         SparseArray<ArrayMap<String, Integer>> uidUserSensitivity =
783                 appUserSensitivityLiveData.loadValueInBackground();
784 
785         // Apply the update
786         int numUids = uidUserSensitivity.size();
787         for (int uidNum = 0; uidNum < numUids; uidNum++) {
788             int uid = uidUserSensitivity.keyAt(uidNum);
789 
790             String[] uidPkgs = pm.getPackagesForUid(uid);
791             if (uidPkgs == null) {
792                 continue;
793             }
794 
795             boolean isOverridden = overriddenUids.contains(String.valueOf(uid));
796             boolean isAssistantUid = ArrayUtils.contains(uidPkgs, assistant);
797 
798             ArrayMap<String, Integer> uidPermissions = uidUserSensitivity.valueAt(uidNum);
799 
800             int numPerms = uidPermissions.size();
801             for (int permNum = 0; permNum < numPerms; permNum++) {
802                 String perm = uidPermissions.keyAt(permNum);
803 
804                 for (String uidPkg : uidPkgs) {
805                     int flags = isOverridden ? FLAGS_ALWAYS_USER_SENSITIVE : uidPermissions.valueAt(
806                             permNum);
807 
808                     if (isAssistantUid && perm.equals(RECORD_AUDIO)) {
809                         flags = showAssistantRecordAudio ? FLAGS_ALWAYS_USER_SENSITIVE : 0;
810                     }
811 
812                     try {
813                         pm.updatePermissionFlags(perm, uidPkg, FLAGS_ALWAYS_USER_SENSITIVE, flags,
814                                 user);
815                         break;
816                     } catch (IllegalArgumentException e) {
817                         Log.e(LOG_TAG, "Unexpected exception while updating flags for "
818                                 + uidPkg + " permission " + perm, e);
819                     }
820                 }
821             }
822         }
823     }
824 
825     /**
826      * Get context of the parent user of the profile group (i.e. usually the 'personal' profile,
827      * not the 'work' profile).
828      *
829      * @param context The context of a user of the profile user group.
830      *
831      * @return The context of the parent user
832      */
getParentUserContext(@onNull Context context)833     public static Context getParentUserContext(@NonNull Context context) {
834         UserHandle parentUser = getSystemServiceSafe(context, UserManager.class)
835                 .getProfileParent(UserHandle.of(myUserId()));
836 
837         if (parentUser == null) {
838             return context;
839         }
840 
841         // In a multi profile environment perform all operations as the parent user of the
842         // current profile
843         try {
844             return context.createPackageContextAsUser(context.getPackageName(), 0,
845                     parentUser);
846         } catch (PackageManager.NameNotFoundException e) {
847             // cannot happen
848             throw new IllegalStateException("Could not switch to parent user " + parentUser, e);
849         }
850     }
851 }
852