Descripción general sobre el modo de accesorio USB

El modo de accesorio USB permite a los usuarios conectar hardware host USB diseñado específicamente para dispositivos con Android. Los accesorios deben cumplir con el protocolo de accesorios de Android descrito en la documentación del Kit de desarrollo de accesorios de Android. Esto permite que los dispositivos con Android que no pueden actuar como host USB interactúen con el hardware USB. Cuando un dispositivo con Android está en modo de accesorio USB, el accesorio USB de Android conectado actúa como host, proporciona energía al bus USB y enumera los dispositivos conectados. Android 3.1 (API nivel 12) admite el modo de accesorio USB y la función también cuenta con portabilidad a versiones anteriores de Android 2.3.4 (API nivel 10) para permitir la compatibilidad con una gama más amplia de dispositivos.

Cómo elegir las API del accesorio USB adecuadas

Si bien las APIs del accesorio USB se introdujeron en la plataforma en Android 3.1, también están disponibles en Android 2.3.4 mediante la biblioteca de complementos de las APIs de Google. Debido a que estas APIs se portaron a versiones anteriores mediante una biblioteca externa, hay dos paquetes que puedes importar para admitir el modo de accesorio USB. En función de los dispositivos Android que desees admitir, es posible que debas usar uno en lugar del otro:

  • com.android.future.usb: Para admitir el modo de accesorio USB en Android 2.3.4, la biblioteca de complementos de las APIs de Google incluye las APIs de accesorios USB con portabilidad a versiones anteriores y se encuentran en este espacio de nombres. Android 3.1 también admite importar y llamar a las clases dentro de este espacio de nombres para admitir aplicaciones escritas con la biblioteca de complementos. Esta biblioteca de complementos es un wrapper liviano que rodea las APIs del accesorio de android.hardware.usb y no admite el modo de host USB. Si quieres brindar compatibilidad con la gama más amplia de dispositivos que admiten el modo de accesorio USB, usa la biblioteca de complementos y, luego, importa este paquete. Es importante tener en cuenta que no todos los dispositivos con Android 2.3.4 son necesarios para admitir la función de accesorio USB. Cada fabricante de dispositivos decide si admitirá o no esta función, por lo que debes declararla en tu archivo de manifiesto.
  • android.hardware.usb: Este espacio de nombres contiene las clases que admiten el modo de accesorio USB en Android 3.1. Este paquete se incluye como parte de las APIs del framework, por lo que Android 3.1 admite el modo de accesorio USB sin necesidad de usar una biblioteca de complementos. Usa este paquete si solo te interesan Android 3.1 o dispositivos más recientes que admiten hardware para el modo de accesorio USB, que puedes declarar en el archivo de manifiesto.

Cómo instalar la biblioteca de complementos de las API de Google

Si quieres instalar el complemento, puedes instalar el paquete de la API 10 de Android de las API de Google con SDK Manager. Consulta Cómo instalar el complemento de las API de Google para obtener más información sobre cómo instalar la biblioteca de complementos.

Descripción general de la API

Debido a que la biblioteca de complementos es un wrapper para las APIs del framework, las clases que admiten la función de accesorio USB son similares. Puedes utilizar la documentación de referencia para el android.hardware.usb incluso aunque uses la biblioteca de complementos.

Nota: Sin embargo, hay una diferencia de uso menor entre la biblioteca de complementos y las APIs del framework que debes tener en cuenta.

En la siguiente tabla, se describen las clases que admiten las API del accesorio USB:

Clase Descripción
UsbManager Te permite enumerar los accesorios USB conectados y establecer una comunicación con ellos.
UsbAccessory Representa un accesorio USB y contiene métodos para acceder a su información de identificación.

Diferencias de uso entre las API de la biblioteca de complementos y las de la plataforma

Existen dos diferencias de uso entre la biblioteca de complementos de las APIs de Google y las APIs de la plataforma.

Si utilizas la biblioteca de complementos, debes obtener el objeto UsbManager de la siguiente manera:

Kotlin

val manager = UsbManager.getInstance(this)

Java

UsbManager manager = UsbManager.getInstance(this);

Si no utilizas la biblioteca de complementos, debes obtener el objeto UsbManager de la siguiente manera:

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager

Java

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);

Cuando filtras por un accesorio conectado con un filtro de intents, el objeto UsbAccessory está contenido dentro del intent que se pasa a tu aplicación. Si utilizas la biblioteca de complementos, debes obtener el objeto UsbAccessory de la siguiente manera:

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

Si no utilizas la biblioteca de complementos, debes obtener el objeto UsbAccessory de la siguiente manera:

Kotlin

val accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY) as UsbAccessory

Java

UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

Requisitos del manifiesto de Android

En la siguiente lista, se describe lo que debes agregar al archivo de manifiesto de tu aplicación para poder trabajar con las APIs del accesorio USB. En los ejemplos de archivos de manifiesto y de recursos, se muestra cómo declarar estos elementos:

  • Dado que no se garantiza que todos los dispositivos con Android admitan las APIs del accesorio USB, incluye un elemento <uses-feature> que declare que tu aplicación usa la función android.hardware.usb.accessory.
  • Si usas la biblioteca de complementos, agrega el elemento <uses-library> y especifica com.android.future.usb.accessory para la biblioteca.
  • Establece la versión mínima del SDK de la aplicación en el nivel de API 10 si usas la biblioteca de complementos o en 12 si usas el paquete android.hardware.usb.
  • Si quieres que tu aplicación reciba una notificación sobre un accesorio USB conectado, especifica un par de elementos <intent-filter> y <meta-data> para el intent android.hardware.usb.action.USB_ACCESSORY_ATTACHED en tu actividad principal. El elemento <meta-data> dirige a un archivo de recursos XML externo que declara información de identificación sobre el accesorio que quieres detectar.

    En el archivo de recursos XML, declara elementos <usb-accessory> para los accesorios que quieres filtrar. Cada <usb-accessory> puede tener los siguientes atributos:

    • manufacturer
    • model
    • version

    No se recomienda filtrar en version. Es posible que un accesorio o dispositivo no siempre especifique una string de versión (de forma intencional o no). Si la app declara un atributo de versión para filtrar y el accesorio o el dispositivo no especifican una string de versión, se genera una NullPointerException en las versiones anteriores de Android. Este problema se corrigió en Android 12.

    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 también se muestra en el ejemplo 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.accessory" />
    
    <uses-sdk android:minSdkVersion="<version>" />
    ...
    <application>
      <uses-library android:name="com.android.future.usb.accessory" />
        <activity ...>
            ...
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
            </intent-filter>

            <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
                android:resource="@xml/accessory_filter" />
        </activity>
    </application>
</manifest>

En este caso, el siguiente archivo de recursos debe guardarse en res/xml/accessory_filter.xml. Especifica que se debe filtrar cualquier accesorio que tenga el modelo, el fabricante y la versión correspondientes. El accesorio envía estos atributos al dispositivo con Android:

<?xml version="1.0" encoding="utf-8"?>

<resources>
    <usb-accessory model="DemoKit" manufacturer="Google" version="1.0"/>
</resources>

Cómo trabajar con accesorios

Cuando los usuarios conectan accesorios USB a un dispositivo con Android, el sistema Android puede determinar si a tu aplicación le interesa el accesorio conectado. Si es así, puedes configurar la comunicación con el accesorio si lo deseas. Para hacerlo, la aplicación debe realizar lo siguiente:

  1. Para descubrir los accesorios conectados, usa un filtro de intents que filtre los eventos de conexión de accesorios o enumera los accesorios conectados y busca el apropiado.
  2. Si aún no lo obtuvo, debe solicitarse permiso al usuario para comunicarse con el accesorio.
  3. Debe comunicarse con el accesorio leyendo y escribiendo datos en los extremos de interfaz apropiados.

Cómo descubrir un accesorio

Tu aplicación puede descubrir accesorios usando un filtro de intents para recibir una notificación cuando el usuario conecta un accesorio o enumerando los accesorios que ya están conectados. El uso de un filtro de intents es útil si quieres que tu aplicación detecte automáticamente un accesorio específico. La enumeración de accesorios conectados es útil si deseas obtener una lista de todos los accesorios 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 accesorio USB en particular, puedes especificar un filtro de intents y así filtrar por el intent android.hardware.usb.action.USB_ACCESSORY_ATTACHED. Junto con este filtro de intents, debes especificar un archivo de recursos que especifique las propiedades del accesorio USB, como el fabricante, el modelo y la versión.

En el siguiente ejemplo, se muestra cómo declarar el filtro de intents:

<activity ...>
    ...
    <intent-filter>
        <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
    </intent-filter>

    <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
        android:resource="@xml/accessory_filter" />
</activity>

En el siguiente ejemplo, se muestra cómo declarar el archivo de recursos correspondiente que especifica los accesorios USB que te interesan:

<?xml version="1.0" encoding="utf-8"?>

<resources>
    <usb-accessory manufacturer="Google, Inc." model="DemoKit" version="1.0" />
</resources>

En tu actividad, puedes obtener el UsbAccessory que representa el accesorio conectado desde el intent de la siguiente manera (con la biblioteca de complementos):

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

O de este modo (con las API de la plataforma):

Kotlin

val accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY) as UsbAccessory

Java

UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

Cómo enumerar accesorios

Puedes hacer que tu aplicación enumere los accesorios que se identificaron mientras se ejecuta la aplicación.

Usa el método getAccessoryList() para obtener un array de todos los accesorios USB conectados:

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager
val accessoryList: Array<out UsbAccessory> = manager.accessoryList

Java

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbAccessory[] accessoryList = manager.getAccessoryList();

Nota: Solo se admite un accesorio conectado a la vez.

Cómo obtener permiso para establecer una comunicación con un accesorio

Antes de comunicarse con el accesorio USB, tu aplicación debe tener el permiso de tus usuarios.

Nota: Si tu aplicación usa un filtro de intents para descubrir los accesorios 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 explícitamente en tu aplicación antes de conectarte al accesorio.

Pedir permiso explícito puede ser necesario en algunas situaciones, como cuando tu aplicación enumera los accesorios que ya están conectados y luego desea comunicarse con uno. Debes verificar que se haya otorgado permiso para acceder a un accesorio antes de intentar comunicarte con él. De lo contrario, si el usuario rechazó el permiso para acceder al accesorio, recibirás un error de tiempo de ejecución.

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 accesorio. 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 accessory: UsbAccessory? = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY)

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    accessory?.apply {
                        // call method to set up accessory communication
                    }
                } else {
                    Log.d(TAG, "permission denied for accessory $accessory")
                }
            }
        }
    }
}

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) {
                UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    if(accessory != null){
                        // call method to set up accessory communication
                    }
                }
                else {
                    Log.d(TAG, "permission denied for accessory " + accessory);
                }
            }
        }
    }
};

Para registrar el receptor de emisión, incluye lo siguiente en el 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), 0)
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), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(usbReceiver, filter);

Para mostrar el diálogo en el que se solicita permiso a los usuarios para conectarse al accesorio, llama al método requestPermission():

Kotlin

lateinit var accessory: UsbAccessory
...
usbManager.requestPermission(accessory, permissionIntent)

Java

UsbAccessory accessory;
...
usbManager.requestPermission(accessory, 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 extra tenga un valor verdadero antes de conectarte al accesorio.

Cómo establecer una comunicación con un accesorio

Si quieres comunicarte con el accesorio, usa UsbManager para obtener un descriptor de archivos en el que puedas configurar flujos de entrada y salida para leer y escribir datos en el descriptor. Las transmisiones representan los extremos masivos de entrada y salida del accesorio. Debes configurar la comunicación entre el dispositivo y el accesorio en otro subproceso de IU, de modo que no se bloquee el subproceso de IU principal. En el siguiente ejemplo, se muestra cómo abrir un accesorio y establecer una comunicación con él:

Kotlin

private lateinit var accessory: UsbAccessory
private var fileDescriptor: ParcelFileDescriptor? = null
private var inputStream: FileInputStream? = null
private var outputStream: FileOutputStream? = null
...

private fun openAccessory() {
    Log.d(TAG, "openAccessory: $mAccessory")
    fileDescriptor = usbManager.openAccessory(accessory)
    fileDescriptor?.fileDescriptor?.also { fd ->
        inputStream = FileInputStream(fd)
        outputStream = FileOutputStream(fd)
        val thread = Thread(null, this, "AccessoryThread")
        thread.start()
    }
}

Java

UsbAccessory accessory;
ParcelFileDescriptor fileDescriptor;
FileInputStream inputStream;
FileOutputStream outputStream;
...

private void openAccessory() {
    Log.d(TAG, "openAccessory: " + accessory);
    fileDescriptor = usbManager.openAccessory(accessory);
    if (fileDescriptor != null) {
        FileDescriptor fd = fileDescriptor.getFileDescriptor();
        inputStream = new FileInputStream(fd);
        outputStream = new FileOutputStream(fd);
        Thread thread = new Thread(null, this, "AccessoryThread");
        thread.start();
    }
}

En el método run() del subproceso, puedes leer el accesorio y escribir en él usando los objetos FileInputStream o FileOutputStream. Cuando leas datos de un accesorio con un objeto FileInputStream, asegúrate de que el búfer que uses sea lo suficientemente grande como para almacenar los datos del paquete USB. El protocolo de accesorios de Android admite búferes de paquetes de hasta 16,384 bytes, por lo que puedes optar por declarar siempre que tu búfer tiene este tamaño para simplificar el proceso.

Nota: En un nivel inferior, los paquetes son de 64 bytes para accesorios USB de velocidad máxima y de 512 bytes para accesorios USB de alta velocidad. El protocolo de accesorios de Android agrupa los paquetes para ambas velocidades en un solo paquete lógico para mayor simplicidad.

Para obtener más información sobre el uso de subprocesos en Android, consulta Procesos y subprocesos.

Cómo finalizar la comunicación con un accesorio

Cuando hayas terminado la comunicación con un accesorio, o si este se desconectó, llama a close() para cerrar el descriptor de archivos que abriste. Para escuchar eventos de desconexión, crea un receptor de emisión como se indica aquí:

Kotlin

var usbReceiver: BroadcastReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {

        if (UsbManager.ACTION_USB_ACCESSORY_DETACHED == intent.action) {
            val accessory: UsbAccessory? = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY)
            accessory?.apply {
                // call your method that cleans up and closes communication with the accessory
            }
        }
    }
}

Java

BroadcastReceiver usbReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
            UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
            if (accessory != null) {
                // call your method that cleans up and closes communication with the accessory
            }
        }
    }
};

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.