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.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP;
46 import static android.os.UserHandle.myUserId;
47 
48 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
49 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
50 
51 import static java.lang.annotation.RetentionPolicy.SOURCE;
52 
53 import android.Manifest;
54 import android.app.AppOpsManager;
55 import android.app.Application;
56 import android.app.admin.DevicePolicyManager;
57 import android.app.ecm.EnhancedConfirmationManager;
58 import android.app.role.RoleManager;
59 import android.content.ActivityNotFoundException;
60 import android.content.ComponentName;
61 import android.content.Context;
62 import android.content.Intent;
63 import android.content.SharedPreferences;
64 import android.content.pm.ApplicationInfo;
65 import android.content.pm.PackageInfo;
66 import android.content.pm.PackageItemInfo;
67 import android.content.pm.PackageManager;
68 import android.content.pm.PackageManager.NameNotFoundException;
69 import android.content.pm.PermissionGroupInfo;
70 import android.content.pm.PermissionInfo;
71 import android.content.pm.ResolveInfo;
72 import android.content.pm.UserProperties;
73 import android.content.res.Resources;
74 import android.content.res.Resources.Theme;
75 import android.graphics.Bitmap;
76 import android.graphics.drawable.BitmapDrawable;
77 import android.graphics.drawable.Drawable;
78 import android.hardware.SensorPrivacyManager;
79 import android.health.connect.HealthConnectManager;
80 import android.os.Binder;
81 import android.os.Build;
82 import android.os.Parcelable;
83 import android.os.UserHandle;
84 import android.os.UserManager;
85 import android.permission.flags.Flags;
86 import android.provider.DeviceConfig;
87 import android.provider.Settings;
88 import android.text.Html;
89 import android.text.TextUtils;
90 import android.text.format.DateFormat;
91 import android.util.ArrayMap;
92 import android.util.Log;
93 import android.util.TypedValue;
94 import android.view.Menu;
95 import android.view.MenuItem;
96 
97 import androidx.annotation.ChecksSdkIntAtLeast;
98 import androidx.annotation.ColorRes;
99 import androidx.annotation.IntDef;
100 import androidx.annotation.NonNull;
101 import androidx.annotation.Nullable;
102 import androidx.annotation.RequiresApi;
103 import androidx.annotation.StringRes;
104 import androidx.core.text.BidiFormatter;
105 import androidx.core.util.Preconditions;
106 
107 import com.android.launcher3.icons.IconFactory;
108 import com.android.modules.utils.build.SdkLevel;
109 import com.android.permissioncontroller.Constants;
110 import com.android.permissioncontroller.DeviceUtils;
111 import com.android.permissioncontroller.PermissionControllerApplication;
112 import com.android.permissioncontroller.R;
113 import com.android.permissioncontroller.permission.model.AppPermissionGroup;
114 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup;
115 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo;
116 
117 import kotlin.Triple;
118 
119 import java.lang.annotation.Retention;
120 import java.time.ZonedDateTime;
121 import java.time.temporal.ChronoUnit;
122 import java.util.ArrayList;
123 import java.util.Calendar;
124 import java.util.HashSet;
125 import java.util.List;
126 import java.util.Locale;
127 import java.util.Random;
128 import java.util.Set;
129 
130 public final class Utils {
131 
132     @Retention(SOURCE)
133     @IntDef(value = {LAST_24H_SENSOR_TODAY, LAST_24H_SENSOR_YESTERDAY,
134             LAST_24H_CONTENT_PROVIDER, NOT_IN_LAST_7D})
135     public @interface AppPermsLastAccessType {}
136     public static final int LAST_24H_SENSOR_TODAY = 1;
137     public static final int LAST_24H_SENSOR_YESTERDAY = 2;
138     public static final int LAST_24H_CONTENT_PROVIDER = 3;
139     public static final int LAST_7D_SENSOR = 4;
140     public static final int LAST_7D_CONTENT_PROVIDER = 5;
141     public static final int NOT_IN_LAST_7D = 6;
142 
143     private static final String LOG_TAG = "Utils";
144 
145     public static final String OS_PKG = "android";
146 
147     public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f;
148 
149     /** The time an app needs to be unused in order to be hibernated */
150     public static final String PROPERTY_HIBERNATION_UNUSED_THRESHOLD_MILLIS =
151             "auto_revoke_unused_threshold_millis2";
152 
153     /** The frequency of running the job for hibernating apps */
154     public static final String PROPERTY_HIBERNATION_CHECK_FREQUENCY_MILLIS =
155             "auto_revoke_check_frequency_millis";
156 
157     /** Whether hibernation targets apps that target a pre-S SDK */
158     public static final String PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS =
159             "app_hibernation_targets_pre_s_apps";
160 
161     /** Whether or not app hibernation is enabled on the device **/
162     public static final String PROPERTY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled";
163 
164     /** Whether the system exempt from hibernation is enabled on the device **/
165     public static final String PROPERTY_SYSTEM_EXEMPT_HIBERNATION_ENABLED =
166             "system_exempt_hibernation_enabled";
167 
168     /** The timeout for one-time permissions */
169     private static final String PROPERTY_ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS =
170             "one_time_permissions_timeout_millis";
171 
172     /** The delay before ending a one-time permission session when all processes are dead */
173     private static final String PROPERTY_ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS =
174             "one_time_permissions_killed_delay_millis";
175 
176     /** Whether to show health permission in various permission controller UIs. */
177     private static final String PROPERTY_HEALTH_PERMISSION_UI_ENABLED =
178             "health_permission_ui_enabled";
179 
180 
181     /** How frequently to check permission event store to scrub old data */
182     public static final String PROPERTY_PERMISSION_EVENTS_CHECK_OLD_FREQUENCY_MILLIS =
183             "permission_events_check_old_frequency_millis";
184 
185     /**
186      * Whether to store the exact time for permission changes. Only for use in tests and should
187      * not be modified in prod.
188      */
189     public static final String PROPERTY_PERMISSION_CHANGES_STORE_EXACT_TIME =
190             "permission_changes_store_exact_time";
191 
192     /** The max amount of time permission data can stay in the storage before being scrubbed */
193     public static final String PROPERTY_PERMISSION_DECISIONS_MAX_DATA_AGE_MILLIS =
194             "permission_decisions_max_data_age_millis";
195 
196     /** All permission whitelists. */
197     public static final int FLAGS_PERMISSION_WHITELIST_ALL =
198             PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
199                     | PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
200                     | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER;
201 
202     /** All permission restriction exemptions. */
203     public static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT =
204             FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
205                     | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
206                     | FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
207 
208     /**
209      * The default length of the timeout for one-time permissions
210      */
211     public static final long ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS = 1 * 60 * 1000; // 1 minute
212 
213     /**
214      * The default length to wait before ending a one-time permission session after all processes
215      * are dead.
216      */
217     public static final long ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS = 5 * 1000;
218 
219     private static final ArrayMap<String, Integer> PERM_GROUP_REQUEST_RES;
220     private static final ArrayMap<String, Integer> PERM_GROUP_REQUEST_DEVICE_AWARE_RES;
221     private static final ArrayMap<String, Integer> PERM_GROUP_REQUEST_DETAIL_RES;
222     private static final ArrayMap<String, Integer> PERM_GROUP_BACKGROUND_REQUEST_RES;
223     private static final ArrayMap<String, Integer> PERM_GROUP_BACKGROUND_REQUEST_DEVICE_AWARE_RES;
224     private static final ArrayMap<String, Integer> PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES;
225     private static final ArrayMap<String, Integer> PERM_GROUP_UPGRADE_REQUEST_RES;
226     private static final ArrayMap<String, Integer> PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES;
227     private static final ArrayMap<String, Integer> PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES;
228 
229     /** Permission -> Sensor codes */
230     private static final ArrayMap<String, Integer> PERM_SENSOR_CODES;
231     /** Permission -> Icon res id */
232     private static final ArrayMap<String, Integer> PERM_BLOCKED_ICON;
233     /** Permission -> Title res id */
234     private static final ArrayMap<String, Integer> PERM_BLOCKED_TITLE;
235     /** Permission -> Title res id */
236     private static final ArrayMap<String, Integer> PERM_BLOCKED_TITLE_AUTOMOTIVE;
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_DEVICE_AWARE_RES = new ArrayMap<>();
PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(CONTACTS, R.string.permgrouprequest_device_aware_contacts)286         PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(CONTACTS,
287                 R.string.permgrouprequest_device_aware_contacts);
PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(LOCATION, R.string.permgrouprequest_device_aware_location)288         PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(LOCATION,
289                 R.string.permgrouprequest_device_aware_location);
PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(NEARBY_DEVICES, R.string.permgrouprequest_device_aware_nearby_devices)290         PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(NEARBY_DEVICES,
291                 R.string.permgrouprequest_device_aware_nearby_devices);
PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(CALENDAR, R.string.permgrouprequest_device_aware_calendar)292         PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(CALENDAR,
293                 R.string.permgrouprequest_device_aware_calendar);
PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(SMS, R.string.permgrouprequest_device_aware_sms)294         PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(SMS, R.string.permgrouprequest_device_aware_sms);
PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(STORAGE, R.string.permgrouprequest_device_aware_storage)295         PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(STORAGE,
296                 R.string.permgrouprequest_device_aware_storage);
PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(READ_MEDIA_AURAL, R.string.permgrouprequest_device_aware_read_media_aural)297         PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(READ_MEDIA_AURAL,
298                 R.string.permgrouprequest_device_aware_read_media_aural);
PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(READ_MEDIA_VISUAL, R.string.permgrouprequest_device_aware_read_media_visual)299         PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(READ_MEDIA_VISUAL,
300                 R.string.permgrouprequest_device_aware_read_media_visual);
PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(MICROPHONE, R.string.permgrouprequest_device_aware_microphone)301         PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(MICROPHONE,
302                 R.string.permgrouprequest_device_aware_microphone);
303         PERM_GROUP_REQUEST_DEVICE_AWARE_RES
put(ACTIVITY_RECOGNITION, R.string.permgrouprequest_device_aware_activityRecognition)304                 .put(ACTIVITY_RECOGNITION,
305                         R.string.permgrouprequest_device_aware_activityRecognition);
PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(CAMERA, R.string.permgrouprequest_device_aware_camera)306         PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(CAMERA,
307                 R.string.permgrouprequest_device_aware_camera);
PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(CALL_LOG, R.string.permgrouprequest_device_aware_calllog)308         PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(CALL_LOG,
309                 R.string.permgrouprequest_device_aware_calllog);
PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(PHONE, R.string.permgrouprequest_device_aware_phone)310         PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(PHONE,
311                 R.string.permgrouprequest_device_aware_phone);
PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(SENSORS, R.string.permgrouprequest_device_aware_sensors)312         PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(SENSORS,
313                 R.string.permgrouprequest_device_aware_sensors);
PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(NOTIFICATIONS, R.string.permgrouprequest_device_aware_notifications)314         PERM_GROUP_REQUEST_DEVICE_AWARE_RES.put(NOTIFICATIONS,
315                 R.string.permgrouprequest_device_aware_notifications);
316 
317         PERM_GROUP_REQUEST_DETAIL_RES = new ArrayMap<>();
PERM_GROUP_REQUEST_DETAIL_RES.put(LOCATION, R.string.permgrouprequestdetail_location)318         PERM_GROUP_REQUEST_DETAIL_RES.put(LOCATION, R.string.permgrouprequestdetail_location);
PERM_GROUP_REQUEST_DETAIL_RES.put(MICROPHONE, R.string.permgrouprequestdetail_microphone)319         PERM_GROUP_REQUEST_DETAIL_RES.put(MICROPHONE, R.string.permgrouprequestdetail_microphone);
PERM_GROUP_REQUEST_DETAIL_RES.put(CAMERA, R.string.permgrouprequestdetail_camera)320         PERM_GROUP_REQUEST_DETAIL_RES.put(CAMERA, R.string.permgrouprequestdetail_camera);
321 
322         PERM_GROUP_BACKGROUND_REQUEST_RES = new ArrayMap<>();
323         PERM_GROUP_BACKGROUND_REQUEST_RES
put(LOCATION, R.string.permgroupbackgroundrequest_location)324                 .put(LOCATION, R.string.permgroupbackgroundrequest_location);
325         PERM_GROUP_BACKGROUND_REQUEST_RES
put(MICROPHONE, R.string.permgroupbackgroundrequest_microphone)326                 .put(MICROPHONE, R.string.permgroupbackgroundrequest_microphone);
327         PERM_GROUP_BACKGROUND_REQUEST_RES
put(CAMERA, R.string.permgroupbackgroundrequest_camera)328                 .put(CAMERA, R.string.permgroupbackgroundrequest_camera);
329         PERM_GROUP_BACKGROUND_REQUEST_RES
put(SENSORS, R.string.permgroupbackgroundrequest_sensors)330                 .put(SENSORS, R.string.permgroupbackgroundrequest_sensors);
331 
332         PERM_GROUP_BACKGROUND_REQUEST_DEVICE_AWARE_RES = new ArrayMap<>();
333         PERM_GROUP_BACKGROUND_REQUEST_DEVICE_AWARE_RES
put(LOCATION, R.string.permgroupbackgroundrequest_device_aware_location)334                 .put(LOCATION, R.string.permgroupbackgroundrequest_device_aware_location);
335         PERM_GROUP_BACKGROUND_REQUEST_DEVICE_AWARE_RES
put(MICROPHONE, R.string.permgroupbackgroundrequest_device_aware_microphone)336                 .put(MICROPHONE, R.string.permgroupbackgroundrequest_device_aware_microphone);
337         PERM_GROUP_BACKGROUND_REQUEST_DEVICE_AWARE_RES
put(CAMERA, R.string.permgroupbackgroundrequest_device_aware_camera)338                 .put(CAMERA, R.string.permgroupbackgroundrequest_device_aware_camera);
339         PERM_GROUP_BACKGROUND_REQUEST_DEVICE_AWARE_RES
put(SENSORS, R.string.permgroupbackgroundrequest_device_aware_sensors)340                 .put(SENSORS, R.string.permgroupbackgroundrequest_device_aware_sensors);
341 
342         PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES = new ArrayMap<>();
343         PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES
put(LOCATION, R.string.permgroupbackgroundrequestdetail_location)344                 .put(LOCATION, R.string.permgroupbackgroundrequestdetail_location);
345         PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES
put(MICROPHONE, R.string.permgroupbackgroundrequestdetail_microphone)346                 .put(MICROPHONE, R.string.permgroupbackgroundrequestdetail_microphone);
347         PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES
put(CAMERA, R.string.permgroupbackgroundrequestdetail_camera)348                 .put(CAMERA, R.string.permgroupbackgroundrequestdetail_camera);
349         PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES
put(SENSORS, R.string.permgroupbackgroundrequestdetail_sensors)350                 .put(SENSORS, R.string.permgroupbackgroundrequestdetail_sensors);
351 
352         PERM_GROUP_UPGRADE_REQUEST_RES = new ArrayMap<>();
PERM_GROUP_UPGRADE_REQUEST_RES.put(LOCATION, R.string.permgroupupgraderequest_location)353         PERM_GROUP_UPGRADE_REQUEST_RES.put(LOCATION, R.string.permgroupupgraderequest_location);
PERM_GROUP_UPGRADE_REQUEST_RES.put(MICROPHONE, R.string.permgroupupgraderequest_microphone)354         PERM_GROUP_UPGRADE_REQUEST_RES.put(MICROPHONE, R.string.permgroupupgraderequest_microphone);
PERM_GROUP_UPGRADE_REQUEST_RES.put(CAMERA, R.string.permgroupupgraderequest_camera)355         PERM_GROUP_UPGRADE_REQUEST_RES.put(CAMERA, R.string.permgroupupgraderequest_camera);
PERM_GROUP_UPGRADE_REQUEST_RES.put(SENSORS, R.string.permgroupupgraderequest_sensors)356         PERM_GROUP_UPGRADE_REQUEST_RES.put(SENSORS, R.string.permgroupupgraderequest_sensors);
357 
358         PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES = new ArrayMap<>();
PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.put(LOCATION, R.string.permgroupupgraderequest_device_aware_location)359         PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.put(LOCATION,
360                 R.string.permgroupupgraderequest_device_aware_location);
PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.put(MICROPHONE, R.string.permgroupupgraderequest_device_aware_microphone)361         PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.put(MICROPHONE,
362                 R.string.permgroupupgraderequest_device_aware_microphone);
PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.put(CAMERA, R.string.permgroupupgraderequest_device_aware_camera)363         PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.put(CAMERA,
364                 R.string.permgroupupgraderequest_device_aware_camera);
PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.put(SENSORS, R.string.permgroupupgraderequest_device_aware_sensors)365         PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.put(SENSORS,
366                 R.string.permgroupupgraderequest_device_aware_sensors);
367 
368         PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES = new ArrayMap<>();
369         PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES
put(LOCATION, R.string.permgroupupgraderequestdetail_location)370                 .put(LOCATION, R.string.permgroupupgraderequestdetail_location);
371         PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES
put(MICROPHONE, R.string.permgroupupgraderequestdetail_microphone)372                 .put(MICROPHONE, R.string.permgroupupgraderequestdetail_microphone);
373         PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES
put(CAMERA, R.string.permgroupupgraderequestdetail_camera)374                 .put(CAMERA, R.string.permgroupupgraderequestdetail_camera);
375         PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES
put(SENSORS, R.string.permgroupupgraderequestdetail_sensors)376                 .put(SENSORS,  R.string.permgroupupgraderequestdetail_sensors);
377 
378         PERM_SENSOR_CODES = new ArrayMap<>();
379         if (SdkLevel.isAtLeastS()) {
PERM_SENSOR_CODES.put(CAMERA, SensorPrivacyManager.Sensors.CAMERA)380             PERM_SENSOR_CODES.put(CAMERA, SensorPrivacyManager.Sensors.CAMERA);
PERM_SENSOR_CODES.put(MICROPHONE, SensorPrivacyManager.Sensors.MICROPHONE)381             PERM_SENSOR_CODES.put(MICROPHONE, SensorPrivacyManager.Sensors.MICROPHONE);
382         }
383 
384         PERM_BLOCKED_ICON = new ArrayMap<>();
PERM_BLOCKED_ICON.put(CAMERA, R.drawable.ic_camera_blocked)385         PERM_BLOCKED_ICON.put(CAMERA, R.drawable.ic_camera_blocked);
PERM_BLOCKED_ICON.put(MICROPHONE, R.drawable.ic_mic_blocked)386         PERM_BLOCKED_ICON.put(MICROPHONE, R.drawable.ic_mic_blocked);
PERM_BLOCKED_ICON.put(LOCATION, R.drawable.ic_location_blocked)387         PERM_BLOCKED_ICON.put(LOCATION, R.drawable.ic_location_blocked);
388 
389         PERM_BLOCKED_TITLE = new ArrayMap<>();
PERM_BLOCKED_TITLE.put(CAMERA, R.string.blocked_camera_title)390         PERM_BLOCKED_TITLE.put(CAMERA, R.string.blocked_camera_title);
PERM_BLOCKED_TITLE.put(MICROPHONE, R.string.blocked_microphone_title)391         PERM_BLOCKED_TITLE.put(MICROPHONE, R.string.blocked_microphone_title);
PERM_BLOCKED_TITLE.put(LOCATION, R.string.blocked_location_title)392         PERM_BLOCKED_TITLE.put(LOCATION, R.string.blocked_location_title);
393 
394         PERM_BLOCKED_TITLE_AUTOMOTIVE = new ArrayMap<>();
PERM_BLOCKED_TITLE_AUTOMOTIVE.put(CAMERA, R.string.automotive_blocked_camera_title)395         PERM_BLOCKED_TITLE_AUTOMOTIVE.put(CAMERA, R.string.automotive_blocked_camera_title);
PERM_BLOCKED_TITLE_AUTOMOTIVE.put(MICROPHONE, R.string.automotive_blocked_microphone_title)396         PERM_BLOCKED_TITLE_AUTOMOTIVE.put(MICROPHONE, R.string.automotive_blocked_microphone_title);
PERM_BLOCKED_TITLE_AUTOMOTIVE.put(LOCATION, R.string.automotive_blocked_location_title)397         PERM_BLOCKED_TITLE_AUTOMOTIVE.put(LOCATION, R.string.automotive_blocked_location_title);
398     }
399 
Utils()400     private Utils() {
401         /* do nothing - hide constructor */
402     }
403 
404     private static Object sLock = new Object();
405 
406     private static ArrayMap<UserHandle, Context> sUserContexts = new ArrayMap<>();
407 
408     /**
409      * Creates and caches a PackageContext for the requested user, or returns the previously cached
410      * value. The package of the PackageContext is the application's package.
411      *
412      * @param context The context of the currently running application
413      * @param user The desired user for the context
414      *
415      * @return The generated or cached Context for the requested user
416      *
417      * @throws RuntimeException If the app has no package name attached, which should never happen
418      */
getUserContext(Context context, UserHandle user)419     public static @NonNull Context getUserContext(Context context, UserHandle user) {
420         synchronized (sLock) {
421             if (!sUserContexts.containsKey(user)) {
422                 sUserContexts.put(user, context.getApplicationContext()
423                         .createContextAsUser(user, 0));
424             }
425             return Preconditions.checkNotNull(sUserContexts.get(user));
426         }
427     }
428 
429     /**
430      * {@code @NonNull} version of {@link Context#getSystemService(Class)}
431      */
getSystemServiceSafe(@onNull Context context, Class<M> clazz)432     public static @NonNull <M> M getSystemServiceSafe(@NonNull Context context, Class<M> clazz) {
433         return Preconditions.checkNotNull(context.getSystemService(clazz),
434                 "Could not resolve " + clazz.getSimpleName());
435     }
436 
437     /**
438      * {@code @NonNull} version of {@link Context#getSystemService(Class)}
439      */
getSystemServiceSafe(@onNull Context context, Class<M> clazz, @NonNull UserHandle user)440     public static @NonNull <M> M getSystemServiceSafe(@NonNull Context context, Class<M> clazz,
441             @NonNull UserHandle user) {
442         try {
443             return Preconditions.checkNotNull(context.createPackageContextAsUser(
444                     context.getPackageName(), 0, user).getSystemService(clazz),
445                     "Could not resolve " + clazz.getSimpleName());
446         } catch (PackageManager.NameNotFoundException neverHappens) {
447             throw new IllegalStateException();
448         }
449     }
450 
451     /**
452      * {@code @NonNull} version of {@link Intent#getParcelableExtra(String)}
453      */
getParcelableExtraSafe(@onNull Intent intent, @NonNull String name)454     public static @NonNull <T extends Parcelable> T getParcelableExtraSafe(@NonNull Intent intent,
455             @NonNull String name) {
456         return Preconditions.checkNotNull(intent.getParcelableExtra(name),
457                 "Could not get parcelable extra for " + name);
458     }
459 
460     /**
461      * {@code @NonNull} version of {@link Intent#getStringExtra(String)}
462      */
getStringExtraSafe(@onNull Intent intent, @NonNull String name)463     public static @NonNull String getStringExtraSafe(@NonNull Intent intent,
464             @NonNull String name) {
465         return Preconditions.checkNotNull(intent.getStringExtra(name),
466                 "Could not get string extra for " + name);
467     }
468 
469     /**
470      * Returns true if a permission is dangerous, installed, and not removed
471      * @param permissionInfo The permission we wish to check
472      * @return If all of the conditions are met
473      */
isPermissionDangerousInstalledNotRemoved(PermissionInfo permissionInfo)474     public static boolean isPermissionDangerousInstalledNotRemoved(PermissionInfo permissionInfo) {
475         return permissionInfo != null
476                   && permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS
477                   && (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0
478                   && (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) == 0;
479     }
480 
481     /**
482      * Get the {@link PermissionInfo infos} for all permission infos belonging to a group.
483      *
484      * @param pm    Package manager to use to resolve permission infos
485      * @param group the group
486      *
487      * @return The infos of permissions belonging to the group or an empty list if the group
488      *         does not have runtime permissions
489      */
getPermissionInfosForGroup( @onNull PackageManager pm, @NonNull String group)490     public static @NonNull List<PermissionInfo> getPermissionInfosForGroup(
491             @NonNull PackageManager pm, @NonNull String group)
492             throws PackageManager.NameNotFoundException {
493         List<PermissionInfo> permissions = pm.queryPermissionsByGroup(group, 0);
494         permissions.addAll(PermissionMapping.getPlatformPermissionsOfGroup(pm, group));
495 
496         /*
497          * If the undefined group is requested, the package manager will return all platform
498          * permissions, since they are marked as Undefined in the manifest. Do not return these
499          * permissions.
500          */
501         if (group.equals(Manifest.permission_group.UNDEFINED)) {
502             List<PermissionInfo> undefinedPerms = new ArrayList<>();
503             for (PermissionInfo permissionInfo : permissions) {
504                 String permGroup =
505                         PermissionMapping.getGroupOfPlatformPermission(permissionInfo.name);
506                 if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) {
507                     undefinedPerms.add(permissionInfo);
508                 }
509             }
510             return undefinedPerms;
511         }
512 
513         return permissions;
514     }
515 
516     /**
517      * Get the {@link PermissionInfo infos} for all runtime installed permission infos belonging to
518      * a group.
519      *
520      * @param pm    Package manager to use to resolve permission infos
521      * @param group the group
522      *
523      * @return The infos of installed runtime permissions belonging to the group or an empty list
524      * if the group does not have runtime permissions
525      */
getInstalledRuntimePermissionInfosForGroup( @onNull PackageManager pm, @NonNull String group)526     public static @NonNull List<PermissionInfo> getInstalledRuntimePermissionInfosForGroup(
527             @NonNull PackageManager pm, @NonNull String group)
528             throws PackageManager.NameNotFoundException {
529         List<PermissionInfo> permissions = pm.queryPermissionsByGroup(group, 0);
530         permissions.addAll(PermissionMapping.getPlatformPermissionsOfGroup(pm, group));
531 
532         List<PermissionInfo> installedRuntime = new ArrayList<>();
533         for (PermissionInfo permissionInfo: permissions) {
534             if (permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS
535                     && (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0
536                     && (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) == 0) {
537                 installedRuntime.add(permissionInfo);
538             }
539         }
540 
541         /*
542          * If the undefined group is requested, the package manager will return all platform
543          * permissions, since they are marked as Undefined in the manifest. Do not return these
544          * permissions.
545          */
546         if (group.equals(Manifest.permission_group.UNDEFINED)) {
547             List<PermissionInfo> undefinedPerms = new ArrayList<>();
548             for (PermissionInfo permissionInfo : installedRuntime) {
549                 String permGroup =
550                         PermissionMapping.getGroupOfPlatformPermission(permissionInfo.name);
551                 if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) {
552                     undefinedPerms.add(permissionInfo);
553                 }
554             }
555             return undefinedPerms;
556         }
557 
558         return installedRuntime;
559     }
560 
561     /**
562      * Get the {@link PackageItemInfo infos} for the given permission group.
563      *
564      * @param groupName the group
565      * @param context the {@code Context} to retrieve {@code PackageManager}
566      *
567      * @return The info of permission group or null if the group does not have runtime permissions.
568      */
getGroupInfo(@onNull String groupName, @NonNull Context context)569     public static @Nullable PackageItemInfo getGroupInfo(@NonNull String groupName,
570             @NonNull Context context) {
571         try {
572             return context.getPackageManager().getPermissionGroupInfo(groupName, 0);
573         } catch (NameNotFoundException e) {
574             /* ignore */
575         }
576         try {
577             return context.getPackageManager().getPermissionInfo(groupName, 0);
578         } catch (NameNotFoundException e) {
579             /* ignore */
580         }
581         return null;
582     }
583 
584     /**
585      * Get the {@link PermissionInfo infos} for all permission infos belonging to a group.
586      *
587      * @param groupName the group
588      * @param context the {@code Context} to retrieve {@code PackageManager}
589      *
590      * @return The infos of permissions belonging to the group or null if the group does not have
591      *         runtime permissions.
592      */
getGroupPermissionInfos(@onNull String groupName, @NonNull Context context)593     public static @Nullable List<PermissionInfo> getGroupPermissionInfos(@NonNull String groupName,
594             @NonNull Context context) {
595         try {
596             return Utils.getPermissionInfosForGroup(context.getPackageManager(), groupName);
597         } catch (NameNotFoundException e) {
598             /* ignore */
599         }
600         try {
601             PermissionInfo permissionInfo = context.getPackageManager()
602                     .getPermissionInfo(groupName, 0);
603             List<PermissionInfo> permissions = new ArrayList<>();
604             permissions.add(permissionInfo);
605             return permissions;
606         } catch (NameNotFoundException e) {
607             /* ignore */
608         }
609         return null;
610     }
611 
612     /**
613      * Get the label for an application, truncating if it is too long.
614      *
615      * @param applicationInfo the {@link ApplicationInfo} of the application
616      * @param context the {@code Context} to retrieve {@code PackageManager}
617      *
618      * @return the label for the application
619      */
620     @NonNull
getAppLabel(@onNull ApplicationInfo applicationInfo, @NonNull Context context)621     public static String getAppLabel(@NonNull ApplicationInfo applicationInfo,
622             @NonNull Context context) {
623         return getAppLabel(applicationInfo, DEFAULT_MAX_LABEL_SIZE_PX, context);
624     }
625 
626     /**
627      * Get the full label for an application without truncation.
628      *
629      * @param applicationInfo the {@link ApplicationInfo} of the application
630      * @param context the {@code Context} to retrieve {@code PackageManager}
631      *
632      * @return the label for the application
633      */
634     @NonNull
getFullAppLabel(@onNull ApplicationInfo applicationInfo, @NonNull Context context)635     public static String getFullAppLabel(@NonNull ApplicationInfo applicationInfo,
636             @NonNull Context context) {
637         return getAppLabel(applicationInfo, 0, context);
638     }
639 
640     /**
641      * Get the label for an application with the ability to control truncating.
642      *
643      * @param applicationInfo the {@link ApplicationInfo} of the application
644      * @param ellipsizeDip see {@link TextUtils#makeSafeForPresentation}.
645      * @param context the {@code Context} to retrieve {@code PackageManager}
646      *
647      * @return the label for the application
648      */
649     @NonNull
getAppLabel(@onNull ApplicationInfo applicationInfo, float ellipsizeDip, @NonNull Context context)650     private static String getAppLabel(@NonNull ApplicationInfo applicationInfo, float ellipsizeDip,
651             @NonNull Context context) {
652         return BidiFormatter.getInstance().unicodeWrap(applicationInfo.loadSafeLabel(
653                 context.getPackageManager(), ellipsizeDip,
654                 TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE)
655                 .toString());
656     }
657 
loadDrawable(PackageManager pm, String pkg, int resId)658     public static Drawable loadDrawable(PackageManager pm, String pkg, int resId) {
659         try {
660             return pm.getResourcesForApplication(pkg).getDrawable(resId, null);
661         } catch (Resources.NotFoundException | PackageManager.NameNotFoundException e) {
662             Log.d(LOG_TAG, "Couldn't get resource", e);
663             return null;
664         }
665     }
666 
667     /**
668      * Should UI show this permission.
669      *
670      * <p>If the user cannot change the group, it should not be shown.
671      *
672      * @param group The group that might need to be shown to the user
673      *
674      * @return
675      */
shouldShowPermission(Context context, AppPermissionGroup group)676     public static boolean shouldShowPermission(Context context, AppPermissionGroup group) {
677         if (!group.isGrantingAllowed()) {
678             return false;
679         }
680 
681         final boolean isPlatformPermission = group.getDeclaringPackage().equals(OS_PKG);
682         // Show legacy permissions only if the user chose that.
683         if (isPlatformPermission
684                 && !PermissionMapping.isPlatformPermissionGroup(group.getName())) {
685             return false;
686         }
687         return true;
688     }
689 
applyTint(Context context, Drawable icon, int attr)690     public static Drawable applyTint(Context context, Drawable icon, int attr) {
691         Theme theme = context.getTheme();
692         TypedValue typedValue = new TypedValue();
693         theme.resolveAttribute(attr, typedValue, true);
694         icon = icon.mutate();
695         icon.setTint(context.getColor(typedValue.resourceId));
696         return icon;
697     }
698 
applyTint(Context context, int iconResId, int attr)699     public static Drawable applyTint(Context context, int iconResId, int attr) {
700         return applyTint(context, context.getDrawable(iconResId), attr);
701     }
702 
703     /**
704      * Get the color resource id based on the attribute
705      *
706      * @return Resource id for the color
707      */
708     @ColorRes
getColorResId(Context context, int attr)709     public static int getColorResId(Context context, int attr) {
710         Theme theme = context.getTheme();
711         TypedValue typedValue = new TypedValue();
712         theme.resolveAttribute(attr, typedValue, true);
713         return typedValue.resourceId;
714     }
715 
716     /**
717      * Is the group or background group user sensitive?
718      *
719      * @param group The group that might be user sensitive
720      *
721      * @return {@code true} if the group (or it's subgroup) is user sensitive.
722      */
isGroupOrBgGroupUserSensitive(AppPermissionGroup group)723     public static boolean isGroupOrBgGroupUserSensitive(AppPermissionGroup group) {
724         return group.isUserSensitive() || (group.getBackgroundPermissions() != null
725                 && group.getBackgroundPermissions().isUserSensitive());
726     }
727 
areGroupPermissionsIndividuallyControlled(Context context, String group)728     public static boolean areGroupPermissionsIndividuallyControlled(Context context, String group) {
729         if (!context.getPackageManager().arePermissionsIndividuallyControlled()) {
730             return false;
731         }
732         return Manifest.permission_group.SMS.equals(group)
733                 || Manifest.permission_group.PHONE.equals(group)
734                 || Manifest.permission_group.CONTACTS.equals(group)
735                 || Manifest.permission_group.CALL_LOG.equals(group);
736     }
737 
isPermissionIndividuallyControlled(Context context, String permission)738     public static boolean isPermissionIndividuallyControlled(Context context, String permission) {
739         if (!context.getPackageManager().arePermissionsIndividuallyControlled()) {
740             return false;
741         }
742         return Manifest.permission.READ_CONTACTS.equals(permission)
743                 || Manifest.permission.WRITE_CONTACTS.equals(permission)
744                 || Manifest.permission.SEND_SMS.equals(permission)
745                 || Manifest.permission.RECEIVE_SMS.equals(permission)
746                 || Manifest.permission.READ_SMS.equals(permission)
747                 || Manifest.permission.RECEIVE_MMS.equals(permission)
748                 || Manifest.permission.CALL_PHONE.equals(permission)
749                 || Manifest.permission.READ_CALL_LOG.equals(permission)
750                 || Manifest.permission.WRITE_CALL_LOG.equals(permission);
751     }
752 
753     /**
754      * Get the message shown to grant a permission group to an app.
755      *
756      * @param appLabel The label of the app
757      * @param packageName The package name of the app
758      * @param groupName The name of the permission group
759      * @param context A context to resolve resources
760      * @param requestRes The resource id of the grant request message
761      * @return The formatted message to be used as title when granting permissions
762      */
763     @NonNull
getRequestMessage( @onNull String appLabel, @NonNull String packageName, @NonNull String groupName, @NonNull Context context, @StringRes int requestRes)764     public static CharSequence getRequestMessage(
765             @NonNull String appLabel,
766             @NonNull String packageName,
767             @NonNull String groupName,
768             @NonNull Context context,
769             @StringRes int requestRes) {
770         String escapedAppLabel = Html.escapeHtml(appLabel);
771 
772         boolean isIsolatedStorage;
773         try {
774             isIsolatedStorage = !isNonIsolatedStorage(context, packageName);
775         } catch (NameNotFoundException e) {
776             isIsolatedStorage = false;
777         }
778         if (groupName.equals(STORAGE) && isIsolatedStorage) {
779             return Html.fromHtml(
780                     String.format(
781                             context.getResources().getConfiguration().getLocales().get(0),
782                             context.getString(R.string.permgrouprequest_storage_isolated),
783                             escapedAppLabel),
784                     0);
785         } else if (requestRes != 0) {
786             return Html.fromHtml(context.getResources().getString(requestRes, escapedAppLabel), 0);
787         }
788 
789         return Html.fromHtml(
790                 context.getString(
791                         R.string.permission_warning_template,
792                         escapedAppLabel,
793                         loadGroupDescription(context, groupName, context.getPackageManager())),
794                 0);
795     }
796 
797     /**
798      * Get the message shown to grant a permission group to an app.
799      *
800      * @param appLabel The label of the app
801      * @param packageName The package name of the app
802      * @param groupName The name of the permission group
803      * @param context A context to resolve resources
804      * @param requestRes The resource id of the grant request message
805      * @return The formatted message to be used as title when granting permissions
806      */
807     @NonNull
getRequestMessage( @onNull String appLabel, @NonNull String packageName, @NonNull String groupName, @NonNull String deviceLabel, @NonNull Context context, Boolean isDeviceAwareMessage, @StringRes int requestRes)808     public static CharSequence getRequestMessage(
809             @NonNull String appLabel,
810             @NonNull String packageName,
811             @NonNull String groupName,
812             @NonNull String deviceLabel,
813             @NonNull Context context,
814             Boolean isDeviceAwareMessage,
815             @StringRes int requestRes) {
816         if (!isDeviceAwareMessage) {
817             return getRequestMessage(appLabel, packageName, groupName, context, requestRes);
818         }
819         String escapedAppLabel = Html.escapeHtml(appLabel);
820 
821         boolean isIsolatedStorage;
822         try {
823             isIsolatedStorage = !isNonIsolatedStorage(context, packageName);
824         } catch (NameNotFoundException e) {
825             isIsolatedStorage = false;
826         }
827         if (groupName.equals(STORAGE) && isIsolatedStorage) {
828             String escapedDeviceLabel = Html.escapeHtml(deviceLabel);
829             return Html.fromHtml(
830                     String.format(
831                             context.getResources().getConfiguration().getLocales().get(0),
832                             context.getString(
833                                     R.string.permgrouprequest_device_aware_storage_isolated),
834                             escapedAppLabel,
835                             escapedDeviceLabel),
836                     0);
837 
838         } else if (requestRes != 0) {
839             String escapedDeviceLabel = Html.escapeHtml(deviceLabel);
840             return Html.fromHtml(context.getResources().getString(requestRes, escapedAppLabel,
841                     escapedDeviceLabel), 0);
842         }
843 
844         return Html.fromHtml(
845                 context.getString(
846                         R.string.permission_warning_template,
847                         escapedAppLabel,
848                         loadGroupDescription(context, groupName, context.getPackageManager())),
849                 0);
850     }
851 
loadGroupDescription(Context context, String groupName, @NonNull PackageManager packageManager)852     private static CharSequence loadGroupDescription(Context context, String groupName,
853             @NonNull PackageManager packageManager) {
854         PackageItemInfo groupInfo = getGroupInfo(groupName, context);
855         CharSequence description = null;
856         if (groupInfo instanceof PermissionGroupInfo) {
857             description = ((PermissionGroupInfo) groupInfo).loadDescription(packageManager);
858         } else if (groupInfo instanceof PermissionInfo) {
859             description = ((PermissionInfo) groupInfo).loadDescription(packageManager);
860         }
861 
862         if (description == null || description.length() <= 0) {
863             description = context.getString(R.string.default_permission_description);
864         }
865 
866         return description;
867     }
868 
869     /**
870      * Whether or not the given package has non-isolated storage permissions
871      * @param context The current context
872      * @param packageName The package name to check
873      * @return True if the package has access to non-isolated storage, false otherwise
874      * @throws NameNotFoundException
875      */
isNonIsolatedStorage(@onNull Context context, @NonNull String packageName)876     public static boolean isNonIsolatedStorage(@NonNull Context context,
877             @NonNull String packageName) throws NameNotFoundException {
878         PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
879         AppOpsManager manager = context.getSystemService(AppOpsManager.class);
880 
881 
882         return packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.P
883                 || (packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.R
884                 && manager.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE,
885                 packageInfo.applicationInfo.uid, packageInfo.packageName) == MODE_ALLOWED);
886     }
887 
888     /**
889      * Gets whether the STORAGE group should be hidden from the UI for this package. This is true
890      * when the platform is T+, and the package has legacy storage access (i.e., either the package
891      * has a targetSdk less than Q, or has a targetSdk equal to Q and has OPSTR_LEGACY_STORAGE).
892      *
893      * TODO jaysullivan: This is always calling AppOpsManager; not taking advantage of LiveData
894      *
895      * @param pkg The package to check
896      */
shouldShowStorage(LightPackageInfo pkg)897     public static boolean shouldShowStorage(LightPackageInfo pkg) {
898         if (!SdkLevel.isAtLeastT()) {
899             return true;
900         }
901         int targetSdkVersion = pkg.getTargetSdkVersion();
902         PermissionControllerApplication app = PermissionControllerApplication.get();
903         Context context = Utils.getUserContext(app, UserHandle.getUserHandleForUid(pkg.getUid()));
904         AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
905         if (appOpsManager == null) {
906             return true;
907         }
908 
909         return targetSdkVersion < Build.VERSION_CODES.Q
910                 || (targetSdkVersion == Build.VERSION_CODES.Q
911                 && appOpsManager.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE, pkg.getUid(),
912                 pkg.getPackageName()) == MODE_ALLOWED);
913     }
914 
915     /**
916      * Build a string representing the given time if it happened on the current day and the date
917      * otherwise.
918      *
919      * @param context the context.
920      * @param lastAccessTime the time in milliseconds.
921      *
922      * @return a string representing the time or date of the given time or null if the time is 0.
923      */
getAbsoluteTimeString(@onNull Context context, long lastAccessTime)924     public static @Nullable String getAbsoluteTimeString(@NonNull Context context,
925             long lastAccessTime) {
926         if (lastAccessTime == 0) {
927             return null;
928         }
929         if (isToday(lastAccessTime)) {
930             return DateFormat.getTimeFormat(context).format(lastAccessTime);
931         } else {
932             return DateFormat.getMediumDateFormat(context).format(lastAccessTime);
933         }
934     }
935 
936     /**
937      * Check whether the given time (in milliseconds) is in the current day.
938      *
939      * @param time the time in milliseconds
940      *
941      * @return whether the given time is in the current day.
942      */
isToday(long time)943     private static boolean isToday(long time) {
944         Calendar today = Calendar.getInstance(Locale.getDefault());
945         today.setTimeInMillis(System.currentTimeMillis());
946         today.set(Calendar.HOUR_OF_DAY, 0);
947         today.set(Calendar.MINUTE, 0);
948         today.set(Calendar.SECOND, 0);
949         today.set(Calendar.MILLISECOND, 0);
950 
951         Calendar date = Calendar.getInstance(Locale.getDefault());
952         date.setTimeInMillis(time);
953         return !date.before(today);
954     }
955 
956     /**
957      * Add a menu item for searching Settings, if there is an activity handling the action.
958      *
959      * @param menu the menu to add the menu item into
960      * @param context the context for checking whether there is an activity handling the action
961      */
prepareSearchMenuItem(@onNull Menu menu, @NonNull Context context)962     public static void prepareSearchMenuItem(@NonNull Menu menu, @NonNull Context context) {
963         Intent intent = new Intent(Settings.ACTION_APP_SEARCH_SETTINGS);
964         if (context.getPackageManager().resolveActivity(intent, 0) == null) {
965             return;
966         }
967         MenuItem searchItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE,
968                 com.android.settingslib.search.widget.R.string.search_menu);
969         searchItem.setIcon(com.android.settingslib.search.widget.R.drawable.ic_search_24dp);
970         searchItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
971         searchItem.setOnMenuItemClickListener(item -> {
972             try {
973                 context.startActivity(intent);
974             } catch (ActivityNotFoundException e) {
975                 Log.e(LOG_TAG, "Cannot start activity to search settings", e);
976             }
977             return true;
978         });
979     }
980 
981     /**
982      * Get badged app icon if necessary, similar as used in the Settings UI.
983      *
984      * @param context The context to use
985      * @param appInfo The app the icon belong to
986      *
987      * @return The icon to use
988      */
getBadgedIcon(@onNull Context context, @NonNull ApplicationInfo appInfo)989     public static @NonNull Drawable getBadgedIcon(@NonNull Context context,
990             @NonNull ApplicationInfo appInfo) {
991         UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid);
992         try (IconFactory iconFactory = IconFactory.obtain(context)) {
993             Bitmap iconBmp = iconFactory.createBadgedIconBitmap(
994                     appInfo.loadUnbadgedIcon(context.getPackageManager()), user, false).icon;
995             return new BitmapDrawable(context.getResources(), iconBmp);
996         }
997     }
998 
999     /**
1000      * Get a string saying what apps with the given permission group can do.
1001      *
1002      * @param context The context to use
1003      * @param groupName The name of the permission group
1004      * @param description The description of the permission group
1005      *
1006      * @return a string saying what apps with the given permission group can do.
1007      */
getPermissionGroupDescriptionString(@onNull Context context, @NonNull String groupName, @NonNull CharSequence description)1008     public static @NonNull String getPermissionGroupDescriptionString(@NonNull Context context,
1009             @NonNull String groupName, @NonNull CharSequence description) {
1010         switch (groupName) {
1011             case ACTIVITY_RECOGNITION:
1012                 return context.getString(
1013                         R.string.permission_description_summary_activity_recognition);
1014             case CALENDAR:
1015                 return context.getString(R.string.permission_description_summary_calendar);
1016             case CALL_LOG:
1017                 return context.getString(R.string.permission_description_summary_call_log);
1018             case CAMERA:
1019                 return context.getString(R.string.permission_description_summary_camera);
1020             case CONTACTS:
1021                 return context.getString(R.string.permission_description_summary_contacts);
1022             case LOCATION:
1023                 return context.getString(R.string.permission_description_summary_location);
1024             case MICROPHONE:
1025                 return context.getString(R.string.permission_description_summary_microphone);
1026             case NEARBY_DEVICES:
1027                 return context.getString(R.string.permission_description_summary_nearby_devices);
1028             case PHONE:
1029                 return context.getString(R.string.permission_description_summary_phone);
1030             case READ_MEDIA_AURAL:
1031                 return context.getString(R.string.permission_description_summary_read_media_aural);
1032             case READ_MEDIA_VISUAL:
1033                 return context.getString(R.string.permission_description_summary_read_media_visual);
1034             case SENSORS:
1035                 return context.getString(R.string.permission_description_summary_sensors);
1036             case SMS:
1037                 return context.getString(R.string.permission_description_summary_sms);
1038             case STORAGE:
1039                 return context.getString(R.string.permission_description_summary_storage);
1040             default:
1041                 return context.getString(R.string.permission_description_summary_generic,
1042                         description);
1043         }
1044     }
1045 
1046     /**
1047      * Whether we should show health permissions as platform permissions in the various
1048      * permission controller UI.
1049      */
1050     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
isHealthPermissionUiEnabled()1051     public static boolean isHealthPermissionUiEnabled() {
1052         final long token = Binder.clearCallingIdentity();
1053         try {
1054             return SdkLevel.isAtLeastU()
1055                     && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
1056                     PROPERTY_HEALTH_PERMISSION_UI_ENABLED, true);
1057         } finally {
1058             Binder.restoreCallingIdentity(token);
1059         }
1060     }
1061 
1062     /**
1063      * Returns true if the group name passed is that of the Platform health group.
1064      * @param permGroupName name of the group that needs to be checked.
1065      */
1066     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
isHealthPermissionGroup(String permGroupName)1067     public static Boolean isHealthPermissionGroup(String permGroupName) {
1068         return SdkLevel.isAtLeastU() && HEALTH_PERMISSION_GROUP.equals(permGroupName);
1069     }
1070 
1071     /**
1072      * Return whether health permission setting entry should be shown or not
1073      *
1074      * Should not show Health permissions preference if the package doesn't handle
1075      * VIEW_PERMISSION_USAGE_INTENT.
1076      *
1077      * Will show if above is true AND permission is already granted.
1078      *
1079      * @param packageInfo the {@link PackageInfo} app which uses the permission
1080      * @param permGroupName the health permission group name to show
1081      * @return {@code TRUE} iff health permission should be shown
1082      */
1083     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
shouldShowHealthPermission(LightPackageInfo packageInfo, String permGroupName)1084     public static Boolean shouldShowHealthPermission(LightPackageInfo packageInfo,
1085             String permGroupName) {
1086         if (!isHealthPermissionGroup(permGroupName)) {
1087             return false;
1088         }
1089 
1090         PermissionControllerApplication app = PermissionControllerApplication.get();
1091         PackageManager pm = app.getPackageManager();
1092         Context context = getUserContext(app, UserHandle.getUserHandleForUid(packageInfo.getUid()));
1093 
1094         List<PermissionInfo> permissions = new ArrayList<>();
1095         try {
1096             permissions.addAll(getPermissionInfosForGroup(pm, permGroupName));
1097         } catch (NameNotFoundException e) {
1098             Log.e(LOG_TAG, "No permissions found for permission group " + permGroupName);
1099             return false;
1100         }
1101 
1102         // Check in permission is already granted as we should not hide it in the UX at that point.
1103         List<String> grantedPermissions = packageInfo.getGrantedPermissions();
1104         for (PermissionInfo permission : permissions) {
1105             boolean isCurrentlyGranted = grantedPermissions.contains(permission.name);
1106             if (isCurrentlyGranted) {
1107                 Log.d(LOG_TAG, "At least one Health permission group permission is granted, "
1108                         + "show permission group entry");
1109                 return true;
1110             }
1111         }
1112 
1113         Intent viewUsageIntent = new Intent(Intent.ACTION_VIEW_PERMISSION_USAGE);
1114         viewUsageIntent.addCategory(HealthConnectManager.CATEGORY_HEALTH_PERMISSIONS);
1115         viewUsageIntent.setPackage(packageInfo.getPackageName());
1116 
1117         ResolveInfo resolveInfo = pm.resolveActivity(viewUsageIntent, PackageManager.MATCH_ALL);
1118         if (resolveInfo == null) {
1119             Log.e(LOG_TAG, "Package that asks for Health permission must also handle "
1120                     + "VIEW_PERMISSION_USAGE_INTENT.");
1121             return false;
1122         }
1123         return true;
1124     }
1125 
1126     /**
1127      * Get a device protected storage based shared preferences. Avoid storing sensitive data in it.
1128      *
1129      * @param context the context to get the shared preferences
1130      * @return a device protected storage based shared preferences
1131      */
1132     @NonNull
getDeviceProtectedSharedPreferences(@onNull Context context)1133     public static SharedPreferences getDeviceProtectedSharedPreferences(@NonNull Context context) {
1134         if (!context.isDeviceProtectedStorage()) {
1135             context = context.createDeviceProtectedStorageContext();
1136         }
1137         return context.getSharedPreferences(Constants.PREFERENCES_FILE, MODE_PRIVATE);
1138     }
1139 
getOneTimePermissionsTimeout()1140     public static long getOneTimePermissionsTimeout() {
1141         return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
1142                 PROPERTY_ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS, ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS);
1143     }
1144 
1145     /**
1146      * Returns the delay in milliseconds before revoking permissions at the end of a one-time
1147      * permission session if all processes have been killed.
1148      * If the session was triggered by a self-revocation, then revocation should happen
1149      * immediately. For a regular one-time permission session, a grace period allows a quick
1150      * app restart without losing the permission.
1151      * @param isSelfRevoked If true, return the delay for a self-revocation session. Otherwise,
1152      *                      return delay for a regular one-time permission session.
1153      */
getOneTimePermissionsKilledDelay(boolean isSelfRevoked)1154     public static long getOneTimePermissionsKilledDelay(boolean isSelfRevoked) {
1155         if (isSelfRevoked) {
1156             // For a self-revoked session, we revoke immediately when the process dies.
1157             return 0;
1158         }
1159         return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
1160                 PROPERTY_ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS,
1161                 ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS);
1162     }
1163 
1164     /**
1165      * Get context of the parent user of the profile group (i.e. usually the 'personal' profile,
1166      * not the 'work' profile).
1167      *
1168      * @param context The context of a user of the profile user group.
1169      *
1170      * @return The context of the parent user
1171      */
getParentUserContext(@onNull Context context)1172     public static Context getParentUserContext(@NonNull Context context) {
1173         UserHandle parentUser = getSystemServiceSafe(context, UserManager.class)
1174                 .getProfileParent(UserHandle.of(myUserId()));
1175 
1176         if (parentUser == null) {
1177             return context;
1178         }
1179 
1180         // In a multi profile environment perform all operations as the parent user of the
1181         // current profile
1182         try {
1183             return context.createPackageContextAsUser(context.getPackageName(), 0,
1184                     parentUser);
1185         } catch (PackageManager.NameNotFoundException e) {
1186             // cannot happen
1187             throw new IllegalStateException("Could not switch to parent user " + parentUser, e);
1188         }
1189     }
1190 
1191     /**
1192      * The resource id for the request message for a permission group
1193      * @param groupName Permission group name
1194      * @return The id or 0 if the permission group doesn't exist or have a message
1195      */
getRequest(String groupName)1196     public static int getRequest(String groupName) {
1197         return getRequest(groupName, false);
1198     }
1199 
1200     /**
1201      * The resource id for the request message for a permission group for a specific device
1202      *
1203      * @param groupName Permission group name
1204      * @return The id or 0 if the permission group doesn't exist or have a message
1205      */
getRequest(String groupName, Boolean isDeviceAwareMessage)1206     public static int getRequest(String groupName, Boolean isDeviceAwareMessage) {
1207         if (isDeviceAwareMessage) {
1208             return PERM_GROUP_REQUEST_DEVICE_AWARE_RES.getOrDefault(groupName, 0);
1209         } else {
1210             return PERM_GROUP_REQUEST_RES.getOrDefault(groupName, 0);
1211         }
1212     }
1213 
1214     /**
1215      * The resource id for the request detail message for a permission group
1216      * @param groupName Permission group name
1217      * @return The id or 0 if the permission group doesn't exist or have a message
1218      */
getRequestDetail(String groupName)1219     public static int getRequestDetail(String groupName) {
1220         return PERM_GROUP_REQUEST_DETAIL_RES.getOrDefault(groupName, 0);
1221     }
1222 
1223     /**
1224      * The resource id for the background request message for a permission group
1225      * @param groupName Permission group name
1226      * @return The id or 0 if the permission group doesn't exist or have a message
1227      */
getBackgroundRequest(String groupName)1228     public static int getBackgroundRequest(String groupName) {
1229         return getBackgroundRequest(groupName, false);
1230     }
1231 
1232     /**
1233      * The resource id for the background request message for a permission group for a specific
1234      * device
1235      *
1236      * @param groupName Permission group name
1237      * @return The id or 0 if the permission group doesn't exist or have a message
1238      */
getBackgroundRequest(String groupName, Boolean isDeviceAwareMessage)1239     public static int getBackgroundRequest(String groupName, Boolean isDeviceAwareMessage) {
1240         if (isDeviceAwareMessage) {
1241             return PERM_GROUP_BACKGROUND_REQUEST_DEVICE_AWARE_RES.getOrDefault(groupName, 0);
1242         } else {
1243             return PERM_GROUP_BACKGROUND_REQUEST_RES.getOrDefault(groupName, 0);
1244         }
1245     }
1246 
1247     /**
1248      * The resource id for the background request detail message for a permission group
1249      * @param groupName Permission group name
1250      * @return The id or 0 if the permission group doesn't exist or have a message
1251      */
getBackgroundRequestDetail(String groupName)1252     public static int getBackgroundRequestDetail(String groupName) {
1253         return PERM_GROUP_BACKGROUND_REQUEST_DETAIL_RES.getOrDefault(groupName, 0);
1254     }
1255 
1256     /**
1257      * The resource id for the upgrade request message for a permission group
1258      * @param groupName Permission group name
1259      * @return The id or 0 if the permission group doesn't exist or have a message
1260      */
getUpgradeRequest(String groupName)1261     public static int getUpgradeRequest(String groupName) {
1262         return getUpgradeRequest(groupName, false);
1263     }
1264 
1265     /**
1266      * The resource id for the upgrade request message for a permission group for a specific device.
1267      *
1268      * @param groupName Permission group name
1269      * @return The id or 0 if the permission group doesn't exist or have a message
1270      */
getUpgradeRequest(String groupName, Boolean isDeviceAwareMessage)1271     public static int getUpgradeRequest(String groupName, Boolean isDeviceAwareMessage) {
1272         if (isDeviceAwareMessage) {
1273             return PERM_GROUP_UPGRADE_REQUEST_DEVICE_AWARE_RES.getOrDefault(groupName, 0);
1274         } else {
1275             return PERM_GROUP_UPGRADE_REQUEST_RES.getOrDefault(groupName, 0);
1276         }
1277     }
1278 
1279     /**
1280      * The resource id for the upgrade request detail message for a permission group
1281      * @param groupName Permission group name
1282      * @return The id or 0 if the permission group doesn't exist or have a message
1283      */
getUpgradeRequestDetail(String groupName)1284     public static int getUpgradeRequestDetail(String groupName) {
1285         return PERM_GROUP_UPGRADE_REQUEST_DETAIL_RES.getOrDefault(groupName, 0);
1286     }
1287 
1288     /**
1289      * The resource id for the fine location request message for a specific device
1290      *
1291      * @return The id
1292      */
getFineLocationRequest(Boolean isDeviceAwareMessage)1293     public static int getFineLocationRequest(Boolean isDeviceAwareMessage) {
1294         if (isDeviceAwareMessage) {
1295             return R.string.permgrouprequest_device_aware_fineupgrade;
1296         } else {
1297             return R.string.permgrouprequest_fineupgrade;
1298         }
1299     }
1300 
1301     /**
1302      * The resource id for the coarse location request message for a specific device
1303      *
1304      * @return The id
1305      */
getCoarseLocationRequest(Boolean isDeviceAwareMessage)1306     public static int getCoarseLocationRequest(Boolean isDeviceAwareMessage) {
1307         if (isDeviceAwareMessage) {
1308             return R.string.permgrouprequest_device_aware_coarselocation;
1309         } else {
1310             return R.string.permgrouprequest_coarselocation;
1311         }
1312     }
1313 
1314     /**
1315      * The resource id for the get more photos request message for a specific device
1316      *
1317      * @return The id
1318      */
getMorePhotosRequest(Boolean isDeviceAwareMessage)1319     public static int getMorePhotosRequest(Boolean isDeviceAwareMessage) {
1320         if (isDeviceAwareMessage) {
1321             return R.string.permgrouprequest_device_aware_more_photos;
1322         } else {
1323             return R.string.permgrouprequest_more_photos;
1324         }
1325     }
1326 
1327     /**
1328      * Returns a random session ID value that's guaranteed to not be {@code INVALID_SESSION_ID}.
1329      *
1330      * @return A valid session ID.
1331      */
getValidSessionId()1332     public static long getValidSessionId() {
1333         long sessionId = INVALID_SESSION_ID;
1334         while (sessionId == INVALID_SESSION_ID) {
1335             sessionId = new Random().nextLong();
1336         }
1337         return sessionId;
1338     }
1339 
1340     /**
1341      * Retrieves an existing session ID from the given intent or generates a new one if none is
1342      * present.
1343      *
1344      * @return A valid session ID.
1345      */
getOrGenerateSessionId(Intent intent)1346     public static long getOrGenerateSessionId(Intent intent) {
1347         long sessionId = intent.getLongExtra(EXTRA_SESSION_ID, INVALID_SESSION_ID);
1348         if (sessionId == INVALID_SESSION_ID) {
1349             sessionId = getValidSessionId();
1350         }
1351         return sessionId;
1352     }
1353 
1354     /**
1355      * Gets the label of the Settings application
1356      *
1357      * @param pm The packageManager used to get the activity resolution
1358      *
1359      * @return The CharSequence title of the settings app
1360      */
1361     @Nullable
getSettingsLabelForNotifications(PackageManager pm)1362     public static CharSequence getSettingsLabelForNotifications(PackageManager pm) {
1363         // We pretend we're the Settings app sending the notification, so figure out its name.
1364         Intent openSettingsIntent = new Intent(Settings.ACTION_SETTINGS);
1365         ResolveInfo resolveInfo = pm.resolveActivity(openSettingsIntent, MATCH_SYSTEM_ONLY);
1366         if (resolveInfo == null) {
1367             return null;
1368         }
1369         return pm.getApplicationLabel(resolveInfo.activityInfo.applicationInfo);
1370     }
1371 
1372     /**
1373      * Determines if a given user is disabled, or is a work profile.
1374      * @param user The user to check
1375      * @return true if the user is disabled, or the user is a work profile
1376      */
isUserDisabledOrWorkProfile(UserHandle user)1377     public static boolean isUserDisabledOrWorkProfile(UserHandle user) {
1378         Application app = PermissionControllerApplication.get();
1379         UserManager userManager = app.getSystemService(UserManager.class);
1380         // In android TV, parental control accounts are managed profiles
1381         return !userManager.getEnabledProfiles().contains(user)
1382                 || (userManager.isManagedProfile(user.getIdentifier())
1383                     && !DeviceUtils.isTelevision(app));
1384     }
1385 
1386     /**
1387      * Determines if a given user ID belongs to a managed profile user.
1388      * @param userId The user ID to check
1389      * @return true if the user is a managed profile
1390      */
isUserManagedProfile(int userId)1391     public static boolean isUserManagedProfile(int userId) {
1392         return PermissionControllerApplication.get()
1393                 .getSystemService(UserManager.class)
1394                 .isManagedProfile(userId);
1395     }
1396 
1397     /**
1398      * Get all the exempted packages.
1399      */
getExemptedPackages(@onNull RoleManager roleManager)1400     public static Set<String> getExemptedPackages(@NonNull RoleManager roleManager) {
1401         Set<String> exemptedPackages = new HashSet<>();
1402 
1403         exemptedPackages.add(OS_PKG);
1404         for (int i = 0; i < EXEMPTED_ROLES.length; i++) {
1405             exemptedPackages.addAll(roleManager.getRoleHolders(EXEMPTED_ROLES[i]));
1406         }
1407 
1408         return exemptedPackages;
1409     }
1410 
1411     /**
1412      * Get the timestamp and lastAccessType for the summary text
1413      * in app permission groups and permission apps screens
1414      * @return Triple<String, Integer, String> with the first being the formatted time
1415      * the second being lastAccessType and the third being the formatted date.
1416      */
getPermissionLastAccessSummaryTimestamp( Long lastAccessTime, Context context, String groupName)1417     public static Triple<String, Integer, String> getPermissionLastAccessSummaryTimestamp(
1418             Long lastAccessTime, Context context, String groupName) {
1419         long midnightToday = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).toEpochSecond()
1420                 * 1000L;
1421         long midnightYesterday = ZonedDateTime.now().minusDays(1).truncatedTo(ChronoUnit.DAYS)
1422                 .toEpochSecond() * 1000L;
1423         long yesterdayAtThisTime = ZonedDateTime.now().minusDays(1).toEpochSecond() * 1000L;
1424 
1425         boolean isLastAccessToday = lastAccessTime != null
1426                 && midnightToday <= lastAccessTime;
1427         boolean isLastAccessWithinPast24h = lastAccessTime != null
1428                 && yesterdayAtThisTime <= lastAccessTime;
1429         boolean isLastAccessTodayOrYesterday = lastAccessTime != null
1430                 && midnightYesterday <= lastAccessTime;
1431 
1432         String lastAccessTimeFormatted = "";
1433         String lastAccessDateFormatted = "";
1434         @AppPermsLastAccessType int lastAccessType = NOT_IN_LAST_7D;
1435 
1436         if (lastAccessTime != null) {
1437             lastAccessTimeFormatted = DateFormat.getTimeFormat(context)
1438                     .format(lastAccessTime);
1439             lastAccessDateFormatted = DateFormat.getDateFormat(context)
1440                     .format(lastAccessTime);
1441 
1442             if (!PermissionMapping.SENSOR_DATA_PERMISSIONS.contains(groupName)) {
1443                 // For content providers we show either the last access is within
1444                 // past 24 hours or past 7 days
1445                 lastAccessType = isLastAccessWithinPast24h
1446                         ? LAST_24H_CONTENT_PROVIDER : LAST_7D_CONTENT_PROVIDER;
1447             } else {
1448                 // For sensor data permissions we show if the last access
1449                 // is today, yesterday or older than yesterday
1450                 lastAccessType = isLastAccessToday
1451                         ? LAST_24H_SENSOR_TODAY : isLastAccessTodayOrYesterday
1452                         ? LAST_24H_SENSOR_YESTERDAY : LAST_7D_SENSOR;
1453             }
1454         }
1455 
1456         return new Triple<>(lastAccessTimeFormatted, lastAccessType, lastAccessDateFormatted);
1457     }
1458 
1459     /**
1460      * Returns if the permission group is Camera or Microphone (status bar indicators).
1461      **/
isStatusBarIndicatorPermission(@onNull String permissionGroupName)1462     public static boolean isStatusBarIndicatorPermission(@NonNull String permissionGroupName) {
1463         return CAMERA.equals(permissionGroupName) || MICROPHONE.equals(permissionGroupName);
1464     }
1465 
1466     /**
1467      * Navigate to notification settings for all apps
1468      * @param context The current Context
1469      */
navigateToNotificationSettings(@onNull Context context)1470     public static void navigateToNotificationSettings(@NonNull Context context) {
1471         Intent notificationIntent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS);
1472         context.startActivity(notificationIntent);
1473     }
1474 
1475     /**
1476      * Navigate to notification settings for an app
1477      * @param context The current Context
1478      * @param packageName The package to navigate to
1479      * @param user Specifies the user of the package which should be navigated to. If null, the
1480      *             current user is used.
1481      */
navigateToAppNotificationSettings(@onNull Context context, @NonNull String packageName, @NonNull UserHandle user)1482     public static void navigateToAppNotificationSettings(@NonNull Context context,
1483             @NonNull String packageName, @NonNull UserHandle user) {
1484         Intent notificationIntent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
1485         notificationIntent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
1486         context.startActivityAsUser(notificationIntent, user);
1487     }
1488 
1489     /**
1490      * Navigate to health connect settings for all apps
1491      * @param context The current Context
1492      */
navigateToHealthConnectSettings(@onNull Context context)1493     public static void navigateToHealthConnectSettings(@NonNull Context context) {
1494         Intent healthConnectIntent = new Intent(ACTION_MANAGE_HEALTH_PERMISSIONS);
1495         context.startActivity(healthConnectIntent);
1496     }
1497 
1498     /**
1499      * Navigate to health connect settings for an app
1500      * @param context The current Context
1501      * @param packageName The package's health connect settings to navigate to
1502      */
navigateToAppHealthConnectSettings(@onNull Context context, @NonNull String packageName, @NonNull UserHandle user)1503     public static void navigateToAppHealthConnectSettings(@NonNull Context context,
1504             @NonNull String packageName, @NonNull UserHandle user) {
1505         Intent appHealthConnectIntent = new Intent(ACTION_MANAGE_HEALTH_PERMISSIONS);
1506         appHealthConnectIntent.putExtra(EXTRA_PACKAGE_NAME, packageName);
1507         appHealthConnectIntent.putExtra(Intent.EXTRA_USER, user);
1508         context.startActivity(appHealthConnectIntent);
1509     }
1510 
1511     /**
1512      * Returns if a card should be shown if the sensor is blocked
1513      **/
shouldDisplayCardIfBlocked(@onNull String permissionGroupName)1514     public static boolean shouldDisplayCardIfBlocked(@NonNull String permissionGroupName) {
1515         return CAMERA.equals(permissionGroupName) || MICROPHONE.equals(permissionGroupName)
1516                 || LOCATION.equals(permissionGroupName);
1517     }
1518 
1519     /**
1520      * Returns the sensor code for a permission
1521      **/
1522     @RequiresApi(Build.VERSION_CODES.S)
getSensorCode(@onNull String permissionGroupName)1523     public static int getSensorCode(@NonNull String permissionGroupName) {
1524         return PERM_SENSOR_CODES.getOrDefault(permissionGroupName, -1);
1525     }
1526 
1527     /**
1528      * Returns the blocked icon code for a permission
1529      **/
getBlockedIcon(@onNull String permissionGroupName)1530     public static int getBlockedIcon(@NonNull String permissionGroupName) {
1531         return PERM_BLOCKED_ICON.getOrDefault(permissionGroupName, -1);
1532     }
1533 
1534     /**
1535      * Returns the blocked title code for a permission
1536      **/
getBlockedTitle(@onNull String permissionGroupName)1537     public static int getBlockedTitle(@NonNull String permissionGroupName) {
1538         return PERM_BLOCKED_TITLE.getOrDefault(permissionGroupName, -1);
1539     }
1540 
1541     /**
1542      * Returns the blocked title code on automotive for a permission
1543      **/
getBlockedTitleAutomotive(@onNull String permissionGroupName)1544     public static int getBlockedTitleAutomotive(@NonNull String permissionGroupName) {
1545         return PERM_BLOCKED_TITLE_AUTOMOTIVE.getOrDefault(permissionGroupName, -1);
1546     }
1547 
1548     /**
1549      * Returns if the permission group has a background mode, even if the background mode is
1550      * introduced in a platform version after the one currently running
1551      **/
hasPermWithBackgroundModeCompat(LightAppPermGroup group)1552     public static boolean hasPermWithBackgroundModeCompat(LightAppPermGroup group) {
1553         if (SdkLevel.isAtLeastS()) {
1554             return group.getHasPermWithBackgroundMode();
1555         }
1556         String groupName = group.getPermGroupName();
1557         return group.getHasPermWithBackgroundMode()
1558                 || Manifest.permission_group.CAMERA.equals(groupName)
1559                 || Manifest.permission_group.MICROPHONE.equals(groupName);
1560     }
1561 
1562     /**
1563      * Returns the appropriate enterprise string for the provided IDs
1564      */
1565     @NonNull
getEnterpriseString(@onNull Context context, @NonNull String updatableStringId, int defaultStringId, @NonNull Object... formatArgs)1566     public static String getEnterpriseString(@NonNull Context context,
1567             @NonNull String updatableStringId, int defaultStringId, @NonNull Object... formatArgs) {
1568         return SdkLevel.isAtLeastT()
1569                 ? getUpdatableEnterpriseString(
1570                         context, updatableStringId, defaultStringId, formatArgs)
1571                 : context.getString(defaultStringId, formatArgs);
1572     }
1573 
1574     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
1575     @NonNull
getUpdatableEnterpriseString(@onNull Context context, @NonNull String updatableStringId, int defaultStringId, @NonNull Object... formatArgs)1576     private static String getUpdatableEnterpriseString(@NonNull Context context,
1577             @NonNull String updatableStringId, int defaultStringId, @NonNull Object... formatArgs) {
1578         DevicePolicyManager dpm = getSystemServiceSafe(context, DevicePolicyManager.class);
1579         return  dpm.getResources().getString(updatableStringId, () -> context.getString(
1580                 defaultStringId, formatArgs), formatArgs);
1581     }
1582 
1583     /**
1584      * Get {@link PackageInfo} for this ComponentName.
1585      *
1586      * @param context The current Context
1587      * @param component component to get package info for
1588      * @return The package info
1589      *
1590      * @throws PackageManager.NameNotFoundException if package does not exist
1591      */
1592     @NonNull
getPackageInfoForComponentName(@onNull Context context, @NonNull ComponentName component)1593     public static PackageInfo getPackageInfoForComponentName(@NonNull Context context,
1594             @NonNull ComponentName component) throws PackageManager.NameNotFoundException {
1595         return context.getPackageManager().getPackageInfo(component.getPackageName(), 0);
1596     }
1597 
1598     /**
1599      * Return the label to use for this application.
1600      *
1601      * @param context The current Context
1602      * @param applicationInfo The {@link ApplicationInfo} of the application to get the label of.
1603      * @return the label associated with this application, or its name if there is no label.
1604      */
1605     @NonNull
getApplicationLabel(@onNull Context context, @NonNull ApplicationInfo applicationInfo)1606     public static String getApplicationLabel(@NonNull Context context,
1607             @NonNull ApplicationInfo applicationInfo) {
1608         return context.getPackageManager().getApplicationLabel(applicationInfo).toString();
1609     }
1610 
1611     /**
1612      * Returns whether the given user should be shown in the Settings UI in SdkLevel V+. This method
1613      * will always return true for SdkLevels below V.
1614      *
1615      * @param userHandle The user for which to check whether it should be shown or not.
1616      * @return true if it should be shown, false otherwise.
1617      */
shouldShowInSettings(UserHandle userHandle, UserManager userManager)1618     public static boolean shouldShowInSettings(UserHandle userHandle, UserManager userManager) {
1619         return !SdkLevel.isAtLeastV() || shouldShowInSettingsInternal(userHandle, userManager);
1620     }
1621 
1622     /**
1623      * Returns whether the given user should be shown in the Settings UI.
1624      *
1625      * @param userHandle The user for which to check whether it should be shown or not.
1626      * @return true if it should be shown, false otherwise.
1627      */
1628     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
1629     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
shouldShowInSettingsInternal( UserHandle userHandle, UserManager userManager)1630     private static boolean shouldShowInSettingsInternal(
1631             UserHandle userHandle, UserManager userManager) {
1632         var userProperties = userManager.getUserProperties(userHandle);
1633         return !userManager.isQuietModeEnabled(userHandle)
1634                 || userProperties.getShowInQuietMode() != UserProperties.SHOW_IN_QUIET_MODE_HIDDEN;
1635     }
1636 
1637     /**
1638      * Check whether an application is restricted for this setting identifier and return the
1639      * {@code Intent} for the restriction if it is.
1640      *
1641      * @param user the user to check for
1642      * @param context the {@code Context} to retrieve system services
1643      *
1644      * @return the {@code Intent} for the restriction if the application is restricted for this
1645      *         setting identifier, or {@code null} otherwise.
1646      */
1647     @Nullable
getApplicationEnhancedConfirmationRestrictedIntentAsUser( @onNull UserHandle user, @NonNull Context context, @Nullable String packageName, @Nullable String settingIdentifier)1648     public static Intent getApplicationEnhancedConfirmationRestrictedIntentAsUser(
1649             @NonNull UserHandle user,
1650             @NonNull Context context,
1651             @Nullable String packageName,
1652             @Nullable String settingIdentifier) {
1653         if (SdkLevel.isAtLeastV() && Flags.enhancedConfirmationModeApisEnabled()) {
1654             Context userContext = Utils.getUserContext(context, user);
1655             EnhancedConfirmationManager userEnhancedConfirmationManager =
1656                     userContext.getSystemService(EnhancedConfirmationManager.class);
1657             if (packageName == null || settingIdentifier == null) return null;
1658             try {
1659                 boolean isRestricted = userEnhancedConfirmationManager.isRestricted(packageName,
1660                         settingIdentifier);
1661                 if (isRestricted) {
1662                     return userEnhancedConfirmationManager.createRestrictedSettingDialogIntent(
1663                             packageName, settingIdentifier);
1664                 }
1665 
1666             } catch (PackageManager.NameNotFoundException e) {
1667                 Log.w(LOG_TAG, "Cannot check enhanced confirmation restriction for package: "
1668                         + packageName, e);
1669             }
1670         }
1671         return null;
1672     }
1673 }
1674