Présentation des hôtes USB

Lorsque votre appareil Android est en mode hôte USB, il agit en tant qu'hôte USB, alimente le bus et énumère les appareils USB connectés. Le mode hôte USB est compatible avec Android 3.1 et versions ultérieures.

Présentation de l'API

Avant de commencer, il est important de comprendre avec quels cours vous allez devoir travailler. Le tableau suivant décrit les API hôtes USB dans le package android.hardware.usb.

Tableau 1. API hôtes USB

Classe Description
UsbManager Vous permet d'énumérer les appareils USB connectés et de communiquer avec eux.
UsbDevice Représente un appareil USB connecté et contient des méthodes permettant d'accéder à ses informations d'identification, interfaces et points de terminaison.
UsbInterface Représente l'interface d'un appareil USB, qui définit un ensemble de fonctionnalités pour l'appareil. Un appareil peut avoir une ou plusieurs interfaces sur lesquelles communiquer.
UsbEndpoint Représente un point de terminaison d'interface, c'est-à-dire un canal de communication pour cette interface. Une interface peut avoir un ou plusieurs points de terminaison, et possède généralement des points de terminaison d'entrée et de sortie pour une communication bidirectionnelle avec l'appareil.
UsbDeviceConnection Représente une connexion à l'appareil, qui transfère des données sur les points de terminaison. Cette classe vous permet d'échanger des données de manière synchrone ou asynchrone.
UsbRequest Représente une requête asynchrone pour communiquer avec un appareil via un UsbDeviceConnection.
UsbConstants Définit les constantes USB qui correspondent aux définitions spécifiées dans linux/usb/ch9.h du kernel Linux.

Dans la plupart des cas, vous devez utiliser toutes ces classes (UsbRequest n'est requis que si vous effectuez une communication asynchrone) lorsque vous communiquez avec un périphérique USB. En général, vous obtenez un UsbManager pour récupérer le UsbDevice souhaité. Une fois que vous disposez de l'appareil, vous devez trouver le UsbInterface et le UsbEndpoint appropriés pour cette interface sur lesquels communiquer. Une fois que vous avez obtenu le point de terminaison approprié, ouvrez un UsbDeviceConnection pour communiquer avec l'appareil USB.

Exigences relatives aux fichiers manifestes Android

La liste suivante décrit ce que vous devez ajouter au fichier manifeste de votre application avant d'utiliser les API hôtes USB:

  • Étant donné que tous les appareils Android ne sont pas garantis pour la compatibilité avec les API hôtes USB, incluez un élément <uses-feature> qui déclare que votre application utilise la fonctionnalité android.hardware.usb.host.
  • Définissez le SDK minimal de l'application sur le niveau d'API 12 ou supérieur. Les API hôtes USB ne sont pas présentes aux niveaux d'API antérieurs.
  • Si vous souhaitez que votre application soit informée d'un appareil USB connecté, spécifiez une paire d'éléments <intent-filter> et <meta-data> pour l'intent android.hardware.usb.action.USB_DEVICE_ATTACHED dans votre activité principale. L'élément <meta-data> pointe vers un fichier de ressources XML externe qui déclare des informations d'identification sur l'appareil que vous souhaitez détecter.

    Dans le fichier de ressources XML, déclarez les éléments <usb-device> pour les appareils USB que vous souhaitez filtrer. La liste suivante décrit les attributs de <usb-device>. En règle générale, utilisez le fournisseur et l'ID produit si vous souhaitez filtrer les données en fonction d'un appareil spécifique, et la classe, la sous-classe et le protocole si vous souhaitez filtrer un groupe de périphériques USB, tels que des périphériques de stockage de masse ou des appareils photo numériques. Vous pouvez spécifier aucun ou tous ces attributs. Si aucun attribut ne correspond à tous les appareils USB, ne procédez ainsi que si votre application le requiert:

    • vendor-id
    • product-id
    • class
    • subclass
    • protocol (appareil ou interface)

    Enregistrez le fichier de ressources dans le répertoire res/xml/. Le nom du fichier de ressources (sans l'extension .xml) doit être identique à celui que vous avez spécifié dans l'élément <meta-data>. Le format du fichier de ressources XML est présenté dans l'exemple ci-dessous.

Exemples de fichiers manifestes et de fichiers de ressources

Voici un exemple de fichier manifeste et le fichier de ressources correspondant:

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

Dans ce cas, le fichier de ressources suivant doit être enregistré dans res/xml/device_filter.xml et spécifie que tout périphérique USB avec les attributs spécifiés doit être filtré:

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

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

Travailler avec des appareils

Lorsque les utilisateurs connectent des appareils USB à un appareil Android, le système Android peut déterminer si votre application est intéressée par l'appareil connecté. Le cas échéant, vous pouvez configurer la communication avec l'appareil si vous le souhaitez. Pour ce faire, votre application doit:

  1. Identifiez les appareils USB connectés à l'aide d'un filtre d'intent afin d'être averti lorsque l'utilisateur connecte un appareil USB ou en énumérant les appareils déjà connectés.
  2. Si ce n'est pas déjà fait, demandez à l'utilisateur l'autorisation de se connecter au périphérique USB.
  3. Communiquer avec le périphérique USB en lisant et en écrivant des données sur les points de terminaison d'interface appropriés

Découvrir un appareil

Votre application peut détecter les appareils USB soit en utilisant un filtre d'intent pour être averti lorsque l'utilisateur le connecte, soit en énumérant les appareils USB qui sont déjà connectés. L'utilisation d'un filtre d'intent est utile si vous souhaitez que votre application détecte automatiquement l'appareil souhaité. L'énumération des appareils USB connectés est utile si vous souhaitez obtenir la liste de tous les appareils connectés ou si votre application n'a filtré aucun intent.

Utiliser un filtre d'intent

Pour que votre application détecte un appareil USB particulier, vous pouvez spécifier un filtre d'intent pour l'intent android.hardware.usb.action.USB_DEVICE_ATTACHED. Parallèlement à ce filtre d'intent, vous devez spécifier un fichier de ressources qui indique les propriétés de l'appareil USB, telles que l'ID du produit et du fournisseur. Lorsque les utilisateurs connectent un appareil correspondant au filtre de votre appareil, le système leur présente une boîte de dialogue qui leur demande s'ils souhaitent lancer votre application. Si les utilisateurs acceptent, votre application est automatiquement autorisée à accéder à l'appareil jusqu'à ce que celui-ci soit déconnecté.

L'exemple suivant montre comment déclarer le filtre d'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'exemple suivant montre comment déclarer le fichier de ressources correspondant, qui spécifie les périphériques USB qui vous intéressent:

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

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

Dans votre activité, vous pouvez obtenir le UsbDevice qui représente l'appareil associé à partir de l'intent comme suit:

Kotlin

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

Java

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

Énumérer les appareils

Si votre application souhaite inspecter tous les appareils USB actuellement connectés lorsqu'elle s'exécute, elle peut énumérer les appareils du bus. Utilisez la méthode getDeviceList() pour obtenir une table de hachage de tous les appareils USB connectés. La table de hachage est associée au nom de l'appareil USB si vous souhaitez en obtenir un à partir de la carte.

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

Si vous le souhaitez, vous pouvez également obtenir un itérateur de la table de hachage et traiter chaque appareil un par un:

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
}

Obtenir l'autorisation de communiquer avec un appareil

Avant de communiquer avec le périphérique USB, votre application doit obtenir l'autorisation de vos utilisateurs.

Remarque:Si votre application utilise un filtre d'intent pour détecter les appareils USB lorsqu'ils sont connectés, elle reçoit automatiquement l'autorisation si l'utilisateur autorise votre application à gérer l'intent. Si ce n'est pas le cas, vous devez demander cette autorisation explicitement dans votre application avant de vous connecter à l'appareil.

Une demande explicite d'autorisation peut être nécessaire dans certaines situations, par exemple lorsque votre application énumère les appareils USB déjà connectés, puis souhaite communiquer avec ces appareils. Vous devez vérifier l'autorisation d'accès à un appareil avant d'essayer de communiquer avec lui. Dans le cas contraire, vous recevrez une erreur d'exécution si l'utilisateur a refusé l'autorisation d'accéder à l'appareil.

Pour obtenir explicitement l'autorisation, commencez par créer un broadcast receiver. Ce récepteur écoute l'intent qui est diffusé lorsque vous appelez requestPermission(). L'appel de requestPermission() affiche une boîte de dialogue demandant à l'utilisateur l'autorisation de se connecter à l'appareil. L'exemple de code suivant montre comment créer le 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);
                }
            }
        }
    }
};

Pour enregistrer le broadcast receiver, ajoutez ceci dans la méthode onCreate() de votre activité:

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

Pour afficher la boîte de dialogue qui demande aux utilisateurs l'autorisation de se connecter à l'appareil, appelez la méthode requestPermission():

Kotlin

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

Java

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

Lorsque les utilisateurs répondent à la boîte de dialogue, votre broadcast receiver reçoit l'intent contenant l'extra EXTRA_PERMISSION_GRANTED, qui est une valeur booléenne représentant la réponse. Vérifiez ce extra pour la valeur "true" avant de vous connecter à l'appareil.

Communiquer avec un appareil

La communication avec un périphérique USB peut être synchrone ou asynchrone. Dans les deux cas, vous devez créer un thread sur lequel effectuer toutes les transmissions de données afin de ne pas bloquer le thread UI. Pour configurer correctement la communication avec un appareil, vous devez obtenir les UsbInterface et UsbEndpoint appropriés pour l'appareil sur lequel vous souhaitez communiquer et envoyer des requêtes sur ce point de terminaison avec un UsbDeviceConnection. En règle générale, votre code doit:

  • Vérifiez les attributs d'un objet UsbDevice, tels que l'ID produit, l'ID du fournisseur ou la classe de l'appareil, pour déterminer si vous souhaitez ou non communiquer avec l'appareil.
  • Lorsque vous êtes certain de vouloir communiquer avec l'appareil, recherchez le UsbInterface approprié que vous souhaitez utiliser pour communiquer ainsi que le UsbEndpoint approprié de cette interface. Les interfaces peuvent avoir un ou plusieurs points de terminaison. Elles possèdent généralement un point de terminaison d'entrée et de sortie pour la communication bidirectionnelle.
  • Une fois que vous avez trouvé le point de terminaison approprié, ouvrez un UsbDeviceConnection sur ce point de terminaison.
  • Fournissez les données que vous souhaitez transmettre sur le point de terminaison à l'aide de la méthode bulkTransfer() ou controlTransfer(). Vous devez effectuer cette étape dans un autre thread pour éviter de bloquer le thread UI principal. Pour en savoir plus sur l'utilisation de threads dans Android, consultez Processus et threads.

L'extrait de code suivant constitue un moyen simple d'effectuer un transfert de données synchrone. Votre code doit disposer de davantage de logique pour trouver correctement l'interface et les points de terminaison appropriés pour communiquer. Il doit également effectuer tout transfert de données dans un thread différent de celui du thread UI principal:

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

Pour envoyer des données de manière asynchrone, utilisez la classe UsbRequest pour initialize et queue une requête asynchrone, puis attendez le résultat avec requestWait().

Interruption de la communication avec un appareil

Lorsque vous avez terminé de communiquer avec un appareil ou si l'appareil a été dissocié, fermez UsbInterface et UsbDeviceConnection en appelant releaseInterface() et close(). Pour écouter les événements dissociés, créez un broadcast receiver comme ci-dessous:

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 création du broadcast receiver dans l'application, et non dans le fichier manifeste, permet à votre application de ne gérer les événements dissociés que pendant son exécution. De cette façon, les événements dissociés ne sont envoyés qu'à l'application en cours d'exécution et non diffusés à toutes les applications.