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