Panoramica degli host USB

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

Panoramica dell'API

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

Tabella 1. API host USB

Classe Descrizione
UsbManager Consente di elencare e comunicare con i dispositivi USB connessi.
UsbDevice Rappresenta un dispositivo USB collegato e contiene metodi per accedere alla sua identificazione informazioni, interfacce ed endpoint.
UsbInterface Rappresenta un'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, che è un canale di comunicazione per questa interfaccia. Un può avere uno o più endpoint e di solito ha endpoint di input e output comunicazione bidirezionale con il dispositivo.
UsbDeviceConnection Rappresenta una connessione al dispositivo che trasferisce i dati sugli endpoint. Questo corso consente di inviare e ricevere dati in modo sincrono o asincrono.
UsbRequest Rappresenta una richiesta asincrona di comunicare con un dispositivo mediante un UsbDeviceConnection.
UsbConstants Definisce le costanti USB che corrispondono alle definizioni in linux/usb/ch9.h dell'ambiente Linux in un kernel.

Nella maggior parte dei casi, devi utilizzare tutte queste classi (UsbRequest è necessaria solo se esegui una comunicazione asincrona) durante la comunicazione con un dispositivo USB. In generale, ottieni un UsbManager per recuperare il UsbDevice desiderato. Una volta in possesso del dispositivo, dovrai trovare il UsbInterface appropriato e i UsbEndpoint di quel dispositivo su cui comunicare. Una volta ottenuto l'endpoint corretto, apri un UsbDeviceConnection per comunicare con il dispositivo USB.

Requisiti per il file manifest di Android

Nell'elenco seguente viene descritto ciò che è necessario aggiungere al file manifest della tua applicazione prima utilizzo delle API host USB:

  • Poiché non è garantito che tutti i dispositivi Android supportino le API host USB, includi un elemento <uses-feature> che dichiara 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 desideri che la tua applicazione riceva notifiche relative a un dispositivo USB collegato, specifica Coppia di elementi <intent-filter> e <meta-data> per android.hardware.usb.action.USB_DEVICE_ATTACHED intent nell'attività principale. La L'elemento <meta-data> rimanda a un file di risorse XML esterno che dichiara informazioni identificative sul dispositivo che vuoi rilevare.

    Nel file di risorse XML, dichiara gli elementi <usb-device> per l'USB dispositivi che vuoi filtrare. Nell'elenco che segue vengono descritti gli attributi di <usb-device>. In generale, utilizza il fornitore e l'ID prodotto se vuoi filtrare per un dispositivo specifico e utilizza classe, sottoclasse e protocollo se vuoi filtrare in base a un gruppo di dispositivi USB, quali fotocamere digitali o dispositivi di archiviazione di massa. Non puoi specificare nessuno o tutti questi attributi. Se non specifichi nessun attributo corrisponde a tutti i dispositivi USB, quindi esegui questa operazione se la tua 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 nel Elemento <meta-data>. Il formato del file di risorse XML è esempio riportato di seguito.

Esempi di file manifest e di 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 res/xml/device_filter.xml e specifica che qualsiasi dispositivo USB con 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>

Lavora con i dispositivi

Quando gli utenti collegano i dispositivi USB a un dispositivo Android, il sistema Android è in grado di determinare se la tua applicazione è interessata al dispositivo connesso. In tal caso, puoi configurare comunicare con il dispositivo, se lo desideri. Per farlo, la tua applicazione deve:

  1. Individuare i dispositivi USB connessi utilizzando un filtro per intent per ricevere notifiche quando l'utente collega un dispositivo USB o elenca i dispositivi USB già connessi.
  2. Chiedi all'utente l'autorizzazione a connettersi al dispositivo USB, se non l'hai già ottenuta.
  3. Comunica con il dispositivo USB leggendo e scrivendo i dati nell'interfaccia appropriata endpoint.

Scopri un dispositivo

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

Utilizzare un filtro per intent

Per fare in modo che l'applicazione rilevi un determinato dispositivo USB, puoi specificare un filtro per intent filtro per l'intent android.hardware.usb.action.USB_DEVICE_ATTACHED. Insieme a questo filtro per intent, devi specificare un file di risorse che specifichi le proprietà dell'interfaccia dispositivo, come l'ID prodotto e l'ID del fornitore. Quando gli utenti connettono un dispositivo che corrisponde al tuo di Google, il sistema visualizza una finestra di dialogo che chiede se desiderano avviare l'applicazione. Se gli utenti accettano, la tua applicazione avrà automaticamente l'autorizzazione ad accedere al dispositivo finché il dispositivo disconnesso.

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 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 collegato per questo scopo:

Kotlin

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

Java

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

Enumera i dispositivi

Se la tua applicazione è interessata a controllare tutti i dispositivi USB attualmente collegati 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 in base al nome del dispositivo USB, se desideri 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 singolo dispositivo per uno:

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 per comunicare con un dispositivo

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

Nota: se la tua applicazione utilizza una filtro per intent per rilevare i dispositivi USB quando sono connessi, riceve automaticamente se l'utente consente alla tua applicazione di gestire l'intent. In caso contrario, devi richiedere esplicitamente nella tua applicazione prima di connetterti al dispositivo.

Potrebbe essere necessario richiedere esplicitamente l'autorizzazione in alcune situazioni, ad esempio quando che elenca i dispositivi USB che sono già connessi e che successivamente vuole comunicare uno. Prima di provare a comunicare con un dispositivo, devi verificare l'autorizzazione di accesso. Se no, riceverai un errore di runtime se l'utente ha negato l'autorizzazione ad accedere al dispositivo.

Per ottenere l'autorizzazione esplicitamente, crea prima un broadcast receiver. Questo ricevitore è in ascolto l'intent che viene trasmesso quando chiami requestPermission(). La chiamata a requestPermission() mostra una finestra di dialogo al utente che chiede l'autorizzazione per connettersi al dispositivo. Il seguente codice di esempio mostra come crea il 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);
                }
            }
        }
    }
};

Per registrare il broadcast receiver, aggiungi questo nel metodo onCreate() nel tuo 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), 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);

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 tuo broadcast receiver riceve l'intent che contiene la EXTRA_PERMISSION_GRANTED extra, che è un valore booleano che rappresentano la risposta. Controlla questo extra per verificare se è presente il valore true prima di eseguire la connessione al dispositivo.

Comunicare con un dispositivo

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

  • Controlla gli attributi di un oggetto UsbDevice, come ID prodotto, dell'ID del fornitore o della classe del dispositivo per capire se vuoi comunicare dispositivo.
  • Quando hai la certezza di voler comunicare con il dispositivo, trova l'app appropriata UsbInterface che vuoi utilizzare per comunicare insieme al UsbEndpoint appropriata dell'interfaccia. Le interfacce possono avere uno o più endpoint e di solito hanno un endpoint di input e output per comunicazione.
  • 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(). Dovresti esegui questo passaggio in un altro thread per evitare il blocco del thread dell'interfaccia utente principale. Per ulteriori informazioni informazioni sull'utilizzo dei thread in Android, consulta l'articolo Processi e Thread.

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

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 a initialize e queue una richiesta asincrona, quindi 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 chiamata a releaseInterface() e close(). Per rimanere in ascolto degli eventi scollegati, crea un broadcast receiver come quello riportato 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 broadcast receiver all'interno dell'applicazione, non del manifest, consente per gestire gli eventi scollegati solo mentre è in esecuzione. In questo modo, gli eventi scollegati vengono inviate solo all'applicazione attualmente in esecuzione e non trasmesse a tutte le applicazioni.