/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.permission; import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT; import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED; import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED; import static android.permission.PermissionControllerService.SERVICE_INTERFACE; import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkArgumentNonnegative; import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull; import static com.android.internal.util.Preconditions.checkFlagsArgument; import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.Preconditions.checkStringNotEmpty; import static java.lang.Math.min; import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.app.ActivityThread; import android.app.admin.DevicePolicyManager.PermissionGrantState; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.AsyncTask; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import com.android.internal.annotations.GuardedBy; import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService; import com.android.internal.infra.AbstractRemoteService; import com.android.internal.util.Preconditions; import libcore.io.IoUtils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * Interface for communicating with the permission controller. * * @hide */ @TestApi @SystemApi @SystemService(Context.PERMISSION_CONTROLLER_SERVICE) public final class PermissionControllerManager { private static final String TAG = PermissionControllerManager.class.getSimpleName(); private static final Object sLock = new Object(); /** * Global remote services (per user) used by all {@link PermissionControllerManager managers} */ @GuardedBy("sLock") private static ArrayMap, RemoteService> sRemoteServices = new ArrayMap<>(1); /** * The key for retrieving the result from the returned bundle. * * @hide */ public static final String KEY_RESULT = "android.permission.PermissionControllerManager.key.result"; /** @hide */ @IntDef(prefix = { "REASON_" }, value = { REASON_MALWARE, REASON_INSTALLER_POLICY_VIOLATION, }) @Retention(RetentionPolicy.SOURCE) public @interface Reason {} /** The permissions are revoked because the apps holding the permissions are malware */ public static final int REASON_MALWARE = 1; /** * The permissions are revoked because the apps holding the permissions violate a policy of the * app that installed it. * *

If this reason is used only permissions of apps that are installed by the caller of the * API can be revoked. */ public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; /** @hide */ @IntDef(prefix = { "COUNT_" }, value = { COUNT_ONLY_WHEN_GRANTED, COUNT_WHEN_SYSTEM, }, flag = true) @Retention(RetentionPolicy.SOURCE) public @interface CountPermissionAppsFlag {} /** Count an app only if the permission is granted to the app. */ public static final int COUNT_ONLY_WHEN_GRANTED = 1; /** Count and app even if it is a system app. */ public static final int COUNT_WHEN_SYSTEM = 2; /** * Callback for delivering the result of {@link #revokeRuntimePermissions}. */ public abstract static class OnRevokeRuntimePermissionsCallback { /** * The result for {@link #revokeRuntimePermissions}. * * @param revoked The actually revoked permissions as * {@code Map>} */ public abstract void onRevokeRuntimePermissions(@NonNull Map> revoked); } /** * Callback for delivering the result of {@link #getRuntimePermissionBackup}. * * @hide */ public interface OnGetRuntimePermissionBackupCallback { /** * The result for {@link #getRuntimePermissionBackup}. * * @param backup The backup file */ void onGetRuntimePermissionsBackup(@NonNull byte[] backup); } /** * Callback for delivering the result of {@link #getAppPermissions}. * * @hide */ @TestApi public interface OnGetAppPermissionResultCallback { /** * The result for {@link #getAppPermissions(String, OnGetAppPermissionResultCallback, * Handler)}. * * @param permissions The permissions list. */ void onGetAppPermissions(@NonNull List permissions); } /** * Callback for delivering the result of {@link #countPermissionApps}. * * @hide */ public interface OnCountPermissionAppsResultCallback { /** * The result for {@link #countPermissionApps(List, int, * OnCountPermissionAppsResultCallback, Handler)}. * * @param numApps The number of apps that have one of the permissions */ void onCountPermissionApps(int numApps); } /** * Callback for delivering the result of {@link #getPermissionUsages}. * * @hide */ public interface OnPermissionUsageResultCallback { /** * The result for {@link #getPermissionUsages}. * * @param users The users list. */ void onPermissionUsageResult(@NonNull List users); } private final @NonNull Context mContext; private final @NonNull RemoteService mRemoteService; /** * Create a new {@link PermissionControllerManager}. * * @param context to create the manager for * @param handler handler to schedule work * * @hide */ public PermissionControllerManager(@NonNull Context context, @NonNull Handler handler) { synchronized (sLock) { Pair key = new Pair<>(context.getUserId(), handler.getLooper().getThread()); RemoteService remoteService = sRemoteServices.get(key); if (remoteService == null) { Intent intent = new Intent(SERVICE_INTERFACE); intent.setPackage(context.getPackageManager().getPermissionControllerPackageName()); ResolveInfo serviceInfo = context.getPackageManager().resolveService(intent, 0); remoteService = new RemoteService(ActivityThread.currentApplication(), serviceInfo.getComponentInfo().getComponentName(), handler, context.getUser()); sRemoteServices.put(key, remoteService); } mRemoteService = remoteService; } mContext = context; } /** * Revoke a set of runtime permissions for various apps. * * @param request The permissions to revoke as {@code Map>} * @param doDryRun Compute the permissions that would be revoked, but not actually revoke them * @param reason Why the permission should be revoked * @param executor Executor on which to invoke the callback * @param callback Callback to receive the result */ @RequiresPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermissions(@NonNull Map> request, boolean doDryRun, @Reason int reason, @NonNull @CallbackExecutor Executor executor, @NonNull OnRevokeRuntimePermissionsCallback callback) { // Check input to fail immediately instead of inside the async request checkNotNull(executor); checkNotNull(callback); checkNotNull(request); for (Map.Entry> appRequest : request.entrySet()) { checkNotNull(appRequest.getKey()); checkCollectionElementsNotNull(appRequest.getValue(), "permissions"); } // Check required permission to fail immediately instead of inside the oneway binder call if (mContext.checkSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS + " required"); } mRemoteService.scheduleRequest(new PendingRevokeRuntimePermissionRequest(mRemoteService, request, doDryRun, reason, mContext.getPackageName(), executor, callback)); } /** * Set the runtime permission state from a device admin. * * @param callerPackageName The package name of the admin requesting the change * @param packageName Package the permission belongs to * @param permission Permission to change * @param grantState State to set the permission into * @param executor Executor to run the {@code callback} on * @param callback The callback * * @hide */ @RequiresPermission(allOf = {Manifest.permission.GRANT_RUNTIME_PERMISSIONS, Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY}, conditional = true) public void setRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName, @NonNull String packageName, @NonNull String permission, @PermissionGrantState int grantState, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer callback) { checkStringNotEmpty(callerPackageName); checkStringNotEmpty(packageName); checkStringNotEmpty(permission); checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED || grantState == PERMISSION_GRANT_STATE_DENIED || grantState == PERMISSION_GRANT_STATE_DEFAULT); checkNotNull(executor); checkNotNull(callback); mRemoteService.scheduleRequest(new PendingSetRuntimePermissionGrantStateByDeviceAdmin( mRemoteService, callerPackageName, packageName, permission, grantState, executor, callback)); } /** * Create a backup of the runtime permissions. * * @param user The user to be backed up * @param executor Executor on which to invoke the callback * @param callback Callback to receive the result * * @hide */ @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull UserHandle user, @NonNull @CallbackExecutor Executor executor, @NonNull OnGetRuntimePermissionBackupCallback callback) { checkNotNull(user); checkNotNull(executor); checkNotNull(callback); mRemoteService.scheduleRequest(new PendingGetRuntimePermissionBackup(mRemoteService, user, executor, callback)); } /** * Restore a backup of the runtime permissions. * * @param backup the backup to restore. The backup is sent asynchronously, hence it should not * be modified after calling this method. * @param user The user to be restore * * @hide */ @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public void restoreRuntimePermissionBackup(@NonNull byte[] backup, @NonNull UserHandle user) { checkNotNull(backup); checkNotNull(user); mRemoteService.scheduleAsyncRequest( new PendingRestoreRuntimePermissionBackup(mRemoteService, backup, user)); } /** * Restore a backup of the runtime permissions that has been delayed. * * @param packageName The package that is ready to have it's permissions restored. * @param user The user to restore * @param executor Executor to execute the callback on * @param callback Is called with {@code true} iff there is still more delayed backup left * * @hide */ @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public void restoreDelayedRuntimePermissionBackup(@NonNull String packageName, @NonNull UserHandle user, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer callback) { checkNotNull(packageName); checkNotNull(user); checkNotNull(executor); checkNotNull(callback); mRemoteService.scheduleRequest( new PendingRestoreDelayedRuntimePermissionBackup(mRemoteService, packageName, user, executor, callback)); } /** * Gets the runtime permissions for an app. * * @param packageName The package for which to query. * @param callback Callback to receive the result. * @param handler Handler on which to invoke the callback. * * @hide */ @TestApi @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getAppPermissions(@NonNull String packageName, @NonNull OnGetAppPermissionResultCallback callback, @Nullable Handler handler) { checkNotNull(packageName); checkNotNull(callback); mRemoteService.scheduleRequest(new PendingGetAppPermissionRequest(mRemoteService, packageName, callback, handler == null ? mRemoteService.getHandler() : handler)); } /** * Revoke the permission {@code permissionName} for app {@code packageName} * * @param packageName The package for which to revoke * @param permissionName The permission to revoke * * @hide */ @RequiresPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermission(@NonNull String packageName, @NonNull String permissionName) { checkNotNull(packageName); checkNotNull(permissionName); mRemoteService.scheduleAsyncRequest(new PendingRevokeAppPermissionRequest(packageName, permissionName)); } /** * Count how many apps have one of a set of permissions. * * @param permissionNames The permissions the app might have * @param flags Modify which apps to count. By default all non-system apps that request a * permission are counted * @param callback Callback to receive the result * @param handler Handler on which to invoke the callback * * @hide */ @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS) public void countPermissionApps(@NonNull List permissionNames, @CountPermissionAppsFlag int flags, @NonNull OnCountPermissionAppsResultCallback callback, @Nullable Handler handler) { checkCollectionElementsNotNull(permissionNames, "permissionNames"); checkFlagsArgument(flags, COUNT_WHEN_SYSTEM | COUNT_ONLY_WHEN_GRANTED); checkNotNull(callback); mRemoteService.scheduleRequest(new PendingCountPermissionAppsRequest(mRemoteService, permissionNames, flags, callback, handler == null ? mRemoteService.getHandler() : handler)); } /** * Count how many apps have used permissions. * * @param countSystem Also count system apps * @param numMillis The number of milliseconds in the past to check for uses * @param executor Executor on which to invoke the callback * @param callback Callback to receive the result * * @hide */ @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getPermissionUsages(boolean countSystem, long numMillis, @NonNull @CallbackExecutor Executor executor, @NonNull OnPermissionUsageResultCallback callback) { checkArgumentNonnegative(numMillis); checkNotNull(executor); checkNotNull(callback); mRemoteService.scheduleRequest(new PendingGetPermissionUsagesRequest(mRemoteService, countSystem, numMillis, executor, callback)); } /** * Grant or upgrade runtime permissions. The upgrade could be performed * based on whether the device upgraded, whether the permission database * version is old, or because the permission policy changed. * * @param executor Executor on which to invoke the callback * @param callback Callback to receive the result * * @hide */ @RequiresPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public void grantOrUpgradeDefaultRuntimePermissions( @NonNull @CallbackExecutor Executor executor, @NonNull Consumer callback) { mRemoteService.scheduleRequest(new PendingGrantOrUpgradeDefaultRuntimePermissionsRequest( mRemoteService, executor, callback)); } /** * A connection to the remote service */ static final class RemoteService extends AbstractMultiplePendingRequestsRemoteService { private static final long UNBIND_TIMEOUT_MILLIS = 10000; private static final long MESSAGE_TIMEOUT_MILLIS = 30000; /** * Create a connection to the remote service * * @param context A context to use * @param componentName The component of the service to connect to * @param user User the remote service should be connected as */ RemoteService(@NonNull Context context, @NonNull ComponentName componentName, @NonNull Handler handler, @NonNull UserHandle user) { super(context, SERVICE_INTERFACE, componentName, user.getIdentifier(), service -> Log.e(TAG, "RemoteService " + service + " died"), handler, 0, false, 1); } /** * @return The default handler used by this service. */ Handler getHandler() { return mHandler; } @Override protected @NonNull IPermissionController getServiceInterface(@NonNull IBinder binder) { return IPermissionController.Stub.asInterface(binder); } @Override protected long getTimeoutIdleBindMillis() { return UNBIND_TIMEOUT_MILLIS; } @Override protected long getRemoteRequestMillis() { return MESSAGE_TIMEOUT_MILLIS; } @Override public void scheduleRequest(@NonNull BasePendingRequest pendingRequest) { super.scheduleRequest(pendingRequest); } @Override public void scheduleAsyncRequest(@NonNull AsyncRequest request) { super.scheduleAsyncRequest(request); } } /** * Task to read a large amount of data from a remote service. */ private static class FileReaderTask> extends AsyncTask { private ParcelFileDescriptor mLocalPipe; private ParcelFileDescriptor mRemotePipe; private final @NonNull Callback mCallback; FileReaderTask(@NonNull Callback callback) { mCallback = callback; } @Override protected void onPreExecute() { ParcelFileDescriptor[] pipe; try { pipe = ParcelFileDescriptor.createPipe(); } catch (IOException e) { Log.e(TAG, "Could not create pipe needed to get runtime permission backup", e); return; } mLocalPipe = pipe[0]; mRemotePipe = pipe[1]; } /** * Get the file descriptor the remote service should write the data to. * *

Needs to be closed locally before the FileReader can finish. * * @return The file the data should be written to */ ParcelFileDescriptor getRemotePipe() { return mRemotePipe; } @Override protected byte[] doInBackground(Void... ignored) { ByteArrayOutputStream combinedBuffer = new ByteArrayOutputStream(); try (InputStream in = new ParcelFileDescriptor.AutoCloseInputStream(mLocalPipe)) { byte[] buffer = new byte[16 * 1024]; while (!isCancelled()) { int numRead = in.read(buffer); if (numRead == -1) { break; } combinedBuffer.write(buffer, 0, numRead); } } catch (IOException | NullPointerException e) { Log.e(TAG, "Error reading runtime permission backup", e); combinedBuffer.reset(); } return combinedBuffer.toByteArray(); } /** * Interrupt the reading of the data. * *

Needs to be called when canceling this task as it might be hung. */ void interruptRead() { IoUtils.closeQuietly(mLocalPipe); } @Override protected void onCancelled() { onPostExecute(new byte[]{}); } @Override protected void onPostExecute(byte[] backup) { IoUtils.closeQuietly(mLocalPipe); mCallback.accept(backup); } } /** * Task to send a large amount of data to a remote service. */ private static class FileWriterTask extends AsyncTask { private static final int CHUNK_SIZE = 4 * 1024; private ParcelFileDescriptor mLocalPipe; private ParcelFileDescriptor mRemotePipe; @Override protected void onPreExecute() { ParcelFileDescriptor[] pipe; try { pipe = ParcelFileDescriptor.createPipe(); } catch (IOException e) { Log.e(TAG, "Could not create pipe needed to send runtime permission backup", e); return; } mRemotePipe = pipe[0]; mLocalPipe = pipe[1]; } /** * Get the file descriptor the remote service should read the data from. * * @return The file the data should be read from */ ParcelFileDescriptor getRemotePipe() { return mRemotePipe; } /** * Send the data to the remove service. * * @param in The data to send * * @return ignored */ @Override protected Void doInBackground(byte[]... in) { byte[] buffer = in[0]; try (OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(mLocalPipe)) { for (int offset = 0; offset < buffer.length; offset += CHUNK_SIZE) { out.write(buffer, offset, min(CHUNK_SIZE, buffer.length - offset)); } } catch (IOException | NullPointerException e) { Log.e(TAG, "Error sending runtime permission backup", e); } return null; } /** * Interrupt the send of the data. * *

Needs to be called when canceling this task as it might be hung. */ void interruptWrite() { IoUtils.closeQuietly(mLocalPipe); } @Override protected void onCancelled() { onPostExecute(null); } @Override protected void onPostExecute(Void ignored) { IoUtils.closeQuietly(mLocalPipe); } } /** * Request for {@link #revokeRuntimePermissions} */ private static final class PendingRevokeRuntimePermissionRequest extends AbstractRemoteService.PendingRequest { private final @NonNull Map> mRequest; private final boolean mDoDryRun; private final int mReason; private final @NonNull String mCallingPackage; private final @NonNull Executor mExecutor; private final @NonNull OnRevokeRuntimePermissionsCallback mCallback; private final @NonNull RemoteCallback mRemoteCallback; private PendingRevokeRuntimePermissionRequest(@NonNull RemoteService service, @NonNull Map> request, boolean doDryRun, @Reason int reason, @NonNull String callingPackage, @NonNull @CallbackExecutor Executor executor, @NonNull OnRevokeRuntimePermissionsCallback callback) { super(service); mRequest = request; mDoDryRun = doDryRun; mReason = reason; mCallingPackage = callingPackage; mExecutor = executor; mCallback = callback; mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> { long token = Binder.clearCallingIdentity(); try { Map> revoked = new ArrayMap<>(); try { Bundle bundleizedRevoked = result.getBundle(KEY_RESULT); for (String packageName : bundleizedRevoked.keySet()) { Preconditions.checkNotNull(packageName); ArrayList permissions = bundleizedRevoked.getStringArrayList(packageName); Preconditions.checkCollectionElementsNotNull(permissions, "permissions"); revoked.put(packageName, permissions); } } catch (Exception e) { Log.e(TAG, "Could not read result when revoking runtime permissions", e); } callback.onRevokeRuntimePermissions(revoked); } finally { Binder.restoreCallingIdentity(token); finish(); } }), null); } @Override protected void onTimeout(RemoteService remoteService) { long token = Binder.clearCallingIdentity(); try { mExecutor.execute( () -> mCallback.onRevokeRuntimePermissions(Collections.emptyMap())); } finally { Binder.restoreCallingIdentity(token); } } @Override public void run() { Bundle bundledizedRequest = new Bundle(); for (Map.Entry> appRequest : mRequest.entrySet()) { bundledizedRequest.putStringArrayList(appRequest.getKey(), new ArrayList<>(appRequest.getValue())); } try { getService().getServiceInterface().revokeRuntimePermissions(bundledizedRequest, mDoDryRun, mReason, mCallingPackage, mRemoteCallback); } catch (RemoteException e) { Log.e(TAG, "Error revoking runtime permission", e); } } } /** * Request for {@link #getRuntimePermissionBackup} */ private static final class PendingGetRuntimePermissionBackup extends AbstractRemoteService.PendingRequest implements Consumer { private final @NonNull FileReaderTask mBackupReader; private final @NonNull Executor mExecutor; private final @NonNull OnGetRuntimePermissionBackupCallback mCallback; private final @NonNull UserHandle mUser; private PendingGetRuntimePermissionBackup(@NonNull RemoteService service, @NonNull UserHandle user, @NonNull @CallbackExecutor Executor executor, @NonNull OnGetRuntimePermissionBackupCallback callback) { super(service); mUser = user; mExecutor = executor; mCallback = callback; mBackupReader = new FileReaderTask<>(this); } @Override protected void onTimeout(RemoteService remoteService) { mBackupReader.cancel(true); mBackupReader.interruptRead(); } @Override public void run() { if (mBackupReader.getStatus() != AsyncTask.Status.PENDING) { return; } mBackupReader.execute(); ParcelFileDescriptor remotePipe = mBackupReader.getRemotePipe(); try { getService().getServiceInterface().getRuntimePermissionBackup(mUser, remotePipe); } catch (RemoteException e) { Log.e(TAG, "Error getting runtime permission backup", e); } finally { // Remote pipe end is duped by binder call. Local copy is not needed anymore IoUtils.closeQuietly(remotePipe); } } /** * Called when the {@link #mBackupReader} finished reading the file. * * @param backup The data read */ @Override public void accept(byte[] backup) { long token = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> mCallback.onGetRuntimePermissionsBackup(backup)); } finally { Binder.restoreCallingIdentity(token); } finish(); } } /** * Request for {@link #getRuntimePermissionBackup} */ private static final class PendingSetRuntimePermissionGrantStateByDeviceAdmin extends AbstractRemoteService.PendingRequest { private final @NonNull String mCallerPackageName; private final @NonNull String mPackageName; private final @NonNull String mPermission; private final @PermissionGrantState int mGrantState; private final @NonNull Executor mExecutor; private final @NonNull Consumer mCallback; private final @NonNull RemoteCallback mRemoteCallback; private PendingSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull RemoteService service, @NonNull String callerPackageName, @NonNull String packageName, @NonNull String permission, @PermissionGrantState int grantState, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer callback) { super(service); mCallerPackageName = callerPackageName; mPackageName = packageName; mPermission = permission; mGrantState = grantState; mExecutor = executor; mCallback = callback; mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> { long token = Binder.clearCallingIdentity(); try { callback.accept(result.getBoolean(KEY_RESULT, false)); } finally { Binder.restoreCallingIdentity(token); finish(); } }), null); } @Override protected void onTimeout(RemoteService remoteService) { long token = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> mCallback.accept(false)); } finally { Binder.restoreCallingIdentity(token); } } @Override public void run() { try { getService().getServiceInterface().setRuntimePermissionGrantStateByDeviceAdmin( mCallerPackageName, mPackageName, mPermission, mGrantState, mRemoteCallback); } catch (RemoteException e) { Log.e(TAG, "Error setting permissions state for device admin " + mPackageName, e); } } } /** * Request for {@link #restoreRuntimePermissionBackup} */ private static final class PendingRestoreRuntimePermissionBackup implements AbstractRemoteService.AsyncRequest { private final @NonNull FileWriterTask mBackupSender; private final @NonNull byte[] mBackup; private final @NonNull UserHandle mUser; private PendingRestoreRuntimePermissionBackup(@NonNull RemoteService service, @NonNull byte[] backup, @NonNull UserHandle user) { mBackup = backup; mUser = user; mBackupSender = new FileWriterTask(); } @Override public void run(@NonNull IPermissionController service) { if (mBackupSender.getStatus() != AsyncTask.Status.PENDING) { return; } mBackupSender.execute(mBackup); ParcelFileDescriptor remotePipe = mBackupSender.getRemotePipe(); try { service.restoreRuntimePermissionBackup(mUser, remotePipe); } catch (RemoteException e) { Log.e(TAG, "Error sending runtime permission backup", e); mBackupSender.cancel(false); mBackupSender.interruptWrite(); } finally { // Remote pipe end is duped by binder call. Local copy is not needed anymore IoUtils.closeQuietly(remotePipe); } } } /** * Request for {@link #restoreDelayedRuntimePermissionBackup(String, UserHandle, Executor, * Consumer)} */ private static final class PendingRestoreDelayedRuntimePermissionBackup extends AbstractRemoteService.PendingRequest { private final @NonNull String mPackageName; private final @NonNull UserHandle mUser; private final @NonNull Executor mExecutor; private final @NonNull Consumer mCallback; private final @NonNull RemoteCallback mRemoteCallback; private PendingRestoreDelayedRuntimePermissionBackup(@NonNull RemoteService service, @NonNull String packageName, @NonNull UserHandle user, @NonNull Executor executor, @NonNull Consumer callback) { super(service); mPackageName = packageName; mUser = user; mExecutor = executor; mCallback = callback; mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> { long token = Binder.clearCallingIdentity(); try { callback.accept(result.getBoolean(KEY_RESULT, false)); } finally { Binder.restoreCallingIdentity(token); finish(); } }), null); } @Override protected void onTimeout(RemoteService remoteService) { long token = Binder.clearCallingIdentity(); try { mExecutor.execute( () -> mCallback.accept(true)); } finally { Binder.restoreCallingIdentity(token); } } @Override public void run() { try { getService().getServiceInterface().restoreDelayedRuntimePermissionBackup( mPackageName, mUser, mRemoteCallback); } catch (RemoteException e) { Log.e(TAG, "Error restoring delayed permissions for " + mPackageName, e); } } } /** * Request for {@link #getAppPermissions} */ private static final class PendingGetAppPermissionRequest extends AbstractRemoteService.PendingRequest { private final @NonNull String mPackageName; private final @NonNull OnGetAppPermissionResultCallback mCallback; private final @NonNull RemoteCallback mRemoteCallback; private PendingGetAppPermissionRequest(@NonNull RemoteService service, @NonNull String packageName, @NonNull OnGetAppPermissionResultCallback callback, @NonNull Handler handler) { super(service); mPackageName = packageName; mCallback = callback; mRemoteCallback = new RemoteCallback(result -> { final List reportedPermissions; List permissions = null; if (result != null) { permissions = result.getParcelableArrayList(KEY_RESULT); } if (permissions == null) { permissions = Collections.emptyList(); } reportedPermissions = permissions; callback.onGetAppPermissions(reportedPermissions); finish(); }, handler); } @Override protected void onTimeout(RemoteService remoteService) { mCallback.onGetAppPermissions(Collections.emptyList()); } @Override public void run() { try { getService().getServiceInterface().getAppPermissions(mPackageName, mRemoteCallback); } catch (RemoteException e) { Log.e(TAG, "Error getting app permission", e); } } } /** * Request for {@link #revokeRuntimePermission} */ private static final class PendingRevokeAppPermissionRequest implements AbstractRemoteService.AsyncRequest { private final @NonNull String mPackageName; private final @NonNull String mPermissionName; private PendingRevokeAppPermissionRequest(@NonNull String packageName, @NonNull String permissionName) { mPackageName = packageName; mPermissionName = permissionName; } @Override public void run(IPermissionController remoteInterface) { try { remoteInterface.revokeRuntimePermission(mPackageName, mPermissionName); } catch (RemoteException e) { Log.e(TAG, "Error revoking app permission", e); } } } /** * Request for {@link #countPermissionApps} */ private static final class PendingCountPermissionAppsRequest extends AbstractRemoteService.PendingRequest { private final @NonNull List mPermissionNames; private final @NonNull OnCountPermissionAppsResultCallback mCallback; private final @CountPermissionAppsFlag int mFlags; private final @NonNull RemoteCallback mRemoteCallback; private PendingCountPermissionAppsRequest(@NonNull RemoteService service, @NonNull List permissionNames, @CountPermissionAppsFlag int flags, @NonNull OnCountPermissionAppsResultCallback callback, @NonNull Handler handler) { super(service); mPermissionNames = permissionNames; mFlags = flags; mCallback = callback; mRemoteCallback = new RemoteCallback(result -> { final int numApps; if (result != null) { numApps = result.getInt(KEY_RESULT); } else { numApps = 0; } callback.onCountPermissionApps(numApps); finish(); }, handler); } @Override protected void onTimeout(RemoteService remoteService) { mCallback.onCountPermissionApps(0); } @Override public void run() { try { getService().getServiceInterface().countPermissionApps(mPermissionNames, mFlags, mRemoteCallback); } catch (RemoteException e) { Log.e(TAG, "Error counting permission apps", e); } } } /** * Request for {@link #getPermissionUsages} */ private static final class PendingGetPermissionUsagesRequest extends AbstractRemoteService.PendingRequest { private final @NonNull OnPermissionUsageResultCallback mCallback; private final boolean mCountSystem; private final long mNumMillis; private final @NonNull RemoteCallback mRemoteCallback; private PendingGetPermissionUsagesRequest(@NonNull RemoteService service, boolean countSystem, long numMillis, @NonNull @CallbackExecutor Executor executor, @NonNull OnPermissionUsageResultCallback callback) { super(service); mCountSystem = countSystem; mNumMillis = numMillis; mCallback = callback; mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> { long token = Binder.clearCallingIdentity(); try { final List reportedUsers; List users = null; if (result != null) { users = result.getParcelableArrayList(KEY_RESULT); } else { users = Collections.emptyList(); } reportedUsers = users; callback.onPermissionUsageResult(reportedUsers); } finally { Binder.restoreCallingIdentity(token); finish(); } }), null); } @Override protected void onTimeout(RemoteService remoteService) { mCallback.onPermissionUsageResult(Collections.emptyList()); } @Override public void run() { try { getService().getServiceInterface().getPermissionUsages(mCountSystem, mNumMillis, mRemoteCallback); } catch (RemoteException e) { Log.e(TAG, "Error counting permission users", e); } } } /** * Request for {@link #grantOrUpgradeDefaultRuntimePermissions(Executor, Consumer)} */ private static final class PendingGrantOrUpgradeDefaultRuntimePermissionsRequest extends AbstractRemoteService.PendingRequest { private final @NonNull Consumer mCallback; private final @NonNull RemoteCallback mRemoteCallback; private PendingGrantOrUpgradeDefaultRuntimePermissionsRequest( @NonNull RemoteService service, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer callback) { super(service); mCallback = callback; mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> { long token = Binder.clearCallingIdentity(); try { callback.accept(result != null); } finally { Binder.restoreCallingIdentity(token); finish(); } }), null); } @Override protected void onTimeout(RemoteService remoteService) { long token = Binder.clearCallingIdentity(); try { mCallback.accept(false); } finally { Binder.restoreCallingIdentity(token); } } @Override public void run() { try { getService().getServiceInterface().grantOrUpgradeDefaultRuntimePermissions( mRemoteCallback); } catch (RemoteException e) { Log.e(TAG, "Error granting or upgrading runtime permissions", e); } } } }