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