O modo de acessório USB permite que os usuários se conectem ao hardware do host USB projetado 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 os dispositivos com tecnologia Android que não podem atuar como host USB ainda interajam com hardware USB. Quando um dispositivo com tecnologia Android está no modo de acessório USB, o acessório USB Android funciona como o host, fornece energia ao barramento USB e enumera os dispositivos conectados. O Android 3.1 (API de nível 12) é compatível com o modo de acessório USB, e o recurso também é compatível com o Android 2.3.4 (API nível de 10) para ativar o suporte a uma gama mais ampla 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 a compatibilidade dessas APIs com versões anteriores foi desenvolvida usando uma biblioteca externa, existem dois pacotes que podem ser importados para serem compatíveis com o modo de acessório USB. Dependendo para quais dispositivos com tecnologia Android você quer oferecer suporte, talvez seja necessário usar um em vez do outro:
com.android.future.usb
: para ser compatível com o 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 compatíveis com versões anteriores e contidas neste namespace. O Android 3.1 também permite a importação e a chamada de classes dentro desse namespace para compatibilidade com apps gravados com a biblioteca de complementos. Essa biblioteca de complementos é um wrapper fino das APIs de acessóriosandroid.hardware.usb
e não é compatível com o modo de host USB. Se você quiser oferecer suporte à 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 ser compatíveis com o recurso de acessório USB. O fabricante de cada dispositivo decide se aceita ou não esse recurso, e é por isso que você precisa declará-lo no arquivo de manifesto.android.hardware.usb
: esse 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, por isso o Android 3.1 é compatível com o modo de acessório USB sem o uso de uma biblioteca de complementos. Use esse pacote se você se preocupar apenas com dispositivos Android 3.1 ou mais recentes que tenham suporte de hardware para o modo de acessório USB, que podem ser declarados no arquivo de manifesto.
Instalar a biblioteca de complementos das APIs do Google
Para instalar o complemento, instale o pacote de APIs do Google para a API 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 de framework, as classes que são 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 as APIs de 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ê aplica um filtro de intent para um acessório conectado, o objeto UsbAccessory
está dentro do intent passado para seu 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ório USB. Os exemplos de arquivo de manifesto e recurso mostram como declarar esses itens:
- Como nem todos os dispositivos com tecnologia Android são compatíveis com as APIs de acessório 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 app 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 de um acessório USB conectado, especifique um par de elementos
<intent-filter>
e<meta-data>
para o 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
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, o fabricante e a versão correspondentes tem que ser filtrado. O acessório envia a esses atributos o dispositivo com tecnologia 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 com tecnologia Android, o sistema Android pode determinar se seu app tem interesse 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 para eventos anexados a acessórios ou enumerando acessórios conectados e encontrando o acessório apropriado;
- pedir permissão ao usuário para se comunicar com o acessório, se ainda não tiver recebido;
- comunicar-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 os acessórios que já estão conectados. Usar um filtro de intent será útil se você quiser que seu app detecte automaticamente um determinado acessório. Enumerar acessórios conectados será útil se você quiser uma lista de todos os acessórios conectados ou se o app não aplicar um filtro de 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. Quando os usuários conectam um acessório que corresponde ao filtro de acessório,
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 ver o UsbAccessory
que representa o acessório anexado do intent como esse (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
Você pode fazer com que seu app enumere acessórios que se identificaram enquanto seu app está em execução.
Use o método getAccessoryList()
para ver 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: apenas 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 permissão dos usuários.
Observação: se seu app usar um filtro de intent para descobrir acessórios 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 acessório.
Solicitar explicitamente permissão pode ser necessário em algumas situações, como quando seu app enumera acessórios que já estão conectados e, em seguida, quer se comunicar com um deles. 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 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 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 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 acessório.
Comunicar-se com um acessório
Você pode se comunicar com o acessório usando UsbManager
para ver um descritor de arquivo que pode 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, você pode 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 Android é compatível com buffers de pacote de até 16.384 bytes. Assim, 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. O protocolo de acessório Android agrupa os pacotes das duas velocidades em pacote lógico para simplificar.
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 o acessório tiver sido desanexado, 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 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.