content: /*
   * Copyright (C) 2018 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 com.android.server.usb;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.app.PendingIntent;
  import android.content.ActivityNotFoundException;
  import android.content.Context;
  import android.content.Intent;
  import android.hardware.usb.UsbAccessory;
  import android.hardware.usb.UsbDevice;
  import android.hardware.usb.UsbManager;
  import android.os.Binder;
  import android.os.Process;
  import android.os.UserHandle;
  import android.service.usb.UsbSettingsAccessoryPermissionProto;
  import android.service.usb.UsbSettingsDevicePermissionProto;
  import android.service.usb.UsbUserSettingsManagerProto;
  import android.util.Slog;
  import android.util.SparseBooleanArray;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.util.dump.DualDumpOutputStream;
  
  import java.util.HashMap;
  
  /**
   * UsbPermissionManager manages usb device or accessory access permissions.
   *
   * @hide
   */
  class UsbPermissionManager {
      private static final String LOG_TAG = UsbPermissionManager.class.getSimpleName();
  
      @GuardedBy("mLock")
      /** Temporary mapping USB device name to list of UIDs with permissions for the device*/
      private final HashMap<String, SparseBooleanArray> mDevicePermissionMap =
              new HashMap<>();
      @GuardedBy("mLock")
      /** Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory*/
      private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap =
              new HashMap<>();
  
      private final UserHandle mUser;
      private final boolean mDisablePermissionDialogs;
  
      private final Object mLock = new Object();
  
      UsbPermissionManager(@NonNull Context context, @NonNull UserHandle user) {
          mUser = user;
          mDisablePermissionDialogs = context.getResources().getBoolean(
                  com.android.internal.R.bool.config_disableUsbPermissionDialogs);
      }
  
      /**
       * Removes access permissions of all packages for the USB accessory.
       *
       * @param accessory to remove permissions for
       */
      void removeAccessoryPermissions(@NonNull UsbAccessory accessory) {
          synchronized (mLock) {
              mAccessoryPermissionMap.remove(accessory);
          }
      }
  
      /**
       * Removes access permissions of all packages for the USB device.
       *
       * @param device to remove permissions for
       */
      void removeDevicePermissions(@NonNull UsbDevice device) {
          synchronized (mLock) {
              mDevicePermissionMap.remove(device.getDeviceName());
          }
      }
  
      /**
       * Grants permission for USB device without showing system dialog for package with uid.
       *
       * @param device to grant permission for
       * @param uid to grant permission for
       */
      void grantDevicePermission(@NonNull UsbDevice device, int uid) {
          synchronized (mLock) {
              String deviceName = device.getDeviceName();
              SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
              if (uidList == null) {
                  uidList = new SparseBooleanArray(1);
                  mDevicePermissionMap.put(deviceName, uidList);
              }
              uidList.put(uid, true);
          }
      }
  
      /**
       * Grants permission for USB accessory without showing system dialog for package with uid.
       *
       * @param accessory to grant permission for
       * @param uid to grant permission for
       */
      void grantAccessoryPermission(@NonNull UsbAccessory accessory, int uid) {
          synchronized (mLock) {
              SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
              if (uidList == null) {
                  uidList = new SparseBooleanArray(1);
                  mAccessoryPermissionMap.put(accessory, uidList);
              }
              uidList.put(uid, true);
          }
      }
  
      /**
       * Returns true if package with uid has permission to access the device.
       *
       * @param device to check permission for
       * @param uid to check permission for
       * @return {@code true} if package with uid has permission
       */
      boolean hasPermission(@NonNull UsbDevice device, int uid) {
          synchronized (mLock) {
              if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
                  return true;
              }
              SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName());
              if (uidList == null) {
                  return false;
              }
              return uidList.get(uid);
          }
      }
  
      /**
       * Returns true if caller has permission to access the accessory.
       *
       * @param accessory to check permission for
       * @param uid to check permission for
       * @return {@code true} if caller has permssion
       */
      boolean hasPermission(@NonNull UsbAccessory accessory, int uid) {
          synchronized (mLock) {
              if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
                  return true;
              }
              SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
              if (uidList == null) {
                  return false;
              }
              return uidList.get(uid);
          }
      }
  
      /**
       * Creates UI dialog to request permission for the given package to access the device
       * or accessory.
       *
       * @param device The USB device attached
       * @param accessory The USB accessory attached
       * @param canBeDefault Whether the calling pacakge can set as default handler
       * of the USB device or accessory
       * @param packageName The package name of the calling package
       * @param uid The uid of the calling package
       * @param userContext The context to start the UI dialog
       * @param pi PendingIntent for returning result
       */
      void requestPermissionDialog(@Nullable UsbDevice device,
                                   @Nullable UsbAccessory accessory,
                                   boolean canBeDefault,
                                   @NonNull String packageName,
                                   int uid,
                                   @NonNull Context userContext,
                                   @NonNull PendingIntent pi) {
          long identity = Binder.clearCallingIdentity();
          Intent intent = new Intent();
          if (device != null) {
              intent.putExtra(UsbManager.EXTRA_DEVICE, device);
          } else {
              intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
          }
          intent.putExtra(Intent.EXTRA_INTENT, pi);
          intent.putExtra(Intent.EXTRA_UID, uid);
          intent.putExtra(UsbManager.EXTRA_CAN_BE_DEFAULT, canBeDefault);
          intent.putExtra(UsbManager.EXTRA_PACKAGE, packageName);
          intent.setClassName("com.android.systemui",
                  "com.android.systemui.usb.UsbPermissionActivity");
          intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  
          try {
              userContext.startActivityAsUser(intent, mUser);
          } catch (ActivityNotFoundException e) {
              Slog.e(LOG_TAG, "unable to start UsbPermissionActivity");
          } finally {
              Binder.restoreCallingIdentity(identity);
          }
      }
  
      void dump(@NonNull DualDumpOutputStream dump) {
          synchronized (mLock) {
              for (String deviceName : mDevicePermissionMap.keySet()) {
                  long devicePermissionToken = dump.start("device_permissions",
                          UsbUserSettingsManagerProto.DEVICE_PERMISSIONS);
  
                  dump.write("device_name", UsbSettingsDevicePermissionProto.DEVICE_NAME, deviceName);
  
                  SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
                  int count = uidList.size();
                  for (int i = 0; i < count; i++) {
                      dump.write("uids", UsbSettingsDevicePermissionProto.UIDS, uidList.keyAt(i));
                  }
  
                  dump.end(devicePermissionToken);
              }
  
              for (UsbAccessory accessory : mAccessoryPermissionMap.keySet()) {
                  long accessoryPermissionToken = dump.start("accessory_permissions",
                          UsbUserSettingsManagerProto.ACCESSORY_PERMISSIONS);
  
                  dump.write("accessory_description",
                          UsbSettingsAccessoryPermissionProto.ACCESSORY_DESCRIPTION,
                          accessory.getDescription());
  
                  SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
                  int count = uidList.size();
                  for (int i = 0; i < count; i++) {
                      dump.write("uids", UsbSettingsAccessoryPermissionProto.UIDS, uidList.keyAt(i));
                  }
  
                  dump.end(accessoryPermissionToken);
              }
          }
      }
  }
  