Friday, September 2, 2011

Implementing USB HID Interface in Android 3.1

If there is a chance that the application will close, then you must close and release the device. If you don't to this, then the user probably won't be able to open the device again until it is re-plugged. In order to prevent this issue, then you might need to consider creating the device interface as a service.



Documentation for using USB Host can be found at http://developer.android.com/guide/topics/usb/host.html. The following are some of the feature that I had to implement in order to get the interface working. 





AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      ... >
      <uses-sdk android:minSdkVersion="12" />
      <uses-feature android:name="android.hardware.usb.host" />



HIDInterface.java



BroadcastReceiver - This is required to receive the registered events
/*
 * Receives a requested broadcast from the operating system.
 * In this case the following actions are handled:
 * USB_PERMISSION
 * UsbManager.ACTION_USB_DEVICE_DETACHED
 * UsbManager.ACTION_USB_DEVICE_ATTACHED
 */
private final BroadcastReceiver usbReceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
// Validate the action against the actions registered
...
}
};

Requesting Permission - In order to connect to the device, the user has to give their permission. This must be implemented in the BroadcastReceiver that will handle the response to the question when it is displayed to the user.

The variable 
private static final String ACTION_USB_PERMISSION = "com.access.device.USB_PERMISSION"; 
is a predefined variable for registering to receive device permissions.

The BroadcastReceiver code
if (ACTION_USB_PERMISSION.equals(action))
{
// A permission response has been received, validate if the user has 
// GRANTED, or DENIED permission
synchronized (this)
{
UsbDevice deviceConnected = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false))
{
// Permission has been granted, so connect to the device
// If this fails, then keep looking
if (deviceConnected != null)
{
// call method to setup device communication
currentDevice = deviceConnected;
if (ShowDebuggingToasts)
{
Toast.makeText(hostDisplayActivity, "Device Permission Acquired", Toast.LENGTH_SHORT).show();
}
if (!ConnectToDeviceInterface(currentDevice))
{
if (ShowDebuggingToasts)
{
Toast.makeText(hostDisplayActivity, "Unable to connect to device", Toast.LENGTH_SHORT).show();
}
}
sendEnabledMessage();
// Create a listener to receive messages from the host
...
}
}
else
{
// Permission has not been granted, so keep looking for another
// device to be attached....
if (ShowDebuggingToasts)
{
Toast.makeText(hostDisplayActivity, "Device Permission Denied", Toast.LENGTH_SHORT).show();
}
currentDevice = null;
}
}
}
}

Registering the intent
usbManager = (UsbManager) hostDisplayActivity.getSystemService(Context.USB_SERVICE);
permissionIntent = PendingIntent.getBroadcast(hostDisplayActivity, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter permissionFilter = new IntentFilter(ACTION_USB_PERMISSION);
hostDisplayActivity.registerReceiver(usbReceiver, permissionFilter);


Requesting permission
UsbDevice currentDevice = .....;
usbManager.requestPermission(currentDevice, permissionIntent);



Device Attached and Detached Intents - Handling a device attached or detached intent, requires that the user register them first.

Registering the intent
usbManager = (UsbManager) hostDisplayActivity.getSystemService(Context.USB_SERVICE);
IntentFilter deviceAttachedFilter = new IntentFilter();
deviceAttachedFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
deviceAttachedFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
hostDisplayActivity.registerReceiver(usbReceiver, deviceAttachedFilter);

The BroadcastReceiver code

if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action))
{
// A device has been detached from the device, so close all the connections
// and restart the search for a new device being attached
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if ((device != null) && (currentDevice != null))
{
if (device.equals(currentDevice))
{
// call your method that cleans up and closes communication with the device
CleanUpAndClose();
}
}
}
else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action))
{
// A device has been attached. If not already connected to a device,
// validate if this device is supported
UsbDevice searchDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if ((searchDevice != null) && (currentDevice == null))
{
// call your method that cleans up and closes communication with the device
ValidateFoundDevice(searchDevice);
}
}




Validating the Connected Device - Before asking for permission to connect to the device, it is essential that you ensure that this is a device that you support or expect to connect to. This can be done by validating the devices Vendor ID and Product ID.

A vendor id is a global identifier for the manufacturer. A product id refers to the product itself, and is unique to the manufacturer. The vendor id, product id combination refers to a particular product manufactured by a vendor.

searchDevice.getProductId()
searchDevice.getVendorId()



Connecting to the Device - If you are reading and writing, then the device can either have two end points on a single interface, or two interfaces each with a single end point. Either way, it is best if you know which interface you need to use and which end points.


UsbInterface usbInterfaceRead = null;
UsbInterface usbInterfaceWrite = null;
UsbEndpoint ep1 = null;
UsbEndpoint ep2 = null;
boolean UsingSingleInterface = false;


if (UsingSingleInterface)
{
// Using the same interface for reading and writing
usbInterfaceRead = connectDevice.getInterface(0x00);
usbInterfaceWrite = usbInterfaceRead;
if (usbInterfaceRead.getEndpointCount() == 2)
{
ep1 = usbInterfaceRead.getEndpoint(0);
ep2 = usbInterfaceRead.getEndpoint(1);
}
}
else        // if (!UsingSingleInterface)
{
usbInterfaceRead = connectDevice.getInterface(0x00);
usbInterfaceWrite = connectDevice.getInterface(0x01);
if ((usbInterfaceRead.getEndpointCount() == 1) && (usbInterfaceWrite.getEndpointCount() == 1))
{
ep1 = usbInterfaceRead.getEndpoint(0);
ep2 = usbInterfaceWrite.getEndpoint(0);
}
}


if ((ep1 == null) || (ep2 == null))
{
return false;
}

// Determine which endpoint is the read, and which is the write

if (ep1.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)
{
if (ep1.getDirection() == UsbConstants.USB_DIR_IN)
{
usbEndpointRead = ep1;
}
else if (ep1.getDirection() == UsbConstants.USB_DIR_OUT)
{
usbEndpointWrite = ep1;
}
}
if (ep2.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)
{
if (ep2.getDirection() == UsbConstants.USB_DIR_IN)
{
usbEndpointRead = ep2;
}
else if (ep2.getDirection() == UsbConstants.USB_DIR_OUT)
{
usbEndpointWrite = ep2;
}
}
if ((usbEndpointRead == null) || (usbEndpointWrite == null))
{
return false;
}
connectionRead = usbManager.openDevice(connectDevice);
connectionRead.claimInterface(usbInterfaceRead, true);


if (UsingSingleInterface)
{
connectionWrite = connectionRead;
}
else // if (!UsingSingleInterface)
{
connectionWrite = usbManager.openDevice(connectDevice);
connectionWrite.claimInterface(usbInterfaceWrite, true);
}

// Start the read thread



Writing to the Device - Some devices require the controlTransfer method for writing data. I don't cover this command in this blog. As USB devices work with reports of a specific length. It is best to format the data being sent into a ByteBuffer of the correct length. The length required can be retrieved from the endpoint.

int bufferDataLength = usbEndpointWrite.getMaxPacketSize();

The report must be formatted correctly for the device being connected to. On some devices, this requires that a specific value must be the first byte in the report. This can be followed by the length of the data in the report. This format is determined by the device, and isn't specified here.

int bufferDataLength = usbEndpointWrite.getMaxPacketSize();
ByteBuffer buffer = ByteBuffer.allocate(bufferDataLength + 1);
UsbRequest request = new UsbRequest();

buffer.put(DataToSend);

request.initialize(connectionWrite, usbEndpointWrite);
request.queue(buffer, bufferDataLength);
try
{
if (request.equals(connectionWrite.requestWait()))
{
return true;
}
}
catch (Exception ex)
{
// An exception has occured
}



Reading from the Device - As USB devices work with reports of a specific length. The data received from the device will always be the size specified by the report length. Even if there isn't enough data to fill the report. Some devices require the controlTransfer method for reading data. I don't cover this command in this blog.

If you are expecting unsolicited data from the device, then a read thread should be started so that the data can be processed as soon as it arrives. 

int bufferDataLength = usbEndpointRead.getMaxPacketSize();
ByteBuffer buffer = ByteBuffer.allocate(bufferDataLength + 1);
UsbRequest requestQueued = null;
UsbRequest request = new UsbRequest();
request.initialize(connectionRead, usbEndpointRead);

try
{
while (!getStopping())
{
request.queue(buffer, bufferDataLength);
requestQueued = connectionRead.requestWait();
if (request.equals(requestQueued))
{
byte[] byteBuffer = new byte[bufferDataLength + 1];
buffer.get(byteBuffer, 0, bufferDataLength);

// Handle data received
... 

buffer.clear();
}
else
{
Thread.sleep(20);
}
}
}
catch (Exception ex)
{
// An exception has occured
}
try
{
request.cancel();
request.close();
}
catch (Exception ex)
{
// An exception has occured
}




This isn't the most comprehensive or complete document, but hopefully it will help someone to implement a USB Host Device interface.






13 comments:

  1. Thank you! I was trying to find if there is any way to use hid get/send reports. This seems to be what I was looking for :)

    ReplyDelete
  2. I've manege to read data from PS3 controller with the help of this tutorial.Thank you !

    ReplyDelete
  3. Hello!
    Could You please send sample code to me (andriy.sakhno@gmail.com)?

    Thanks in advice,
    Andriy.

    ReplyDelete
  4. Could I get the source? Thanks. rrTenz@gmail.com

    ReplyDelete
  5. Hello
    Could you please send the code source? Thanks
    vincent.delaunay76@gmail.com

    ReplyDelete
  6. Hello
    Could you please send code source to me
    swwwwaa@gmail.com

    ReplyDelete
  7. Hello

    Could you pleasme send code source to me ?
    jeremayor@netplus.ch

    Thanks

    ReplyDelete
  8. Hello
    Could you please send the code source? Thanks
    serif@silivriotomasyon.com

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. May i have the full code or project?
    tsvetkov.oleg@gmail.com

    ReplyDelete
  11. Hello!
    Could You please send sample code to me?
    (paulojpteixeir@gmail.com)

    Thanks,
    paulo

    ReplyDelete
  12. VocĂȘ poderia me enviar o codigo fonte... carloseduardo1984@gmail.com.... preciso montar um aplicativo para ler o canal ad do via USB pic18f4550

    ReplyDelete
  13. Hello! Could You please send sample code to me (davidculebras@gmail.com)?

    Thanks in advice.
    David.

    ReplyDelete