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 android.permission;
18 
19 import static android.permission.PermissionControllerService.SERVICE_INTERFACE;
20 
21 import static com.android.internal.util.FunctionalUtils.uncheckExceptions;
22 import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
23 import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
24 import static com.android.internal.util.Preconditions.checkFlagsArgument;
25 import static com.android.internal.util.Preconditions.checkNotNull;
26 import static com.android.internal.util.Preconditions.checkStringNotEmpty;
27 
28 import android.Manifest;
29 import android.annotation.CallbackExecutor;
30 import android.annotation.IntDef;
31 import android.annotation.NonNull;
32 import android.annotation.Nullable;
33 import android.annotation.RequiresPermission;
34 import android.annotation.SystemApi;
35 import android.annotation.SystemService;
36 import android.annotation.TestApi;
37 import android.app.ActivityThread;
38 import android.content.Context;
39 import android.content.Intent;
40 import android.content.pm.PackageManager;
41 import android.content.pm.ResolveInfo;
42 import android.os.Binder;
43 import android.os.Bundle;
44 import android.os.Handler;
45 import android.os.Process;
46 import android.os.UserHandle;
47 import android.util.ArrayMap;
48 import android.util.Log;
49 import android.util.Pair;
50 
51 import com.android.internal.annotations.GuardedBy;
52 import com.android.internal.infra.AndroidFuture;
53 import com.android.internal.infra.RemoteStream;
54 import com.android.internal.infra.ServiceConnector;
55 import com.android.internal.os.BackgroundThread;
56 import com.android.internal.util.CollectionUtils;
57 
58 import libcore.util.EmptyArray;
59 
60 import java.io.FileDescriptor;
61 import java.lang.annotation.Retention;
62 import java.lang.annotation.RetentionPolicy;
63 import java.util.ArrayList;
64 import java.util.Arrays;
65 import java.util.Collections;
66 import java.util.List;
67 import java.util.Map;
68 import java.util.Objects;
69 import java.util.concurrent.Executor;
70 import java.util.concurrent.TimeUnit;
71 import java.util.function.Consumer;
72 
73 /**
74  * Interface for communicating with the permission controller.
75  *
76  * @hide
77  */
78 @SystemApi
79 @SystemService(Context.PERMISSION_CONTROLLER_SERVICE)
80 public final class PermissionControllerManager {
81     private static final String TAG = PermissionControllerManager.class.getSimpleName();
82 
83     private static final long REQUEST_TIMEOUT_MILLIS = 60000;
84     private static final long UNBIND_TIMEOUT_MILLIS = 10000;
85     private static final int CHUNK_SIZE = 4 * 1024;
86 
87     private static final Object sLock = new Object();
88 
89     /**
90      * Global remote services (per user) used by all {@link PermissionControllerManager managers}
91      */
92     @GuardedBy("sLock")
93     private static ArrayMap<Pair<Integer, Thread>, ServiceConnector<IPermissionController>>
94             sRemoteServices = new ArrayMap<>(1);
95 
96     /** @hide */
97     @IntDef(prefix = { "REASON_" }, value = {
98             REASON_MALWARE,
99             REASON_INSTALLER_POLICY_VIOLATION,
100     })
101     @Retention(RetentionPolicy.SOURCE)
102     public @interface Reason {}
103 
104     /** The permissions are revoked because the apps holding the permissions are malware */
105     public static final int REASON_MALWARE = 1;
106 
107     /**
108      * The permissions are revoked because the apps holding the permissions violate a policy of the
109      * app that installed it.
110      *
111      * <p>If this reason is used only permissions of apps that are installed by the caller of the
112      * API can be revoked.
113      */
114     public static final int REASON_INSTALLER_POLICY_VIOLATION = 2;
115 
116     /** @hide */
117     @IntDef(prefix = { "COUNT_" }, value = {
118             COUNT_ONLY_WHEN_GRANTED,
119             COUNT_WHEN_SYSTEM,
120     }, flag = true)
121     @Retention(RetentionPolicy.SOURCE)
122     public @interface CountPermissionAppsFlag {}
123 
124     /** Count an app only if the permission is granted to the app. */
125     public static final int COUNT_ONLY_WHEN_GRANTED = 1;
126 
127     /** Count and app even if it is a system app. */
128     public static final int COUNT_WHEN_SYSTEM = 2;
129 
130     /**
131      * Callback for delivering the result of {@link #revokeRuntimePermissions}.
132      */
133     public abstract static class OnRevokeRuntimePermissionsCallback {
134         /**
135          * The result for {@link #revokeRuntimePermissions}.
136          *
137          * @param revoked The actually revoked permissions as
138          *                {@code Map<packageName, List<permission>>}
139          */
onRevokeRuntimePermissions(@onNull Map<String, List<String>> revoked)140         public abstract void onRevokeRuntimePermissions(@NonNull Map<String, List<String>> revoked);
141     }
142 
143     /**
144      * Callback for delivering the result of {@link #getAppPermissions}.
145      *
146      * @hide
147      */
148     @TestApi
149     public interface OnGetAppPermissionResultCallback {
150         /**
151          * The result for {@link #getAppPermissions(String, OnGetAppPermissionResultCallback,
152          * Handler)}.
153          *
154          * @param permissions The permissions list.
155          */
onGetAppPermissions(@onNull List<RuntimePermissionPresentationInfo> permissions)156         void onGetAppPermissions(@NonNull List<RuntimePermissionPresentationInfo> permissions);
157     }
158 
159     /**
160      * Callback for delivering the result of {@link #countPermissionApps}.
161      *
162      * @hide
163      */
164     @TestApi
165     public interface OnCountPermissionAppsResultCallback {
166         /**
167          * The result for {@link #countPermissionApps(List, int,
168          * OnCountPermissionAppsResultCallback, Handler)}.
169          *
170          * @param numApps The number of apps that have one of the permissions
171          */
onCountPermissionApps(int numApps)172         void onCountPermissionApps(int numApps);
173     }
174 
175     /**
176      * Callback for delivering the result of {@link #getPermissionUsages}.
177      *
178      * @hide
179      */
180     public interface OnPermissionUsageResultCallback {
181         /**
182          * The result for {@link #getPermissionUsages}.
183          *
184          * @param users The users list.
185          */
onPermissionUsageResult(@onNull List<RuntimePermissionUsageInfo> users)186         void onPermissionUsageResult(@NonNull List<RuntimePermissionUsageInfo> users);
187     }
188 
189     private final @NonNull Context mContext;
190     private final @NonNull ServiceConnector<IPermissionController> mRemoteService;
191     private final @NonNull Handler mHandler;
192 
193     /**
194      * Create a new {@link PermissionControllerManager}.
195      *
196      * @param context to create the manager for
197      * @param handler handler to schedule work
198      *
199      * @hide
200      */
PermissionControllerManager(@onNull Context context, @NonNull Handler handler)201     public PermissionControllerManager(@NonNull Context context, @NonNull Handler handler) {
202         synchronized (sLock) {
203             Pair<Integer, Thread> key = new Pair<>(context.getUserId(),
204                     handler.getLooper().getThread());
205             ServiceConnector<IPermissionController> remoteService = sRemoteServices.get(key);
206             if (remoteService == null) {
207                 Intent intent = new Intent(SERVICE_INTERFACE);
208                 String pkgName = context.getPackageManager().getPermissionControllerPackageName();
209                 intent.setPackage(pkgName);
210                 ResolveInfo serviceInfo = context.getPackageManager().resolveService(intent, 0);
211                 if (serviceInfo == null) {
212                     String errorMsg = "No PermissionController package (" + pkgName + ") for user "
213                             + context.getUserId();
214                     Log.wtf(TAG, errorMsg);
215                     throw new IllegalStateException(errorMsg);
216                 }
217                 remoteService = new ServiceConnector.Impl<IPermissionController>(
218                         ActivityThread.currentApplication() /* context */,
219                         new Intent(SERVICE_INTERFACE)
220                                 .setComponent(serviceInfo.getComponentInfo().getComponentName()),
221                         0 /* bindingFlags */, context.getUserId(),
222                         IPermissionController.Stub::asInterface) {
223 
224                     @Override
225                     protected Handler getJobHandler() {
226                         return handler;
227                     }
228 
229                     @Override
230                     protected long getRequestTimeoutMs() {
231                         return REQUEST_TIMEOUT_MILLIS;
232                     }
233 
234                     @Override
235                     protected long getAutoDisconnectTimeoutMs() {
236                         return UNBIND_TIMEOUT_MILLIS;
237                     }
238                 };
239                 sRemoteServices.put(key, remoteService);
240             }
241 
242             mRemoteService = remoteService;
243         }
244 
245         mContext = context;
246         mHandler = handler;
247     }
248 
249     /**
250      * Throw a {@link SecurityException} if not at least one of the permissions is granted.
251      *
252      * @param requiredPermissions A list of permissions. Any of of them if sufficient to pass the
253      *                            check
254      */
enforceSomePermissionsGrantedToSelf(@onNull String... requiredPermissions)255     private void enforceSomePermissionsGrantedToSelf(@NonNull String... requiredPermissions) {
256         for (String requiredPermission : requiredPermissions) {
257             if (mContext.checkSelfPermission(requiredPermission)
258                     == PackageManager.PERMISSION_GRANTED) {
259                 return;
260             }
261         }
262 
263         throw new SecurityException("At lest one of the following permissions is required: "
264                 + Arrays.toString(requiredPermissions));
265     }
266 
267     /**
268      * Revoke a set of runtime permissions for various apps.
269      *
270      * @param request The permissions to revoke as {@code Map<packageName, List<permission>>}
271      * @param doDryRun Compute the permissions that would be revoked, but not actually revoke them
272      * @param reason Why the permission should be revoked
273      * @param executor Executor on which to invoke the callback
274      * @param callback Callback to receive the result
275      */
276     @RequiresPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
revokeRuntimePermissions(@onNull Map<String, List<String>> request, boolean doDryRun, @Reason int reason, @NonNull @CallbackExecutor Executor executor, @NonNull OnRevokeRuntimePermissionsCallback callback)277     public void revokeRuntimePermissions(@NonNull Map<String, List<String>> request,
278             boolean doDryRun, @Reason int reason, @NonNull @CallbackExecutor Executor executor,
279             @NonNull OnRevokeRuntimePermissionsCallback callback) {
280         // Check input to fail immediately instead of inside the async request
281         checkNotNull(executor);
282         checkNotNull(callback);
283         checkNotNull(request);
284         for (Map.Entry<String, List<String>> appRequest : request.entrySet()) {
285             checkNotNull(appRequest.getKey());
286             checkCollectionElementsNotNull(appRequest.getValue(), "permissions");
287         }
288 
289         // Check required permission to fail immediately instead of inside the oneway binder call
290         enforceSomePermissionsGrantedToSelf(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
291 
292         mRemoteService.postAsync(service -> {
293             Bundle bundledizedRequest = new Bundle();
294             for (Map.Entry<String, List<String>> appRequest : request.entrySet()) {
295                 bundledizedRequest.putStringArrayList(appRequest.getKey(),
296                         new ArrayList<>(appRequest.getValue()));
297             }
298 
299             AndroidFuture<Map<String, List<String>>> revokeRuntimePermissionsResult =
300                     new AndroidFuture<>();
301             service.revokeRuntimePermissions(bundledizedRequest, doDryRun, reason,
302                     mContext.getPackageName(),
303                     revokeRuntimePermissionsResult);
304             return revokeRuntimePermissionsResult;
305         }).whenCompleteAsync((revoked, err) -> {
306             final long token = Binder.clearCallingIdentity();
307             try {
308                 if (err != null) {
309                     Log.e(TAG, "Failure when revoking runtime permissions " + revoked, err);
310                     callback.onRevokeRuntimePermissions(Collections.emptyMap());
311                 } else {
312                     callback.onRevokeRuntimePermissions(revoked);
313                 }
314             } finally {
315                 Binder.restoreCallingIdentity(token);
316             }
317         }, executor);
318     }
319 
320     /**
321      * Set the runtime permission state from a device admin.
322      * This variant takes into account whether the admin may or may not grant sensors-related
323      * permissions.
324      *
325      * @param callerPackageName The package name of the admin requesting the change
326      * @param params Information about the permission being granted.
327      * @param executor Executor to run the {@code callback} on
328      * @param callback The callback
329      *
330      * @hide
331      */
332     @RequiresPermission(allOf = {Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
333             Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
334             Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY},
335             conditional = true)
setRuntimePermissionGrantStateByDeviceAdmin(@onNull String callerPackageName, @NonNull AdminPermissionControlParams params, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback)336     public void setRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName,
337             @NonNull AdminPermissionControlParams params,
338             @NonNull @CallbackExecutor Executor executor,
339             @NonNull Consumer<Boolean> callback) {
340         checkStringNotEmpty(callerPackageName);
341         Objects.requireNonNull(executor);
342         Objects.requireNonNull(callback);
343         Objects.requireNonNull(params, "Admin control params must not be null.");
344 
345         mRemoteService.postAsync(service -> {
346             AndroidFuture<Boolean> setRuntimePermissionGrantStateResult = new AndroidFuture<>();
347             service.setRuntimePermissionGrantStateByDeviceAdminFromParams(
348                     callerPackageName, params,
349                     setRuntimePermissionGrantStateResult);
350             return setRuntimePermissionGrantStateResult;
351         }).whenCompleteAsync((setRuntimePermissionGrantStateResult, err) -> {
352             final long token = Binder.clearCallingIdentity();
353             try {
354                 if (err != null) {
355                     Log.e(TAG,
356                             "Error setting permissions state for device admin "
357                                     + callerPackageName, err);
358                     callback.accept(false);
359                 } else {
360                     callback.accept(Boolean.TRUE.equals(setRuntimePermissionGrantStateResult));
361                 }
362             } finally {
363                 Binder.restoreCallingIdentity(token);
364             }
365         }, executor);
366     }
367 
368     /**
369      * Create a backup of the runtime permissions.
370      *
371      * @param user The user to be backed up
372      * @param executor Executor on which to invoke the callback
373      * @param callback Callback to receive the result. The resulting backup-file is opaque and no
374      *                 guarantees are made other than that the file can be send to
375      *                 {@link #restoreRuntimePermissionBackup} in this and future versions of
376      *                 Android.
377      */
378     @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
getRuntimePermissionBackup(@onNull UserHandle user, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<byte[]> callback)379     public void getRuntimePermissionBackup(@NonNull UserHandle user,
380             @NonNull @CallbackExecutor Executor executor,
381             @NonNull Consumer<byte[]> callback) {
382         checkNotNull(user);
383         checkNotNull(executor);
384         checkNotNull(callback);
385 
386         // Check required permission to fail immediately instead of inside the oneway binder call
387         enforceSomePermissionsGrantedToSelf(Manifest.permission.GET_RUNTIME_PERMISSIONS);
388 
389         mRemoteService.postAsync(service -> RemoteStream.receiveBytes(remotePipe -> {
390             service.getRuntimePermissionBackup(user, remotePipe);
391         })).whenCompleteAsync((bytes, err) -> {
392             if (err != null) {
393                 Log.e(TAG, "Error getting permission backup", err);
394                 callback.accept(EmptyArray.BYTE);
395             } else {
396                 callback.accept(bytes);
397             }
398         }, executor);
399     }
400 
401     /**
402      * Restore a {@link #getRuntimePermissionBackup backup-file} of the runtime permissions.
403      *
404      * <p>This might leave some part of the backup-file unapplied if an package mentioned in the
405      * backup-file is not yet installed. It is required that
406      * {@link #applyStagedRuntimePermissionBackup} is called after any package is installed to
407      * apply the rest of the backup-file.
408      *
409      * @param backup the backup-file to restore. The backup is sent asynchronously, hence it should
410      *               not be modified after calling this method.
411      * @param user The user to be restore
412      */
413     @RequiresPermission(anyOf = {
414             Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
415             Manifest.permission.RESTORE_RUNTIME_PERMISSIONS
416     })
stageAndApplyRuntimePermissionsBackup(@onNull byte[] backup, @NonNull UserHandle user)417     public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[] backup,
418             @NonNull UserHandle user) {
419         checkNotNull(backup);
420         checkNotNull(user);
421 
422         // Check required permission to fail immediately instead of inside the oneway binder call
423         enforceSomePermissionsGrantedToSelf(Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
424                 Manifest.permission.RESTORE_RUNTIME_PERMISSIONS);
425 
426         mRemoteService.postAsync(service -> RemoteStream.sendBytes(remotePipe -> {
427             service.stageAndApplyRuntimePermissionsBackup(user, remotePipe);
428         }, backup))
429                 .whenComplete((nullResult, err) -> {
430                     if (err != null) {
431                         Log.e(TAG, "Error sending permission backup", err);
432                     }
433                 });
434     }
435 
436     /**
437      * Restore unapplied parts of a {@link #stageAndApplyRuntimePermissionsBackup previously staged}
438      * backup-file of the runtime permissions.
439      *
440      * <p>This should be called every time after a package is installed until the callback
441      * reports that there is no more unapplied backup left.
442      *
443      * @param packageName The package that is ready to have it's permissions restored.
444      * @param user The user the package belongs to
445      * @param executor Executor to execute the callback on
446      * @param callback Is called with {@code true} iff there is still more unapplied backup left
447      */
448     @RequiresPermission(anyOf = {
449             Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
450             Manifest.permission.RESTORE_RUNTIME_PERMISSIONS
451     })
applyStagedRuntimePermissionBackup(@onNull String packageName, @NonNull UserHandle user, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback)452     public void applyStagedRuntimePermissionBackup(@NonNull String packageName,
453             @NonNull UserHandle user,
454             @NonNull @CallbackExecutor Executor executor,
455             @NonNull Consumer<Boolean> callback) {
456         checkNotNull(packageName);
457         checkNotNull(user);
458         checkNotNull(executor);
459         checkNotNull(callback);
460 
461         // Check required permission to fail immediately instead of inside the oneway binder call
462         enforceSomePermissionsGrantedToSelf(Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
463                 Manifest.permission.RESTORE_RUNTIME_PERMISSIONS);
464 
465         mRemoteService.postAsync(service -> {
466             AndroidFuture<Boolean> applyStagedRuntimePermissionBackupResult =
467                     new AndroidFuture<>();
468             service.applyStagedRuntimePermissionBackup(packageName, user,
469                     applyStagedRuntimePermissionBackupResult);
470             return applyStagedRuntimePermissionBackupResult;
471         }).whenCompleteAsync((applyStagedRuntimePermissionBackupResult, err) -> {
472             final long token = Binder.clearCallingIdentity();
473             try {
474                 if (err != null) {
475                     Log.e(TAG, "Error restoring delayed permissions for " + packageName, err);
476                     callback.accept(true);
477                 } else {
478                     callback.accept(
479                             Boolean.TRUE.equals(applyStagedRuntimePermissionBackupResult));
480                 }
481             } finally {
482                 Binder.restoreCallingIdentity(token);
483             }
484         }, executor);
485     }
486 
487     /**
488      * Dump permission controller state.
489      *
490      * @hide
491      */
dump(@onNull FileDescriptor fd, @Nullable String[] args)492     public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) {
493         try {
494             mRemoteService.postAsync(service -> {
495                 return AndroidFuture.runAsync(uncheckExceptions(() -> {
496                     service.asBinder().dump(fd, args);
497                 }), BackgroundThread.getExecutor());
498             }).get(REQUEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
499         } catch (Exception e) {
500             Log.e(TAG, "Could not get dump", e);
501         }
502     }
503 
504     /**
505      * Gets the runtime permissions for an app.
506      *
507      * @param packageName The package for which to query.
508      * @param callback Callback to receive the result.
509      * @param handler Handler on which to invoke the callback.
510      *
511      * @hide
512      */
513     @TestApi
514     @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
getAppPermissions(@onNull String packageName, @NonNull OnGetAppPermissionResultCallback callback, @Nullable Handler handler)515     public void getAppPermissions(@NonNull String packageName,
516             @NonNull OnGetAppPermissionResultCallback callback, @Nullable Handler handler) {
517         checkNotNull(packageName);
518         checkNotNull(callback);
519         Handler finalHandler = handler != null ? handler : mHandler;
520 
521         mRemoteService.postAsync(service -> {
522             AndroidFuture<List<RuntimePermissionPresentationInfo>> getAppPermissionsResult =
523                     new AndroidFuture<>();
524             service.getAppPermissions(packageName, getAppPermissionsResult);
525             return getAppPermissionsResult;
526         }).whenComplete((getAppPermissionsResult, err) -> finalHandler.post(() -> {
527             if (err != null) {
528                 Log.e(TAG, "Error getting app permission", err);
529                 callback.onGetAppPermissions(Collections.emptyList());
530             } else {
531                 callback.onGetAppPermissions(CollectionUtils.emptyIfNull(getAppPermissionsResult));
532             }
533         }));
534     }
535 
536     /**
537      * Revoke the permission {@code permissionName} for app {@code packageName}
538      *
539      * @param packageName The package for which to revoke
540      * @param permissionName The permission to revoke
541      *
542      * @hide
543      */
544     @TestApi
545     @RequiresPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
revokeRuntimePermission(@onNull String packageName, @NonNull String permissionName)546     public void revokeRuntimePermission(@NonNull String packageName,
547             @NonNull String permissionName) {
548         checkNotNull(packageName);
549         checkNotNull(permissionName);
550 
551         mRemoteService.run(service -> service.revokeRuntimePermission(packageName, permissionName));
552     }
553 
554     /**
555      * Count how many apps have one of a set of permissions.
556      *
557      * @param permissionNames The permissions the app might have
558      * @param flags Modify which apps to count. By default all non-system apps that request a
559      *              permission are counted
560      * @param callback Callback to receive the result
561      * @param handler Handler on which to invoke the callback
562      *
563      * @hide
564      */
565     @TestApi
566     @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
countPermissionApps(@onNull List<String> permissionNames, @CountPermissionAppsFlag int flags, @NonNull OnCountPermissionAppsResultCallback callback, @Nullable Handler handler)567     public void countPermissionApps(@NonNull List<String> permissionNames,
568             @CountPermissionAppsFlag int flags,
569             @NonNull OnCountPermissionAppsResultCallback callback, @Nullable Handler handler) {
570         checkCollectionElementsNotNull(permissionNames, "permissionNames");
571         checkFlagsArgument(flags, COUNT_WHEN_SYSTEM | COUNT_ONLY_WHEN_GRANTED);
572         checkNotNull(callback);
573         Handler finalHandler = handler != null ? handler : mHandler;
574 
575         mRemoteService.postAsync(service -> {
576             AndroidFuture<Integer> countPermissionAppsResult = new AndroidFuture<>();
577             service.countPermissionApps(permissionNames, flags, countPermissionAppsResult);
578             return countPermissionAppsResult;
579         }).whenComplete((countPermissionAppsResult, err) -> finalHandler.post(() -> {
580             if (err != null) {
581                 Log.e(TAG, "Error counting permission apps", err);
582                 callback.onCountPermissionApps(0);
583             } else {
584                 callback.onCountPermissionApps(countPermissionAppsResult);
585             }
586         }));
587     }
588 
589     /**
590      * Count how many apps have used permissions.
591      *
592      * @param countSystem Also count system apps
593      * @param numMillis The number of milliseconds in the past to check for uses
594      * @param executor Executor on which to invoke the callback
595      * @param callback Callback to receive the result
596      *
597      * @hide
598      */
599     @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
getPermissionUsages(boolean countSystem, long numMillis, @NonNull @CallbackExecutor Executor executor, @NonNull OnPermissionUsageResultCallback callback)600     public void getPermissionUsages(boolean countSystem, long numMillis,
601             @NonNull @CallbackExecutor Executor executor,
602             @NonNull OnPermissionUsageResultCallback callback) {
603         checkArgumentNonnegative(numMillis);
604         checkNotNull(executor);
605         checkNotNull(callback);
606 
607 
608         mRemoteService.postAsync(service -> {
609             AndroidFuture<List<RuntimePermissionUsageInfo>> getPermissionUsagesResult =
610                     new AndroidFuture<>();
611             service.getPermissionUsages(countSystem, numMillis, getPermissionUsagesResult);
612             return getPermissionUsagesResult;
613         }).whenCompleteAsync((getPermissionUsagesResult, err) -> {
614             if (err != null) {
615                 Log.e(TAG, "Error getting permission usages", err);
616                 callback.onPermissionUsageResult(Collections.emptyList());
617             } else {
618                 final long token = Binder.clearCallingIdentity();
619                 try {
620                     callback.onPermissionUsageResult(
621                             CollectionUtils.emptyIfNull(getPermissionUsagesResult));
622                 } finally {
623                     Binder.restoreCallingIdentity(token);
624                 }
625             }
626         }, executor);
627     }
628 
629     /**
630      * Grant or upgrade runtime permissions. The upgrade could be performed
631      * based on whether the device upgraded, whether the permission database
632      * version is old, or because the permission policy changed.
633      *
634      * @param executor Executor on which to invoke the callback
635      * @param callback Callback to receive the result
636      *
637      * @hide
638      */
639     @RequiresPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY)
grantOrUpgradeDefaultRuntimePermissions( @onNull @allbackExecutor Executor executor, @NonNull Consumer<Boolean> callback)640     public void grantOrUpgradeDefaultRuntimePermissions(
641             @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
642         mRemoteService.postAsync(service -> {
643             AndroidFuture<Boolean> grantOrUpgradeDefaultRuntimePermissionsResult =
644                     new AndroidFuture<>();
645             service.grantOrUpgradeDefaultRuntimePermissions(
646                     grantOrUpgradeDefaultRuntimePermissionsResult);
647             return grantOrUpgradeDefaultRuntimePermissionsResult;
648         }).whenCompleteAsync((grantOrUpgradeDefaultRuntimePermissionsResult, err) -> {
649             if (err != null) {
650                 Log.e(TAG, "Error granting or upgrading runtime permissions", err);
651                 callback.accept(false);
652             } else {
653                 callback.accept(Boolean.TRUE.equals(grantOrUpgradeDefaultRuntimePermissionsResult));
654             }
655         }, executor);
656     }
657 
658     /**
659      * Gets the description of the privileges associated with the given device profiles
660      *
661      * @param profileName Name of the device profile
662      * @param executor Executor on which to invoke the callback
663      * @param callback Callback to receive the result
664      *
665      * @hide
666      */
667     @RequiresPermission(Manifest.permission.MANAGE_COMPANION_DEVICES)
getPrivilegesDescriptionStringForProfile( @onNull String profileName, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<CharSequence> callback)668     public void getPrivilegesDescriptionStringForProfile(
669             @NonNull String profileName,
670             @NonNull @CallbackExecutor Executor executor,
671             @NonNull Consumer<CharSequence> callback) {
672         mRemoteService.postAsync(service -> {
673             AndroidFuture<String> future = new AndroidFuture<>();
674             service.getPrivilegesDescriptionStringForProfile(profileName, future);
675             return future;
676         }).whenCompleteAsync((description, err) -> {
677             if (err != null) {
678                 Log.e(TAG, "Error from getPrivilegesDescriptionStringForProfile", err);
679                 callback.accept(null);
680             } else {
681                 callback.accept(description);
682             }
683         }, executor);
684     }
685 
686     /**
687      * @see PermissionControllerManager#updateUserSensitiveForApp
688      * @hide
689      */
updateUserSensitive()690     public void updateUserSensitive() {
691         updateUserSensitiveForApp(Process.INVALID_UID);
692     }
693 
694     /**
695      * @see PermissionControllerService#onUpdateUserSensitiveForApp
696      * @hide
697      */
updateUserSensitiveForApp(int uid)698     public void updateUserSensitiveForApp(int uid) {
699         mRemoteService.postAsync(service -> {
700             AndroidFuture<Void> future = new AndroidFuture<>();
701             service.updateUserSensitiveForApp(uid, future);
702             return future;
703         }).whenComplete((res, err) -> {
704             if (err != null) {
705                 Log.e(TAG, "Error updating user_sensitive flags for uid " + uid, err);
706             }
707         });
708     }
709 
710     /**
711      * Called when a package that has permissions registered as "one-time" is considered
712      * inactive.
713      *
714      * @param packageName The package which became inactive
715      *
716      * @hide
717      */
718     @RequiresPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
notifyOneTimePermissionSessionTimeout(@onNull String packageName)719     public void notifyOneTimePermissionSessionTimeout(@NonNull String packageName) {
720         mRemoteService.run(
721                 service -> service.notifyOneTimePermissionSessionTimeout(packageName));
722     }
723 
724     /**
725      * Get the platform permissions which belong to a particular permission group.
726      *
727      * @param permissionGroupName The permission group whose permissions are desired
728      * @param executor Executor on which to invoke the callback
729      * @param callback A callback which will receive a list of the platform permissions in the
730      *                 group, or empty if the group is not a valid platform group, or there
731      *                 was an exception.
732      *
733      * @hide
734      */
getPlatformPermissionsForGroup(@onNull String permissionGroupName, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<List<String>> callback)735     public void getPlatformPermissionsForGroup(@NonNull String permissionGroupName,
736             @NonNull @CallbackExecutor Executor executor,
737             @NonNull Consumer<List<String>> callback) {
738         mRemoteService.postAsync(service -> {
739             AndroidFuture<List<String>> future = new AndroidFuture<>();
740             service.getPlatformPermissionsForGroup(permissionGroupName, future);
741             return future;
742         }).whenCompleteAsync((result, err) -> {
743             final long token = Binder.clearCallingIdentity();
744             try {
745                 if (err != null) {
746                     Log.e(TAG, "Failed to get permissions of " + permissionGroupName, err);
747                     callback.accept(new ArrayList<>());
748                 } else {
749                     callback.accept(result);
750                 }
751             } finally {
752                 Binder.restoreCallingIdentity(token);
753             }
754         }, executor);
755     }
756 
757     /**
758      * Get the platform group of a particular permission, if the permission is a platform
759      * permission.
760      *
761      * @param permissionName The permission name whose group is desired
762      * @param executor Executor on which to invoke the callback
763      * @param callback A callback which will receive the name of the permission group this
764      *                 permission belongs to, or null if it has no group, is not a platform
765      *                 permission, or there was an exception.
766      *
767      * @hide
768      */
getGroupOfPlatformPermission(@onNull String permissionName, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<String> callback)769     public void getGroupOfPlatformPermission(@NonNull String permissionName,
770             @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<String> callback) {
771         mRemoteService.postAsync(service -> {
772             AndroidFuture<String> future = new AndroidFuture<>();
773             service.getGroupOfPlatformPermission(permissionName, future);
774             return future;
775         }).whenCompleteAsync((result, err) -> {
776             final long token = Binder.clearCallingIdentity();
777             try {
778                 if (err != null) {
779                     Log.e(TAG, "Failed to get group of " + permissionName, err);
780                     callback.accept(null);
781                 } else {
782                     callback.accept(result);
783                 }
784             } finally {
785                 Binder.restoreCallingIdentity(token);
786             }
787         }, executor);
788     }
789 }
790