Únete a ⁠ #Android11: The Beta Launch Show el 3 de junio.

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 funcionan como host USB puedan seguir interactuando con el hardware USB. Cuando un dispositivo con Android está en modo de accesorio USB, el accesorio USB de Android conectado funciona como host, proporciona energía al bus USB y enumera los dispositivos conectados. Android 3.1 (API nivel 12) es compatible con el modo de accesorio USB y la función también se agregó a Android 2.3.4 (API nivel 10) para posibilitar la compatibilidad con una gama más amplia de dispositivos.

Cómo elegir las API del accesorio USB adecuadas

Aunque las API del accesorio USB se introdujeron a la plataforma en Android 3.1, también están disponibles en Android 2.3.4 si se usa la biblioteca de complementos de las API de Google. Debido a que se usó una biblioteca externa para adaptar estas API, hay dos paquetes que puedes importar para brindar compatibilidad con el modo de accesorio USB. En función de los dispositivos Android que quieras admitir, es posible que debas usar uno en lugar del otro:

  • com.android.future.usb: Para ser compatible con el modo de accesorio USB en Android 2.3.4, la biblioteca de complementos de las API de Google incluye las API del accesorio USB adaptadas, que se almacenan en este espacio de nombres. Android 3.1 también admite importar las clases y llamarlas dentro de este espacio de nombres para brindar compatibilidad con aplicaciones escritas con la biblioteca de complementos. Esta biblioteca es un wrapper liviano que rodea las API del accesorio de android.hardware.usb y no admite el modo host USB. Si deseas brindar compatibilidad con la gama más amplia de dispositivos que admiten el modo de accesorio USB, usa la biblioteca de complementos e 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 quiere ofrecer compatibilidad con la función, por lo que debes declarar esa decisión 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 API del marco de trabajo, por lo que Android 3.1 admite el modo de accesorio USB sin necesidad de una biblioteca de complementos. Usa este paquete si solo te interesan Android 3.1 o dispositivos más recientes que brindan compatibilidad de hardware con 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

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

Descripción general de la API

Debido a que la biblioteca de complementos es un wrapper para las API del marco de trabajo, 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, debes tener en cuenta una leve diferencia de uso entre la biblioteca de complementos y las API del marco de trabajo.

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 API de Google y las API 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 necesitas agregar al archivo de manifiesto de tu aplicación antes de que funcione con las API del accesorio USB. En los ejemplos del archivo de manifiesto y del de recursos, se muestra cómo declarar estos elementos:

  • Debido a que no se garantiza que todos los dispositivos con Android admitan las API del accesorio USB, incluye un elemento <uses-feature> que declare que tu aplicación usa la función android.hardware.usb.accessory.
  • Si utilizas 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 API nivel 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 deseas filtrar. Cada <usb-accessory> puede tener los siguientes atributos:

    • manufacturer
    • model
    • version

    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 especificado en el elemento <meta-data>. El formato del archivo de recursos XML también se muestra 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.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. De ser así, puedes configurar la comunicación con el accesorio si lo deseas. Para hacerlo, la aplicación debe realizar lo siguiente:

  1. Debe descubrir los accesorios conectados usando un filtro de intents que filtre los eventos de conexión de accesorios o enumerando los accesorios conectados y buscando el apropiado.
  2. Si aún no lo tiene, debe solicitar permiso al usuario para comunicarse con el accesorio.
  3. Debe comunicarse con el accesorio leyendo y escribiendo datos en los extremos apropiados de la interfaz.

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 detalle las propiedades del accesorio USB, como el fabricante, el modelo y la versión, cuando los usuarios conectan un accesorio que coincide con el filtro del tuyo.

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 objeto 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 arreglo 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 por 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 cuando se conectan, automáticamente recibe permiso si el usuario autoriza a la aplicación a manejar el intent. De lo contrario, debes solicitar permiso de manera explícita en la aplicación antes de conectarte al accesorio.

Pedir permiso explícito puede ser necesario en algunos casos, 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, aparecerá un error de tiempo de ejecución si el usuario rechazó el permiso para acceder al accesorio.

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 usuario en el que le solicita permiso para conectarse al accesorio. En el siguiente ejemplo de código, 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, 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), 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 que aparezca el diálogo en el que se solicita permiso de acceso 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 al 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 booleano adicional tenga un valor verdadero antes de conectarte al accesorio.

Cómo establecer una comunicación con un accesorio

Puedes comunicarte con el accesorio mediante UsbManager para obtener un descriptor de archivos en el que configuras flujos de entrada y salida a fin de 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 manera 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 utilizando los objetos FileInputStream o FileOutputStream. Al leer datos de un accesorio con un objeto FileInputStream, asegúrate de que el búfer que usas sea lo suficientemente grande como para almacenar los datos del paquete USB. El protocolo de accesorios de Android es compatible con los 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 menor, los paquetes son de 64 bytes para accesorios USB de máxima velocidad 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 a fin de simplificar el proceso.

Para obtener más información sobre cómo usar subprocesos en Android, consulta Procesos y subprocesos.

Cómo finalizar la comunicación con un accesorio

Cuando hayas finalizado 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 del manifiesto, permite que la app 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 en el momento y no se emiten a todas las aplicaciones.