Descripción general del host USB

Cuando tu dispositivo con Android está en modo de host USB, funciona 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. El En la siguiente tabla, se describen las APIs del host USB en el paquete android.hardware.usb.

Tabla 1: API del host 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 identificación información, interfaces y extremos.
UsbInterface Representa una interfaz de un dispositivo USB, que define un conjunto de funcionalidades para la 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. Los puede tener uno o más extremos, y generalmente tiene extremos de entrada y salida para y 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 de Linux kernel.

En la mayoría de las situaciones, debes usar todas estas clases (UsbRequest solo es necesario si realizas una comunicación asíncrona). cuando te comuniques con un dispositivo USB. En general, obtienes un UsbManager para recuperar el UsbDevice deseado. Una vez que tengas el dispositivo, deberás encontrar el UsbInterface adecuado y el UsbEndpoint de ese interfaz de usuario para comunicarnos. Una vez que obtengas el extremo correcto, abre una UsbDeviceConnection para comunicarte con el dispositivo USB.

Requisitos del manifiesto de Android

La siguiente lista describe lo que necesitas agregar al archivo de manifiesto de tu aplicación antes de que funcionan con las APIs del host USB:

  • Como no se garantiza que todos los dispositivos con Android admitan las APIs del host USB, incluir un elemento <uses-feature> que declare que tu aplicación usa la función android.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 en niveles de API anteriores.
  • Si deseas que tu aplicación reciba notificaciones sobre un dispositivo USB conectado, especifica un Par de elementos <intent-filter> y <meta-data> para el android.hardware.usb.action.USB_DEVICE_ATTACHED en tu actividad principal. El El elemento <meta-data> apunta a un archivo de recursos XML externo que declara información de identificación sobre el dispositivo que deseas detectar.

    En el archivo de recursos XML, declara elementos <usb-device> para la unidad USB. dispositivos que quieres filtrar. La siguiente lista describe los atributos de <usb-device> En general, usa el ID del producto y el proveedor si deseas filtrar para un dispositivo específico y usa la clase, la subclase y el protocolo si deseas filtrar por grupo de dispositivos USB, como dispositivos de almacenamiento masivo o cámaras digitales. Puedes especificar ninguno o todos estos atributos. Si no se especifica ningún atributo, se recomienda hacerlo con todos los dispositivos USB. 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 del recurso (sin la extensión .xml) debe ser la misma que especificaste en el archivo <meta-data>. El formato del archivo de recursos XML se encuentra en el ejemplo a continuación.

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 se debe guardar en res/xml/device_filter.xml y especifica que cualquier dispositivo USB con el valor atributos deben filtrarse:

<?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 Android, el sistema Android puede determinar si tu aplicación está interesada en el dispositivo conectado. Si es así, puedes configurar y comunicación con el dispositivo, si así lo deseas. Para hacerlo, la aplicación debe realizar lo siguiente:

  1. Descubrir dispositivos USB conectados usando un filtro de intents para recibir notificaciones cuando el usuario conecta un dispositivo USB o enumera los dispositivos USB que ya están conectados.
  2. Si aún no lo tiene, debe solicitar permiso al usuario para conectarse al dispositivo USB.
  3. Debe comunicarse con el dispositivo USB leyendo y escribiendo datos en la interfaz adecuada. en los extremos.

Cómo descubrir un dispositivo

Tu aplicación puede descubrir dispositivos USB utilizando un filtro de intents para recibir notificaciones cuando el usuario conecta un dispositivo o enumerando los dispositivos USB que ya están conectados. Con un es útil si deseas que tu aplicación detecte automáticamente dispositivo deseado. La enumeración de los dispositivos USB conectados es útil si quieres obtener una lista de todos 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 para filtro para 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 de la interfaz USB dispositivo, como el ID del producto y del proveedor. Cuando los usuarios conectan un dispositivo que coincide con el tuyo filtro, el sistema les presenta un diálogo que les pregunta si desean iniciar su aplicación. Si los usuarios aceptan, tu aplicación automáticamente tiene permiso para acceder al dispositivo hasta que dispositivo está desconectado.

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 la 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 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 tu aplicación está interesada en inspeccionar todos los dispositivos USB conectados actualmente mientras se ejecuta tu aplicación, puede enumerar los dispositivos en el bus. Usa el método getDeviceList() para obtener un mapa hash de todos los elementos los dispositivos USB que están conectados. Si quieres obtener un dispositivo desde el mapa.

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 uno de los dispositivos 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 permiso de tu usuarios.

Nota: Si tu aplicación usa un filtro de intents para detectar dispositivos USB cuando se conectan, recibe automáticamente permiso si el usuario permite que tu aplicación maneje el intent. De lo contrario, debes solicitar explícitamente en tu aplicación antes de conectarte al dispositivo.

Puede ser necesario pedir permiso de forma explícita en algunas situaciones, como cuando tu En la aplicación, se enumeran los dispositivos USB que ya están conectados y con los que se desea comunicarse. uno. Debes verificar que se haya otorgado permiso para acceder a un dispositivo antes de intentar comunicarte con él. Si de lo contrario, recibirás 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 emite cuando llamas a requestPermission(). La llamada a requestPermission() muestra un diálogo al un usuario que solicita permiso para conectarse al dispositivo. En el siguiente código de muestra, se muestra la manera de Crea 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 esto al método onCreate() en 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 al diálogo, tu receptor de emisión recibe el intent que contiene el EXTRA_PERMISSION_GRANTED adicional, que es un valor booleano que representan la respuesta. Verifica este extra para un 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 nuevo subproceso en el que se lleven a cabo todas las transmisiones de datos, de modo que no bloquees el subproceso de IU. Para configurar la comunicación con un dispositivo correctamente, debes obtener las UsbInterface y UsbEndpoint de las dispositivo en el que quieras 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, ID de proveedor o clase de dispositivo para averiguar si deseas comunicarte o no con el dispositivo.
  • Cuando sepas con certeza que deseas comunicarte con el dispositivo, busca la UsbInterface que desees usar para comunicarte con el la UsbEndpoint apropiada de esa interfaz. Las interfaces pueden tener o más endpoints, y comúnmente tendrá un extremo de entrada y uno de salida para la comunicación.
  • Cuando encuentres el extremo correcto, abre un UsbDeviceConnection. en ese extremo.
  • Proporciona los datos que deseas transmitir en el extremo con el método bulkTransfer() o controlTransfer(). Deberías realiza este paso en otro subproceso para evitar que se bloquee el subproceso de IU principal. Para ver 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ían tener más lógica para encontrar correctamente la interfaz y los extremos correctos para comunicarse Además, debes 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 initialize y queue una solicitud asíncrona y, 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ó, cierra UsbInterface y UsbDeviceConnection. llamando a releaseInterface() y close() 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 del manifiesto, permite que tu que solo controle eventos de desconexión mientras se ejecuta. De esta forma, los eventos desvinculados Solo se envían a la aplicación que está actualmente en ejecución y no se transmiten a todas las aplicaciones.