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.packageinstaller.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.COUNT_ONLY_WHEN_GRANTED;
24 import static android.permission.PermissionControllerManager.COUNT_WHEN_SYSTEM;
25 import static android.permission.PermissionControllerManager.REASON_INSTALLER_POLICY_VIOLATION;
26 import static android.permission.PermissionControllerManager.REASON_MALWARE;
27 import static android.util.Xml.newSerializer;
28 
29 import static com.android.packageinstaller.permission.utils.Utils.shouldShowPermission;
30 
31 import static java.nio.charset.StandardCharsets.UTF_8;
32 
33 import android.content.Context;
34 import android.content.pm.PackageInfo;
35 import android.content.pm.PackageManager;
36 import android.os.AsyncTask;
37 import android.os.UserHandle;
38 import android.permission.PermissionControllerService;
39 import android.permission.PermissionManager;
40 import android.permission.RuntimePermissionPresentationInfo;
41 import android.permission.RuntimePermissionUsageInfo;
42 import android.util.ArrayMap;
43 import android.util.Log;
44 import android.util.Xml;
45 
46 import androidx.annotation.NonNull;
47 import androidx.annotation.Nullable;
48 
49 import com.android.packageinstaller.permission.model.AppPermissionGroup;
50 import com.android.packageinstaller.permission.model.AppPermissions;
51 import com.android.packageinstaller.permission.model.Permission;
52 import com.android.packageinstaller.permission.utils.Utils;
53 
54 import org.xmlpull.v1.XmlPullParser;
55 import org.xmlpull.v1.XmlSerializer;
56 
57 import java.io.InputStream;
58 import java.io.OutputStream;
59 import java.nio.charset.StandardCharsets;
60 import java.util.ArrayList;
61 import java.util.Collections;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.function.Consumer;
65 import java.util.function.IntConsumer;
66 
67 /**
68  * Calls from the system into the permission controller.
69  *
70  * All reading methods are called async, and all writing method are called on the AsyncTask single
71  * thread executor so that multiple writes won't override each other concurrently.
72  */
73 public final class PermissionControllerServiceImpl extends PermissionControllerService {
74     private static final String LOG_TAG = PermissionControllerServiceImpl.class.getSimpleName();
75 
76     /**
77      * Expand {@code perms} by split permissions for an app with the given targetSDK.
78      *
79      * @param perms The permissions that should be expanded
80      * @param targetSDK The target SDK to expand for
81      *
82      * @return The expanded permissions
83      */
addSplitPermissions(@onNull List<String> perms, int targetSDK)84     private @NonNull ArrayList<String> addSplitPermissions(@NonNull List<String> perms,
85             int targetSDK) {
86         List<PermissionManager.SplitPermissionInfo> splitPerms =
87                 getSystemService(PermissionManager.class).getSplitPermissions();
88 
89         // Add split permissions to the request
90         ArrayList<String> expandedPerms = new ArrayList<>(perms);
91         int numReqPerms = perms.size();
92         for (int reqPermNum = 0; reqPermNum < numReqPerms; reqPermNum++) {
93             String reqPerm = perms.get(reqPermNum);
94 
95             int numSplitPerms = splitPerms.size();
96             for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
97                 PermissionManager.SplitPermissionInfo splitPerm = splitPerms.get(splitPermNum);
98 
99                 if (targetSDK < splitPerm.getTargetSdk()
100                         && splitPerm.getSplitPermission().equals(reqPerm)) {
101                     expandedPerms.addAll(splitPerm.getNewPermissions());
102                 }
103             }
104         }
105 
106         return expandedPerms;
107     }
108 
109     /**
110      * Get the package info for a package.
111      *
112      * @param pkg The package name
113      *
114      * @return the package info or {@code null} if the package could not be found
115      */
getPkgInfo(@onNull String pkg)116     private @Nullable PackageInfo getPkgInfo(@NonNull String pkg) {
117         try {
118             return getPackageManager().getPackageInfo(pkg, GET_PERMISSIONS);
119         } catch (PackageManager.NameNotFoundException e) {
120             Log.w(LOG_TAG, pkg + " not found", e);
121             return null;
122         }
123     }
124 
125     /**
126      * Given a set of permissions, find all permission groups of an app that can be revoked and that
127      * contain any of the permissions.
128      *
129      * @param permissions The permissions to revoke
130      * @param appPerms The {@link AppPermissions} for the app that is currently investigated
131      *
132      * @return The groups to revoke
133      */
getRevocableGroupsForPermissions( @onNull ArrayList<String> permissions, @NonNull AppPermissions appPerms)134     private @NonNull ArrayList<AppPermissionGroup> getRevocableGroupsForPermissions(
135             @NonNull ArrayList<String> permissions, @NonNull AppPermissions appPerms) {
136         ArrayList<AppPermissionGroup> groupsToRevoke = new ArrayList<>();
137         int numGroups = appPerms.getPermissionGroups().size();
138         for (int groupNum = 0; groupNum < numGroups; groupNum++) {
139             AppPermissionGroup group = appPerms.getPermissionGroups().get(groupNum);
140 
141             // Do not override fixed permissions
142             if (group.isPolicyFixed() || group.isSystemFixed()) {
143                 continue;
144             }
145 
146             int numPerms = permissions.size();
147             for (int permNum = 0; permNum < numPerms; permNum++) {
148                 String reqPerm = permissions.get(permNum);
149 
150                 if (group.hasPermission(reqPerm)) {
151                     groupsToRevoke.add(group);
152 
153                     // If fg permissions get revoked also revoke bg permissions as bg
154                     // permissions require fg permissions.
155                     AppPermissionGroup bgPerms = group.getBackgroundPermissions();
156                     if (bgPerms != null) {
157                         groupsToRevoke.add(bgPerms);
158                     }
159                 } else {
160                     AppPermissionGroup bgPerms = group.getBackgroundPermissions();
161                     if (bgPerms != null && bgPerms.hasPermission(reqPerm)) {
162                         groupsToRevoke.add(bgPerms);
163                     }
164                 }
165             }
166         }
167 
168         return groupsToRevoke;
169     }
170 
171     /**
172      * Revoke all permissions of some groups.
173      *
174      * @param groupsToRevoke The groups
175      *
176      * @return The permissions that were revoked
177      */
revokePermissionGroups( @onNull ArrayList<AppPermissionGroup> groupsToRevoke)178     private @NonNull ArrayList<String> revokePermissionGroups(
179             @NonNull ArrayList<AppPermissionGroup> groupsToRevoke) {
180         ArrayList<String> revokedPerms = new ArrayList<>();
181 
182         int numGroupsToRevoke = groupsToRevoke.size();
183         for (int groupsToRevokeNum = 0; groupsToRevokeNum < numGroupsToRevoke;
184                 groupsToRevokeNum++) {
185             AppPermissionGroup group = groupsToRevoke.get(groupsToRevokeNum);
186             ArrayList<Permission> perms = group.getPermissions();
187 
188             // Mark the permissions as reviewed as we don't want to use to accidentally grant
189             // the permission during review
190             group.unsetReviewRequired();
191 
192             int numPerms = perms.size();
193             for (int permNum = 0; permNum < numPerms; permNum++) {
194                 Permission perm = perms.get(permNum);
195 
196                 // Only count individual permissions that are actually revoked
197                 if (perm.isGrantedIncludingAppOp()) {
198                     revokedPerms.add(perm.getName());
199                 }
200             }
201 
202             group.revokeRuntimePermissions(false);
203         }
204 
205         return revokedPerms;
206     }
207 
208     @Override
onRevokeRuntimePermissions(@onNull Map<String, List<String>> request, boolean doDryRun, int reason, @NonNull String callerPackageName, @NonNull Consumer<Map<String, List<String>>> callback)209     public void onRevokeRuntimePermissions(@NonNull Map<String, List<String>> request,
210             boolean doDryRun, int reason, @NonNull String callerPackageName,
211             @NonNull Consumer<Map<String, List<String>>> callback) {
212         AsyncTask.execute(() -> callback.accept(onRevokeRuntimePermissions(request, doDryRun,
213                 reason, callerPackageName)));
214     }
215 
onRevokeRuntimePermissions( @onNull Map<String, List<String>> request, boolean doDryRun, int reason, @NonNull String callerPackageName)216     private @NonNull Map<String, List<String>> onRevokeRuntimePermissions(
217             @NonNull Map<String, List<String>> request, boolean doDryRun,
218             int reason, @NonNull String callerPackageName) {
219         // The reason parameter is not checked by platform code as this might need to be updated
220         // async to platform releases.
221         if (reason != REASON_MALWARE && reason != REASON_INSTALLER_POLICY_VIOLATION) {
222             Log.e(LOG_TAG, "Invalid reason " + reason);
223             return Collections.emptyMap();
224         }
225 
226         PackageManager pm = getPackageManager();
227 
228         PackageInfo callerPkgInfo = getPkgInfo(callerPackageName);
229         if (callerPkgInfo == null) {
230             return Collections.emptyMap();
231         }
232         int callerTargetSdk = callerPkgInfo.applicationInfo.targetSdkVersion;
233 
234         Map<String, List<String>> actuallyRevokedPerms = new ArrayMap<>();
235         ArrayList<AppPermissions> appsWithRevokedPerms = new ArrayList<>();
236 
237         for (Map.Entry<String, List<String>> appRequest : request.entrySet()) {
238             PackageInfo requestedPkgInfo = getPkgInfo(appRequest.getKey());
239             if (requestedPkgInfo == null) {
240                 continue;
241             }
242 
243             // Permissions are per UID. Hence permissions will be removed from all apps sharing an
244             // UID.
245             String[] pkgNames = pm.getPackagesForUid(requestedPkgInfo.applicationInfo.uid);
246             if (pkgNames == null) {
247                 continue;
248             }
249 
250             int numPkgNames = pkgNames.length;
251             for (int pkgNum = 0; pkgNum < numPkgNames; pkgNum++) {
252                 String pkgName = pkgNames[pkgNum];
253 
254                 PackageInfo pkgInfo = getPkgInfo(pkgName);
255                 if (pkgInfo == null) {
256                     continue;
257                 }
258 
259                 // If the revocation is because of a market policy violation only the installer can
260                 // revoke the permissions.
261                 if (reason == REASON_INSTALLER_POLICY_VIOLATION
262                         && !callerPackageName.equals(pm.getInstallerPackageName(pkgName))) {
263                     Log.i(LOG_TAG, "Ignoring " + pkgName + " as it is not installed by "
264                             + callerPackageName);
265                     continue;
266                 }
267 
268                 // In rare cases the caller does not know about the permissions that have been added
269                 // due to splits. Hence add them now.
270                 ArrayList<String> expandedPerms = addSplitPermissions(appRequest.getValue(),
271                         callerTargetSdk);
272 
273                 AppPermissions appPerms = new AppPermissions(this, pkgInfo, false, true, null);
274 
275                 // First find the groups that should be revoked and then revoke all permissions of
276                 // these groups. This is needed as soon as a single permission in the group is
277                 // granted, all other permissions get auto-granted on request.
278                 ArrayList<AppPermissionGroup> groupsToRevoke = getRevocableGroupsForPermissions(
279                         expandedPerms, appPerms);
280                 ArrayList<String> revokedPerms = revokePermissionGroups(groupsToRevoke);
281 
282                 // In racy conditions the group might not have had granted permissions anymore
283                 if (!revokedPerms.isEmpty()) {
284                     actuallyRevokedPerms.put(pkgName, revokedPerms);
285                     appsWithRevokedPerms.add(appPerms);
286                 }
287             }
288         }
289 
290         // Persist changes after we computed everything to remove
291         // This is necessary as we would otherwise only look at the first app of a shared UID.
292         if (!doDryRun) {
293             int numChangedApps = appsWithRevokedPerms.size();
294             for (int i = 0; i < numChangedApps; i++) {
295                 appsWithRevokedPerms.get(i).persistChanges(true);
296             }
297         }
298 
299         return actuallyRevokedPerms;
300     }
301 
302     @Override
onGetRuntimePermissionsBackup(@onNull UserHandle user, @NonNull OutputStream backup, @NonNull Runnable callback)303     public void onGetRuntimePermissionsBackup(@NonNull UserHandle user,
304             @NonNull OutputStream backup, @NonNull Runnable callback) {
305         AsyncTask.execute(() -> {
306             onGetRuntimePermissionsBackup(user, backup);
307             callback.run();
308         });
309     }
310 
onGetRuntimePermissionsBackup(@onNull UserHandle user, @NonNull OutputStream backup)311     private void onGetRuntimePermissionsBackup(@NonNull UserHandle user,
312                 @NonNull OutputStream backup) {
313         BackupHelper backupHelper = new BackupHelper(this, user);
314 
315         try {
316             XmlSerializer serializer = newSerializer();
317             serializer.setOutput(backup, UTF_8.name());
318 
319             backupHelper.writeState(serializer);
320             serializer.flush();
321         } catch (Exception e) {
322             Log.e(LOG_TAG, "Unable to write permissions backup", e);
323         }
324     }
325 
326     @Override
onRestoreRuntimePermissionsBackup(@onNull UserHandle user, @NonNull InputStream backup, Runnable callback)327     public void onRestoreRuntimePermissionsBackup(@NonNull UserHandle user,
328             @NonNull InputStream backup, Runnable callback) {
329         AsyncTask.execute(() -> {
330             onRestoreRuntimePermissionsBackup(user, backup);
331             callback.run();
332         });
333     }
334 
onRestoreRuntimePermissionsBackup(@onNull UserHandle user, @NonNull InputStream backup)335     private void onRestoreRuntimePermissionsBackup(@NonNull UserHandle user,
336             @NonNull InputStream backup) {
337         try {
338             XmlPullParser parser = Xml.newPullParser();
339             parser.setInput(backup, StandardCharsets.UTF_8.name());
340 
341             new BackupHelper(this, user).restoreState(parser);
342         } catch (Exception e) {
343             Log.e(LOG_TAG, "Exception restoring permissions: " + e.getMessage());
344         }
345     }
346 
347     @Override
onRestoreDelayedRuntimePermissionsBackup(@onNull String packageName, @NonNull UserHandle user, @NonNull Consumer<Boolean> callback)348     public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String packageName,
349             @NonNull UserHandle user, @NonNull Consumer<Boolean> callback) {
350         AsyncTask.execute(() -> callback.accept(
351                 onRestoreDelayedRuntimePermissionsBackup(packageName, user)));
352     }
353 
onRestoreDelayedRuntimePermissionsBackup(@onNull String packageName, @NonNull UserHandle user)354     private boolean onRestoreDelayedRuntimePermissionsBackup(@NonNull String packageName,
355             @NonNull UserHandle user) {
356         try {
357             return new BackupHelper(this, user).restoreDelayedState(packageName);
358         } catch (Exception e) {
359             Log.e(LOG_TAG, "Exception restoring delayed permissions: " + e.getMessage());
360             return false;
361         }
362     }
363 
364     @Override
onGetAppPermissions(@onNull String packageName, @NonNull Consumer<List<RuntimePermissionPresentationInfo>> callback)365     public void onGetAppPermissions(@NonNull String packageName,
366             @NonNull Consumer<List<RuntimePermissionPresentationInfo>> callback) {
367         AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> callback.accept(
368                 onGetAppPermissions(this, packageName)));
369     }
370 
371     /**
372      * Implementation of {@link PermissionControllerService#onGetAppPermissions(String)}}.
373      * Called by this class and the legacy implementation.
374      */
onGetAppPermissions( @onNull Context context, @NonNull String packageName)375     static @NonNull List<RuntimePermissionPresentationInfo> onGetAppPermissions(
376             @NonNull Context context, @NonNull String packageName) {
377         final PackageInfo packageInfo;
378         try {
379             packageInfo = context.getPackageManager().getPackageInfo(packageName, GET_PERMISSIONS);
380         } catch (PackageManager.NameNotFoundException e) {
381             Log.e(LOG_TAG, "Error getting package:" + packageName, e);
382             return Collections.emptyList();
383         }
384 
385         List<RuntimePermissionPresentationInfo> permissions = new ArrayList<>();
386 
387         AppPermissions appPermissions = new AppPermissions(context, packageInfo, false, null);
388         for (AppPermissionGroup group : appPermissions.getPermissionGroups()) {
389             if (shouldShowPermission(context, group)) {
390                 final boolean granted = group.areRuntimePermissionsGranted();
391                 final boolean standard = Utils.OS_PKG.equals(group.getDeclaringPackage());
392                 RuntimePermissionPresentationInfo permission =
393                         new RuntimePermissionPresentationInfo(group.getLabel(),
394                                 granted, standard);
395                 permissions.add(permission);
396             }
397         }
398 
399         return permissions;
400     }
401 
402     @Override
onRevokeRuntimePermission(@onNull String packageName, @NonNull String permissionName, @NonNull Runnable callback)403     public void onRevokeRuntimePermission(@NonNull String packageName,
404             @NonNull String permissionName, @NonNull Runnable callback) {
405         AsyncTask.execute(() -> {
406             onRevokeRuntimePermission(packageName, permissionName);
407             callback.run();
408         });
409     }
410 
onRevokeRuntimePermission(@onNull String packageName, @NonNull String permissionName)411     private void onRevokeRuntimePermission(@NonNull String packageName,
412             @NonNull String permissionName) {
413         try {
414             final PackageInfo packageInfo = getPackageManager().getPackageInfo(packageName,
415                     GET_PERMISSIONS);
416             final AppPermissions appPermissions = new AppPermissions(this, packageInfo, false,
417                     null);
418 
419             final AppPermissionGroup appPermissionGroup = appPermissions.getGroupForPermission(
420                     permissionName);
421 
422             if (appPermissionGroup != null) {
423                 appPermissionGroup.revokeRuntimePermissions(false);
424             }
425         } catch (PackageManager.NameNotFoundException e) {
426             Log.e(LOG_TAG, "Error getting package:" + packageName, e);
427         }
428     }
429 
430     @Override
onCountPermissionApps(@onNull List<String> permissionNames, int flags, @NonNull IntConsumer callback)431     public void onCountPermissionApps(@NonNull List<String> permissionNames, int flags,
432             @NonNull IntConsumer callback) {
433         AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> callback.accept(
434                 onCountPermissionApps(permissionNames, flags)));
435     }
436 
onCountPermissionApps(@onNull List<String> permissionNames, int flags)437     private int onCountPermissionApps(@NonNull List<String> permissionNames, int flags) {
438         boolean countSystem = (flags & COUNT_WHEN_SYSTEM) != 0;
439         boolean countOnlyGranted = (flags & COUNT_ONLY_WHEN_GRANTED) != 0;
440 
441         List<PackageInfo> pkgs = getPackageManager().getInstalledPackages(GET_PERMISSIONS);
442 
443         int numApps = 0;
444 
445         int numPkgs = pkgs.size();
446         for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
447             PackageInfo pkg = pkgs.get(pkgNum);
448 
449             int numPerms = permissionNames.size();
450             for (int permNum = 0; permNum < numPerms; permNum++) {
451                 String perm = permissionNames.get(permNum);
452 
453                 AppPermissionGroup group = AppPermissionGroup.create(this, pkg,
454                         permissionNames.get(permNum), true);
455                 if (group == null || !shouldShowPermission(this, group)) {
456                     continue;
457                 }
458 
459                 AppPermissionGroup subGroup = null;
460                 if (group.hasPermission(perm)) {
461                     subGroup = group;
462                 } else {
463                     AppPermissionGroup bgGroup = group.getBackgroundPermissions();
464                     if (bgGroup != null && bgGroup.hasPermission(perm)) {
465                         subGroup = bgGroup;
466                     }
467                 }
468 
469                 if (subGroup != null) {
470                     if (!countSystem && !subGroup.isUserSensitive()) {
471                         continue;
472                     }
473 
474                     if (!countOnlyGranted || subGroup.areRuntimePermissionsGranted()) {
475                         // The permission might not be granted, but some permissions of the group
476                         // are granted. In this case the permission is granted silently when the app
477                         // asks for it.
478                         // Hence this is as-good-as-granted and we count it.
479                         numApps++;
480                         break;
481                     }
482                 }
483             }
484         }
485 
486         return numApps;
487     }
488 
489     @Override
onGetPermissionUsages(boolean countSystem, long numMillis, @NonNull Consumer<List<RuntimePermissionUsageInfo>> callback)490     public void onGetPermissionUsages(boolean countSystem, long numMillis,
491             @NonNull Consumer<List<RuntimePermissionUsageInfo>> callback) {
492         AsyncTask.THREAD_POOL_EXECUTOR.execute(
493                 () -> callback.accept(onGetPermissionUsages(countSystem, numMillis)));
494     }
495 
onGetPermissionUsages( boolean countSystem, long numMillis)496     private @NonNull List<RuntimePermissionUsageInfo> onGetPermissionUsages(
497             boolean countSystem, long numMillis) {
498         return Collections.emptyList();
499     }
500 
501     @Override
onSetRuntimePermissionGrantStateByDeviceAdmin(@onNull String callerPackageName, @NonNull String packageName, @NonNull String unexpandedPermission, int grantState, @NonNull Consumer<Boolean> callback)502     public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName,
503             @NonNull String packageName, @NonNull String unexpandedPermission, int grantState,
504             @NonNull Consumer<Boolean> callback) {
505         AsyncTask.execute(() -> callback.accept(onSetRuntimePermissionGrantStateByDeviceAdmin(
506                 callerPackageName, packageName, unexpandedPermission, grantState)));
507     }
508 
onSetRuntimePermissionGrantStateByDeviceAdmin(@onNull String callerPackageName, @NonNull String packageName, @NonNull String unexpandedPermission, int grantState)509     private boolean onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName,
510             @NonNull String packageName, @NonNull String unexpandedPermission, int grantState) {
511         PackageInfo callerPkgInfo = getPkgInfo(callerPackageName);
512         if (callerPkgInfo == null) {
513             Log.w(LOG_TAG, "Cannot fix " + unexpandedPermission + " as admin "
514                     + callerPackageName + " cannot be found");
515             return false;
516         }
517 
518         PackageInfo pkgInfo = getPkgInfo(packageName);
519         if (pkgInfo == null) {
520             Log.w(LOG_TAG, "Cannot fix " + unexpandedPermission + " as " + packageName
521                     + " cannot be found");
522             return false;
523         }
524 
525         ArrayList<String> expandedPermissions = addSplitPermissions(
526                 Collections.singletonList(unexpandedPermission),
527                 callerPkgInfo.applicationInfo.targetSdkVersion);
528 
529         AppPermissions app = new AppPermissions(this, pkgInfo, false, true, null);
530 
531         int numPerms = expandedPermissions.size();
532         for (int i = 0; i < numPerms; i++) {
533             String permName = expandedPermissions.get(i);
534             AppPermissionGroup group = app.getGroupForPermission(permName);
535             if (group == null || group.isSystemFixed()) {
536                 continue;
537             }
538 
539             Permission perm = group.getPermission(permName);
540             if (perm == null) {
541                 continue;
542             }
543 
544             switch (grantState) {
545                 case PERMISSION_GRANT_STATE_GRANTED:
546                     perm.setPolicyFixed(true);
547                     group.grantRuntimePermissions(false, new String[]{permName});
548                     break;
549                 case PERMISSION_GRANT_STATE_DENIED:
550                     perm.setPolicyFixed(true);
551                     group.revokeRuntimePermissions(false, new String[]{permName});
552                     break;
553                 case PERMISSION_GRANT_STATE_DEFAULT:
554                     perm.setPolicyFixed(false);
555                     break;
556                 default:
557                     return false;
558             }
559         }
560 
561         app.persistChanges(grantState == PERMISSION_GRANT_STATE_DENIED
562                 || !callerPackageName.equals(packageName));
563 
564         return true;
565     }
566 
567     @Override
onGrantOrUpgradeDefaultRuntimePermissions(@onNull Runnable callback)568     public void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable callback) {
569         AsyncTask.execute(() -> {
570             onGrantOrUpgradeDefaultRuntimePermissions();
571             callback.run();
572         });
573     }
574 
onGrantOrUpgradeDefaultRuntimePermissions()575     private void onGrantOrUpgradeDefaultRuntimePermissions() {
576         // TODO: Default permission grants should go here
577         RuntimePermissionsUpgradeController.upgradeIfNeeded(this);
578     }
579 }
580