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