Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android 3.1 USB-Host - BroadcastReceiver does not receive USB_DEVICE_ATTACHED

Tags:

java

android

usb

I worked through the description and samples for USB host at developer.android.com to detect attached and detached USB-devices.

If I use an intent-filter in the manifest file to start my application when a device is attached, it works perfectly fine: Plug in, device is detected, android asks for permission to start the application, device information is displayed in a table.

The application I'm developing shouldn't be started/finished only if a device is attached/detached (e.g. data management purposes). Also I do not want the open-dialog to pop up if the app is already running. So I decided not to start the activity directly if a device is attached, but to register a BroadcastReceiver, which is (later) supposed to notify the activity if a device is at-/detached. This receiver recognizes the detach-action just fine, but not the attach-action.

Am I missing a permission or data attribute or something like that? The tutorial and samples don't say anything about additional necessary attributes.

Here is the manifest file:

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

And the receiver:

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 } 

I don't understand why the same intent-filter works, if I use them on an activity, but not if they are applied to a receiver? Even if I set up the receiver and filter in code, attaches are not recognized.

My work environment: IDE: Eclipse 3.7 with Android Plugin

Device: Acer Iconia Tab A500

Android: 3.1

Thanks in advance

like image 794
miffi Avatar asked Aug 08 '11 11:08

miffi


People also ask

What is USB host mode Android?

In USB host mode, the Android-powered device acts as the host. Examples of devices include digital cameras, keyboards, mice, and game controllers. USB devices that are designed for a wide range of applications and environments can still interact with Android applications that can correctly communicate with the device.

What is BroadcastReceiver Android?

A broadcast receiver (receiver) is an Android component which allows you to register for system or application events. All registered receivers for an event are notified by the Android runtime once this event happens.


2 Answers

Aha! I figured it out. I was having the exact same problem.

The gist of it is - if you have your application launch automatically when a device is plugged in (using the manifest file), then it appears the Android system gets the ACTION_USB_DEVICE_ATTACHED intent, and then since it knows your application wants to run in that situation, it actually sends your application the android.intent.action.MAIN intent. It never sends the ACTION_USB_DEVICE_ATTACHED action to your application because it thinks it already knows what your application wants to do in that situation.

I've just now identified the problem, and I think I have a solution, but I can tell you what I've found:

Even if your app is running and in the foreground, when you plug in the USB device and the Android system gets the ACTION_USB_DEVICE_ATTACHED intent, it will call onResume() in your activity.

Unfortunately, you cannot just do this:

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

Because the intent will come back as android.intent.action.MAIN, NOT ACTION_USB_DEVICE_ATTACHED.

An annoyingly, you also get android.intent.action.MAIN if you just leave the app, but don't unplug USB. I imagine putting the device to sleep and waking it back up will do the same thing.

So from what I have found, you can't get the intent directly, but it does appear that you can rely on onResume() being called when a USB device is plugged in, so the solution is to just check to see if USB is connected every time you get an onResume. You can also set a flag when USB is disconnected, because of course the USB disconnect intent fires just fine.

So in total, your broadcast receiver might look like this:

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

You'd have this inside of onCreate:

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

This goes inside of the activity tag in your manifest:

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

You'll have a device_filter.xml file in your /res/xml/ folder that looks like this:

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

(of course with whatever vendor IDs and product IDs you need)

And then your onCreate looks something like this:

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

I don't have specific code for checking to see if USB is connected as I actually haven't delved into that yet. I'm using a library that will just connect if it can, so for my application I can just start that loop and I'm good.

Its also probably important to set the launchmode of your activity in the manifest to "singleTask" to prevent it from running again when its already running, or else plugging in a USB device will just launch a second instance of your application!

So my whole activity tag in my manifest looks like this:

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

Anyway, I hope this helps someone! I was surprised that I was unable to find a solution for this already!

like image 164
Taylor Alexander Avatar answered Sep 19 '22 17:09

Taylor Alexander


Just to follow on from @Gusdor's insightful comment (+1): I implemented a check in onNewIntent() that, as @Gusdor points out, is called when your activity launchMode is set as singleTask or singleTop. Then, rather than checking for boolean flags as the accepted answer suggests, simply pass on the intent to your USB broadcast receiver using a LocalBroadcastManager. For example,

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

Then, wherever you're registering your existing (system) USB broadcast receiver, just register the same receiver with a local broadcast manager instance, i.e.,

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

You could send another system broadcast rather than a local broadcast, but I don't think you'll be able to use the action UsbManager.ACTION_USB_ACCESSORY_ATTACHED (system would see that as potential security risk), so you'd have to define your own action. No big deal, but why bother, especially as there's no IPC overhead with local broadcasts.

like image 39
James B Avatar answered Sep 19 '22 17:09

James B