Panoramica degli host USB

Quando il dispositivo Android è in modalità host USB, funge da host USB, alimenta il bus e elenca i dispositivi USB collegati. La modalità host USB è supportata in Android 3.1 e versioni successive.

Panoramica dell'API

Prima di iniziare, è importante capire con quali corsi devi lavorare. La seguente tabella descrive le API host USB nel pacchetto android.hardware.usb.

Tabella 1. API host USB

Classe Descrizione
UsbManager Consente di enumerare e comunicare con i dispositivi USB collegati.
UsbDevice Rappresenta un dispositivo USB connesso e contiene metodi per accedere alle relative informazioni identificative, interfacce ed endpoint.
UsbInterface Rappresenta l'interfaccia di un dispositivo USB, che definisce un insieme di funzionalità per il dispositivo. Un dispositivo può avere una o più interfacce su cui comunicare.
UsbEndpoint Rappresenta un endpoint dell'interfaccia, ovvero un canale di comunicazione per questa interfaccia. Un'interfaccia può avere uno o più endpoint e di solito ha endpoint di input e output per la comunicazione bidirezionale con il dispositivo.
UsbDeviceConnection Rappresenta una connessione al dispositivo, che trasferisce i dati sugli endpoint. Questa classe consente di inviare e ricevere dati in modo sincrono o asincrono.
UsbRequest Rappresenta una richiesta asincrona di comunicare con un dispositivo tramite un UsbDeviceConnection.
UsbConstants Definisce le costanti USB che corrispondono alle definizioni in linux/usb/ch9.h del kernel Linux.

Nella maggior parte dei casi, è necessario utilizzare tutte queste classi (UsbRequest è necessario solo se si effettua una comunicazione asincrona) per comunicare con un dispositivo USB. In generale, si ottiene un UsbManager per recuperare il UsbDevice desiderato. Una volta in possesso del dispositivo, devi trovare i UsbInterface appropriati e i UsbEndpoint dell'interfaccia su cui comunicare. Dopo aver ottenuto l'endpoint corretto, apri UsbDeviceConnection per comunicare con il dispositivo USB.

Requisiti per i file manifest Android

Nell'elenco che segue vengono descritti gli elementi da aggiungere al file manifest dell'applicazione prima di utilizzare le API host USB:

  • Poiché non tutti i dispositivi basati su Android supportano le API host USB, includi un elemento <uses-feature> che dichiari che l'applicazione utilizza la funzionalità android.hardware.usb.host.
  • Imposta l'SDK minimo dell'applicazione sul livello API 12 o superiore. Le API host USB non sono presenti nei livelli API precedenti.
  • Se vuoi che la tua applicazione riceva le notifiche relative a un dispositivo USB collegato, specifica una coppia di elementi <intent-filter> e <meta-data> per l'intent android.hardware.usb.action.USB_DEVICE_ATTACHED nell'attività principale. L'elemento <meta-data> rimanda a un file di risorse XML esterno che dichiara le informazioni di identificazione sul dispositivo che vuoi rilevare.

    Nel file di risorse XML, dichiara gli elementi <usb-device> per i dispositivi USB che vuoi filtrare. Nell'elenco seguente vengono descritti gli attributi di <usb-device>. In generale, utilizza il fornitore e l'ID prodotto se vuoi applicare un filtro in base a un dispositivo specifico e utilizza la classe, la sottoclasse e il protocollo se vuoi applicare filtri per un gruppo di dispositivi USB, ad esempio dispositivi di archiviazione di massa o fotocamere digitali. Puoi specificare nessuno o tutti questi attributi. Se non specifichi nessun attributo per ogni dispositivo USB, esegui questa operazione solo se l'applicazione lo richiede:

    • vendor-id
    • product-id
    • class
    • subclass
    • protocol (dispositivo o interfaccia)

    Salva il file della risorsa nella directory res/xml/. Il nome del file della risorsa (senza l'estensione .xml) deve essere uguale a quello specificato nell'elemento <meta-data>. Il formato del file di risorse XML è riportato nell'esempio di seguito.

Esempi di file manifest e risorse

L'esempio seguente mostra un manifest di esempio e il file di risorse corrispondente:

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

In questo caso, il seguente file di risorse deve essere salvato in res/xml/device_filter.xml e specifica che tutti i dispositivi USB con gli attributi specificati devono essere filtrati:

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

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

Lavorare con i dispositivi

Quando gli utenti collegano dispositivi USB a un dispositivo Android, il sistema Android può determinare se la tua applicazione è interessata al dispositivo connesso. In questo caso, puoi configurare la comunicazione con il dispositivo, se vuoi. Per farlo, l'applicazione deve:

  1. Rileva i dispositivi USB connessi utilizzando un filtro per intent per ricevere notifiche quando l'utente connette un dispositivo USB o enumerando i dispositivi USB già connessi.
  2. Chiedi all'utente l'autorizzazione a connettersi al dispositivo USB, se non l'hai già ricevuta.
  3. Comunicazione con il dispositivo USB leggendo e scrivendo dati sugli endpoint di interfaccia appropriati.

Scopri un dispositivo

L'applicazione può rilevare i dispositivi USB utilizzando un filtro per intent che consente di ricevere notifiche quando l'utente connette un dispositivo oppure enumerando i dispositivi USB già connessi. L'utilizzo di un filtro per intent è utile se vuoi che la tua applicazione rilevi automaticamente il dispositivo desiderato. L'elenco dei dispositivi USB connessi è utile se vuoi ottenere un elenco di tutti i dispositivi connessi o se la tua applicazione non ha filtrato un intent.

Utilizzare un filtro per intent

Per fare in modo che la tua applicazione rilevi un determinato dispositivo USB, puoi specificare un filtro per intent per filtrare per l'intent android.hardware.usb.action.USB_DEVICE_ATTACHED. Oltre a questo filtro per intent, devi specificare un file di risorse che specifichi le proprietà del dispositivo USB, come l'ID del prodotto e del fornitore. Quando gli utenti collegano un dispositivo che corrisponde al filtro del dispositivo, il sistema visualizza una finestra di dialogo che chiede se vogliono avviare l'applicazione. Se gli utenti accettano, la tua applicazione sarà automaticamente autorizzata ad accedere al dispositivo fino alla disconnessione.

L'esempio seguente mostra come dichiarare il filtro per 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>

L'esempio seguente mostra come dichiarare il file di risorse corrispondente che specifica i dispositivi USB che ti interessano:

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

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

Nella tua attività, puoi ottenere il UsbDevice che rappresenta il dispositivo associato dall'intent in questo modo:

Kotlin

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

Java

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

Enumera dispositivi

Se la tua applicazione è interessata a ispezionare tutti i dispositivi USB attualmente connessi mentre l'applicazione è in esecuzione, può enumerare i dispositivi sul bus. Utilizza il metodo getDeviceList() per ottenere una mappa hash di tutti i dispositivi USB collegati. La mappa hash è digitata dal nome del dispositivo USB se vuoi ottenere un dispositivo dalla mappa.

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 vuoi, puoi anche ottenere un iteratore dalla mappa hash ed elaborare ogni dispositivo uno alla volta:

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
}

Ottenere l'autorizzazione a comunicare con un dispositivo

Prima di comunicare con il dispositivo USB, l'applicazione deve avere l'autorizzazione degli utenti.

Nota: se la tua applicazione utilizza un filtro di intent per rilevare i dispositivi USB mentre sono connessi, riceverà automaticamente l'autorizzazione se l'utente consente alla tua applicazione di gestire l'intent. In caso contrario, devi richiedere esplicitamente l'autorizzazione nell'applicazione prima di connetterti al dispositivo.

In alcune situazioni potrebbe essere necessario chiedere esplicitamente l'autorizzazione, ad esempio quando l'applicazione enumera i dispositivi USB già connessi e poi vuole comunicare con uno di questi dispositivi. Devi verificare l'autorizzazione per accedere a un dispositivo prima di provare a comunicare con il dispositivo. In caso contrario, riceverai un errore di runtime se l'utente ha negato l'autorizzazione ad accedere al dispositivo.

Per ottenere esplicitamente l'autorizzazione, crea prima un ricevitore broadcast. Questo ricevitore ascolta l'intent che viene trasmesso quando chiami requestPermission(). La chiamata a requestPermission() mostra all'utente una finestra di dialogo che chiede l'autorizzazione per connettersi al dispositivo. Il seguente codice campione mostra come creare il ricevitore di trasmissione:

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

Per registrare il ricevitore di trasmissione, aggiungi questo messaggio nel tuo metodo onCreate() all'attività:

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

Per visualizzare la finestra di dialogo che chiede agli utenti l'autorizzazione per connettersi al dispositivo, chiama il metodo requestPermission():

Kotlin

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

Java

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

Quando gli utenti rispondono alla finestra di dialogo, il ricevitore della trasmissione riceve l'intent che contiene l'elemento EXTRA_PERMISSION_GRANTED aggiuntivo, che è un valore booleano che rappresenta la risposta. Seleziona questo valore aggiuntivo per il valore true prima di eseguire la connessione al dispositivo.

Comunicazione con un dispositivo

La comunicazione con un dispositivo USB può essere sincrona o asincrona. In entrambi i casi, devi creare un nuovo thread su cui eseguire tutte le trasmissioni di dati, in modo da non bloccare il thread dell'interfaccia utente. Per configurare correttamente la comunicazione con un dispositivo, devi ottenere i valori UsbInterface e UsbEndpoint appropriati del dispositivo su cui vuoi comunicare e inviare richieste su questo endpoint con un UsbDeviceConnection. In generale, il codice dovrebbe:

  • Controlla gli attributi di un oggetto UsbDevice, come ID prodotto, ID fornitore o classe dispositivo, per capire se vuoi comunicare o meno con il dispositivo.
  • Quando hai la certezza di voler comunicare con il dispositivo, trova il UsbInterface appropriato che vuoi utilizzare per comunicare insieme al UsbEndpoint appropriato di quell'interfaccia. Le interfacce possono avere uno o più endpoint e in genere hanno un endpoint di input e di output per la comunicazione bidirezionale.
  • Quando trovi l'endpoint corretto, apri un UsbDeviceConnection su quell'endpoint.
  • Fornisci i dati che vuoi trasmettere sull'endpoint con il metodo bulkTransfer() o controlTransfer(). Devi eseguire questo passaggio in un altro thread per evitare di bloccare il thread dell'interfaccia utente principale. Per ulteriori informazioni sull'utilizzo dei thread in Android, consulta Processi e Thread.

Lo snippet di codice riportato di seguito rappresenta un modo semplice per eseguire un trasferimento di dati sincrono. Il tuo codice dovrebbe avere più logica per trovare correttamente l'interfaccia e gli endpoint corretti con cui comunicare. Inoltre, deve eseguire il trasferimento dei dati in un thread diverso rispetto al thread dell'interfaccia utente principale:

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

Per inviare i dati in modo asincrono, utilizza la classe UsbRequest per initialize e queue una richiesta asincrona, poi attendi il risultato con requestWait().

Interruzione della comunicazione con un dispositivo

Quando hai finito di comunicare con un dispositivo o se il dispositivo è stato scollegato, chiudi UsbInterface e UsbDeviceConnection chiamando releaseInterface() e close(). Per ascoltare eventi scollegati, crea un ricevitore di trasmissione come di seguito:

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

La creazione del ricevitore di trasmissione all'interno dell'applicazione e non del manifest consente all'applicazione di gestire gli eventi scollegati solo mentre è in esecuzione. In questo modo, gli eventi scollegati vengono inviati solo all'applicazione attualmente in esecuzione e non trasmessi a tutte le applicazioni.