Cuando tu dispositivo con Android está en modo de host USB, actúa como host USB, alimenta el bus y enumera los dispositivos USB conectados. El modo de host USB es compatible con Android 3.1 y versiones posteriores.
Descripción general de la API
Antes de comenzar, es importante comprender las clases con las que necesitas trabajar. En la siguiente tabla, se describen las APIs del host USB en el paquete android.hardware.usb
.
Clase | Descripción |
---|---|
UsbManager |
Te permite enumerar los dispositivos USB conectados y establecer una comunicación con ellos. |
UsbDevice |
Representa un dispositivo USB conectado y contiene métodos para acceder a su información de identificación, interfaces y extremos. |
UsbInterface |
Representa una interfaz de un dispositivo USB, que define un conjunto de funcionalidades del dispositivo. Un dispositivo puede tener una o más interfaces para comunicarse. |
UsbEndpoint |
Representa un extremo de la interfaz, que es un canal de comunicación para esta. Una interfaz puede tener uno o más extremos y, por lo general, tiene extremos de entrada y salida para una comunicación bidireccional con el dispositivo. |
UsbDeviceConnection |
Representa una conexión con el dispositivo, que transfiere datos en los extremos. Esta clase te permite enviar y recibir datos de forma síncrona o asíncrona. |
UsbRequest |
Representa una solicitud asíncrona para comunicarse con un dispositivo a través de una UsbDeviceConnection . |
UsbConstants |
Define las constantes USB que corresponden a las definiciones en linux/usb/ch9.h del kernel de Linux. |
En la mayoría de las situaciones, debes usar todas estas clases (UsbRequest
solo se requiere si realizas una comunicación asíncrona) cuando te comunicas con un dispositivo USB. En general, obtienes un UsbManager
para recuperar el UsbDevice
deseado.
Cuando tienes el dispositivo, debes encontrar el UsbInterface
apropiado y el UsbEndpoint
de esa interfaz para comunicarte. Una vez que obtengas el extremo correcto, abre una UsbDeviceConnection
para comunicarte con el dispositivo USB.
Requisitos del manifiesto de Android
En la siguiente lista, se describe lo que necesitas agregar al archivo de manifiesto de tu aplicación antes de que funcione con las APIs del host USB:
- Dado que no se garantiza que todos los dispositivos con Android admitan las APIs del host USB, incluye un elemento
<uses-feature>
que declare que tu aplicación usa la funciónandroid.hardware.usb.host
. - Establece la versión mínima del SDK de la aplicación en API nivel 12 o posterior. Las APIs del host USB no están presentes en los niveles de API anteriores.
- Si quieres que tu aplicación reciba una notificación sobre un dispositivo USB conectado, especifica un par de elementos
<intent-filter>
y<meta-data>
para el intentandroid.hardware.usb.action.USB_DEVICE_ATTACHED
en tu actividad principal. El elemento<meta-data>
apunta a un archivo de recursos XML externo que declara información de identificación sobre el dispositivo que quieres detectar.En el archivo de recursos XML, declara elementos
<usb-device>
para los dispositivos USB que deseas filtrar. En la siguiente lista, se describen los atributos de<usb-device>
. En general, usa el proveedor y el ID del producto si deseas filtrar por un dispositivo específico, y también la clase, la subclase y el protocolo si deseas filtrar por un grupo de dispositivos USB, como dispositivos de almacenamiento masivo o cámaras digitales. Puedes especificar ninguno de estos atributos o todos ellos. No especificar ningún atributo coincide con todos los dispositivos USB. Por lo tanto, haz esto solo si tu aplicación lo requiere:vendor-id
product-id
class
subclass
protocol
(dispositivo o interfaz)
Guarda el archivo de recursos en el directorio
res/xml/
. El nombre del archivo de recursos (sin la extensión .xml) debe ser el mismo que el que especificaste en el elemento<meta-data>
. El formato del archivo de recursos XML se indica en el ejemplo de más abajo.
Ejemplos de archivos de manifiesto y de recursos
A continuación, se muestra un ejemplo del manifiesto y de su archivo de recursos correspondiente:
<manifest ...> <uses-feature android:name="android.hardware.usb.host" /> <uses-sdk android:minSdkVersion="12" /> ... <application> <activity ...> ... <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" /> </activity> </application> </manifest>
En este caso, el siguiente archivo de recursos debe guardarse en res/xml/device_filter.xml
. Especifica que se debe filtrar cualquier dispositivo USB con los atributos especificados:
<?xml version="1.0" encoding="utf-8"?> <resources> <usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" /> </resources>
Cómo trabajar con dispositivos
Cuando los usuarios conectan dispositivos USB a un dispositivo con tecnología Android, el sistema Android puede determinar si a tu aplicación le interesa el dispositivo conectado. De ser así, puedes configurar la comunicación con el dispositivo si lo deseas. Para hacerlo, la aplicación debe realizar lo siguiente:
- Descubre los dispositivos USB conectados usando un filtro de intents para recibir notificaciones cuando el usuario conecte un dispositivo USB o enumerando los dispositivos USB que ya están conectados.
- Si aún no lo tiene, debe solicitar permiso al usuario para conectarse al dispositivo USB.
- Debe comunicarse con el dispositivo USB leyendo y escribiendo datos en los extremos de interfaz apropiados.
Cómo descubrir un dispositivo
Tu aplicación puede descubrir dispositivos USB usando un filtro de intents para recibir notificaciones cuando el usuario conecta un dispositivo o enumerando los dispositivos USB que ya están conectados. El uso de un filtro de intents es útil si deseas que tu aplicación detecte automáticamente un dispositivo deseado. La enumeración de dispositivos USB conectados es útil si deseas obtener una lista de todos los dispositivos conectados o si tu aplicación no filtró por un intent.
Cómo usar un filtro de intents
Para que tu aplicación descubra un dispositivo USB en particular, puedes especificar un filtro de intents y así filtrar por el intent android.hardware.usb.action.USB_DEVICE_ATTACHED
. Junto con este filtro de intents, debes especificar un archivo de recursos que especifique las propiedades del dispositivo USB, como el ID del producto y del proveedor. Cuando los usuarios conectan un dispositivo que coincide con el filtro de tu dispositivo, el sistema les presenta un diálogo que les pregunta si desean iniciar tu aplicación.
Si los usuarios aceptan, tu aplicación automáticamente tiene permiso para acceder al dispositivo hasta que este se desconecta.
En el siguiente ejemplo, se muestra cómo declarar el filtro de intents:
<activity ...> ... <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" /> </activity>
En el siguiente ejemplo, se muestra cómo declarar el archivo de recursos correspondiente que especifica los dispositivos USB que te interesan:
<?xml version="1.0" encoding="utf-8"?> <resources> <usb-device vendor-id="1234" product-id="5678" /> </resources>
En tu actividad, puedes obtener el UsbDevice
que representa el dispositivo conectado a partir del intent de la siguiente manera:
Kotlin
val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
Java
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
Cómo enumerar dispositivos
Si a tu aplicación le interesa inspeccionar todos los dispositivos USB conectados actualmente mientras se está ejecutando, puede enumerar los dispositivos en el bus. Usa el método getDeviceList()
para obtener un mapa hash de todos los dispositivos USB que están conectados. Si deseas obtener un dispositivo del mapa hash, usa como clave el nombre del dispositivo USB.
Kotlin
val manager = getSystemService(Context.USB_SERVICE) as UsbManager ... val deviceList = manager.getDeviceList() val device = deviceList.get("deviceName")
Java
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); ... HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); UsbDevice device = deviceList.get("deviceName");
Si lo deseas, también puedes obtener un iterador del mapa hash y procesar cada dispositivo uno por uno:
Kotlin
val manager = getSystemService(Context.USB_SERVICE) as UsbManager .. val deviceList: HashMap<String, UsbDevice> = manager.deviceList deviceList.values.forEach { device -> // your code }
Java
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(); // your code }
Cómo obtener permiso para comunicarse con un dispositivo
Antes de comunicarse con el dispositivo USB, tu aplicación debe tener el permiso de tus usuarios.
Nota: Si tu aplicación usa un filtro de intents para descubrir dispositivos USB a medida que se conectan, recibe permiso automáticamente si el usuario permite que tu aplicación controle el intent. De lo contrario, debes solicitar permiso de forma explícita en tu aplicación antes de conectarte al dispositivo.
Pedir permiso explícito puede ser necesario en algunas situaciones, como cuando tu aplicación enumera los dispositivos USB que ya están conectados y luego desea comunicarse con uno. Debes verificar que se haya otorgado permiso para acceder a un dispositivo antes de intentar comunicarte con él. De lo contrario, aparecerá un error de tiempo de ejecución si el usuario rechazó el permiso para acceder al dispositivo.
A fin de obtener permiso de manera explícita, primero crea un receptor de emisión. Este receptor escucha el intent que se transmite cuando llamas a requestPermission()
. La llamada a requestPermission()
muestra un diálogo al usuario en el que le solicita permiso para conectarse al dispositivo. En el siguiente código de ejemplo, se muestra cómo crear el receptor de emisión:
Kotlin
private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION" private val usbReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (ACTION_USB_PERMISSION == intent.action) { synchronized(this) { val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { device?.apply { // call method to set up device communication } } else { Log.d(TAG, "permission denied for device $device") } } } } }
Java
private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"; private final BroadcastReceiver usbReceiver = 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)) { if(device != null){ // call method to set up device communication } } else { Log.d(TAG, "permission denied for device " + device); } } } } };
Para registrar el receptor de emisión, agrega lo siguiente al método onCreate()
de tu actividad:
Kotlin
private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION" ... val manager = getSystemService(Context.USB_SERVICE) as UsbManager ... permissionIntent = PendingIntent.getBroadcast(this, 0, Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE) val filter = IntentFilter(ACTION_USB_PERMISSION) registerReceiver(usbReceiver, filter)
Java
UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"; ... permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE); IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); registerReceiver(usbReceiver, filter);
Para que aparezca el diálogo en el que se solicita permiso de acceso al dispositivo, llama al método requestPermission()
:
Kotlin
lateinit var device: UsbDevice ... usbManager.requestPermission(device, permissionIntent)
Java
UsbDevice device; ... usbManager.requestPermission(device, permissionIntent);
Cuando los usuarios responden el diálogo, tu receptor de emisión recibe el intent que contiene el EXTRA_PERMISSION_GRANTED
adicional, que es un booleano que representa la respuesta. Verifica que este valor adicional tenga el valor verdadero antes de conectarte al dispositivo.
Cómo comunicarse con un dispositivo
La comunicación con un dispositivo USB puede ser síncrona o asíncrona. En cualquier caso, debes crear un subproceso nuevo en el que se lleven a cabo todas las transmisiones de datos, de modo que no bloquees el subproceso de IU. Para configurar correctamente la comunicación con un dispositivo, debes obtener los UsbInterface
y UsbEndpoint
apropiados del dispositivo con el que quieres comunicarte y enviar solicitudes en este extremo con un UsbDeviceConnection
. En general, debes realizar lo siguiente:
- Verifica los atributos de un objeto
UsbDevice
, como el ID del producto, el ID del proveedor o la clase de dispositivo, para determinar si deseas comunicarte con el dispositivo o no. - Cuando tengas la certeza de que quieres comunicarte con el dispositivo, busca el
UsbInterface
apropiado que quieres usar para comunicarte junto con elUsbEndpoint
apropiado de esa interfaz. Las interfaces pueden tener uno o más extremos y, por lo general, tienen un extremo de entrada y uno de salida para una comunicación bidireccional. - Cuando encuentres el extremo correcto, abre una
UsbDeviceConnection
en ese extremo. - Proporciona los datos que deseas transmitir en el extremo con el método
bulkTransfer()
ocontrolTransfer()
. Debes realizar este paso en otro subproceso para evitar que se bloquee el subproceso de IU principal. Para obtener más información sobre el uso de subprocesos en Android, consulta Procesos y subprocesos.
El siguiente fragmento de código es una forma trivial de realizar una transferencia de datos síncrona. Tu código debería tener más lógica para encontrar correctamente la interfaz y los extremos correctos con los cuales comunicarse, y también debe realizar las transferencias de datos en un subproceso diferente al subproceso de IU principal:
Kotlin
private lateinit var bytes: ByteArray private val TIMEOUT = 0 private val forceClaim = true ... device?.getInterface(0)?.also { intf -> intf.getEndpoint(0)?.also { endpoint -> usbManager.openDevice(device)?.apply { claimInterface(intf, forceClaim) bulkTransfer(endpoint, bytes, bytes.size, TIMEOUT) //do in another thread } } }
Java
private Byte[] bytes; private static int TIMEOUT = 0; private boolean forceClaim = true; ... UsbInterface intf = device.getInterface(0); UsbEndpoint endpoint = intf.getEndpoint(0); UsbDeviceConnection connection = usbManager.openDevice(device); connection.claimInterface(intf, forceClaim); connection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT); //do in another thread
Si quieres enviar datos de forma asíncrona, usa la clase UsbRequest
para realizar initialize
y queue
en una solicitud asíncrona. Luego, espera el resultado con requestWait()
.
Cómo finalizar la comunicación con un dispositivo
Cuando hayas terminado de comunicarte con un dispositivo, o si este se desconectó, llama a releaseInterface()
y close()
para cerrar UsbInterface
y UsbDeviceConnection
. Para escuchar eventos de desconexión, crea un receptor de emisión como se muestra a continuación:
Kotlin
var usbReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (UsbManager.ACTION_USB_DEVICE_DETACHED == intent.action) { val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) device?.apply { // call your method that cleans up and closes communication with the device } } } }
Java
BroadcastReceiver usbReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (device != null) { // call your method that cleans up and closes communication with the device } } } };
Crear el receptor de emisión dentro de la aplicación, y no en el manifiesto, permite que la aplicación solo maneje eventos de desconexión mientras se está ejecutando. De esta manera, los eventos de desconexión solo se envían a la aplicación que se está ejecutando actualmente y no se transmiten a todas las aplicaciones.