1 /*
2  * Copyright (C) 2016 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.service;
18 
19 import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
20 import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
21 import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
22 import static android.content.pm.PackageManager.GET_PERMISSIONS;
23 import static android.permission.PermissionControllerManager.REASON_INSTALLER_POLICY_VIOLATION;
24 import static android.permission.PermissionControllerManager.REASON_MALWARE;
25 import static android.util.Xml.newSerializer;
26 
27 import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_ONE_TIME_PERMISSION_REVOKED;
28 
29 import static java.nio.charset.StandardCharsets.UTF_8;
30 
31 import android.Manifest;
32 import android.app.admin.DevicePolicyManager;
33 import android.app.role.RoleManager;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.pm.PackageInfo;
37 import android.content.pm.PackageManager;
38 import android.os.AsyncTask;
39 import android.os.Build;
40 import android.os.Handler;
41 import android.os.Looper;
42 import android.os.Process;
43 import android.os.UserHandle;
44 import android.os.UserManager;
45 import android.permission.AdminPermissionControlParams;
46 import android.permission.PermissionManager;
47 import android.permission.RuntimePermissionPresentationInfo;
48 import android.permission.RuntimePermissionUsageInfo;
49 import android.util.ArrayMap;
50 import android.util.ArraySet;
51 import android.util.Log;
52 import android.util.Xml;
53 
54 import androidx.annotation.NonNull;
55 import androidx.annotation.Nullable;
56 import androidx.annotation.RequiresApi;
57 
58 import com.android.permissioncontroller.PermissionControllerProto.PermissionControllerDumpProto;
59 import com.android.permissioncontroller.PermissionControllerStatsLog;
60 import com.android.permissioncontroller.ecm.EnhancedConfirmationStatsLogUtils;
61 import com.android.permissioncontroller.permission.model.AppPermissionGroup;
62 import com.android.permissioncontroller.permission.model.AppPermissions;
63 import com.android.permissioncontroller.permission.model.Permission;
64 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo;
65 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState;
66 import com.android.permissioncontroller.permission.ui.AutoGrantPermissionsNotifier;
67 import com.android.permissioncontroller.permission.utils.ArrayUtils;
68 import com.android.permissioncontroller.permission.utils.ContextCompat;
69 import com.android.permissioncontroller.permission.utils.KotlinUtils;
70 import com.android.permissioncontroller.permission.utils.PermissionMapping;
71 import com.android.permissioncontroller.permission.utils.UserSensitiveFlagsUtils;
72 import com.android.permissioncontroller.permission.utils.Utils;
73 import com.android.permissioncontroller.permission.utils.v31.AdminRestrictedPermissionsUtils;
74 import com.android.role.controller.model.Role;
75 import com.android.role.controller.model.Roles;
76 
77 import kotlin.Pair;
78 
79 import org.xmlpull.v1.XmlPullParser;
80 import org.xmlpull.v1.XmlSerializer;
81 
82 import java.io.FileDescriptor;
83 import java.io.FileOutputStream;
84 import java.io.IOException;
85 import java.io.InputStream;
86 import java.io.OutputStream;
87 import java.io.PrintWriter;
88 import java.nio.charset.StandardCharsets;
89 import java.util.ArrayList;
90 import java.util.Collections;
91 import java.util.HashSet;
92 import java.util.List;
93 import java.util.Map;
94 import java.util.Set;
95 import java.util.concurrent.Executor;
96 import java.util.function.Consumer;
97 import java.util.function.IntConsumer;
98 import java.util.stream.Collectors;
99 
100 import kotlinx.coroutines.BuildersKt;
101 import kotlinx.coroutines.GlobalScope;
102 
103 /**
104  * Calls from the system into the permission controller.
105  *
106  * All reading methods are called async, and all writing method are called on the AsyncTask single
107  * thread executor so that multiple writes won't override each other concurrently.
108  */
109 public final class PermissionControllerServiceImpl extends PermissionControllerLifecycleService {
110     private static final String LOG_TAG = PermissionControllerServiceImpl.class.getSimpleName();
111     public static final String ONE_TIME_PERMISSION_REVOKED_REASON = "one-time permission revoked";
112     private static final int MAX_RETRY_ATTEMPTS = 3;
113     private static final long RETRY_DELAY_MS = 500;
114 
115 
116     private final PermissionControllerServiceModel mServiceModel = new
117             PermissionControllerServiceModel(this);
118 
119     @Override
onUnbind(@ullable Intent intent)120     public boolean onUnbind(@Nullable Intent intent) {
121         mServiceModel.removeObservers();
122         return super.onUnbind(intent);
123     }
124 
125     @Override
dump(FileDescriptor fd, PrintWriter writer, String[] args)126     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
127         PermissionControllerDumpProto dump;
128         try {
129             dump = BuildersKt.runBlocking(
130                     GlobalScope.INSTANCE.getCoroutineContext(),
131                     (coroutineScope, continuation) -> mServiceModel.onDump(continuation));
132         } catch (Exception e) {
133             Log.e(LOG_TAG, "Cannot produce dump", e);
134             return;
135         }
136 
137         if (ArrayUtils.contains(args, "--proto")) {
138             try (OutputStream out = new FileOutputStream(fd)) {
139                 dump.writeTo(out);
140             } catch (IOException e) {
141                 Log.e(LOG_TAG, "Cannot write dump", e);
142             }
143         } else {
144             writer.println(dump.toString());
145             writer.flush();
146         }
147     }
148 
149     /**
150      * Expand {@code perms} by split permissions for an app with the given targetSDK.
151      *
152      * @param perms The permissions that should be expanded
153      * @param targetSDK The target SDK to expand for
154      *
155      * @return The expanded permissions
156      */
addSplitPermissions(@onNull List<String> perms, int targetSDK)157     private @NonNull ArrayList<String> addSplitPermissions(@NonNull List<String> perms,
158             int targetSDK) {
159         List<PermissionManager.SplitPermissionInfo> splitPerms =
160                 getSystemService(PermissionManager.class).getSplitPermissions();
161 
162         // Add split permissions to the request
163         ArrayList<String> expandedPerms = new ArrayList<>(perms);
164         int numReqPerms = perms.size();
165         for (int reqPermNum = 0; reqPermNum < numReqPerms; reqPermNum++) {
166             String reqPerm = perms.get(reqPermNum);
167 
168             int numSplitPerms = splitPerms.size();
169             for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
170                 PermissionManager.SplitPermissionInfo splitPerm = splitPerms.get(splitPermNum);
171 
172                 if (targetSDK < splitPerm.getTargetSdk()
173                         && splitPerm.getSplitPermission().equals(reqPerm)) {
174                     expandedPerms.addAll(splitPerm.getNewPermissions());
175                 }
176             }
177         }
178 
179         return expandedPerms;
180     }
181 
182     /**
183      * Get the package info for a package.
184      *
185      * @param pkg The package name
186      *
187      * @return the package info or {@code null} if the package could not be found
188      */
getPkgInfo(@onNull String pkg)189     private @Nullable PackageInfo getPkgInfo(@NonNull String pkg) {
190         try {
191             return getPackageManager().getPackageInfo(pkg, GET_PERMISSIONS);
192         } catch (PackageManager.NameNotFoundException e) {
193             Log.w(LOG_TAG, pkg + " not found", e);
194             return null;
195         }
196     }
197 
198     /**
199      * Given a set of permissions, find all permission groups of an app that can be revoked and that
200      * contain any of the permissions.
201      *
202      * @param permissions The permissions to revoke
203      * @param appPerms The {@link AppPermissions} for the app that is currently investigated
204      *
205      * @return The groups to revoke
206      */
getRevocableGroupsForPermissions( @onNull ArrayList<String> permissions, @NonNull AppPermissions appPerms)207     private @NonNull ArrayList<AppPermissionGroup> getRevocableGroupsForPermissions(
208             @NonNull ArrayList<String> permissions, @NonNull AppPermissions appPerms) {
209         ArrayList<AppPermissionGroup> groupsToRevoke = new ArrayList<>();
210         int numGroups = appPerms.getPermissionGroups().size();
211         for (int groupNum = 0; groupNum < numGroups; groupNum++) {
212             AppPermissionGroup group = appPerms.getPermissionGroups().get(groupNum);
213 
214             // Do not override fixed permissions
215             if (group.isPolicyFixed() || group.isSystemFixed()) {
216                 continue;
217             }
218 
219             int numPerms = permissions.size();
220             for (int permNum = 0; permNum < numPerms; permNum++) {
221                 String reqPerm = permissions.get(permNum);
222 
223                 if (group.hasPermission(reqPerm)) {
224                     groupsToRevoke.add(group);
225 
226                     // If fg permissions get revoked also revoke bg permissions as bg
227                     // permissions require fg permissions.
228                     AppPermissionGroup bgPerms = group.getBackgroundPermissions();
229                     if (bgPerms != null) {
230                         groupsToRevoke.add(bgPerms);
231                     }
232                 } else {
233                     AppPermissionGroup bgPerms = group.getBackgroundPermissions();
234                     if (bgPerms != null && bgPerms.hasPermission(reqPerm)) {
235                         groupsToRevoke.add(bgPerms);
236                     }
237                 }
238             }
239         }
240 
241         return groupsToRevoke;
242     }
243 
244     /**
245      * Revoke all permissions of some groups.
246      *
247      * @param groupsToRevoke The groups
248      *
249      * @return The permissions that were revoked
250      */
revokePermissionGroups( @onNull ArrayList<AppPermissionGroup> groupsToRevoke)251     private @NonNull ArrayList<String> revokePermissionGroups(
252             @NonNull ArrayList<AppPermissionGroup> groupsToRevoke) {
253         ArrayList<String> revokedPerms = new ArrayList<>();
254 
255         int numGroupsToRevoke = groupsToRevoke.size();
256         for (int groupsToRevokeNum = 0; groupsToRevokeNum < numGroupsToRevoke;
257                 groupsToRevokeNum++) {
258             AppPermissionGroup group = groupsToRevoke.get(groupsToRevokeNum);
259             ArrayList<Permission> perms = group.getPermissions();
260 
261             // Mark the permissions as reviewed as we don't want to use to accidentally grant
262             // the permission during review
263             group.unsetReviewRequired();
264 
265             int numPerms = perms.size();
266             for (int permNum = 0; permNum < numPerms; permNum++) {
267                 Permission perm = perms.get(permNum);
268 
269                 // Only count individual permissions that are actually revoked
270                 if (perm.isGrantedIncludingAppOp()) {
271                     revokedPerms.add(perm.getName());
272                 }
273             }
274 
275             group.revokeRuntimePermissions(false);
276         }
277 
278         return revokedPerms;
279     }
280 
281     @Override
onRevokeRuntimePermissions(@onNull Map<String, List<String>> request, boolean doDryRun, int reason, @NonNull String callerPackageName, @NonNull Consumer<Map<String, List<String>>> callback)282     public void onRevokeRuntimePermissions(@NonNull Map<String, List<String>> request,
283             boolean doDryRun, int reason, @NonNull String callerPackageName,
284             @NonNull Consumer<Map<String, List<String>>> callback) {
285         AsyncTask.execute(() -> callback.accept(onRevokeRuntimePermissions(request, doDryRun,
286                 reason, callerPackageName)));
287     }
288 
onRevokeRuntimePermissions( @onNull Map<String, List<String>> request, boolean doDryRun, int reason, @NonNull String callerPackageName)289     private @NonNull Map<String, List<String>> onRevokeRuntimePermissions(
290             @NonNull Map<String, List<String>> request, boolean doDryRun,
291             int reason, @NonNull String callerPackageName) {
292         // The reason parameter is not checked by platform code as this might need to be updated
293         // async to platform releases.
294         if (reason != REASON_MALWARE && reason != REASON_INSTALLER_POLICY_VIOLATION) {
295             Log.e(LOG_TAG, "Invalid reason " + reason);
296             return Collections.emptyMap();
297         }
298 
299         PackageManager pm = getPackageManager();
300 
301         PackageInfo callerPkgInfo = getPkgInfo(callerPackageName);
302         if (callerPkgInfo == null) {
303             return Collections.emptyMap();
304         }
305         int callerTargetSdk = callerPkgInfo.applicationInfo.targetSdkVersion;
306 
307         Map<String, List<String>> actuallyRevokedPerms = new ArrayMap<>();
308         ArrayList<AppPermissions> appsWithRevokedPerms = new ArrayList<>();
309 
310         for (Map.Entry<String, List<String>> appRequest : request.entrySet()) {
311             PackageInfo requestedPkgInfo = getPkgInfo(appRequest.getKey());
312             if (requestedPkgInfo == null) {
313                 continue;
314             }
315 
316             // Permissions are per UID. Hence permissions will be removed from all apps sharing an
317             // UID.
318             String[] pkgNames = pm.getPackagesForUid(requestedPkgInfo.applicationInfo.uid);
319             if (pkgNames == null) {
320                 continue;
321             }
322 
323             int numPkgNames = pkgNames.length;
324             for (int pkgNum = 0; pkgNum < numPkgNames; pkgNum++) {
325                 String pkgName = pkgNames[pkgNum];
326 
327                 PackageInfo pkgInfo = getPkgInfo(pkgName);
328                 if (pkgInfo == null) {
329                     continue;
330                 }
331 
332                 // If the revocation is because of a market policy violation only the installer can
333                 // revoke the permissions.
334                 if (reason == REASON_INSTALLER_POLICY_VIOLATION
335                         && !callerPackageName.equals(pm.getInstallerPackageName(pkgName))) {
336                     Log.i(LOG_TAG, "Ignoring " + pkgName + " as it is not installed by "
337                             + callerPackageName);
338                     continue;
339                 }
340 
341                 // In rare cases the caller does not know about the permissions that have been added
342                 // due to splits. Hence add them now.
343                 ArrayList<String> expandedPerms = addSplitPermissions(appRequest.getValue(),
344                         callerTargetSdk);
345 
346                 AppPermissions appPerms = new AppPermissions(this, pkgInfo, false, true, null);
347 
348                 // First find the groups that should be revoked and then revoke all permissions of
349                 // these groups. This is needed as soon as a single permission in the group is
350                 // granted, all other permissions get auto-granted on request.
351                 ArrayList<AppPermissionGroup> groupsToRevoke = getRevocableGroupsForPermissions(
352                         expandedPerms, appPerms);
353                 ArrayList<String> revokedPerms = revokePermissionGroups(groupsToRevoke);
354 
355                 // In racy conditions the group might not have had granted permissions anymore
356                 if (!revokedPerms.isEmpty()) {
357                     actuallyRevokedPerms.put(pkgName, revokedPerms);
358                     appsWithRevokedPerms.add(appPerms);
359                 }
360             }
361         }
362 
363         // Persist changes after we computed everything to remove
364         // This is necessary as we would otherwise only look at the first app of a shared UID.
365         if (!doDryRun) {
366             int numChangedApps = appsWithRevokedPerms.size();
367             for (int i = 0; i < numChangedApps; i++) {
368                 appsWithRevokedPerms.get(i).persistChanges(true);
369             }
370         }
371 
372         return actuallyRevokedPerms;
373     }
374 
375     @Override
onGetRuntimePermissionsBackup(@onNull UserHandle user, @NonNull OutputStream backup, @NonNull Runnable callback)376     public void onGetRuntimePermissionsBackup(@NonNull UserHandle user,
377             @NonNull OutputStream backup, @NonNull Runnable callback) {
378         AsyncTask.execute(() -> {
379             onGetRuntimePermissionsBackup(user, backup);
380             callback.run();
381         });
382     }
383 
onGetRuntimePermissionsBackup(@onNull UserHandle user, @NonNull OutputStream backup)384     private void onGetRuntimePermissionsBackup(@NonNull UserHandle user,
385             @NonNull OutputStream backup) {
386         BackupHelper backupHelper = new BackupHelper(this, user);
387 
388         try {
389             XmlSerializer serializer = newSerializer();
390             serializer.setOutput(backup, UTF_8.name());
391 
392             backupHelper.writeState(serializer);
393             serializer.flush();
394         } catch (Exception e) {
395             Log.e(LOG_TAG, "Unable to write permissions backup", e);
396         }
397     }
398 
399     @Override
onStageAndApplyRuntimePermissionsBackup(@onNull UserHandle user, @NonNull InputStream backup, @NonNull Runnable callback)400     public void onStageAndApplyRuntimePermissionsBackup(@NonNull UserHandle user,
401             @NonNull InputStream backup, @NonNull Runnable callback) {
402         AsyncTask.execute(() -> {
403             onRestoreRuntimePermissionsBackup(user, backup);
404             callback.run();
405         });
406     }
407 
onRestoreRuntimePermissionsBackup(@onNull UserHandle user, @NonNull InputStream backup)408     private void onRestoreRuntimePermissionsBackup(@NonNull UserHandle user,
409             @NonNull InputStream backup) {
410         try {
411             XmlPullParser parser = Xml.newPullParser();
412             parser.setInput(backup, StandardCharsets.UTF_8.name());
413 
414             new BackupHelper(this, user).restoreState(parser);
415         } catch (Exception e) {
416             Log.e(LOG_TAG, "Exception restoring permissions: " + e.getMessage());
417         }
418     }
419 
420     @Override
onApplyStagedRuntimePermissionBackup(@onNull String packageName, @NonNull UserHandle user, @NonNull Consumer<Boolean> callback)421     public void onApplyStagedRuntimePermissionBackup(@NonNull String packageName,
422             @NonNull UserHandle user, @NonNull Consumer<Boolean> callback) {
423         AsyncTask.execute(() -> callback.accept(
424                 onRestoreDelayedRuntimePermissionsBackup(packageName, user)));
425     }
426 
onRestoreDelayedRuntimePermissionsBackup(@onNull String packageName, @NonNull UserHandle user)427     private boolean onRestoreDelayedRuntimePermissionsBackup(@NonNull String packageName,
428             @NonNull UserHandle user) {
429         try {
430             return new BackupHelper(this, user).restoreDelayedState(packageName);
431         } catch (Exception e) {
432             Log.e(LOG_TAG, "Exception restoring delayed permissions: " + e.getMessage());
433             return false;
434         }
435     }
436 
437     @Override
onGetAppPermissions(@onNull String packageName, @NonNull Consumer<List<RuntimePermissionPresentationInfo>> callback)438     public void onGetAppPermissions(@NonNull String packageName,
439             @NonNull Consumer<List<RuntimePermissionPresentationInfo>> callback) {
440         mServiceModel.onGetAppPermissions(packageName, (groupUiInfos) -> {
441             List<RuntimePermissionPresentationInfo> permissions = new ArrayList<>();
442 
443             for (Pair<String, AppPermGroupUiInfo> groupNameAndUiInfo : groupUiInfos) {
444                 String groupName = groupNameAndUiInfo.getFirst();
445                 AppPermGroupUiInfo uiInfo = groupNameAndUiInfo.getSecond();
446                 boolean isPlatform =
447                         PermissionMapping.getPlatformPermissionGroups().contains(groupName);
448                 CharSequence label = KotlinUtils.INSTANCE.getPermGroupLabel(this, groupName);
449 
450                 RuntimePermissionPresentationInfo permission =
451                         new RuntimePermissionPresentationInfo(label,
452                                 uiInfo.getPermGrantState() != PermGrantState.PERMS_DENIED
453                                         && uiInfo.getPermGrantState() != PermGrantState.PERMS_ASK,
454                                 isPlatform);
455                 permissions.add(permission);
456             }
457             callback.accept(permissions);
458         });
459     }
460 
461     @Override
onRevokeRuntimePermission(@onNull String packageName, @NonNull String permissionName, @NonNull Runnable callback)462     public void onRevokeRuntimePermission(@NonNull String packageName,
463             @NonNull String permissionName, @NonNull Runnable callback) {
464         AsyncTask.execute(() -> {
465             onRevokeRuntimePermission(packageName, permissionName);
466             callback.run();
467         });
468     }
469 
onRevokeRuntimePermission(@onNull String packageName, @NonNull String permissionName)470     private void onRevokeRuntimePermission(@NonNull String packageName,
471             @NonNull String permissionName) {
472         try {
473             final PackageInfo packageInfo = getPackageManager().getPackageInfo(packageName,
474                     GET_PERMISSIONS);
475             final AppPermissions appPermissions = new AppPermissions(this, packageInfo, false,
476                     null);
477 
478             final AppPermissionGroup appPermissionGroup = appPermissions.getGroupForPermission(
479                     permissionName);
480 
481             if (appPermissionGroup != null) {
482                 appPermissionGroup.revokeRuntimePermissions(false);
483             }
484         } catch (PackageManager.NameNotFoundException e) {
485             Log.e(LOG_TAG, "Error getting package:" + packageName, e);
486         }
487     }
488 
489     @Override
onCountPermissionApps(@onNull List<String> permissionNames, int flags, @NonNull IntConsumer callback)490     public void onCountPermissionApps(@NonNull List<String> permissionNames, int flags,
491             @NonNull IntConsumer callback) {
492         // There is no data processing needed, so we just directly pass the result onto the callback
493         mServiceModel.onCountPermissionAppsLiveData(permissionNames, flags,
494                 callback);
495     }
496 
497     /**
498      * Deprecated api call, only returns null.
499      */
500     @Override
501     @Deprecated
onGetPermissionUsages(boolean countSystem, long numMillis, @NonNull Consumer<List<RuntimePermissionUsageInfo>> callback)502     public void onGetPermissionUsages(boolean countSystem, long numMillis,
503             @NonNull Consumer<List<RuntimePermissionUsageInfo>> callback) {
504         callback.accept(null);
505     }
506 
507     @Override
onSetRuntimePermissionGrantStateByDeviceAdmin(@onNull String callerPackageName, @NonNull String packageName, @NonNull String unexpandedPermission, int grantState, @NonNull Consumer<Boolean> callback)508     public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName,
509             @NonNull String packageName, @NonNull String unexpandedPermission, int grantState,
510             @NonNull Consumer<Boolean> callback) {
511         AsyncTask.execute(() -> callback.accept(onSetRuntimePermissionGrantStateByDeviceAdmin(
512                 callerPackageName, packageName, unexpandedPermission, grantState, true)));
513     }
514 
515     /**
516      * Admin control based on params.
517      */
518     @Override
onSetRuntimePermissionGrantStateByDeviceAdmin( @onNull String callerPackageName, @NonNull AdminPermissionControlParams params, @NonNull Consumer<Boolean> callback)519     public void onSetRuntimePermissionGrantStateByDeviceAdmin(
520             @NonNull String callerPackageName, @NonNull AdminPermissionControlParams params,
521             @NonNull Consumer<Boolean> callback) {
522         AsyncTask.execute(() -> callback.accept(onSetRuntimePermissionGrantStateByDeviceAdmin(
523                 callerPackageName, params.getGranteePackageName(), params.getPermission(),
524                 params.getGrantState(), params.canAdminGrantSensorsPermissions())));
525     }
526 
onSetRuntimePermissionGrantStateByDeviceAdmin(@onNull String callerPackageName, @NonNull String packageName, @NonNull String unexpandedPermission, int grantState, boolean canAdminGrantSensorsPermissions)527     private boolean onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName,
528             @NonNull String packageName, @NonNull String unexpandedPermission, int grantState,
529             boolean canAdminGrantSensorsPermissions) {
530         PackageInfo callerPkgInfo = getPkgInfo(callerPackageName);
531         if (callerPkgInfo == null) {
532             Log.w(LOG_TAG, "Cannot fix " + unexpandedPermission + " as admin "
533                     + callerPackageName + " cannot be found");
534             return false;
535         }
536 
537         PackageInfo pkgInfo = getPkgInfo(packageName);
538         if (pkgInfo == null) {
539             Log.w(LOG_TAG, "Cannot fix " + unexpandedPermission + " as " + packageName
540                     + " cannot be found");
541             return false;
542         }
543 
544         // TODO(b/333867076): Switch to !SdkLevel.isAtLeastW()
545         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
546                 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
547             RoleManager roleManager = getSystemService(RoleManager.class);
548             List<String> roleHolders =
549                     roleManager.getRoleHolders(RoleManager.ROLE_SYSTEM_SUPERVISION);
550             if (roleHolders.contains(callerPackageName)) {
551                 canAdminGrantSensorsPermissions = true;
552             }
553         }
554 
555         ArrayList<String> expandedPermissions = addSplitPermissions(
556                 Collections.singletonList(unexpandedPermission),
557                 callerPkgInfo.applicationInfo.targetSdkVersion);
558 
559         AppPermissions app = new AppPermissions(this, pkgInfo, false, true, null);
560         AutoGrantPermissionsNotifier autoGrantPermissionsNotifier =
561                 new AutoGrantPermissionsNotifier(this, pkgInfo);
562 
563         final boolean isManagedProfile = getSystemService(UserManager.class).isManagedProfile();
564         DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
565         int numPerms = expandedPermissions.size();
566         for (int i = 0; i < numPerms; i++) {
567             String permName = expandedPermissions.get(i);
568             AppPermissionGroup group = app.getGroupForPermission(permName);
569             if (group == null || group.isSystemFixed()) {
570                 continue;
571             }
572 
573             Permission perm = group.getPermission(permName);
574             if (perm == null) {
575                 continue;
576             }
577 
578             switch (grantState) {
579                 case PERMISSION_GRANT_STATE_GRANTED:
580                     if (AdminRestrictedPermissionsUtils.mayAdminGrantPermission(perm.getName(),
581                             group.getName(), canAdminGrantSensorsPermissions, isManagedProfile,
582                             dpm)) {
583                         perm.setPolicyFixed(true);
584                         group.grantRuntimePermissions(false, false, new String[]{permName});
585                         autoGrantPermissionsNotifier.onPermissionAutoGranted(permName);
586                     } else {
587                         // similar to PERMISSION_GRANT_STATE_DEFAULT
588                         perm.setPolicyFixed(false);
589                     }
590                     break;
591                 case PERMISSION_GRANT_STATE_DENIED:
592                     perm.setPolicyFixed(true);
593                     group.revokeRuntimePermissions(false, new String[]{permName});
594                     break;
595                 case PERMISSION_GRANT_STATE_DEFAULT:
596                     perm.setPolicyFixed(false);
597                     break;
598                 default:
599                     return false;
600             }
601         }
602 
603         app.persistChanges(grantState == PERMISSION_GRANT_STATE_DENIED
604                 || !callerPackageName.equals(packageName));
605         autoGrantPermissionsNotifier.notifyOfAutoGrantPermissions(false);
606 
607         return true;
608     }
609 
610     @Override
onGrantOrUpgradeDefaultRuntimePermissions(@onNull Runnable callback)611     public void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable callback) {
612         performDefaultPermissionGrants();
613         RuntimePermissionsUpgradeController.INSTANCE.upgradeIfNeeded(this, () -> {
614             callback.run();
615         });
616     }
617 
performDefaultPermissionGrants()618     private void performDefaultPermissionGrants() {
619         // TODO: Default permission grants should go here
620     }
621 
622     @Override
onUpdateUserSensitivePermissionFlags(int uid, Executor executor, Runnable callback)623     public void onUpdateUserSensitivePermissionFlags(int uid, Executor executor,
624             Runnable callback) {
625         onUpdateUserSensistivePermissionFlagsWithRetry(uid, executor, callback, 0);
626     }
627 
onUpdateUserSensistivePermissionFlagsWithRetry(int uid, Executor executor, Runnable callback, int numAttempts)628     private void onUpdateUserSensistivePermissionFlagsWithRetry(int uid, Executor executor,
629             Runnable callback, int numAttempts) {
630         String idString = uid == Process.INVALID_UID
631                 ? "user " + Process.myUserHandle().getIdentifier() : "uid " + uid;
632         try {
633             Log.i(LOG_TAG, "Updating user sensitive for " + idString);
634             if (uid == Process.INVALID_UID) {
635                 UserSensitiveFlagsUtils.updateUserSensitiveForUser(Process.myUserHandle(),
636                         () -> executor.execute(callback));
637             } else {
638                 UserSensitiveFlagsUtils.updateUserSensitiveForUid(uid,
639                         () -> executor.execute(callback));
640             }
641         } catch (Exception e) {
642             // We specifically want to catch DeadSystemExceptions, but cannot explicitly request
643             // them, as it results in a compiler error
644             Log.w(LOG_TAG, "Failed to complete user sensitive update for " + idString
645                     + ", attempt number " + (numAttempts + 1) + " of " + MAX_RETRY_ATTEMPTS, e);
646             if (numAttempts == MAX_RETRY_ATTEMPTS) {
647                 throw e;
648             } else {
649                 int attempts = numAttempts + 1;
650                 Handler h = new Handler(Looper.getMainLooper());
651                 h.postDelayed(() -> onUpdateUserSensistivePermissionFlagsWithRetry(uid,
652                         executor, callback, attempts), RETRY_DELAY_MS);
653             }
654         }
655 
656     }
657 
658     @Override
onOneTimePermissionSessionTimeout(@onNull String packageName)659     public void onOneTimePermissionSessionTimeout(@NonNull String packageName) {
660         onOneTimePermissionSessionTimeout(packageName, ContextCompat.DEVICE_ID_DEFAULT);
661     }
662 
663     @Override
onOneTimePermissionSessionTimeout(@onNull String packageName, int deviceId)664     public void onOneTimePermissionSessionTimeout(@NonNull String packageName,
665             int deviceId) {
666         Context deviceContext = ContextCompat.createDeviceContext(this, deviceId);
667         PackageManager pm = deviceContext.getPackageManager();
668         PackageInfo packageInfo;
669         int uid;
670         try {
671             packageInfo = pm.getPackageInfo(packageName, GET_PERMISSIONS);
672             uid = pm.getPackageUid(packageName, 0);
673         } catch (PackageManager.NameNotFoundException e) {
674             throw new RuntimeException(e);
675         }
676 
677         String[] permissions = packageInfo.requestedPermissions;
678         if (permissions == null) {
679             return;
680         }
681         Set<AppPermissionGroup> groups = new ArraySet<>();
682         for (String permission : permissions) {
683             AppPermissionGroup group = AppPermissionGroup.create(deviceContext, packageInfo,
684                     permission, true);
685             if (group != null) {
686                 AppPermissionGroup bgGroup = group.getBackgroundPermissions();
687                 boolean isBgGroupOneTime = bgGroup != null && bgGroup.isOneTime();
688                 if (group.isOneTime() || isBgGroupOneTime) {
689                     groups.add(group);
690                 }
691             }
692         }
693         long requestId = Utils.getValidSessionId();
694         for (AppPermissionGroup group : groups) {
695             AppPermissionGroup bgGroup = group.getBackgroundPermissions();
696             if (group.areRuntimePermissionsGranted(null, true, false)) {
697                 logOneTimeSessionRevoke(packageName, uid, group, requestId);
698                 // Revoke only one time granted permissions if not all
699                 List<String> oneTimeGrantedPermissions = group.getPermissions().stream()
700                         .filter(Permission::isOneTime).filter(Permission::isGranted)
701                         .map(Permission::getName).collect(Collectors.toList());
702                 if (group.getPermissions().size() == oneTimeGrantedPermissions.size()) {
703                     group.revokeRuntimePermissions(false);
704                 } else {
705                     group.revokeRuntimePermissions(false,
706                             oneTimeGrantedPermissions.toArray(new String[0]));
707                 }
708                 for (String permissionName : oneTimeGrantedPermissions) {
709                     // We only reset the USER_SET and REVOKED_COMPAT flags if the permission was
710                     // granted.
711                     Permission permission = group.getPermission(permissionName);
712                     if (permission != null) {
713                         permission.setUserSet(false);
714                         if (!permission.isGranted() && permission.isRevokedCompat()) {
715                             // If we revoked the permission, but the Revoked Compat flag is set,
716                             // reset it
717                             permission.setRevokedCompat(false);
718                         }
719                     }
720                 }
721                 if (bgGroup != null) {
722                     // We also revoke background permissions if all foreground permissions are
723                     // getting revoked.
724                     if (group.getPermissions().size() == oneTimeGrantedPermissions.size()) {
725                         bgGroup.revokeRuntimePermissions(false);
726                     } else {
727                         bgGroup.revokeRuntimePermissions(false,
728                                 bgGroup.getPermissions().stream()
729                                         .filter(Permission::isOneTime).filter(Permission::isGranted)
730                                         .map(Permission::getName).toArray(String[]::new));
731                     }
732                 }
733             }
734             if (!group.supportsOneTimeGrant()) {
735                 group.setOneTime(false);
736             }
737             group.persistChanges(false, ONE_TIME_PERMISSION_REVOKED_REASON);
738             if (bgGroup != null) {
739                 if (!bgGroup.supportsOneTimeGrant()) {
740                     bgGroup.setOneTime(false);
741                 }
742                 bgGroup.persistChanges(false, ONE_TIME_PERMISSION_REVOKED_REASON);
743             }
744         }
745     }
746 
logOneTimeSessionRevoke(@onNull String packageName, int uid, AppPermissionGroup group, long requestId)747     private void logOneTimeSessionRevoke(@NonNull String packageName, int uid,
748             AppPermissionGroup group, long requestId) {
749         // used to keep lines below 100 chars
750         int r = PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_ONE_TIME_PERMISSION_REVOKED;
751 
752         for (Permission permission : group.getPermissions()) {
753             if (permission.isGranted()) {
754                 String permName = permission.getName();
755                 Log.i(LOG_TAG,
756                         "Permission grant result requestId=" + requestId + " callingUid="
757                                 + uid + " callingPackage=" + packageName + " permission="
758                                 + permName + " isImplicit=false" + " result=" + r);
759                 boolean isPackageRestrictedByEnhancedConfirmation =
760                         EnhancedConfirmationStatsLogUtils.INSTANCE.isPackageEcmRestricted(this,
761                         packageName, uid);
762                 PermissionControllerStatsLog.write(
763                         PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED,
764                         requestId, uid, packageName, permName, false, r,
765                         /* permission_rationale_shown = */ false,
766                         isPackageRestrictedByEnhancedConfirmation);
767             }
768         }
769     }
770 
771     @Override
getPrivilegesDescriptionStringForProfile(@onNull String deviceProfileName)772     public String getPrivilegesDescriptionStringForProfile(@NonNull String deviceProfileName) {
773         Role role = Roles.get(this).get(deviceProfileName);
774         if (role == null) {
775             throw new IllegalArgumentException("No such role: " + deviceProfileName);
776         }
777         return getString(role.getDescriptionResource(), "APP_NAME");
778     }
779 
780     @Override
onGetPlatformPermissionsForGroup(@onNull String permissionGroupName, @NonNull Consumer<List<String>> callback)781     public void onGetPlatformPermissionsForGroup(@NonNull String permissionGroupName,
782             @NonNull Consumer<List<String>> callback) {
783         callback.accept(PermissionMapping.getPlatformPermissionNamesOfGroup(permissionGroupName));
784     }
785 
786     @Override
onGetGroupOfPlatformPermission(@onNull String permissionName, @NonNull Consumer<String> callback)787     public void onGetGroupOfPlatformPermission(@NonNull String permissionName,
788             @NonNull Consumer<String> callback) {
789         callback.accept(PermissionMapping.getGroupOfPlatformPermission(permissionName));
790     }
791 
792     @Override
onGetUnusedAppCount(@onNull IntConsumer callback)793     public void onGetUnusedAppCount(@NonNull IntConsumer callback) {
794         mServiceModel.onCountUnusedApps(callback);
795     }
796 
797     @Override
onGetHibernationEligibility(@onNull String packageName, @NonNull IntConsumer callback)798     public void onGetHibernationEligibility(@NonNull String packageName,
799             @NonNull IntConsumer callback) {
800         mServiceModel.onGetHibernationEligibility(packageName, callback);
801     }
802 
803     @Override
804     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
onRevokeSelfPermissionsOnKill(@onNull String packageName, @NonNull List<String> permissions, @NonNull Runnable callback)805     public void onRevokeSelfPermissionsOnKill(@NonNull String packageName,
806             @NonNull List<String> permissions, @NonNull Runnable callback) {
807         onRevokeSelfPermissionsOnKill(
808                 packageName, permissions, ContextCompat.DEVICE_ID_DEFAULT, callback);
809     }
810 
811     @Override
onRevokeSelfPermissionsOnKill(@onNull String packageName, @NonNull List<String> permissions, int deviceId, @NonNull Runnable callback)812     public void onRevokeSelfPermissionsOnKill(@NonNull String packageName,
813             @NonNull List<String> permissions, int deviceId, @NonNull Runnable callback) {
814         Context deviceContext = ContextCompat.createDeviceContext(this, deviceId);
815         PackageInfo pkgInfo = getPkgInfo(packageName);
816         if (pkgInfo == null) {
817             throw new SecurityException("Cannot revoke permission " + String.join(",", permissions)
818                     + " for package " + packageName);
819         }
820         Set<AppPermissionGroup> groups = new HashSet<>();
821         AppPermissions app = new AppPermissions(deviceContext, pkgInfo, false, true, null);
822         for (String permName : permissions) {
823             AppPermissionGroup group = app.getGroupForPermission(permName);
824             if (group == null) {
825                 throw new IllegalArgumentException("Cannot revoke permission " + permName
826                         + " for package " + packageName + " since " + permName
827                         + " does not belong to a permission group");
828             }
829             if (!group.doesSupportRuntimePermissions()) {
830                 throw new IllegalArgumentException("Cannot revoke permission " + permName
831                         + " for package " + packageName + " since it is not a runtime permission");
832             }
833             Permission perm = group.getPermission(permName);
834             if (!perm.isGranted()) {
835                 continue;
836             }
837             perm.setOneTime(true);
838             groups.add(group);
839 
840             if (permName.equals(Manifest.permission.ACCESS_COARSE_LOCATION)) {
841                 group.getPermission(Manifest.permission.ACCESS_FINE_LOCATION).setOneTime(true);
842             } else if (permName.equals(Manifest.permission.ACCESS_FINE_LOCATION)) {
843                 // Set coarse as the selected location accuracy
844                 perm.setSelectedLocationAccuracy(false);
845                 group.getPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
846                         .setSelectedLocationAccuracy(true);
847             }
848 
849             if (group.isStrictlyOneTime()) {
850                 // All remaining permissions in the group are one-time, we should also revoke
851                 // the background permissions if there are any
852                 Permission bgPerm = perm.getBackgroundPermission();
853                 if (bgPerm != null && bgPerm.isGranted()) {
854                     bgPerm.setOneTime(true);
855                     AppPermissionGroup bgGroup = group.getBackgroundPermissions();
856                     groups.add(bgGroup);
857                 }
858             }
859         }
860         for (AppPermissionGroup group : groups) {
861             group.setSelfRevoked();
862             group.persistChanges(false);
863         }
864         getMainExecutor().execute(callback);
865     }
866 }
867