O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Visão geral do host USB

Quando o dispositivo com tecnologia Android está no modo de host USB, ele funciona como o host USB, alimenta o barramento e enumera os dispositivos USB conectados. O modo de host USB é compatível com o Android 3.1 e posterior.

Visão geral da API

Antes de começar, é importante entender as classes com as quais você precisa trabalhar. A tabela a seguir descreve as APIs de host USB no pacote android.hardware.usb.

Tabela 1. APIs de host USB

Classe Descrição
UsbManager Permite que você enumere e se comunique com dispositivos USB conectados.
UsbDevice Representa um dispositivo USB conectado e contém métodos para acessar informações de identificação, interfaces e endpoints.
UsbInterface Representa uma interface de um dispositivo USB, que define um conjunto de funcionalidades para o dispositivo. Um dispositivo pode ter uma ou mais interfaces de comunicação.
UsbEndpoint Representa um endpoint da interface, que é um canal de comunicação dessa interface. Uma interface pode ter um ou mais endpoints e geralmente tem endpoints de entrada e saída para comunicação bidirecional com o dispositivo.
UsbDeviceConnection Representa uma conexão com o dispositivo, que transfere dados em endpoints. Essa classe permite enviar dados de um lado para o outro de maneira síncrona ou assíncrona.
UsbRequest Representa uma solicitação assíncrona para se comunicar com um dispositivo por meio de UsbDeviceConnection.
UsbConstants Define as constantes USB que correspondem às definições em linux/usb/ch9.h do kernel do Linux.

Na maioria das situações, você precisa usar todas essas classes ao se comunicar com um dispositivo USB. Só será necessário usar UsbRequest se estiver fazendo comunicação assíncrona. Em geral, você usa um UsbManager para recuperar o UsbDevice desejado. Quando você tiver o dispositivo, precisará encontrar as UsbInterface e UsbEndpoint correspondentes dessa interface para comunicação. Depois de ter o endpoint correto, abra um UsbDeviceConnection para se comunicar com o dispositivo USB.

Requisitos de manifesto do Android

A lista a seguir descreve o que você precisa adicionar ao arquivo de manifesto do app antes de trabalhar com as APIs de host USB.

  • Como nem todos os dispositivos com tecnologia Android são compatíveis com as APIs de host USB, inclua um elemento <uses-feature> que declare que seu app usa o recurso android.hardware.usb.host.
  • Defina o SDK mínimo do app como a API nível 12 ou posterior. As APIs de host USB não estão presentes nos níveis anteriores da API.
  • Se você quiser que seu app seja notificado de um dispositivo USB conectado, especifique um par de elementos <intent-filter> e <meta-data> para o intent android.hardware.usb.action.USB_DEVICE_ATTACHED na sua atividade principal. O elemento <meta-data> aponta para um arquivo de recurso XML externo que declara informações de identificação sobre o dispositivo que você quer detectar.

    No arquivo de recurso XML, declare elementos <usb-device> para os dispositivos USB que você quer filtrar. A lista a seguir descreve os atributos de <usb-device>. Em geral, use o fornecedor e o ID do produto se você quiser aplicar um filtro para um dispositivo específico. E use classe, subclasse e protocolo se quiser aplicar um filtro para um grupo de dispositivos USB, como dispositivos de armazenamento em massa ou câmeras digitais. Você pode especificar nenhum ou todos esses atributos. Especificar que nenhum atributo corresponde a cada dispositivo USB. Portanto, faça isso apenas se o app exigir:

    • vendor-id
    • product-id
    • class
    • subclass
    • protocol (dispositivo ou interface)

    Salve o arquivo de recurso no diretório res/xml/. O nome do arquivo de recurso (sem a extensão .xml) precisa ser o mesmo que você especificou no elemento <meta-data>. O formato do arquivo de recurso XML está no exemplo abaixo.

Exemplos de arquivo de manifesto e recurso

O exemplo a seguir mostra um manifesto e o arquivo de recurso correspondente:

    <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>
    

Nesse caso, o seguinte arquivo de recurso precisa ser salvo em res/xml/device_filter.xml e especifica que qualquer dispositivo USB com os atributos especificados tem que ser filtrado:

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

    <resources>
        <usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" />
    </resources>
    

Trabalhar com dispositivos

Quando os usuários conectam dispositivos USB a um dispositivo com tecnologia Android, o sistema Android pode determinar se seu app tem interesse no dispositivo conectado. Nesse caso, você pode configurar a comunicação com o dispositivo, se quiser. Para fazer isso, seu app precisa:

  1. descobrir dispositivos USB conectados usando um filtro de intent para ser notificado quando o usuário conectar um dispositivo USB ou enumerando dispositivos USB que já estejam conectados;
  2. pedir permissão ao usuário para se conectar ao dispositivo USB, se ainda não tiver recebido;
  3. comunicar-se com o dispositivo USB lendo e gravando dados nos endpoints da interface apropriados.

Descobrir um dispositivo

Seu app pode descobrir dispositivos USB usando um filtro de intent para ser notificado quando o usuário conectar um dispositivo ou enumerando dispositivos USB que já estão conectados. Usar um filtro de intent será útil se você quiser que seu app detecte automaticamente um determinado dispositivo. Enumerar dispositivos USB conectados será útil se você quiser uma lista de todos os dispositivos conectados ou se o app não aplicar um filtro de intent.

Usar um filtro de intent

Para que seu app descubra um dispositivo USB específico, você pode especificar um filtro para o intent android.hardware.usb.action.USB_DEVICE_ATTACHED. Junto com esse filtro de intent, você precisa especificar um arquivo de recurso que especifique as propriedades do dispositivo USB, como o ID do produto e do fornecedor. Quando os usuários conectam um dispositivo que corresponde ao filtro do dispositivo, o sistema apresenta uma caixa de diálogo perguntando se eles querem iniciar o app. Se os usuários aceitarem, seu app terá permissão automática para acessar o dispositivo até que o dispositivo seja desconectado.

O exemplo a seguir mostra como declarar o filtro de intent:

    <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>
    

O exemplo a seguir mostra como declarar o arquivo de recurso correspondente que especifica os dispositivos USB em que você tem interesse:

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

    <resources>
        <usb-device vendor-id="1234" product-id="5678" />
    </resources>
    

Na sua atividade, você pode ver o UsbDevice que representa o dispositivo anexado no intent desta forma:

Kotlin

    val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
    

Java

    UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
    

Enumerar dispositivos

Se seu app estiver interessado em inspecionar todos os dispositivos USB conectados enquanto estiver em execução, ele poderá enumerar dispositivos no barramento. Use o método getDeviceList() para ver um mapa de hash de todos os dispositivos USB conectados. O mapa de hash é codificado pelo nome do dispositivo USB, caso você queira ver um dispositivo no 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");
    

Se quiser, você também pode extrair um iterador do mapa de hash e processar cada dispositivo um por um:

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
    }
    

Receber permissão para se comunicar com um dispositivo

Antes de se comunicar com o dispositivo USB, seu app precisa ter permissão dos usuários.

Observação: se seu app usar um filtro de intent para descobrir dispositivos USB enquanto estão conectados, ele receberá automaticamente a permissão se o usuário permitir que seu app manipule o intent. Caso contrário, solicite a permissão explicitamente no seu app antes de se conectar ao dispositivo.

Solicitar explicitamente permissão pode ser necessário em algumas situações, como quando seu app enumera dispositivos USB que já estão conectados e, em seguida, busca comunicação com um deles. Você precisa verificar a permissão para acessar um dispositivo antes de tentar se comunicar com ele. Caso contrário, você receberá um erro de tempo de execução se o usuário tiver negado a permissão para acessar o dispositivo.

Para receber permissão explicitamente, primeiro crie um broadcast receiver. Esse receptor detecta o intent que recebe a transmissão quando você chama requestPermission(). A chamada para requestPermission() exibe uma caixa de diálogo para o usuário pedir permissão para se conectar ao dispositivo. O exemplo de código a seguir mostra como criar o broadcast receiver:

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 o broadcast receiver, adicione-o no método onCreate() na sua atividade:

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 exibir a caixa de diálogo que solicita aos usuários permissão para se conectar ao dispositivo, chame o método requestPermission():

Kotlin

    lateinit var device: UsbDevice
    ...
    usbManager.requestPermission(device, permissionIntent)
    

Java

    UsbDevice device;
    ...
    usbManager.requestPermission(device, permissionIntent);
    

Quando os usuários respondem à caixa de diálogo, o destinatário do broadcast receiver recebe o intent que contém o EXTRA_PERMISSION_GRANTED extra, que é um booleano que representa a resposta. Verifique se esse extra tem um valor "true" antes de se conectar ao dispositivo.

Comunicar-se com um dispositivo

A comunicação com um dispositivo USB pode ser síncrona ou assíncrona. Em ambos os casos, você precisa criar uma nova linha de execução na qual realizar todas as transmissões de dados, de modo que não bloqueie a linha de execução de IU. Para configurar corretamente a comunicação com um dispositivo, você precisa ter o UsbInterface e o UsbEndpoint apropriados do dispositivo para se comunicar e enviar solicitações nesse endpoint com um UsbDeviceConnection. Em geral, seu código precisa:

  • verificar os atributos de um objeto UsbDevice, como ID do produto, ID do fornecedor ou classe de dispositivo, para descobrir se você quer se comunicar com o dispositivo;
  • encontrar o UsbInterface apropriado para comunicação com o UsbEndpoint apropriado dessa interface quando você tiver certeza de que quer se comunicar com o dispositivo. As interfaces podem ter um ou mais endpoints, e normalmente terão um endpoint de entrada e saída para comunicação bidirecional;
  • abrir um UsbDeviceConnection no endpoint correto, quando você encontrá-lo;
  • fornecer os dados que você quer transmitir no endpoint com o método bulkTransfer() ou controlTransfer(). Você precisa realizar essa etapa em outra linha de execução para evitar o bloqueio da linha de execução de IU principal. Para mais informações sobre o uso de linhas de execução no Android, consulte Processos e linhas de execução.

O snippet de código a seguir é uma maneira fácil de fazer uma transferência de dados síncrona. Seu código precisa ter mais lógica para encontrar corretamente a interface e os endpoints corretos para comunicação e também precisa fazer qualquer transferência em uma linha de execução diferente da linha de execução 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
    

Para enviar dados de forma assíncrona, use a classe UsbRequest para initialize e queue uma solicitação assíncrona e aguarde o resultado com requestWait().

Para mais informações, consulte a amostra de AdbTest, que demonstra como fazer transferências em massa assíncronas, e a amostra de MissileLauncher, que demonstra como ouvir de maneira assíncrona em um endpoint de interrupção.

Como encerrar a comunicação com um dispositivo

Quando você terminar de se comunicar com um dispositivo ou se o dispositivo tiver sido desanexado, feche UsbInterface e UsbDeviceConnection chamando releaseInterface() e close(). Para ouvir eventos independentes, crie um broadcast receiver como abaixo:

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
                }
            }
        }
    };
    

Criar o broadcast receiver dentro do app, e não o manifesto, permite que seu app manipule apenas eventos independentes enquanto está em execução. Dessa forma, os eventos independentes são enviados apenas para o app em execução no momento e não transmitidos para todos os apps.