The conventional way to use an Android tablet or smartphone is as a USB peripheral that connects to a USB host (e.g. PC) and synchronizes its data and media files. However an Android device can also behave as a USB host since the API level 12 (Android 3.1). With this feature, you can for example connect and use a USB mouse, keyboard or memory stick with an Android phone. In order to physically attach the peripheral device, you need a special cable adapter called OTG (On The Go). The most common form factor includes a female Type-A USB connector at one end, where the peripheral device is connected to, and a male micro USB connector (at the other end) that plugs into the android device.
USB OTG cable

Android is quite flexible on what can be done with a connected USB device, but when writing an App we need to make sure that we configure it properly and that we abide by the security requirements implemented by the Android team.

In this post we will explain the different approaches for fulfilling the said security requirements, either hardcoded at the manifest file or defined dynamically inside the application code.

The first step is to ask for permission to access the USB port, which is done in our manifest file:

<uses-feature android:name="android.hardware.usb.host" />  

Now we have two options, to configure the USB connectivity statically in the manifest file or to do it dynamically in the code of our App.

USB configuration in the Manifest file (declarative)

This first mechanism is less flexible but it is a good choice if we know beforehand the type of devices that our App will connect to. The USB functionality is defined at the Activity level, and we need to register for USB intents:

<activity  
    android:name="com.blecentral.MainActivity"
    android:label="@string/app_name"
    android:launchMode="singleInstance">
    <intent-filter>
      <action android:name="android.intent.action.MAIN" />
      <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <intent-filter>
         <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
    </intent-filter>
    <intent-filter>
         <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
    </intent-filter>
    <meta-data  android:name=
        "android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />    
    <meta-data android:name=
        "android.hardware.usb.action.USB_DEVICE_DETACHED"
                android:resource="@xml/device_filter" />
</activity>  

In this example MainActivity registers to be informed when a USB device is connected (USB_DEVICE_ATTACHED) or disconneted (USB_DEVICE_DETACHED). Note that this is done at the Activity level. Other activities or services in our project can also register to these events.

If we want to restrict the type of devices that will trigger our Activity, we can include a device filter XML file indicated in the meta-data element. This file is stored in /res/xml folder and lists combinations of vendor id and/or product id as shown in our example file:

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
    <!-- 0x0403 / 0x6001: FTDI FT232R UART -->
    <usb-device vendor-id="1027" product-id="24577" />
    <!-- 0x2341 / Arduino -->
    <usb-device vendor-id="9025" />
</resources>  

If we register an Activity like this, the event USB_DEVICE_ATTACHED will arrive as an intent, but the triggering method will depend on the state of the Activity:

  • If the Activity has not been created yet, it will be created and the intent arrive through the onCreate() method:
   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      ...
      if (ACTION_USB_ATACHED.equalsIgnoreCase(getIntent().getAction())) { ... }
      ...
   }
  • If the activity has already been created/instantiated, the event will arrive through the 'onNewIntent()' method:
   @Override
   protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        if (ACTION_USB_ATACHED.equalsIgnoreCase(intent().getAction())) { ... }   }

To receive a USB_DEVICE_DETACHED event we need to register first a BroadcastReceiver, as explained in the following section.

USB configuration in the application code (imperative)

To achieve the equivalent functionality, now programmatically from our code, we need to create a BroadcastReceiver and then register it to receive the USB events.

private static final String ACTION_USB_ATTACHED  = "android.hardware.usb.action.USB_DEVICE_ATTACHED";  
private static final String ACTION_USB_DETACHED  = "android.hardware.usb.action.USB_DEVICE_DETACHED"; 

BroadcastReceiver bReceiver= new BroadcastReceiver() {...};  
IntentFilter filter = new IntentFilter();  
filter.addAction(ACTION_USB_ATTACHED);  
filter.addAction(ACTION_USB_DETACHED);  
registerReceiver(bReceiver,filter);  

In this case, the events will be received via the onReceive() method of BroadcastReceiver.

public void onReceive(Context context, Intent intent) {  
   String action = intent.getAction();          
   if (action.equalsIgnoreCase(ACTION_USB_DETACHED)) {...}         ...
}

To stop receiving events, just unregister the receiver with:
unregisterReceiver(bReceiver);

The main advantage of this method is that we can control when we want to receive USB events. On the other hand, if our Activity or service is not started previously, plugging a USB device will not trigger it.

Security and user permission to connect to a USB device

In order to avoid security threats, Android mandates that the user must give explicit permission to an application willing to use a USB peripheral device. This user permission is granted at application level, that is, once obtained for an Activity, the rest of the Activities and services packaged in the application will also be granted the permission.

When a USB device is connected, Android looks for apps/Activities registered for the USB_DEVICE_ATTACHED event, either in the Manifest or pogrammatically. If XML filters have been defined, these are also applied.

Android then presents a dialog to the user with the list of all registered applications. The user is requested to select one of the Apps and decide if he wants to grant permission only once or always (i.e. default). Only the selected App will receive the USB_DEVICE_ATTACHED intent.

If the user selects default, Android will remember this option and from that moment on will trigger automatically the chosen application whenever that USB device (vendor id + product id) is connected in the future.

There is no way to avoid this user permission dialog behaviour. It must be executed at least once (if the user selects always/default).

Since our App does not know if the user has already granted permission, we need to check the user permission flag always, every time that we want to start a USB connection:

void checkUSB(){  
  UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
  // Get the list of attached devices
  HashMap<String, UsbDevice> devices = manager.getDeviceList();                
  // Iterate over all devices
  Iterator<String> it = devices.keySet().iterator();
  while (it.hasNext()) {
    String deviceName = it.next();
    UsbDevice device = devices.get(deviceName);
    String VID = Integer.toHexString(device.getVendorId()).toUpperCase();
    String PID = Integer.toHexString(device.getProductId()).toUpperCase();
    if (!manager.hasPermission(device)) {
       private static final String ACTION_USB_PERMISSION  = "com.blecentral.USB_PERMISSION"; 
       PendingIntent mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
       manager.requestPermission(device, mPermissionIntent);
       return;
    } else {
       ... //user permission already granted; prceed to access USB device   
    }
  }
}

If we require user permission, we requestPermission() for the device with a PendingIntent. Android will pop up the user permission dialog and then will contact back our application with the PendingIntent. We need a BroadcastReceiver to receive this notification:

private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {  
  public void onReceive(Context context, Intent intent) {
    String action = intent.getAction();
    if (ACTION_USB_PERMISSION.equals(action)) {
      synchronized (this) {
        UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
        if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { 
          ... //Permission granted. Proceed with USB connection
        } else {
          ... //Permission revoked by user
        }
      }
    }  
  }
}

As happened in the previous user permission dialog, the user can grant access only once or forever ("Use by default for this USB device”).

Once we have the user permission, we can start the communication with the peripheral USB device. For instance, in one of our projects we are using the USBSerial library created by Felipe Herranz to connect an Android device to Arduino.

Images

The images used in this post are solely owned by their respective rights holders:


We hope that you found this post useful. Do subscribe to our mailing list if you want to keep updated about new articles in our blog.

About the Author