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