34 votes

Android 3.1 USB-Host - BroadcastReceiver ne reçoit pas USB_DEVICE_ATTACHED

J'ai travaillé la description et les échantillons pour l'hôte USB à developer.Android.com pour détecter les périphériques USB attachés et détachés.

Si j'utilise un filtre d'intention dans le fichier manifeste pour lancer mon application lorsqu'un périphérique est connecté, tout fonctionne parfaitement : Branchez, le périphérique est détecté, Android demande la permission de lancer l'application, les informations sur le périphérique sont affichées dans un tableau.

L'application que je développe ne doit pas être lancée/terminée uniquement si un appareil est attaché/détaché (par exemple, à des fins de gestion des données). De plus, je ne veux pas que la boîte de dialogue d'ouverture s'affiche si l'application est déjà en cours d'exécution. J'ai donc décidé de ne pas démarrer l'activité directement si un appareil est attaché, mais d'enregistrer un BroadcastReceiver, qui est (plus tard) censé notifier l'activité si un appareil est attaché/détaché. Ce récepteur reconnaît très bien l'action de détachement, mais pas l'action d'attachement.

Est-ce qu'il me manque une permission ou un attribut de données ou quelque chose comme ça ? Le tutoriel et les exemples ne disent rien sur les attributs supplémentaires nécessaires.

Voici le fichier du manifeste :

<?xml version="1.0" encoding="utf-8"?>
<manifest 
  xmlns:android="http://schemas.android.com/apk/res/android"
  package="de.visira.smartfdr"
  android:versionCode="1"
  android:versionName="1.0">

<uses-sdk android:minSdkVersion="12" />
<uses-feature android:name="android.hardware.usb.host" />

<application android:icon="@drawable/icon" android:label="@string/app_name">

    <receiver android:name=".usb.Detector">
        <intent-filter>
            <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
        </intent-filter>

        <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
            android:resource="@xml/device_filter" />
        <meta-data android:name="android.hardware.usb.action.USB_DEVICE_DETACHED"
            android:resource="@xml/device_filter" />
    </receiver>
</application>

Et le récepteur :

public class FDRDetector extends BroadcastReceiver{

@Override
public void onReceive(Context context, Intent intent) {
    String action = intent.getAction();

    Toast.makeText(context, "Action: " + action, 3).show();
            // pops up only if action == DETACHED
}

Je ne comprends pas pourquoi le même filtre d'intention fonctionne, si je l'utilise sur une activité, mais pas s'il est appliqué à un récepteur ? Même si je configure le récepteur et le filtre dans le code, les pièces jointes ne sont pas reconnues.

Mon environnement de travail : IDE : Eclipse 3.7 avec le plugin Android

Dispositif : Acer Iconia Tab A500

Android : 3.1

Merci d'avance

38voto

Taylor Alexander Points 449

Aha ! J'ai trouvé. J'avais exactement le même problème.

En résumé, si votre application est lancée automatiquement lorsqu'un appareil est branché (à l'aide du fichier manifeste), il semble que l'application Android soit en train de se lancer. système reçoit l'intention ACTION_USB_DEVICE_ATTACHED et, comme il sait que votre application veut s'exécuter dans cette situation, il lui envoie l'intention Android.intent.action.MAIN. Il n'envoie jamais l'action ACTION_USB_DEVICE_ATTACHED à votre application car il pense qu'il sait déjà ce que votre application veut faire dans cette situation.

Je viens juste d'identifier le problème, et je pense avoir une solution, mais je peux vous dire ce que j'ai trouvé :

Même si votre application est en cours d'exécution et au premier plan, lorsque vous branchez le périphérique USB et que le système Android reçoit l'intention ACTION_USB_DEVICE_ATTACHED, il appelle onResume() dans votre activité.

Malheureusement, vous ne pouvez pas faire ça tout simplement :

@Override
public void onResume() {
    super.onResume();

    Intent intent = getIntent();
    Log.d(TAG, "intent: " + intent);
    String action = intent.getAction();

    if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
        //do something
    } 
}

Parce que l'intention reviendra sous la forme Android.intent.action.MAIN, et non ACTION_USB_DEVICE_ATTACHED.

Un ennuyeux, vous également obtenir Android.intent.action.MAIN si vous quittez l'application, mais ne débranchez pas le port USB. J'imagine que mettre l'appareil en veille et le réveiller fera la même chose.

D'après ce que j'ai trouvé, vous ne pouvez pas obtenir l'intention directement, mais il semble que vous pouvez compter sur l'appel de onResume() lorsqu'un périphérique USB est branché, donc la solution est de vérifier si l'USB est connecté à chaque fois que vous obtenez un onResume. La solution consiste donc à vérifier si l'USB est connecté à chaque fois que vous recevez un onResume. Vous pouvez également définir un drapeau lorsque l'USB est déconnecté, car bien entendu, l'intention de déconnexion de l'USB se déclenche parfaitement.

Au total, votre récepteur de diffusion pourrait donc ressembler à ceci :

// BroadcastReceiver when remove the device USB plug from a USB port  
BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
         if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {                
        usbConnected=false;             
        }
    }
};

Vous l'aurez à l'intérieur de onCreate :

  // listen for new devices
 IntentFilter filter = new IntentFilter();
 filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
 registerReceiver(mUsbReceiver, filter);

Cela va à l'intérieur de la balise d'activité dans votre manifeste :

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

Vous aurez un fichier device_filter.xml dans votre dossier /res/xml/ qui ressemblera à ceci :

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

<resources>
    <usb-device vendor-id="1027" product-id="24577" />  
    <usb-device vendor-id="1118" product-id="688" /> 
</resources>

(bien sûr avec les identifiants des fournisseurs et des produits dont vous avez besoin)

Et puis votre onCreate ressemble à quelque chose comme ça :

@Override
public void onResume() {
    super.onResume();

    Intent intent = getIntent();
    Log.d(TAG, "intent: " + intent);
    String action = intent.getAction();

    if (usbConnected==false ) {
        //check to see if USB is now connected
    } 
}

Je n'ai pas de code spécifique pour vérifier si l'USB est connecté car je ne me suis pas encore penché sur la question. J'utilise une bibliothèque qui se connectera simplement si elle le peut, donc pour mon application, je peux simplement lancer cette boucle et c'est bon.

Il est également important de définir le mode de lancement de votre activité dans le manifeste sur "singleTask" pour éviter qu'elle ne se lance à nouveau lorsqu'elle est déjà en cours d'exécution, sinon le fait de brancher un périphérique USB lancera une deuxième instance de votre application !

Donc l'ensemble de mes activités dans mon manifeste ressemble à ça :

    <activity
        android:label="@string/app_name"
        android:name="com.awitness.common.TorqueTablet"
        android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen"
        android:screenOrientation="landscape"
        android:configChanges="orientation|keyboardHidden" 
        android:launchMode="singleTask"
        >
        <intent-filter >
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.HOME"/>
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter> 

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

En tout cas, j'espère que cela aidera quelqu'un ! J'ai été surpris de ne pas avoir déjà trouvé de solution à ce problème !

8voto

James B Points 558

Juste pour faire suite au commentaire perspicace de @Gusdor (+1) : J'ai implémenté un contrôle dans onNewIntent() qui, comme le souligne @Gusdor, est appelé lorsque votre activité launchMode est défini comme singleTask o singleTop . Ensuite, plutôt que de vérifier les drapeaux booléens comme le suggère la réponse acceptée, il suffit de transmettre l'intention à votre récepteur de diffusion USB à l'aide d'une balise LocalBroadcastManager . Par exemple,

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    if (UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(intent.getAction())) {
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }
}

Ensuite, quel que soit l'endroit où vous enregistrez votre récepteur de diffusion USB existant (système), il suffit d'enregistrer ce même récepteur avec une instance locale du gestionnaire de diffusion, c'est-à-dire,

@Override
protected void onResume() {
    super.onResume();
    myContext.registerReceiver(myUsbBroadcastReceiver, myIntent); // system receiver
    LocalBroadcastManager.getInstance(myContext).registerReceiver(myUsbBroadcastReceiver, intent); // local receiver
}

@Override
protected void onPause() {
    super.onResume();
    myContext.unregisterReceiver(myUsbBroadcastReceiver); // system receiver
    LocalBroadcastManager.getInstance(myContext).unregisterReceiver(myUsbBroadcastReceiver); // local receiver
}

Vous pourriez envoyer une autre diffusion système plutôt qu'une diffusion locale, mais je ne pense pas que vous pourrez utiliser l'action UsbManager.ACTION_USB_ACCESSORY_ATTACHED (le système y verrait un risque potentiel pour la sécurité), vous devrez donc définir votre propre action. Ce n'est pas un gros problème, mais pourquoi s'en préoccuper, d'autant plus qu'il n'y a pas de surcharge IPC avec les diffusions locales.

5voto

soreal Points 31

La création du récepteur de diffusion dans l'application, et non dans le manifeste, permet à votre application de ne traiter que les événements détachés pendant son exécution. Ainsi, les événements détachés sont uniquement envoyés à l'application en cours d'exécution et ne sont pas diffusés à toutes les applications.

0voto

Pablo Valdes Points 16

Je pense que le problème est donné parce que vous avez deux IntentFilters déclarés. Un sur votre manifeste et un sur votre programme. Celui de votre manifeste a plus de priorité que celui de votre programme, c'est pourquoi l'Intent est d'abord filtré par le filtre du manifeste, puis transmis à onNewIntent() de votre activité.

Notez également que si vous n'utilisez pas le filtre manifeste et que vous utilisez uniquement le filtre programmé, votre application ne démarrera pas automatiquement lorsque le périphérique USB sera branché (puisque le filtre programme java n'est même pas encore instancié). Néanmoins, elle le recevra, une fois que votre application est déjà en cours d'exécution et que le filtre d'intention a été enregistré.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X