Home | History | Annotate | Download | only in shadows
      1 package org.robolectric.shadows;
      2 
      3 import static android.os.Build.VERSION_CODES.M;
      4 import static android.os.Build.VERSION_CODES.N;
      5 import static android.os.Build.VERSION_CODES.N_MR1;
      6 import static android.os.Build.VERSION_CODES.P;
      7 import static org.robolectric.RuntimeEnvironment.application;
      8 import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
      9 import static org.robolectric.util.ReflectionHelpers.callConstructor;
     10 import static org.robolectric.util.ReflectionHelpers.getStaticField;
     11 
     12 import android.content.Intent;
     13 import android.hardware.usb.UsbAccessory;
     14 import android.hardware.usb.UsbDevice;
     15 import android.hardware.usb.UsbManager;
     16 import android.hardware.usb.UsbPort;
     17 import android.hardware.usb.UsbPortStatus;
     18 import android.os.Build;
     19 import android.os.ParcelFileDescriptor;
     20 import com.google.common.base.Preconditions;
     21 import java.io.File;
     22 import java.io.FileNotFoundException;
     23 import java.util.ArrayList;
     24 import java.util.HashMap;
     25 import java.util.List;
     26 import org.robolectric.RuntimeEnvironment;
     27 import org.robolectric.annotation.HiddenApi;
     28 import org.robolectric.annotation.Implementation;
     29 import org.robolectric.annotation.Implements;
     30 import org.robolectric.annotation.RealObject;
     31 
     32 /** Robolectric implementation of {@link android.hardware.usb.UsbManager}. */
     33 @Implements(value = UsbManager.class, looseSignatures = true)
     34 public class ShadowUsbManager {
     35 
     36   @RealObject
     37   private UsbManager realUsbManager;
     38 
     39   /**
     40    * A mapping from the package names to a list of USB devices for which permissions are granted.
     41    */
     42   private final HashMap<String, List<UsbDevice>> grantedPermissions = new HashMap<>();
     43 
     44   /**
     45    * A mapping from the USB device names to the USB device instances.
     46    *
     47    * @see UsbManager#getDeviceList()
     48    */
     49   private final HashMap<String, UsbDevice> usbDevices = new HashMap<>();
     50 
     51   /** A mapping from USB port to the status of that port. */
     52   private final HashMap<UsbPort, UsbPortStatus> usbPorts = new HashMap<>();
     53 
     54   private UsbAccessory attachedUsbAccessory = null;
     55 
     56   /** Returns true if the caller has permission to access the device. */
     57   @Implementation
     58   protected boolean hasPermission(UsbDevice device) {
     59     return hasPermissionForPackage(device, RuntimeEnvironment.application.getPackageName());
     60   }
     61 
     62   /** Returns true if the given package has permission to access the device. */
     63   public boolean hasPermissionForPackage(UsbDevice device, String packageName) {
     64     List<UsbDevice> usbDevices = grantedPermissions.get(packageName);
     65     return usbDevices != null && usbDevices.contains(device);
     66   }
     67 
     68   @Implementation(minSdk = N)
     69   @HiddenApi
     70   protected void grantPermission(UsbDevice device) {
     71     grantPermission(device, RuntimeEnvironment.application.getPackageName());
     72   }
     73 
     74   @Implementation(minSdk = N_MR1)
     75   @HiddenApi // SystemApi
     76   protected void grantPermission(UsbDevice device, String packageName) {
     77     List<UsbDevice> usbDevices = grantedPermissions.get(packageName);
     78     if (usbDevices == null) {
     79       usbDevices = new ArrayList<>();
     80       grantedPermissions.put(packageName, usbDevices);
     81     }
     82     usbDevices.add(device);
     83   }
     84 
     85   /**
     86    * Revokes permission to a USB device granted to a package. This method does nothing if the
     87    * package doesn't have permission to access the device.
     88    */
     89   public void revokePermission(UsbDevice device, String packageName) {
     90     List<UsbDevice> usbDevices = grantedPermissions.get(packageName);
     91     if (usbDevices != null) {
     92       usbDevices.remove(device);
     93     }
     94   }
     95 
     96   /**
     97    * Returns a HashMap containing all USB devices currently attached. USB device name is the key for
     98    * the returned HashMap. The result will be empty if no devices are attached, or if USB host mode
     99    * is inactive or unsupported.
    100    */
    101   @Implementation
    102   protected HashMap<String, UsbDevice> getDeviceList() {
    103     return new HashMap<>(usbDevices);
    104   }
    105 
    106   @Implementation
    107   protected UsbAccessory[] getAccessoryList() {
    108     // Currently Android only supports having a single accessory attached, and if nothing
    109     // is attached, this method actually returns null in the real implementation.
    110     if (attachedUsbAccessory == null) {
    111       return null;
    112     }
    113 
    114     return new UsbAccessory[] {attachedUsbAccessory};
    115   }
    116 
    117   /** Sets the currently attached Usb accessory returned in #getAccessoryList. */
    118   public void setAttachedUsbAccessory(UsbAccessory usbAccessory) {
    119     this.attachedUsbAccessory = usbAccessory;
    120   }
    121 
    122   /**
    123    * Adds a USB device into available USB devices map with permission value. If the USB device
    124    * already exists, updates the USB device with new permission value.
    125    */
    126   public void addOrUpdateUsbDevice(UsbDevice usbDevice, boolean hasPermission) {
    127     Preconditions.checkNotNull(usbDevice);
    128     Preconditions.checkNotNull(usbDevice.getDeviceName());
    129     usbDevices.put(usbDevice.getDeviceName(), usbDevice);
    130     if (hasPermission) {
    131       grantPermission(usbDevice);
    132     } else {
    133       revokePermission(usbDevice, RuntimeEnvironment.application.getPackageName());
    134     }
    135   }
    136 
    137   /** Removes a USB device from available USB devices map. */
    138   public void removeUsbDevice(UsbDevice usbDevice) {
    139     Preconditions.checkNotNull(usbDevice);
    140     usbDevices.remove(usbDevice.getDeviceName());
    141     revokePermission(usbDevice, RuntimeEnvironment.application.getPackageName());
    142   }
    143 
    144   @Implementation(minSdk = M)
    145   @HiddenApi
    146   protected /* UsbPort[] */ Object getPorts() {
    147 // BEGIN-INTERNAL
    148     if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.Q) {
    149       return new ArrayList<>(usbPorts.keySet());
    150     }
    151 
    152 // END-INTERNAL
    153     return usbPorts.keySet().toArray(new UsbPort[usbPorts.size()]);
    154   }
    155 
    156   /** Remove all added ports from UsbManager. */
    157   public void clearPorts() {
    158     usbPorts.clear();
    159   }
    160 
    161   /** Adds a USB port to UsbManager. */
    162   public void addPort(String portId) {
    163 // BEGIN-INTERNAL
    164     if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.Q) {
    165       usbPorts.put(
    166           (UsbPort) createUsbPort(realUsbManager, portId, UsbPortStatus.MODE_DUAL),
    167           (UsbPortStatus) createUsbPortStatus(
    168               UsbPortStatus.MODE_DUAL,
    169               UsbPortStatus.POWER_ROLE_SINK,
    170               UsbPortStatus.DATA_ROLE_DEVICE,
    171               0));
    172       return;
    173     }
    174 
    175 // END-INTERNAL
    176     usbPorts.put(
    177         callConstructor(UsbPort.class,
    178             from(String.class, portId),
    179             from(int.class, getStaticField(UsbPort.class, "MODE_DUAL"))),
    180         (UsbPortStatus) createUsbPortStatus(
    181             getStaticField(UsbPort.class, "MODE_DUAL"),
    182             getStaticField(UsbPort.class, "POWER_ROLE_SINK"),
    183             getStaticField(UsbPort.class, "DATA_ROLE_DEVICE"),
    184             0));
    185   }
    186 
    187   @Implementation(minSdk = M)
    188   @HiddenApi
    189   protected /* UsbPortStatus */ Object getPortStatus(/* UsbPort */ Object port) {
    190     return usbPorts.get(port);
    191   }
    192 
    193   @Implementation(minSdk = M)
    194   @HiddenApi
    195   protected void setPortRoles(
    196       /* UsbPort */ Object port, /* int */ Object powerRole, /* int */ Object dataRole) {
    197     UsbPortStatus status = usbPorts.get(port);
    198     usbPorts.put(
    199         (UsbPort) port,
    200         (UsbPortStatus) createUsbPortStatus(
    201             status.getCurrentMode(),
    202             (int) powerRole,
    203             (int) dataRole,
    204             status.getSupportedRoleCombinations()));
    205     application.sendBroadcast(new Intent(UsbManager.ACTION_USB_PORT_CHANGED));
    206   }
    207 
    208   /** Opens a file descriptor from a temporary file. */
    209   @Implementation
    210   protected ParcelFileDescriptor openAccessory(UsbAccessory accessory) {
    211     try {
    212       File tmpUsbDir =
    213           RuntimeEnvironment.getTempDirectory().createIfNotExists("usb-accessory").toFile();
    214       return ParcelFileDescriptor.open(
    215           new File(tmpUsbDir, "usb-accessory-file"), ParcelFileDescriptor.MODE_READ_WRITE);
    216     } catch (FileNotFoundException error) {
    217       throw new RuntimeException("Error shadowing openAccessory", error);
    218     }
    219   }
    220 
    221   /**
    222    * Helper method for creating a {@link UsbPortStatus}.
    223    *
    224    * Returns Object to avoid referencing the API M+ UsbPortStatus when running on older platforms.
    225    */
    226   private static Object createUsbPortStatus(
    227       int currentMode,
    228       int currentPowerRole,
    229       int currentDataRole,
    230       int supportedRoleCombinations) {
    231     if (RuntimeEnvironment.getApiLevel() <= P) {
    232       return callConstructor(UsbPortStatus.class,
    233           from(int.class, currentMode),
    234           from(int.class, currentPowerRole),
    235           from(int.class, currentDataRole),
    236           from(int.class, supportedRoleCombinations));
    237     }
    238     // BEGIN-INTERNAL
    239     return new UsbPortStatus(
    240         currentMode,
    241      currentPowerRole,
    242      currentDataRole,
    243      supportedRoleCombinations,
    244         0,
    245         0
    246     );
    247     // END-INTERNAL
    248   }
    249 
    250   /**
    251    * Helper method for creating a {@link UsbPort}.
    252    *
    253    * Returns Object to avoid referencing the API M+ UsbPort when running on older platforms.
    254    */
    255   private static Object createUsbPort(
    256       UsbManager usbManager,
    257       String id,
    258       int supportedModes) {
    259     if (RuntimeEnvironment.getApiLevel() <= P) {
    260       return callConstructor(UsbPort.class,
    261           from(UsbManager.class, usbManager),
    262           from(String.class, id),
    263           from(int.class, supportedModes));
    264     }
    265     // BEGIN-INTERNAL
    266     return new UsbPort(
    267         usbManager,
    268         id,
    269         supportedModes,
    270         0,
    271         false,
    272         false
    273     );
    274     // END-INTERNAL
    275   }
    276 
    277 }
    278