Monday, February 10, 2014

Use intent filter to detect a specified USB device and auto start application

My former exercise show how to "Search USB device for specified Vendor ID and Product ID". In this post,  is added in AndroidManifest.xml, to discover specified device. If USB device with specified vendor-id and product-id inserted, user will be prompted to enable auto-start the app.


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidusbhost"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-feature android:name="android.hardware.usb.host" />
    <uses-sdk
        android:minSdkVersion="13"
        android:targetSdkVersion="18" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.androidusbhost.MainActivity"
            android:label="@string/app_name"
            android:configChanges="keyboard|orientation" >
            <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>
            <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />
            
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
            </intent-filter>
            
        </activity>
    </application>

</manifest>

Create /res/xml/device_filter.xml to define our target vendor-id and product-id. The file name, device_filter, should be matched with android:resource inside <meta-data> of AndroidManifest.xml. Here vendor-id="9025" and product-id="32828" target to Arduino Esplora board.
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-device 
        vendor-id="9025" 
        product-id="32828" />
</resources>

MainActivity.java
package com.example.androidusbhost;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;

public class MainActivity extends Activity {

 TextView textInfo;
 TextView textInfoInterface;
 TextView textEndPoint;
 
 TextView textDeviceName;
 TextView textStatus;
 
 Spinner spInterface;
 ArrayList<String> listInterface;
 ArrayList<UsbInterface> listUsbInterface;
 ArrayAdapter<String> adapterInterface;
 
 Spinner spEndPoint;
 ArrayList<String> listEndPoint;
 ArrayList<UsbEndpoint> listUsbEndpoint;
 ArrayAdapter<String> adapterEndpoint;
 
 private static final int targetVendorID= 9025;
 private static final int targetProductID = 32828;
 UsbDevice deviceFound = null;

 private static final String ACTION_USB_PERMISSION = 
   "com.android.example.USB_PERMISSION";
 PendingIntent mPermissionIntent;
 
 UsbInterface usbInterface;
 UsbDeviceConnection usbDeviceConnection;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  textStatus = (TextView)findViewById(R.id.textstatus);
  
  textDeviceName = (TextView)findViewById(R.id.textdevicename);
  spInterface = (Spinner)findViewById(R.id.spinnerinterface);
  spEndPoint = (Spinner)findViewById(R.id.spinnerendpoint);
  textInfo = (TextView) findViewById(R.id.info);
  textInfoInterface = (TextView)findViewById(R.id.infointerface);
  textEndPoint = (TextView)findViewById(R.id.infoendpoint);
  
  //register the broadcast receiver
  mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
  IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
  registerReceiver(mUsbReceiver, filter);
  
  registerReceiver(mUsbDeviceReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED));
  registerReceiver(mUsbDeviceReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED));
  
  connectUsb();
 }

 @Override
 protected void onDestroy() {
  releaseUsb();
  unregisterReceiver(mUsbReceiver);
  unregisterReceiver(mUsbDeviceReceiver);
  super.onDestroy();
 }
 
 private void connectUsb(){
  
  Toast.makeText(MainActivity.this, 
    "connectUsb()", 
    Toast.LENGTH_LONG).show();
  textStatus.setText("connectUsb()");
  
  checkDeviceInfo();
  if(deviceFound != null){
   doRawDescriptors(); 
  }
 }

 private void releaseUsb(){
  
  Toast.makeText(MainActivity.this, 
    "releaseUsb()", 
    Toast.LENGTH_LONG).show();
  textStatus.setText("releaseUsb()");
  
  if(usbDeviceConnection != null){
   if(usbInterface != null){
    usbDeviceConnection.releaseInterface(usbInterface);
    usbInterface = null;
   }
   usbDeviceConnection.close();
   usbDeviceConnection = null;
  }
  deviceFound = null;
 }
 
 private void doRawDescriptors(){
  UsbDevice deviceToRead = deviceFound;
  UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
  
  Boolean permitToRead = manager.hasPermission(deviceToRead);
  
  if(permitToRead){
   doReadRawDescriptors(deviceToRead);
  }else{
   manager.requestPermission(deviceToRead, mPermissionIntent);
   Toast.makeText(MainActivity.this, 
     "Permission: " + permitToRead, 
     Toast.LENGTH_LONG).show();
   textStatus.setText("Permission: " + permitToRead);
  }
 }
 
 private final BroadcastReceiver mUsbReceiver = 
   new BroadcastReceiver() {

    @Override
    public void onReceive(Context context, Intent intent) {
     String action = intent.getAction();
     if (ACTION_USB_PERMISSION.equals(action)) {
      
      Toast.makeText(MainActivity.this, 
        "ACTION_USB_PERMISSION", 
        Toast.LENGTH_LONG).show();
      textStatus.setText("ACTION_USB_PERMISSION");
      
                  synchronized (this) {
                      UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                      if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                          if(device != null){
                           doReadRawDescriptors(device);
                         }
                      } 
                      else {
                          Toast.makeText(MainActivity.this, 
                            "permission denied for device " + device, 
                            Toast.LENGTH_LONG).show();
                          textStatus.setText("permission denied for device " + device);
                      }
                  }
              }
    }
 };
 
 private final BroadcastReceiver mUsbDeviceReceiver = 
   new BroadcastReceiver() {

    @Override
    public void onReceive(Context context, Intent intent) {
     String action = intent.getAction();
     if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
      
      deviceFound = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
      Toast.makeText(MainActivity.this, 
        "ACTION_USB_DEVICE_ATTACHED: \n" +
        deviceFound.toString(), 
        Toast.LENGTH_LONG).show();
      textStatus.setText("ACTION_USB_DEVICE_ATTACHED: \n" +
        deviceFound.toString());
      
      checkDeviceInfoSkipDeviceSearching();
      doRawDescriptors();
     }else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
      
      UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
      
      Toast.makeText(MainActivity.this, 
        "ACTION_USB_DEVICE_DETACHED: \n" +
        device.toString(), 
        Toast.LENGTH_LONG).show();
      textStatus.setText("ACTION_USB_DEVICE_DETACHED: \n" +
        device.toString());
      
      if(device!=null){
       if(device == deviceFound){
        releaseUsb();
       }
      }
      
      textInfo.setText("");
      textInfoInterface.setText("");
      textEndPoint.setText("");
      
      listInterface.clear();
      listUsbInterface.clear();
      adapterInterface.notifyDataSetChanged();
      
      listEndPoint.clear();
      listUsbEndpoint.clear();
      adapterEndpoint.notifyDataSetChanged();
     }
    }
  
 };
 
 private void doReadRawDescriptors(UsbDevice device){
  final int STD_USB_REQUEST_GET_DESCRIPTOR = 0x06;
  final int LIBUSB_DT_STRING = 0x03;
  
  boolean forceClaim = true;
  
  byte[] buffer = new byte[255];
        int indexManufacturer = 14;
        int indexProduct = 15;
        String stringManufacturer = "";
        String stringProduct = "";
  
  UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
  usbDeviceConnection = manager.openDevice(device);
  if(usbDeviceConnection != null){
   usbInterface = device.getInterface(0);
   usbDeviceConnection.claimInterface(usbInterface, forceClaim);
   
   byte[] rawDescriptors = usbDeviceConnection.getRawDescriptors();
   
   int lengthManufacturer = usbDeviceConnection.controlTransfer(
     UsbConstants.USB_DIR_IN|UsbConstants.USB_TYPE_STANDARD,
     STD_USB_REQUEST_GET_DESCRIPTOR,
     (LIBUSB_DT_STRING << 8) | rawDescriptors[indexManufacturer],
     0,
     buffer,
     0xFF,
     0);
   try {
    stringManufacturer = new String(buffer, 2, lengthManufacturer-2, "UTF-16LE");
   } catch (UnsupportedEncodingException e) {
    Toast.makeText(MainActivity.this, e.toString(), Toast.LENGTH_LONG).show();
    textStatus.setText(e.toString());
   }
   
   int lengthProduct = usbDeviceConnection.controlTransfer(
     UsbConstants.USB_DIR_IN|UsbConstants.USB_TYPE_STANDARD,
     STD_USB_REQUEST_GET_DESCRIPTOR,
     (LIBUSB_DT_STRING << 8) | rawDescriptors[indexProduct],
     0,
     buffer,
     0xFF,
     0);
   try {
    stringProduct = new String(buffer, 2, lengthProduct-2, "UTF-16LE");
   } catch (UnsupportedEncodingException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
   
   Toast.makeText(MainActivity.this, 
     "Manufacturer: " + stringManufacturer + "\n" +
     "Product: " + stringProduct, 
     Toast.LENGTH_LONG).show();
   textStatus.setText("Manufacturer: " + stringManufacturer + "\n" +
     "Product: " + stringProduct);
  }else{
   Toast.makeText(MainActivity.this, 
     "open failed", 
     Toast.LENGTH_LONG).show();
   textStatus.setText("open failed");
  }
 }

 private void checkDeviceInfo() {

  deviceFound = null;
  
  UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
  HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
  Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();

  while (deviceIterator.hasNext()) {
   UsbDevice device = deviceIterator.next();

   if(device.getVendorId()==targetVendorID){
    if(device.getProductId()==targetProductID){
     deviceFound = device;
    }
   }
  }
  
  textInfo.setText("");
  textInfoInterface.setText("");
  textEndPoint.setText("");
  
  if(deviceFound==null){
   Toast.makeText(MainActivity.this, 
     "device not found", 
     Toast.LENGTH_LONG).show();
   textStatus.setText("device not found");
  }else{
   String i = deviceFound.toString() + "\n" + 
     "DeviceID: " + deviceFound.getDeviceId() + "\n" +
     "DeviceName: " + deviceFound.getDeviceName() + "\n" +
     "DeviceClass: " + deviceFound.getDeviceClass() + " - " 
     + translateDeviceClass(deviceFound.getDeviceClass()) + "\n" +
     "DeviceSubClass: " + deviceFound.getDeviceSubclass() + "\n" +
     "VendorID: " + deviceFound.getVendorId() + "\n" +
     "ProductID: " + deviceFound.getProductId() + "\n" +
     "InterfaceCount: " + deviceFound.getInterfaceCount();
   textInfo.setText(i);
      
   checkUsbDevicve(deviceFound);
  }

 }
 
 private void checkDeviceInfoSkipDeviceSearching() {
  //called when ACTION_USB_DEVICE_ATTACHED, 
  //device already found, skip device searching
  
  textInfo.setText("");
  textInfoInterface.setText("");
  textEndPoint.setText("");
  
  String i = deviceFound.toString() + "\n" + 
    "DeviceID: " + deviceFound.getDeviceId() + "\n" +
    "DeviceName: " + deviceFound.getDeviceName() + "\n" +
    "DeviceClass: " + deviceFound.getDeviceClass() + " - " 
    + translateDeviceClass(deviceFound.getDeviceClass()) + "\n" +
    "DeviceSubClass: " + deviceFound.getDeviceSubclass() + "\n" +
    "VendorID: " + deviceFound.getVendorId() + "\n" +
    "ProductID: " + deviceFound.getProductId() + "\n" +
    "InterfaceCount: " + deviceFound.getInterfaceCount();
  textInfo.setText(i);
      
  checkUsbDevicve(deviceFound);

 }
 
 OnItemSelectedListener deviceOnItemSelectedListener = 
   new OnItemSelectedListener(){

  @Override
  public void onItemSelected(AdapterView<?> parent, 
    View view, int position, long id) {
   UsbDevice device = deviceFound;
   
   String i = device.toString() + "\n" + 
     "DeviceID: " + device.getDeviceId() + "\n" +
     "DeviceName: " + device.getDeviceName() + "\n" +
     "DeviceClass: " + device.getDeviceClass() + " - " 
      + translateDeviceClass(device.getDeviceClass()) + "\n" +
     "DeviceSubClass: " + device.getDeviceSubclass() + "\n" +
     "VendorID: " + device.getVendorId() + "\n" +
     "ProductID: " + device.getProductId() + "\n" +
     "InterfaceCount: " + device.getInterfaceCount();
   textInfo.setText(i);
   
   checkUsbDevicve(device);
  }

  @Override
  public void onNothingSelected(AdapterView<?> parent) {}
  
 };
 
 private void checkUsbDevicve(UsbDevice d) {
  listInterface = new ArrayList<String>();
  listUsbInterface = new ArrayList<UsbInterface>();
  
  for(int i=0; i<d.getInterfaceCount(); i++){
   UsbInterface usbif = d.getInterface(i);
   listInterface.add(usbif.toString());
   listUsbInterface.add(usbif);
  }
  
  adapterInterface = new ArrayAdapter<String>(this, 
    android.R.layout.simple_spinner_item, listInterface);
  adapterInterface.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
  spInterface.setAdapter(adapterInterface);
  spInterface.setOnItemSelectedListener(interfaceOnItemSelectedListener);
 }
 
 OnItemSelectedListener interfaceOnItemSelectedListener = 
   new OnItemSelectedListener(){

  @Override
  public void onItemSelected(AdapterView<?> parent, 
    View view, int position, long id) {
   
   UsbInterface selectedUsbIf = listUsbInterface.get(position);
   
   String sUsbIf = "\n" + selectedUsbIf.toString() + "\n"
     + "Id: " + selectedUsbIf.getId() + "\n"
     + "InterfaceClass: " + selectedUsbIf.getInterfaceClass() + "\n"
     + "InterfaceProtocol: " + selectedUsbIf.getInterfaceProtocol() + "\n"
     + "InterfaceSubclass: " + selectedUsbIf.getInterfaceSubclass() + "\n"
     + "EndpointCount: " + selectedUsbIf.getEndpointCount();
   
   textInfoInterface.setText(sUsbIf);
   checkUsbInterface(selectedUsbIf);
  }

  @Override
  public void onNothingSelected(AdapterView<?> parent) {}
  
 };
 
 private void checkUsbInterface(UsbInterface uif) {
  listEndPoint = new ArrayList<String>();
  listUsbEndpoint = new ArrayList<UsbEndpoint>();

  for(int i=0; i<uif.getEndpointCount(); i++){
   UsbEndpoint usbEndpoint = uif.getEndpoint(i);
   listEndPoint.add(usbEndpoint.toString());
   listUsbEndpoint.add(usbEndpoint);
  }
  
  adapterEndpoint = new ArrayAdapter<String>(this, 
    android.R.layout.simple_spinner_item, listEndPoint);
  adapterEndpoint.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
  spEndPoint.setAdapter(adapterEndpoint);
  spEndPoint.setOnItemSelectedListener(endpointOnItemSelectedListener);
 }
 
 OnItemSelectedListener endpointOnItemSelectedListener = 
   new OnItemSelectedListener(){

  @Override
  public void onItemSelected(AdapterView<?> parent, 
    View view, int position, long id) {
   
   UsbEndpoint selectedEndpoint = listUsbEndpoint.get(position);
   
   String sEndpoint = "\n" + selectedEndpoint.toString() + "\n"
    + translateEndpointType(selectedEndpoint.getType());
   
   textEndPoint.setText(sEndpoint);
  }

  @Override
  public void onNothingSelected(AdapterView<?> parent) {}
  
 };
 
 private String translateEndpointType(int type){
  switch(type){
  case UsbConstants.USB_ENDPOINT_XFER_CONTROL:
   return "USB_ENDPOINT_XFER_CONTROL (endpoint zero)";
  case UsbConstants.USB_ENDPOINT_XFER_ISOC:
   return "USB_ENDPOINT_XFER_ISOC (isochronous endpoint)";
  case UsbConstants.USB_ENDPOINT_XFER_BULK :
   return "USB_ENDPOINT_XFER_BULK (bulk endpoint)";
  case UsbConstants.USB_ENDPOINT_XFER_INT:
   return "USB_ENDPOINT_XFER_INT (interrupt endpoint)";
  default: 
   return "unknown";
  }
 }
 
 private String translateDeviceClass(int deviceClass){
  switch(deviceClass){
  case UsbConstants.USB_CLASS_APP_SPEC: 
   return "Application specific USB class";
  case UsbConstants.USB_CLASS_AUDIO: 
   return "USB class for audio devices";
  case UsbConstants.USB_CLASS_CDC_DATA: 
   return "USB class for CDC devices (communications device class)";
  case UsbConstants.USB_CLASS_COMM: 
   return "USB class for communication devices";
  case UsbConstants.USB_CLASS_CONTENT_SEC: 
   return "USB class for content security devices";
  case UsbConstants.USB_CLASS_CSCID: 
   return "USB class for content smart card devices";
  case UsbConstants.USB_CLASS_HID: 
   return "USB class for human interface devices (for example, mice and keyboards)";
  case UsbConstants.USB_CLASS_HUB: 
   return "USB class for USB hubs";
  case UsbConstants.USB_CLASS_MASS_STORAGE: 
   return "USB class for mass storage devices";
  case UsbConstants.USB_CLASS_MISC: 
   return "USB class for wireless miscellaneous devices";
  case UsbConstants.USB_CLASS_PER_INTERFACE: 
   return "USB class indicating that the class is determined on a per-interface basis";
  case UsbConstants.USB_CLASS_PHYSICA: 
   return "USB class for physical devices";
  case UsbConstants.USB_CLASS_PRINTER: 
   return "USB class for printers";
  case UsbConstants.USB_CLASS_STILL_IMAGE: 
   return "USB class for still image devices (digital cameras)";
  case UsbConstants.USB_CLASS_VENDOR_SPEC: 
   return "Vendor specific USB class";
  case UsbConstants.USB_CLASS_VIDEO: 
   return "USB class for video devices";
  case UsbConstants.USB_CLASS_WIRELESS_CONTROLLER: 
   return "USB class for wireless controller devices";
  default: return "Unknown USB class!";
  
  }
 }

}

activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />
    
    <TextView
        android:id="@+id/textstatus"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/textdevicename"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textStyle="bold|italic" />

    <Spinner
        android:id="@+id/spinnerinterface"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    
    <Spinner
        android:id="@+id/spinnerendpoint"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >

            <TextView
                android:id="@+id/info"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

            <TextView
                android:id="@+id/infointerface"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textStyle="italic" />
            
            <TextView
                android:id="@+id/infoendpoint"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textStyle="bold" />
            
        </LinearLayout>
    </ScrollView>

</LinearLayout>

download filesDownload the files.




Step-by-step: Android USB Host Mode programming

7 comments:

Swapna said...

I have used the above code with some modifications to connect to a PSOC5.I am facing following issues :

1. IN most of the cases it gives open failed though we give consent for access.
2.The app auto starts multiple number of times continuously
3. in very few cases it gave the product info and manufacturer name.

How can i solve the first two issues???
Android application is able to recognize the device.Since my device is vendor specific. Do i need a separate driver to enable data transfer from PSOC5 to android application(when i tried in windows platform device was not recognized before installation of driver).

any input is greatly appreciated.


Thanks and Regards
swapna

lalit said...

I want to read/write file from usb pen drive.Can u help me with any of our valuable examples.

Ramanand Bhat said...

Great. Really great. I am trying to connect Android to a control circuit in a Truck/Bus to control many parameters. I was looking for a good example.

And this is it.

Ramanand Bhat

Anonymous said...

Hi, could you please help me. I use your code for my app. One of the button on my app is to browse local storage. I can browse internal storage but not external storage(USB). I analyse the code and found out that when app is open, it will removes the USB attached(maybe to avoid clash when auto-open app) but when I close the app, USB will be detected an reinitialized.

Anonymous said...

usbDeviceConnection.claimInterface(usbInterface, forceClaim);

After execute code above, phone unmounted the attached USB. If I delete code above, my app cannot communicate with USB.

Erik said...

hello Anonymous,

What is your usb devices? As I remember (may be), usb external storage and input devices (such as keyboard, mouse) should have something else to handle. I forgot the details, may be I'm wrong!

Anonymous said...

My USB is FAT32. Oh, I will try to search and find out how to handle the external devices. Thank you!