Visão geral de acessórios USB

O modo de acessório USB permite que os usuários conectem hardwares de host USB projetados especificamente para dispositivos com tecnologia Android. Os acessórios precisam aderir ao protocolo de acessórios Android descrito na documentação do Kit de desenvolvimento de acessórios Android. Isso permite que dispositivos com tecnologia Android que não podem atuar como host USB ainda interajam com hardware USB. Quando um dispositivo Android está no modo de acessório USB, o acessório USB Android funciona como o host, fornece energia para o barramento USB e enumera os dispositivos conectados. O Android 3.1 (API de nível 12) oferece suporte ao modo de acessório USB, e o recurso também tem backport para o Android 2.3.4 (API de nível 10) para oferecer suporte a uma variedade maior de dispositivos.

Escolha as APIs de acessório USB certas

Embora as APIs de acessório USB tenham sido introduzidas na plataforma no Android 3.1, elas também estão disponíveis no Android 2.3.4 usando a biblioteca de complementos das APIs do Google. Como essas APIs passaram por backport usando uma biblioteca externa, há dois pacotes que você pode importar para oferecer suporte ao modo de acessório USB. Dependendo para quais dispositivos Android você quer oferecer suporte, talvez seja necessário usar um em vez do outro:

  • com.android.future.usb: para oferecer suporte ao modo de acessório USB no Android 2.3.4, a biblioteca de complementos das APIs do Google inclui as APIs de acessórios USB com backport e elas estão contidas nesse namespace. O Android 3.1 também oferece suporte à importação e chamada de classes dentro desse namespace para oferecer suporte a aplicativos criados com a biblioteca de complementos. Essa biblioteca de complementos é um wrapper fino em torno das APIs de acessórios android.hardware.usb e não oferece suporte ao modo de host USB. Se você quiser oferecer compatibilidade com a maior variedade de dispositivos compatíveis com o modo de acessório USB, use a biblioteca de complementos e importe esse pacote. É importante observar que nem todos os dispositivos Android 2.3.4 precisam oferecer suporte ao recurso de acessório USB. Cada fabricante de dispositivo decide se oferece suporte a esse recurso, e é por isso que você precisa declará-lo no arquivo de manifesto.
  • android.hardware.usb: este namespace contém as classes compatíveis com o modo de acessório USB no Android 3.1. Esse pacote está incluído como parte das APIs de framework, então o Android 3.1 oferece suporte ao modo de acessório USB sem o uso de uma biblioteca de complementos. Use esse pacote se você se importar apenas com dispositivos Android 3.1 ou mais recentes que tenham suporte de hardware para o modo de acessório USB, que pode ser declarado no arquivo de manifesto.

Instalar a biblioteca de complementos das APIs do Google

Se você quiser instalar o complemento, instale o pacote de APIs do Google para o Android 10 com o SDK Manager. Consulte Instalar o complemento de APIs do Google para mais informações sobre como instalar a biblioteca de complementos.

Visão geral da API

Como a biblioteca de complementos é um wrapper para as APIs do framework, as classes compatíveis com o recurso de acessório USB são semelhantes. Você pode usar a documentação de referência do android.hardware.usb mesmo se estiver usando a biblioteca de complementos.

Observação:há, no entanto, uma pequena diferença de uso entre a biblioteca de complementos e as APIs de framework de que você precisa estar ciente.

A tabela a seguir descreve as classes compatíveis com as APIs de acessório USB:

Classe. Descrição
UsbManager Permite que você enumere e se comunique com acessórios USB conectados.
UsbAccessory Representa um acessório USB e contém métodos para acessar as informações de identificação.

Diferenças de uso entre a biblioteca de complementos e as APIs de plataforma

Há duas diferenças de uso entre o uso da biblioteca de complementos das APIs do Google e das APIs da plataforma.

Se você estiver usando a biblioteca de complementos, precisará recuperar o objeto UsbManager da seguinte maneira:

Kotlin

val manager = UsbManager.getInstance(this)

Java

UsbManager manager = UsbManager.getInstance(this);

Se você não estiver usando a biblioteca de complementos, precisará recuperar o objeto UsbManager da seguinte maneira:

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager

Java

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

Quando você filtra um acessório conectado com um filtro de intent, o objeto UsbAccessory fica dentro da intent transmitida ao app. Se você estiver usando a biblioteca de complementos, precisará recuperar o objeto UsbAccessory da seguinte maneira:

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

Se você não estiver usando a biblioteca de complementos, precisará recuperar o objeto UsbAccessory da seguinte maneira:

Kotlin

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

Java

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

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 acessórios USB. Os exemplos de arquivo de manifesto e recurso mostram como declarar esses itens:

  • Como nem todos os dispositivos Android têm suporte às APIs de acessórios USB, inclua um elemento <uses-feature> que declare que seu app usa o recurso android.hardware.usb.accessory.
  • Se você estiver usando a biblioteca de complementos, adicione o elemento <uses-library> especificando com.android.future.usb.accessory para a biblioteca.
  • Defina o SDK mínimo do aplicativo como a API de nível 10, se você estiver usando a biblioteca de complementos, ou 12, se estiver usando o pacote android.hardware.usb.
  • Se você quiser que seu app seja notificado sobre um acessório USB conectado, especifique um par de elementos <intent-filter> e <meta-data> para a intent android.hardware.usb.action.USB_ACCESSORY_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 acessório que você quer detectar.

    No arquivo de recurso XML, declare elementos <usb-accessory> para os acessórios que você quer filtrar. Cada <usb-accessory> pode ter os seguintes atributos:

    • manufacturer
    • model
    • version

    Não é recomendável filtrar por version. Um acessório ou dispositivo nem sempre pode especificar uma string de versão (intencionalmente ou não). Quando o app declara um atributo de versão para filtrar e o acessório ou dispositivo não especifica uma string de versão, isso causa uma NullPointerException em versões anteriores do Android. Esse problema foi corrigido no Android 12.

    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 também é mostrado 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.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>

Nesse caso, o seguinte arquivo de recurso precisa ser salvo em res/xml/accessory_filter.xml e especifica que qualquer acessório que tenha o modelo, fabricante e versão correspondentes precisa ser filtrado. O acessório envia estes atributos ao dispositivo Android:

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

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

Trabalhar com acessórios

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

  1. descobrir acessórios conectados usando um filtro de intent que filtre por eventos anexados a acessórios ou enumerando acessórios conectados e encontrando o adequado;
  2. Peça ao usuário permissão para se comunicar com o acessório, caso ainda não tenha recebido.
  3. Comunique-se com o acessório lendo e gravando dados nos endpoints da interface apropriados.

Descobrir um acessório

Seu app pode descobrir acessórios usando um filtro de intent para ser notificado quando o usuário conectar um acessório ou enumerando acessórios que já estão conectados. Usar um filtro de intent é útil se você quiser que seu app detecte automaticamente um acessório desejado. Enumerar acessórios conectados será útil se você quiser ter uma lista de todos os acessórios conectados ou se o aplicativo não filtrar uma intent.

Usar um filtro de intent

Para que seu app descubra um acessório USB específico, você pode especificar um filtro para o intent android.hardware.usb.action.USB_ACCESSORY_ATTACHED. Junto com esse filtro de intent, você precisa especificar um arquivo de recurso que especifique as propriedades do acessório USB, como fabricante, modelo e versão.

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

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

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

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

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

Na sua atividade, você pode acessar o UsbAccessory que representa o acessório anexado da intent desta forma (com a biblioteca de complementos):

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

ou assim (com as APIs de plataforma):

Kotlin

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

Java

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

Enumerar acessórios

É possível fazer com que o app enumere acessórios que se identificaram enquanto ele está em execução.

Use o método getAccessoryList() para ter uma matriz de todos os acessórios 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();

Observação : somente um acessório conectado é aceito por vez.

Receber permissão para se comunicar com um acessório

Antes de se comunicar com o acessório USB, seu app precisa ter a permissão dos usuários.

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

Solicitar explicitamente permissão pode ser necessário em algumas situações, como quando o app enumera acessórios que já estão conectados e depois quer se comunicar com um. Você precisa verificar a permissão para acessar um acessório 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 acessório.

Para receber permissão explicitamente, primeiro crie um broadcast receiver. Esse receptor detecta a intent que recebe a transmissão quando você chama requestPermission(). A chamada para requestPermission() exibe uma caixa de diálogo para o usuário solicitando permissão para se conectar ao acessório. 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 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 o broadcast receiver, coloque-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 acessório, chame o método requestPermission():

Kotlin

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

Java

UsbAccessory accessory;
...
usbManager.requestPermission(accessory, permissionIntent);

Quando o usuário responde à caixa de diálogo, o 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 acessório.

Comunicar-se com um acessório

É possível se comunicar com o acessório usando o UsbManager para acessar um descritor de arquivo que pode ser configurado para configurar streams de entrada e saída para ler e gravar dados no descritor. Os streams representam os endpoints em massa de entrada e saída do acessório. Você precisa configurar a comunicação entre o dispositivo e o acessório em outra linha de execução, para não bloquear a linha de execução de IU principal. O exemplo a seguir mostra como abrir um acessório para comunicação:

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

No método run() da linha de execução, é possível ler e gravar no acessório usando os objetos FileInputStream ou FileOutputStream. Ao ler dados de um acessório com um objeto FileInputStream, verifique se o buffer usado é grande o suficiente para armazenar os dados do pacote USB. O protocolo de acessório do Android oferece suporte a buffers de pacote de até 16.384 bytes. Portanto, você pode optar por sempre declarar seu buffer como desse tamanho para simplificar.

Observação:em um nível inferior, os pacotes são de 64 bytes para acessórios USB de velocidade máxima e 512 bytes para acessórios USB de alta velocidade. Para simplificar, o protocolo de acessório Android agrupa os pacotes para ambas as velocidades em um único pacote lógico.

Para mais informações sobre o uso de linhas de execução no Android, consulte Processos e linhas de execução.

Encerrar a comunicação com um acessório

Quando terminar de se comunicar com um acessório ou se ele tiver sido removido, feche o descritor de arquivo que você abriu chamando 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_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
            }
        }
    }
};

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