Présentation des accessoires USB

Le mode accessoire USB permet aux utilisateurs de connecter le matériel hôte USB spécialement conçu pour les appareils Android. Les accessoires doivent respecter le protocole d'accessoires Android décrit dans la documentation du kit de développement d'accessoires Android. Cela permet aux appareils Android qui ne peuvent pas servir d'hôte USB d'interagir avec le matériel USB. Lorsqu'un appareil Android est en mode accessoire USB, l'accessoire USB Android connecté agit en tant qu'hôte, alimente le bus USB et énumère les appareils connectés. Android 3.1 (niveau d'API 12) est compatible avec le mode accessoire USB. Cette fonctionnalité est également rétroportée vers Android 2.3.4 (niveau d'API 10) pour permettre la prise en charge d'un plus large éventail d'appareils.

Choisir les bonnes API d'accessoires USB

Bien que les API d'accessoires USB aient été introduites dans la plate-forme sous Android 3.1, elles sont également disponibles dans Android 2.3.4 via la bibliothèque de modules complémentaires des API Google. Comme ces API ont été rétroportées à l'aide d'une bibliothèque externe, vous pouvez importer deux packages pour prendre en charge le mode accessoire USB. En fonction des appareils Android que vous souhaitez prendre en charge, vous devrez peut-être utiliser l'un plutôt que l'autre:

  • com.android.future.usb: pour prendre en charge le mode accessoire USB sous Android 2.3.4, la bibliothèque de modules complémentaires des API Google inclut les API rétroportées d'accessoires USB, qui sont contenues dans cet espace de noms. Android 3.1 permet également d'importer et d'appeler les classes de cet espace de noms pour prendre en charge les applications écrites avec la bibliothèque de modules complémentaires. Cette bibliothèque de modules complémentaires est un wrapper fin pour les API d'accessoires android.hardware.usb. Elle n'est pas compatible avec le mode hôte USB. Si vous souhaitez prendre en charge le plus large éventail d'appareils compatibles avec le mode accessoire USB, utilisez la bibliothèque de modules complémentaires et importez ce package. Il est important de noter que tous les appareils Android 2.3.4 ne sont pas nécessaires pour prendre en charge la fonctionnalité d'accessoire USB. Chaque fabricant d'appareil décide de prendre en charge ou non cette fonctionnalité. C'est pourquoi vous devez la déclarer dans votre fichier manifeste.
  • android.hardware.usb: cet espace de noms contient les classes compatibles avec le mode accessoire USB sous Android 3.1. Ce package est inclus dans les API du framework. Par conséquent, Android 3.1 est compatible avec le mode accessoire USB sans utiliser de bibliothèque de modules complémentaires. Utilisez ce package si vous ne vous intéressez qu'aux appareils équipés d'Android 3.1 ou d'une version ultérieure, compatibles matériellement avec le mode accessoire USB, que vous pouvez déclarer dans votre fichier manifeste.

Installer la bibliothèque de modules complémentaires des API Google

Si vous souhaitez installer le module complémentaire, vous pouvez installer le package d'API Google pour Android 10 avec SDK Manager. Pour en savoir plus sur l'installation de la bibliothèque de modules complémentaires, consultez la page Installer le module complémentaire API Google.

Présentation de l'API

Étant donné que la bibliothèque de modules complémentaires est un wrapper pour les API de framework, les classes compatibles avec la fonctionnalité d'accessoires USB sont similaires. Vous pouvez utiliser la documentation de référence sur android.hardware.usb même si vous utilisez la bibliothèque de modules complémentaires.

Remarque:Il existe toutefois une différence mineure d'utilisation entre la bibliothèque de modules complémentaires et les API de framework que vous devez connaître.

Le tableau suivant décrit les classes compatibles avec les API d'accessoires USB:

Classe Description
UsbManager Vous permet d'énumérer les accessoires USB connectés et de communiquer avec eux.
UsbAccessory Représente un accessoire USB et contient des méthodes permettant d'accéder à ses informations d'identification.

Différences d'utilisation entre la bibliothèque de modules complémentaires et les API de la plate-forme

Il existe deux différences d'utilisation entre la bibliothèque de modules complémentaires des API Google et les API de la plate-forme.

Si vous utilisez la bibliothèque de modules complémentaires, vous devez obtenir l'objet UsbManager de la manière suivante:

Kotlin

val manager = UsbManager.getInstance(this)

Java

UsbManager manager = UsbManager.getInstance(this);

Si vous n'utilisez pas la bibliothèque de modules complémentaires, vous devez obtenir l'objet UsbManager de la manière suivante:

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager

Java

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);

Lorsque vous filtrez un accessoire connecté avec un filtre d'intent, l'objet UsbAccessory est contenu dans l'intent transmis à votre application. Si vous utilisez la bibliothèque de modules complémentaires, vous devez obtenir l'objet UsbAccessory de la manière suivante:

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

Si vous n'utilisez pas la bibliothèque de modules complémentaires, vous devez obtenir l'objet UsbAccessory de la manière suivante:

Kotlin

val accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY) as UsbAccessory

Java

UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

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 d'accessoires USB. Les exemples de fichiers manifestes et de fichiers de ressources montrent comment déclarer ces éléments:

  • Étant donné que tous les appareils Android ne sont pas forcément compatibles avec les API d'accessoires USB, incluez un élément <uses-feature> qui déclare que votre application utilise la fonctionnalité android.hardware.usb.accessory.
  • Si vous utilisez la bibliothèque complémentaire, ajoutez l'élément <uses-library> en spécifiant com.android.future.usb.accessory pour la bibliothèque.
  • Définissez le SDK minimal de l'application sur le niveau d'API 10 si vous utilisez la bibliothèque de modules complémentaires ou 12 si vous utilisez le package android.hardware.usb.
  • Si vous souhaitez que votre application soit informée d'un accessoire USB associé, spécifiez une paire d'éléments <intent-filter> et <meta-data> pour l'intent android.hardware.usb.action.USB_ACCESSORY_ATTACHED dans votre activité principale. L'élément <meta-data> pointe vers un fichier de ressources XML externe qui déclare les informations d'identification de l'accessoire que vous souhaitez détecter.

    Dans le fichier de ressources XML, déclarez les éléments <usb-accessory> pour les accessoires que vous souhaitez filtrer. Chaque <usb-accessory> peut avoir les attributs suivants:

    • manufacturer
    • model
    • version

    Le filtrage sur version n'est pas recommandé. Un accessoire ou un appareil ne spécifie pas toujours une chaîne de version (intentionnellement ou non). Lorsque votre application déclare un attribut de version à filtrer et que l'accessoire ou l'appareil ne spécifie pas de chaîne de version, une NullPointerException est générée sur les versions antérieures d'Android. Ce problème est résolu dans Android 12.

    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 également 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.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>

Dans ce cas, le fichier de ressources suivant doit être enregistré dans res/xml/accessory_filter.xml et spécifie que tout accessoire associé au modèle, au fabricant et à la version correspondants doit être filtré. L'accessoire envoie les attributs suivants à l'appareil Android:

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

<resources>
    <usb-accessory model="DemoKit" manufacturer="Google" version="1.0"/>
</resources>

Travailler avec des accessoires

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

  1. Découvrez les accessoires connectés à l'aide d'un filtre d'intent qui filtre les événements associés aux accessoires, ou en énumérant les accessoires connectés et en recherchant celui qui convient.
  2. Si ce n'est pas déjà fait, demandez à l'utilisateur l'autorisation de communiquer avec l'accessoire.
  3. Communiquez avec l'accessoire en lisant et en écrivant des données sur les points de terminaison d'interface appropriés.

Découvrir un accessoire

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

Utiliser un filtre d'intent

Pour que votre application détecte un accessoire USB spécifique, vous pouvez spécifier un filtre d'intent qui filtre l'intent android.hardware.usb.action.USB_ACCESSORY_ATTACHED. Parallèlement à ce filtre d'intent, vous devez spécifier un fichier de ressources spécifiant les propriétés de l'accessoire USB, telles que le fabricant, le modèle et la version.

L'exemple suivant montre comment déclarer le filtre d'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>

L'exemple suivant montre comment déclarer le fichier de ressources correspondant, qui spécifie les accessoires USB qui vous intéressent:

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

<resources>
    <usb-accessory manufacturer="Google, Inc." model="DemoKit" version="1.0" />
</resources>

Dans votre activité, vous pouvez obtenir le UsbAccessory qui représente l'accessoire associé à partir de l'intent comme ceci (avec la bibliothèque de modules complémentaires):

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

ou comme ceci (avec les API de la plate-forme):

Kotlin

val accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY) as UsbAccessory

Java

UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

Énumérer les accessoires

Vous pouvez demander à votre application d'énumérer les accessoires qui se sont identifiés pendant son exécution.

Utilisez la méthode getAccessoryList() pour obtenir un tableau de tous les accessoires USB connectés:

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

Remarque : Un seul accessoire connecté à la fois est accepté.

Obtenir l'autorisation de communiquer avec un accessoire

Avant de communiquer avec l'accessoire USB, votre application doit obtenir l'autorisation de vos utilisateurs.

Remarque:Si votre application utilise un filtre d'intent pour détecter les accessoires 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'accessoire.

Une demande explicite d'autorisation peut être nécessaire dans certaines situations, par exemple lorsque votre application énumère des accessoires déjà connectés, puis souhaite communiquer avec ces accessoires. Vous devez vérifier l'autorisation d'accès à un accessoire 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'accessoire.

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'accessoire. 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 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);
                }
            }
        }
    }
};

Pour enregistrer le broadcast receiver, placez 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'accessoire, appelez la méthode requestPermission():

Kotlin

lateinit var accessory: UsbAccessory
...
usbManager.requestPermission(accessory, permissionIntent)

Java

UsbAccessory accessory;
...
usbManager.requestPermission(accessory, 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'accessoire.

Communiquer avec un accessoire

Vous pouvez communiquer avec l'accessoire à l'aide de UsbManager pour obtenir un descripteur de fichier. Vous pouvez configurer des flux d'entrée et de sortie pour lire et écrire des données dans le descripteur. Les flux représentent les points de terminaison groupés d'entrée et de sortie de l'accessoire. Vous devez configurer la communication entre l'appareil et l'accessoire dans un autre thread afin de ne pas verrouiller le thread UI principal. L'exemple suivant montre comment ouvrir un accessoire pour communiquer avec:

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

Dans la méthode run() du thread, vous pouvez lire et écrire sur l'accessoire à l'aide des objets FileInputStream ou FileOutputStream. Lorsque vous lisez des données à partir d'un accessoire avec un objet FileInputStream, assurez-vous que le tampon que vous utilisez est suffisamment volumineux pour stocker les données des paquets USB. Le protocole des accessoires Android accepte les tampons de paquets allant jusqu'à 16 384 octets. Vous pouvez donc choisir de toujours déclarer que votre tampon est de cette taille pour plus de simplicité.

Remarque:À un niveau inférieur, les paquets sont de 64 octets pour les accessoires USB pleine vitesse et de 512 octets pour les accessoires USB haut débit. Le protocole d'accessoire Android regroupe les paquets pour les deux vitesses dans un seul paquet logique par souci de simplicité.

Pour en savoir plus sur l'utilisation de threads dans Android, consultez Processus et threads.

Mettre fin à la communication avec un accessoire

Lorsque vous avez terminé de communiquer avec un accessoire ou si l'accessoire a été dissocié, fermez le descripteur de fichier que vous avez ouvert en appelant 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_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
            }
        }
    }
};

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.