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