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 = pm.queryPermissionsByGroup(group, 0);
320         permissions.addAll(getPlatformPermissionsOfGroup(pm, group));
321 
322         return permissions;
323     }
324 
325     /**
326      * Get the {@link PackageItemInfo infos} for the given permission group.
327      *
328      * @param groupName the group
329      * @param context the {@code Context} to retrieve {@code PackageManager}
330      *
331      * @return The info of permission group or null if the group does not have runtime permissions.
332      */
getGroupInfo(@onNull String groupName, @NonNull Context context)333     public static @Nullable PackageItemInfo getGroupInfo(@NonNull String groupName,
334             @NonNull Context context) {
335         try {
336             return context.getPackageManager().getPermissionGroupInfo(groupName, 0);
337         } catch (NameNotFoundException e) {
338             /* ignore */
339         }
340         try {
341             return context.getPackageManager().getPermissionInfo(groupName, 0);
342         } catch (NameNotFoundException e) {
343             /* ignore */
344         }
345         return null;
346     }
347 
348     /**
349      * Get the {@link PermissionInfo infos} for all permission infos belonging to a group.
350      *
351      * @param groupName the group
352      * @param context the {@code Context} to retrieve {@code PackageManager}
353      *
354      * @return The infos of permissions belonging to the group or null if the group does not have
355      *         runtime permissions.
356      */
getGroupPermissionInfos(@onNull String groupName, @NonNull Context context)357     public static @Nullable List<PermissionInfo> getGroupPermissionInfos(@NonNull String groupName,
358             @NonNull Context context) {
359         try {
360             return Utils.getPermissionInfosForGroup(context.getPackageManager(), groupName);
361         } catch (NameNotFoundException e) {
362             /* ignore */
363         }
364         try {
365             PermissionInfo permissionInfo = context.getPackageManager()
366                     .getPermissionInfo(groupName, 0);
367             List<PermissionInfo> permissions = new ArrayList<>();
368             permissions.add(permissionInfo);
369             return permissions;
370         } catch (NameNotFoundException e) {
371             /* ignore */
372         }
373         return null;
374     }
375 
376     /**
377      * Get the label for an application, truncating if it is too long.
378      *
379      * @param applicationInfo the {@link ApplicationInfo} of the application
380      * @param context the {@code Context} to retrieve {@code PackageManager}
381      *
382      * @return the label for the application
383      */
384     @NonNull
getAppLabel(@onNull ApplicationInfo applicationInfo, @NonNull Context context)385     public static String getAppLabel(@NonNull ApplicationInfo applicationInfo,
386             @NonNull Context context) {
387         return getAppLabel(applicationInfo, DEFAULT_MAX_LABEL_SIZE_PX, context);
388     }
389 
390     /**
391      * Get the full label for an application without truncation.
392      *
393      * @param applicationInfo the {@link ApplicationInfo} of the application
394      * @param context the {@code Context} to retrieve {@code PackageManager}
395      *
396      * @return the label for the application
397      */
398     @NonNull
getFullAppLabel(@onNull ApplicationInfo applicationInfo, @NonNull Context context)399     public static String getFullAppLabel(@NonNull ApplicationInfo applicationInfo,
400             @NonNull Context context) {
401         return getAppLabel(applicationInfo, 0, context);
402     }
403 
404     /**
405      * Get the label for an application with the ability to control truncating.
406      *
407      * @param applicationInfo the {@link ApplicationInfo} of the application
408      * @param ellipsizeDip see {@link TextUtils#makeSafeForPresentation}.
409      * @param context the {@code Context} to retrieve {@code PackageManager}
410      *
411      * @return the label for the application
412      */
413     @NonNull
getAppLabel(@onNull ApplicationInfo applicationInfo, float ellipsizeDip, @NonNull Context context)414     private static String getAppLabel(@NonNull ApplicationInfo applicationInfo, float ellipsizeDip,
415             @NonNull Context context) {
416         return BidiFormatter.getInstance().unicodeWrap(applicationInfo.loadSafeLabel(
417                 context.getPackageManager(), ellipsizeDip,
418                 TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE)
419                 .toString());
420     }
421 
loadDrawable(PackageManager pm, String pkg, int resId)422     public static Drawable loadDrawable(PackageManager pm, String pkg, int resId) {
423         try {
424             return pm.getResourcesForApplication(pkg).getDrawable(resId, null);
425         } catch (Resources.NotFoundException | PackageManager.NameNotFoundException e) {
426             Log.d(LOG_TAG, "Couldn't get resource", e);
427             return null;
428         }
429     }
430 
isModernPermissionGroup(String name)431     public static boolean isModernPermissionGroup(String name) {
432         return PLATFORM_PERMISSION_GROUPS.containsKey(name);
433     }
434 
435     /**
436      * Get the names of the platform permission groups.
437      *
438      * @return the names of the platform permission groups.
439      */
getPlatformPermissionGroups()440     public static List<String> getPlatformPermissionGroups() {
441         return new ArrayList<>(PLATFORM_PERMISSION_GROUPS.keySet());
442     }
443 
444     /**
445      * Get the names of the platform permissions.
446      *
447      * @return the names of the platform permissions.
448      */
getPlatformPermissions()449     public static Set<String> getPlatformPermissions() {
450         return PLATFORM_PERMISSIONS.keySet();
451     }
452 
453     /**
454      * Should UI show this permission.
455      *
456      * <p>If the user cannot change the group, it should not be shown.
457      *
458      * @param group The group that might need to be shown to the user
459      *
460      * @return
461      */
shouldShowPermission(Context context, AppPermissionGroup group)462     public static boolean shouldShowPermission(Context context, AppPermissionGroup group) {
463         if (!group.isGrantingAllowed()) {
464             return false;
465         }
466 
467         final boolean isPlatformPermission = group.getDeclaringPackage().equals(OS_PKG);
468         // Show legacy permissions only if the user chose that.
469         if (isPlatformPermission
470                 && !Utils.isModernPermissionGroup(group.getName())) {
471             return false;
472         }
473         return true;
474     }
475 
applyTint(Context context, Drawable icon, int attr)476     public static Drawable applyTint(Context context, Drawable icon, int attr) {
477         Theme theme = context.getTheme();
478         TypedValue typedValue = new TypedValue();
479         theme.resolveAttribute(attr, typedValue, true);
480         icon = icon.mutate();
481         icon.setTint(context.getColor(typedValue.resourceId));
482         return icon;
483     }
484 
applyTint(Context context, int iconResId, int attr)485     public static Drawable applyTint(Context context, int iconResId, int attr) {
486         return applyTint(context, context.getDrawable(iconResId), attr);
487     }
488 
getLauncherPackages(Context context)489     public static ArraySet<String> getLauncherPackages(Context context) {
490         ArraySet<String> launcherPkgs = new ArraySet<>();
491         for (ResolveInfo info : context.getPackageManager().queryIntentActivities(LAUNCHER_INTENT,
492                 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE)) {
493             launcherPkgs.add(info.activityInfo.packageName);
494         }
495 
496         return launcherPkgs;
497     }
498 
getAllInstalledApplications(Context context)499     public static List<ApplicationInfo> getAllInstalledApplications(Context context) {
500         return context.getPackageManager().getInstalledApplications(0);
501     }
502 
503     /**
504      * Is the group or background group user sensitive?
505      *
506      * @param group The group that might be user sensitive
507      *
508      * @return {@code true} if the group (or it's subgroup) is user sensitive.
509      */
isGroupOrBgGroupUserSensitive(AppPermissionGroup group)510     public static boolean isGroupOrBgGroupUserSensitive(AppPermissionGroup group) {
511         return group.isUserSensitive() || (group.getBackgroundPermissions() != null
512                 && group.getBackgroundPermissions().isUserSensitive());
513     }
514 
areGroupPermissionsIndividuallyControlled(Context context, String group)515     public static boolean areGroupPermissionsIndividuallyControlled(Context context, String group) {
516         if (!context.getPackageManager().arePermissionsIndividuallyControlled()) {
517             return false;
518         }
519         return Manifest.permission_group.SMS.equals(group)
520                 || Manifest.permission_group.PHONE.equals(group)
521                 || Manifest.permission_group.CONTACTS.equals(group);
522     }
523 
isPermissionIndividuallyControlled(Context context, String permission)524     public static boolean isPermissionIndividuallyControlled(Context context, String permission) {
525         if (!context.getPackageManager().arePermissionsIndividuallyControlled()) {
526             return false;
527         }
528         return Manifest.permission.READ_CONTACTS.equals(permission)
529                 || Manifest.permission.WRITE_CONTACTS.equals(permission)
530                 || Manifest.permission.SEND_SMS.equals(permission)
531                 || Manifest.permission.RECEIVE_SMS.equals(permission)
532                 || Manifest.permission.READ_SMS.equals(permission)
533                 || Manifest.permission.RECEIVE_MMS.equals(permission)
534                 || Manifest.permission.CALL_PHONE.equals(permission)
535                 || Manifest.permission.READ_CALL_LOG.equals(permission)
536                 || Manifest.permission.WRITE_CALL_LOG.equals(permission);
537     }
538 
539     /**
540      * Get the message shown to grant a permission group to an app.
541      *
542      * @param appLabel The label of the app
543      * @param group the group to be granted
544      * @param context A context to resolve resources
545      * @param requestRes The resource id of the grant request message
546      *
547      * @return The formatted message to be used as title when granting permissions
548      */
getRequestMessage(CharSequence appLabel, AppPermissionGroup group, Context context, @StringRes int requestRes)549     public static CharSequence getRequestMessage(CharSequence appLabel, AppPermissionGroup group,
550             Context context, @StringRes int requestRes) {
551         if (group.getName().equals(STORAGE) && !group.isNonIsolatedStorage()) {
552             return Html.fromHtml(
553                     String.format(context.getResources().getConfiguration().getLocales().get(0),
554                             context.getString(R.string.permgrouprequest_storage_isolated),
555                             appLabel), 0);
556         } else if (requestRes != 0) {
557             try {
558                 return Html.fromHtml(context.getPackageManager().getResourcesForApplication(
559                         group.getDeclaringPackage()).getString(requestRes, appLabel), 0);
560             } catch (PackageManager.NameNotFoundException ignored) {
561             }
562         }
563 
564         return Html.fromHtml(context.getString(R.string.permission_warning_template, appLabel,
565                 group.getDescription()), 0);
566     }
567 
568     /**
569      * Build a string representing the given time if it happened on the current day and the date
570      * otherwise.
571      *
572      * @param context the context.
573      * @param lastAccessTime the time in milliseconds.
574      *
575      * @return a string representing the time or date of the given time or null if the time is 0.
576      */
getAbsoluteTimeString(@onNull Context context, long lastAccessTime)577     public static @Nullable String getAbsoluteTimeString(@NonNull Context context,
578             long lastAccessTime) {
579         if (lastAccessTime == 0) {
580             return null;
581         }
582         if (isToday(lastAccessTime)) {
583             return DateFormat.getTimeFormat(context).format(lastAccessTime);
584         } else {
585             return DateFormat.getMediumDateFormat(context).format(lastAccessTime);
586         }
587     }
588 
589     /**
590      * Build a string representing the number of milliseconds passed in.  It rounds to the nearest
591      * unit.  For example, given a duration of 3500 and an English locale, this can return
592      * "3 seconds".
593      * @param context The context.
594      * @param duration The number of milliseconds.
595      * @return a string representing the given number of milliseconds.
596      */
getTimeDiffStr(@onNull Context context, long duration)597     public static @NonNull String getTimeDiffStr(@NonNull Context context, long duration) {
598         long seconds = Math.max(1, duration / 1000);
599         if (seconds < 60) {
600             return context.getResources().getQuantityString(R.plurals.seconds, (int) seconds,
601                     seconds);
602         }
603         long minutes = seconds / 60;
604         if (minutes < 60) {
605             return context.getResources().getQuantityString(R.plurals.minutes, (int) minutes,
606                     minutes);
607         }
608         long hours = minutes / 60;
609         if (hours < 24) {
610             return context.getResources().getQuantityString(R.plurals.hours, (int) hours, hours);
611         }
612         long days = hours / 24;
613         return context.getResources().getQuantityString(R.plurals.days, (int) days, days);
614     }
615 
616     /**
617      * Check whether the given time (in milliseconds) is in the current day.
618      *
619      * @param time the time in milliseconds
620      *
621      * @return whether the given time is in the current day.
622      */
isToday(long time)623     private static boolean isToday(long time) {
624         Calendar today = Calendar.getInstance(Locale.getDefault());
625         today.setTimeInMillis(System.currentTimeMillis());
626         today.set(Calendar.HOUR_OF_DAY, 0);
627         today.set(Calendar.MINUTE, 0);
628         today.set(Calendar.SECOND, 0);
629         today.set(Calendar.MILLISECOND, 0);
630 
631         Calendar date = Calendar.getInstance(Locale.getDefault());
632         date.setTimeInMillis(time);
633         return !date.before(today);
634     }
635 
636     /**
637      * Add a menu item for searching Settings, if there is an activity handling the action.
638      *
639      * @param menu the menu to add the menu item into
640      * @param context the context for checking whether there is an activity handling the action
641      */
prepareSearchMenuItem(@onNull Menu menu, @NonNull Context context)642     public static void prepareSearchMenuItem(@NonNull Menu menu, @NonNull Context context) {
643         Intent intent = new Intent(Settings.ACTION_APP_SEARCH_SETTINGS);
644         if (context.getPackageManager().resolveActivity(intent, 0) == null) {
645             return;
646         }
647         MenuItem searchItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, R.string.search_menu);
648         searchItem.setIcon(R.drawable.ic_search_24dp);
649         searchItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
650         searchItem.setOnMenuItemClickListener(item -> {
651             try {
652                 context.startActivity(intent);
653             } catch (ActivityNotFoundException e) {
654                 Log.e(LOG_TAG, "Cannot start activity to search settings", e);
655             }
656             return true;
657         });
658     }
659 
660     /**
661      * Get badged app icon if necessary, similar as used in the Settings UI.
662      *
663      * @param context The context to use
664      * @param appInfo The app the icon belong to
665      *
666      * @return The icon to use
667      */
getBadgedIcon(@onNull Context context, @NonNull ApplicationInfo appInfo)668     public static @NonNull Drawable getBadgedIcon(@NonNull Context context,
669             @NonNull ApplicationInfo appInfo) {
670         UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid);
671         try (IconFactory iconFactory = IconFactory.obtain(context)) {
672             Bitmap iconBmp = iconFactory.createBadgedIconBitmap(
673                     appInfo.loadUnbadgedIcon(context.getPackageManager()), user, false).icon;
674             return new BitmapDrawable(context.getResources(), iconBmp);
675         }
676     }
677 
678     /**
679      * Get a string saying what apps with the given permission group can do.
680      *
681      * @param context The context to use
682      * @param groupName The name of the permission group
683      * @param description The description of the permission group
684      *
685      * @return a string saying what apps with the given permission group can do.
686      */
getPermissionGroupDescriptionString(@onNull Context context, @NonNull String groupName, @NonNull CharSequence description)687     public static @NonNull String getPermissionGroupDescriptionString(@NonNull Context context,
688             @NonNull String groupName, @NonNull CharSequence description) {
689         switch (groupName) {
690             case ACTIVITY_RECOGNITION:
691                 return context.getString(
692                         R.string.permission_description_summary_activity_recognition);
693             case CALENDAR:
694                 return context.getString(R.string.permission_description_summary_calendar);
695             case CALL_LOG:
696                 return context.getString(R.string.permission_description_summary_call_log);
697             case CAMERA:
698                 return context.getString(R.string.permission_description_summary_camera);
699             case CONTACTS:
700                 return context.getString(R.string.permission_description_summary_contacts);
701             case LOCATION:
702                 return context.getString(R.string.permission_description_summary_location);
703             case MICROPHONE:
704                 return context.getString(R.string.permission_description_summary_microphone);
705             case PHONE:
706                 return context.getString(R.string.permission_description_summary_phone);
707             case SENSORS:
708                 return context.getString(R.string.permission_description_summary_sensors);
709             case SMS:
710                 return context.getString(R.string.permission_description_summary_sms);
711             case STORAGE:
712                 return context.getString(R.string.permission_description_summary_storage);
713             default:
714                 return context.getString(R.string.permission_description_summary_generic,
715                         description);
716         }
717     }
718 
719     /**
720      * Whether the Location Access Check is enabled.
721      *
722      * @return {@code true} iff the Location Access Check is enabled.
723      */
isLocationAccessCheckEnabled()724     public static boolean isLocationAccessCheckEnabled() {
725         return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
726                 PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, true);
727     }
728 
729     /**
730      * Get a device protected storage based shared preferences. Avoid storing sensitive data in it.
731      *
732      * @param context the context to get the shared preferences
733      * @return a device protected storage based shared preferences
734      */
735     @NonNull
getDeviceProtectedSharedPreferences(@onNull Context context)736     public static SharedPreferences getDeviceProtectedSharedPreferences(@NonNull Context context) {
737         if (!context.isDeviceProtectedStorage()) {
738             context = context.createDeviceProtectedStorageContext();
739         }
740         return context.getSharedPreferences(Constants.PREFERENCES_FILE, MODE_PRIVATE);
741     }
742 
743     /**
744      * Update the {@link PackageManager#FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED} and
745      * {@link PackageManager#FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED} for all apps of this user.
746      *
747      * @see PerUserUidToSensitivityLiveData#loadValueInBackground()
748      */
updateUserSensitive(@onNull Application application, @NonNull UserHandle user)749     public static void updateUserSensitive(@NonNull Application application,
750             @NonNull UserHandle user) {
751         Context userContext = getParentUserContext(application);
752         PackageManager pm = userContext.getPackageManager();
753         RoleManager rm = userContext.getSystemService(RoleManager.class);
754         SharedPreferences prefs = userContext.getSharedPreferences(PREFERENCES_FILE, MODE_PRIVATE);
755 
756         boolean showAssistantRecordAudio = prefs.getBoolean(
757                 ASSISTANT_RECORD_AUDIO_IS_USER_SENSITIVE_KEY, false);
758         Set<String> overriddenUids = prefs.getStringSet(FORCED_USER_SENSITIVE_UIDS_KEY,
759                 Collections.emptySet());
760 
761         List<String> assistants = rm.getRoleHolders(ROLE_ASSISTANT);
762         String assistant = null;
763         if (!assistants.isEmpty()) {
764             if (assistants.size() > 1) {
765                 Log.wtf(LOG_TAG, "Assistant role is not exclusive");
766             }
767 
768             // Assistant is an exclusive role
769             assistant = assistants.get(0);
770         }
771 
772         PerUserUidToSensitivityLiveData appUserSensitivityLiveData =
773                 PerUserUidToSensitivityLiveData.get(user, application);
774 
775         // uid -> permission -> flags
776         SparseArray<ArrayMap<String, Integer>> uidUserSensitivity =
777                 appUserSensitivityLiveData.loadValueInBackground();
778 
779         // Apply the update
780         int numUids = uidUserSensitivity.size();
781         for (int uidNum = 0; uidNum < numUids; uidNum++) {
782             int uid = uidUserSensitivity.keyAt(uidNum);
783 
784             String[] uidPkgs = pm.getPackagesForUid(uid);
785             if (uidPkgs == null) {
786                 continue;
787             }
788 
789             boolean isOverridden = overriddenUids.contains(String.valueOf(uid));
790             boolean isAssistantUid = ArrayUtils.contains(uidPkgs, assistant);
791 
792             ArrayMap<String, Integer> uidPermissions = uidUserSensitivity.valueAt(uidNum);
793 
794             int numPerms = uidPermissions.size();
795             for (int permNum = 0; permNum < numPerms; permNum++) {
796                 String perm = uidPermissions.keyAt(permNum);
797 
798                 for (String uidPkg : uidPkgs) {
799                     int flags = isOverridden ? FLAGS_ALWAYS_USER_SENSITIVE : uidPermissions.valueAt(
800                             permNum);
801 
802                     if (isAssistantUid && perm.equals(RECORD_AUDIO)) {
803                         flags = showAssistantRecordAudio ? FLAGS_ALWAYS_USER_SENSITIVE : 0;
804                     }
805 
806                     try {
807                         pm.updatePermissionFlags(perm, uidPkg, FLAGS_ALWAYS_USER_SENSITIVE, flags,
808                                 user);
809                         break;
810                     } catch (IllegalArgumentException e) {
811                         Log.e(LOG_TAG, "Unexpected exception while updating flags for "
812                                 + uidPkg + " permission " + perm, e);
813                     }
814                 }
815             }
816         }
817     }
818 
819     /**
820      * Get context of the parent user of the profile group (i.e. usually the 'personal' profile,
821      * not the 'work' profile).
822      *
823      * @param context The context of a user of the profile user group.
824      *
825      * @return The context of the parent user
826      */
getParentUserContext(@onNull Context context)827     public static Context getParentUserContext(@NonNull Context context) {
828         UserHandle parentUser = getSystemServiceSafe(context, UserManager.class)
829                 .getProfileParent(UserHandle.of(myUserId()));
830 
831         if (parentUser == null) {
832             return context;
833         }
834 
835         // In a multi profile environment perform all operations as the parent user of the
836         // current profile
837         try {
838             return context.createPackageContextAsUser(context.getPackageName(), 0,
839                     parentUser);
840         } catch (PackageManager.NameNotFoundException e) {
841             // cannot happen
842             throw new IllegalStateException("Could not switch to parent user " + parentUser, e);
843         }
844     }
845 }
846