Visão geral do host USB

Quando seu dispositivo com tecnologia Android está no modo de host USB, ele atua como 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 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 o sistema de identificação informações, 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. Um 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. Esta turma permite que você envie dados de forma síncrona ou assíncrona.
UsbRequest Representa uma solicitação assíncrona para se comunicar com um dispositivo por meio de UsbDeviceConnection.
UsbConstants Define constantes USB que correspondem às definições em linux/usb/ch9.h do sistema operacional Linux. grão

Na maioria das situações, você precisa usar todas essas classes (UsbRequest só é necessário se você estiver fazendo comunicação assíncrona). ao se comunicar com um dispositivo USB. Em geral, você usa um UsbManager para recuperar o UsbDevice desejado. Quando você tiver o dispositivo, vai precisar encontrar o UsbInterface e o UsbEndpoint apropriados para se comunicar. 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 aplicativo antes trabalhando com as APIs de host USB:

  • Como nem todos os dispositivos Android têm suporte às APIs de host USB, incluir um elemento <uses-feature> que declare que seu aplicativo 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 são presentes em níveis anteriores de API.
  • Se quiser que seu aplicativo seja notificado sobre um dispositivo USB conectado, especifique um Par de elementos <intent-filter> e <meta-data> para o android.hardware.usb.action.USB_DEVICE_ATTACHED na sua atividade principal. A 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 o USB. os dispositivos que você quer filtrar. A lista a seguir descreve os atributos <usb-device>: Em geral, use o ID do produto e do fornecedor se você quiser filtrar para um dispositivo específico e use classe, subclasse e protocolo se você quiser filtrar por um grupo de dispositivos USB, como dispositivos de armazenamento em massa ou câmeras digitais. É possível especificar "nenhum" ou todos esses atributos. Especificar que nenhum atributo corresponde a cada dispositivo USB, portanto, faça isso apenas se o aplicativo 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) deve ser igual ao especificado na <meta-data>. O formato do arquivo de recurso XML está na 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 arquivo de recurso a seguir deve ser salvo res/xml/device_filter.xml e especifica que qualquer dispositivo USB com a atributos devem ser filtrados:

<?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 o aplicativo está interessado no dispositivo conectado. Nesse caso, configure comunicação com o dispositivo, se quiser. Para fazer isso, seu app precisa:

  1. Descubra dispositivos USB conectados usando um filtro de intent para ser notificado quando o usuário conecta um dispositivo USB ou enumera os dispositivos USB que já estão 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 na interface adequada endpoints.

Descobrir um dispositivo

Seu aplicativo pode descobrir dispositivos USB usando um filtro de intent para ser notificado quando o usuário conecta um dispositivo ou enumera os dispositivos USB que já estão conectados. Usar um o filtro de intent é útil quando você quer que seu aplicativo detecte automaticamente dispositivo desejado. Enumerar os dispositivos USB conectados é útil se você quiser uma lista de todos os dispositivos conectados ou se o aplicativo não filtrou uma intent.

Usar um filtro de intent

Para que seu aplicativo descubra um dispositivo USB específico, você pode especificar um filtro de intents para filtro para a intent android.hardware.usb.action.USB_DEVICE_ATTACHED. Junto com filtro de intent, é preciso especificar um arquivo de recurso que especifique as propriedades do USB dispositivo, como ID do produto e do fornecedor. Quando os usuários conectam um dispositivo que corresponde ao seu filtro, o sistema apresentará a eles uma caixa de diálogo perguntando se eles desejam iniciar seu aplicativo. Se os usuários aceitarem, seu aplicativo automaticamente terá permissão para acessar o dispositivo até que a dispositivo está 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 do seu interesse:

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

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

Na sua atividade, você pode acessar o UsbDevice que representa o dispositivo anexado pelo 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 aplicativo estiver interessado em inspecionar todos os dispositivos USB conectados no momento enquanto seu aplicativo está em execução, ele pode enumerar dispositivos no barramento. Use o método getDeviceList() para acessar um mapa de hash de todos os dispositivos USB conectados. O mapa de hash é codificado pelo nome do dispositivo USB, se você quiser obter um dispositivo do 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 simplesmente obter um iterador do mapa hash e processar cada um do dispositivo 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, o aplicativo deve ter permissão do usuários.

Observação: caso seu aplicativo use uma filtro de intent para descobrir dispositivos USB à medida que eles estão conectados, ele recebe automaticamente permissão se o usuário permitir que seu aplicativo manipule a intent. Caso contrário, solicite explicitamente em seu aplicativo antes de se conectar ao dispositivo.

Pedir permissão explicitamente pode ser necessário em algumas situações, como quando seu aplicativo enumera os dispositivos USB que já estão conectados e depois querem se comunicar com um. Você precisa verificar a permissão para acessar um dispositivo antes de tentar se comunicar com ele. Se não, você receberá um erro de tempo de execução se o usuário negar a permissão para acessar o dispositivo.

Para receber permissão explicitamente, primeiro crie um broadcast receiver. Este receptor detecta a intent que é transmitida quando você chama requestPermission(). A chamada para requestPermission() mostra uma caixa de diálogo para o o usuário está pedindo permissão para se conectar ao dispositivo. O código de exemplo a seguir mostra como crie 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 isso ao 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), 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 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, seu broadcast receiver recebe a intent que contém o EXTRA_PERMISSION_GRANTED extra, que é um booleano que representam 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, deve criar uma nova sequência na qual realizar todas as transmissões de dados, de modo que você não bloqueie o linha de execução de IU. Para configurar corretamente a comunicação com um dispositivo, é necessário ter o UsbInterface e UsbEndpoint do dispositivo em que você quer se comunicar e enviar solicitações nesse endpoint com um UsbDeviceConnection. Em geral, seu código precisa:

  • Verifique os atributos de um objeto UsbDevice, como o ID do produto, ID do fornecedor ou classe do dispositivo para descobrir se você quer ou não se comunicar com o dispositivo.
  • Quando tiver certeza de que deseja se comunicar com o dispositivo, encontre o UsbInterface que você queira usar para se comunicar com o o UsbEndpoint apropriado dessa interface. As interfaces podem ter um ou mais endpoints e geralmente terão um endpoint de entrada e saída para comunicação.
  • Quando encontrar o endpoint correto, abra um UsbDeviceConnection nesse endpoint.
  • fornecer os dados que você quer transmitir no endpoint com o método bulkTransfer() ou controlTransfer(). Você deve realize essa etapa em outra linha de execução para evitar o bloqueio da linha de execução de interface 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 deve ter mais lógica para encontrar corretamente a interface e os endpoints corretos para comunicação e também deve fazer qualquer transferência de dados em uma linha de execução diferente da linha de execução de interface 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().

Como encerrar a comunicação com um dispositivo

Quando você terminar de se comunicar com um dispositivo ou se ele tiver sido desconectado, feche o UsbInterface e o UsbDeviceConnection. chamando releaseInterface() e close(). Para detectar 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 aplicativo, e não o manifesto, permite que seu para processar apenas eventos desconectados enquanto estiver em execução. Dessa forma, os eventos independentes são enviados somente para o aplicativo em execução no momento e não transmitidos para todos os aplicativos.