Host USB de Android 3.1: BroadcastReceiver no recibe USB_DEVICE_ATTACHED

Trabajé con la descripción y las muestras para el host USB en developer.android.com para detectar dispositivos USB conectados y desconectados.

Si utilizo un filtro de intención en el archivo de manifiesto para iniciar mi aplicación cuando se conecta un dispositivo, funciona perfectamente bien: Enchúfelo, se detecta el dispositivo, Android solicita permiso para iniciar la aplicación, la información del dispositivo se muestra en una tabla.

La aplicación que estoy desarrollando no se debe iniciar / finalizar solo si un dispositivo está conectado / desconectado (por ejemplo, con fines de gestión de datos). Además, no quiero que aparezca el cuadro de diálogo abierto si la aplicación ya se está ejecutando. Así que decidí no iniciar la actividad directamente si un dispositivo está conectado, sino registrar un BroadcastReceiver, que (más tarde) se supone que notifica la actividad si un dispositivo está conectado / desconectado. Este receptor reconoce la acción de separación muy bien, pero no la acción de adjuntar.

¿Me falta un permiso o atributo de datos o algo así? El tutorial y las muestras no dicen nada sobre los atributos adicionales necesarios.

Aquí está el archivo de manifiesto:

              

Y el receptor:

 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 } 

No entiendo por qué funciona el mismo filtro de intención, si los uso en una actividad, pero no si se aplican a un receptor. Incluso si configuro el receptor y filtro en el código, los archivos adjuntos no son reconocidos.

Mi entorno de trabajo: IDE: Eclipse 3.7 con complemento de Android

Dispositivo: Acer Iconia Tab A500

Android: 3.1

Gracias por adelantado

Aha! Me lo imaginé. Estaba teniendo exactamente el mismo problema.

La esencia de esto es: si tu aplicación se inicia automáticamente cuando un dispositivo está conectado (usando el archivo de manifiesto), entonces parece que el sistema Android obtiene el bash ACTION_USB_DEVICE_ATTACHED y luego sabe que tu aplicación quiere funcionar en esa situación. , en realidad envía a tu aplicación el bash android.intent.action.MAIN. Nunca envía la acción ACTION_USB_DEVICE_ATTACHED a su aplicación porque cree que ya sabe lo que su aplicación quiere hacer en esa situación.

Acabo de identificar el problema y creo que tengo una solución, pero puedo decirles lo que he encontrado:

Incluso si su aplicación se está ejecutando y está en primer plano, cuando conecta el dispositivo USB y el sistema Android obtiene el bash ACTION_USB_DEVICE_ATTACHED, invocará aResume () en su actividad.

Lamentablemente, no puedes simplemente hacer esto:

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

Porque la intención volverá como android.intent.action.MAIN, NO ACTION_USB_DEVICE_ATTACHED.

De manera molesta, también obtienes android.intent.action.MAIN si solo dejas la aplicación, pero no desconectas el USB. Me imagino que poner el dispositivo a dormir y despertarlo nuevamente hará lo mismo.

Por lo que he encontrado, no se puede obtener el bash directamente, pero parece que se puede confiar en que se llame a Reesume () cuando se conecta un dispositivo USB, por lo que la solución es simplemente verificar si USB es conectado cada vez que obtiene un onResume. También puede establecer un indicador cuando el USB está desconectado, porque por supuesto el bash de desconexión del USB dispara muy bien.

Entonces, en total, su receptor de difusión podría verse así:

 // 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; } } }; 

Tendrás esto dentro de onCreate:

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

Esto va dentro de la etiqueta de actividad en su manifiesto:

      

Tendrás un archivo device_filter.xml en tu carpeta / res / xml / que se ve así:

      

(por supuesto con cualquier ID de vendedor e ID de producto que necesite)

Y luego tu onCreate se ve así:

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

No tengo un código específico para verificar si el USB está conectado, ya que aún no me he adentrado en él. Estoy usando una biblioteca que se conectará solo si puede, así que para mi aplicación puedo comenzar ese ciclo y estoy bien.

También es probable que sea importante establecer el modo de inicio de su actividad en el manifiesto en “singleTask” para evitar que se ejecute nuevamente cuando ya se está ejecutando, o si no conecta un dispositivo USB, ¡se iniciará una segunda instancia de su aplicación!

Así que toda mi etiqueta de actividad en mi manifiesto se ve así:

              

De todos modos, espero que esto ayude a alguien! ¡Me sorprendió que ya no pudiera encontrar una solución para esto!

Solo para seguir el perspicaz comentario de @Gusdor (+1): implementé un check en onNewIntent() que, como señala @Gusdor, se llama cuando tu activity launchMode se establece como singleTask o singleTop . Luego, en lugar de buscar indicadores booleanos como lo sugiere la respuesta aceptada, simplemente LocalBroadcastManager la intención a su receptor de difusión USB utilizando LocalBroadcastManager . Por ejemplo,

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

Luego, donde sea que esté registrando su receptor de transmisión USB (de sistema) existente, simplemente registre el mismo receptor con una instancia del administrador de difusión local, es decir,

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

Puede enviar otra transmisión del sistema en lugar de una transmisión local, pero no creo que pueda usar la acción UsbManager.ACTION_USB_ACCESSORY_ATTACHED (el sistema lo consideraría un riesgo de seguridad potencial), por lo que tendría que definir su propia cuenta. acción. No es gran cosa, pero por qué molestarse, especialmente porque no hay gastos indirectos del IPC con transmisiones locales.

La creación del receptor de difusión dentro de la aplicación, y no del manifiesto, le permite a su aplicación manejar solo eventos separados mientras se está ejecutando. De esta forma, los eventos separados solo se envían a la aplicación que se está ejecutando actualmente y no se difunde a todas las aplicaciones.