content: /*
   * 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<Pair<Integer, Thread>, 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.
       *
       * <p>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<packageName, List<permission>>}
           */
          public abstract void onRevokeRuntimePermissions(@NonNull Map<String, List<String>> 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<RuntimePermissionPresentationInfo> 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<RuntimePermissionUsageInfo> 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<Integer, Thread> 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<packageName, List<permission>>}
       * @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<String, List<String>> 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<String, List<String>> 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<Boolean> 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<Boolean> 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<String> 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<Boolean> callback) {
          mRemoteService.scheduleRequest(new PendingGrantOrUpgradeDefaultRuntimePermissionsRequest(
                  mRemoteService, executor, callback));
      }
  
      /**
       * A connection to the remote service
       */
      static final class RemoteService extends
              AbstractMultiplePendingRequestsRemoteService<RemoteService, IPermissionController> {
          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<RemoteService,
                  IPermissionController> pendingRequest) {
              super.scheduleRequest(pendingRequest);
          }
  
          @Override
          public void scheduleAsyncRequest(@NonNull AsyncRequest<IPermissionController> request) {
              super.scheduleAsyncRequest(request);
          }
      }
  
      /**
       * Task to read a large amount of data from a remote service.
       */
      private static class FileReaderTask<Callback extends Consumer<byte[]>>
              extends AsyncTask<Void, Void, byte[]> {
          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.
           *
           * <p>Needs to be closed <u>locally</u> 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.
           *
           * <p>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<byte[], Void, Void> {
          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.
           *
           * <p>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<RemoteService, IPermissionController> {
          private final @NonNull Map<String, List<String>> 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<String, List<String>> 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<String, List<String>> revoked = new ArrayMap<>();
                      try {
                          Bundle bundleizedRevoked = result.getBundle(KEY_RESULT);
  
                          for (String packageName : bundleizedRevoked.keySet()) {
                              Preconditions.checkNotNull(packageName);
  
                              ArrayList<String> 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<String, List<String>> 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<RemoteService, IPermissionController>
              implements Consumer<byte[]> {
          private final @NonNull FileReaderTask<PendingGetRuntimePermissionBackup> 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<RemoteService, IPermissionController> {
          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<Boolean> 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<Boolean> 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<IPermissionController> {
          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<Boolean>)}
       */
      private static final class PendingRestoreDelayedRuntimePermissionBackup extends
              AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> {
          private final @NonNull String mPackageName;
          private final @NonNull UserHandle mUser;
          private final @NonNull Executor mExecutor;
          private final @NonNull Consumer<Boolean> mCallback;
  
          private final @NonNull RemoteCallback mRemoteCallback;
  
          private PendingRestoreDelayedRuntimePermissionBackup(@NonNull RemoteService service,
                  @NonNull String packageName, @NonNull UserHandle user, @NonNull Executor executor,
                  @NonNull Consumer<Boolean> 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<RemoteService, IPermissionController> {
          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<RuntimePermissionPresentationInfo> reportedPermissions;
                  List<RuntimePermissionPresentationInfo> 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<IPermissionController> {
          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<RemoteService, IPermissionController> {
          private final @NonNull List<String> mPermissionNames;
          private final @NonNull OnCountPermissionAppsResultCallback mCallback;
          private final @CountPermissionAppsFlag int mFlags;
  
          private final @NonNull RemoteCallback mRemoteCallback;
  
          private PendingCountPermissionAppsRequest(@NonNull RemoteService service,
                  @NonNull List<String> 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<RemoteService, IPermissionController> {
          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<RuntimePermissionUsageInfo> reportedUsers;
                      List<RuntimePermissionUsageInfo> 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<RemoteService, IPermissionController> {
          private final @NonNull Consumer<Boolean> mCallback;
  
          private final @NonNull RemoteCallback mRemoteCallback;
  
          private PendingGrantOrUpgradeDefaultRuntimePermissionsRequest(
                  @NonNull RemoteService service,  @NonNull @CallbackExecutor Executor executor,
                  @NonNull Consumer<Boolean> 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);
              }
          }
      }
  }
  