web-dev-qa-db-fra.com

Android 3.1 Hôte USB - BroadcastReceiver ne reçoit pas USB_DEVICE_ATTACHED

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

Si j'utilise un filtre d'intention dans le fichier manifeste pour démarrer mon application lorsqu'un périphérique est connecté, cela fonctionne parfaitement: Plug-in, le périphérique est détecté, Android demande l'autorisation de démarrer 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 démarrée/terminée uniquement si un périphérique est connecté/détaché (par exemple à des fins de gestion des données). De plus, je ne veux pas que la boîte de dialogue ouverte 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 périphérique est connecté, mais d'enregistrer un BroadcastReceiver, qui est (plus tard) censé notifier l'activité si un périphérique est connecté/déconnecté. Ce récepteur reconnaît très bien l'action de détachement, mais pas l'action d'attachement.

Suis-je absent d'une autorisation ou d'un attribut de données ou quelque chose comme ça? Le didacticiel et les exemples ne disent rien sur les attributs supplémentaires nécessaires.

Voici le fichier 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 les utilise sur une activité, mais pas s'ils sont appliqués à un récepteur? Même si j'ai configuré le récepteur et le filtre en code, les attaches ne sont pas reconnues.

Mon environnement de travail: IDE: Eclipse 3.7 avec Android Plugin

Appareil: Acer Iconia Tab A500

Android: 3.1

Merci d'avance

35
miffi

Ah! Je l'ai compris. J'avais exactement le même problème.

L'essentiel est que - si vous lancez automatiquement votre application lorsqu'un appareil est branché (en utilisant le fichier manifeste), alors il apparaît le Android system obtient le ACTION_USB_DEVICE_ATTACHED intention, puis comme il sait que votre application veut s'exécuter dans cette situation, il envoie réellement à votre application 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à quelle est 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 obtient l'intention ACTION_USB_DEVICE_ATTACHED, il appellera onResume () dans votre activité.

Malheureusement, vous ne pouvez pas simplement faire ceci:

@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, PAS ACTION_USB_DEVICE_ATTACHED.

De façon ennuyeuse, vous aussi obtenez Android.intent.action.MAIN si vous quittez simplement l'application, mais ne débranchez pas l'USB. J'imagine que mettre l'appareil en veille et le réveiller fera la même chose.

Donc, 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 Resume () lorsqu'un périphérique USB est branché, donc la solution est de simplement vérifier si USB est connecté chaque fois que vous obtenez un onResume. Vous pouvez également définir un indicateur lorsque l'USB est déconnecté, car bien sûr, l'intention de déconnexion USB se déclenche très bien.

Donc, au total, votre récepteur de diffusion pourrait 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 auriez ceci à 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 ressemble à 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 ID de fournisseur et de produit dont vous avez besoin)

Et puis votre onCreate ressemble à ceci:

@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 l'ai pas encore exploré. J'utilise une bibliothèque qui se connectera simplement si c'est possible, donc pour mon application, je peux simplement démarrer cette boucle et je vais bien.

Il est également probablement important de définir le mode de lancement de votre activité dans le manifeste sur "singleTask" pour l'empêcher de s'exécuter à nouveau lorsqu'il est déjà en cours d'exécution, ou bien le branchement d'un périphérique USB ne fera que lancer une deuxième instance de votre application!

Donc, toute ma balise d'activité dans mon manifeste ressemble à ceci:

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

Quoi qu'il en soit, j'espère que cela aide quelqu'un! J'ai été surpris de ne pas avoir déjà trouvé de solution à cela!

40
Taylor Alexander

Juste pour faire suite au commentaire perspicace de @ Gusdor (+1): j'ai implémenté une vérification dans onNewIntent() qui, comme le souligne @Gusdor, est appelée lorsque votre activité launchMode est définie comme singleTask ou singleTop. Ensuite, plutôt que de rechercher des indicateurs booléens comme le suggère la réponse acceptée, transmettez simplement l'intention à votre récepteur de diffusion USB à l'aide d'un 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, partout où vous enregistrez votre récepteur de diffusion USB (système) existant, enregistrez simplement le même récepteur avec une instance de gestionnaire de diffusion locale, 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 pouvez 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 verrait cela comme un risque potentiel pour la sécurité), vous devez donc définir votre propre action. Ce n'est pas grave, mais pourquoi s'en préoccuper, d'autant plus qu'il n'y a pas de surcharge IPC avec les émissions locales.

8
James B

La création du récepteur de diffusion dans l'application, et non dans le manifeste, permet à votre application de gérer uniquement les événements détachés pendant son exécution. De cette façon, 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.

5
soreal