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