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