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óriosandroid.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 recursoandroid.hardware.usb.accessory
. - Se você estiver usando a
biblioteca de complementos,
adicione o elemento
<uses-library>
especificandocom.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 intentandroid.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 umaNullPointerException
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:
- 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;
- Peça ao usuário permissão para se comunicar com o acessório, caso ainda não tenha recebido.
- 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.