3

The following code was working on API 23, but it is not working on API 27. I read that there are some restrictions in Android API 26 and above for receiving Broadcast Intents but those are only for the ones specified in Manifest file :

public class USBTest extends Service
{
    private USBMountBroadcastReceiver mountBroadcastReceiver;

    private class USBMountBroadcastReceiver extends BroadcastReceiver
    {
        @Override
        public void onReceive(final Context context, Intent intent)
        {
            String action = intent.getAction();

            if (action == null)
            {
                Log.i(TAG, " got NULL action");
                return;
            }

            if (action.equals(Intent.ACTION_MEDIA_MOUNTED))
            {
                String root = intent.getData().getPath();
                Log.d(TAG, "USB mount path is" + root);
            }
        }
    }

    @Override
    public void onCreate()
    {
        mountBroadcastReceiver = new USBMountBroadcastReceiver();
        IntentFilter usbIntent = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
        usbIntent.addAction(Intent.ACTION_MEDIA_EJECT);
        usbIntent.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
        usbIntent.addAction(Intent.ACTION_MEDIA_REMOVED);
        usbIntent.addDataScheme("file");
        registerReceiver(mountBroadcastReceiver, usbIntent);
    }
}

Update 1 : BroadcastReceiver without service :

public class USBBroadcastReceiver  extends BroadcastReceiver
{
    private static final String TAG = "USBBroadcastReceiver";

    /**
     * @see android.content.BroadcastReceiver#onReceive(Context,Intent)
     */
    @Override public void onReceive(Context context, Intent intent) {

        String action = intent.getAction();

        if (action!=null) Log.i(TAG, "got action = " + action);

        if (action==null)
        {
            Log.i(TAG, "got NULL action");
        }
        else if (Intent.ACTION_MEDIA_MOUNTED.equals(action))
        {
            Log.i(TAG, "Received media mounted : " + action);
        }
    }
}

In manifest :

<receiver android:name="USBBroadcastReceiver" android:exported="True">
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_EJECT" />
        <action android:name="android.intent.action.MEDIA_REMOVED" />
        <action android:name="android.intent.action.MEDIA_MOUNTED" />
        <action android:name="android.intent.action.MEDIA_BAD_REMOVAL" />
        <data android:scheme="file" />
    </intent-filter>
</receiver>

Update 2 : JobIntent service :

public class TestUSB extends JobIntentService
{
    private static final String TAG = "TestUSB";

    /**
     * Unique job ID for this service.
     */
    static final int JOB_ID = 1001;

    @Override
    protected void onHandleWork(Intent intent)
    {
        Log.i(TAG, "onHandleWork");

        String action = intent.getAction();

        if (action!=null) Log.i(TAG, "got action = " + action);
    }
}

In manifest :

<service android:name="TestUSB"
        android:permission="android.permission.BIND_JOB_SERVICE">
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_MOUNTED" />
    </intent-filter>
</service>

Both approaches above are not working.

androidFan
  • 611
  • 2
  • 19
  • 31

3 Answers3

2

From documentation about Oreo's background execution limits:

Android 8.0 places limitations on what apps can do while users aren't directly interacting with them. Apps are restricted in two ways:

  • Background Service Limitations: While an app is idle, there are limits to its use of background services. This does not apply to foreground services, which are more noticeable to the user.
  • Broadcast Limitations: With limited exceptions, apps cannot use their manifest to register for implicit broadcasts. They can still register for these broadcasts at runtime, and they can use the manifest to register for explicit broadcasts targeted specifically at their app.

So there are important Background Service Limitations:

Services running in the background can consume device resources, potentially resulting in a worse user experience. To mitigate this problem, the system applies a number of limitations on services. While an app is in the foreground, it can create and run both foreground and background services freely. When an app goes into the background, it has a window of several minutes in which it is still allowed to create and use services. At the end of that window, the app is considered to be idle. At this time, the system stops the app's background services, just as if the app had called the services' Service.stopSelf() methods.

Under certain circumstances, a background app is placed on a temporary whitelist for several minutes. While an app is on the whitelist, it can launch services without limitation, and its background services are permitted to run.

So, in order to make it work in Android 8.0 (API 26), you can usually replace background services with JobScheduler jobs. Which will schedule tasks to be executed when matching certain conditions.

Or, you can have a service running in the background constantly checking this, but notify the user that there is something running in the background, with a notification, which practically makes this service a foreground service. But once the notification is gone or dismissed, the service stops.

Finally, I think that in your case, you can register your broadcast receiver in the manifest, because although the documentation says

Apps that target Android 8.0 or higher can no longer register broadcast receivers for implicit broadcasts in their manifest.

these broadcasts are exempted from the mentioned limitation:

ACTION_MEDIA_MOUNTED, ACTION_MEDIA_CHECKING, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_EJECT, ACTION_MEDIA_UNMOUNTABLE, ACTION_MEDIA_REMOVED, ACTION_MEDIA_BAD_REMOVAL

so you should be able to make it work, declaring your broadcast receiver in manifest.

Edit:

Try using this code in your manifest

<receiver android:name="USBBroadcastReceiver" android:exported="True">
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_EJECT" />
        <action android:name="android.intent.action.MEDIA_REMOVED" />
        <action android:name="android.intent.action.MEDIA_MOUNTED" />
        <action android:name="android.intent.action.MEDIA_BAD_REMOVAL" />
        <data android:scheme="file" />
    </intent-filter>
</receiver>
  • So if I add it to the manifest, can I use it in a service ? Or do I need to still use a JobScheduler ? – androidFan Aug 16 '18 at 16:43
  • You dont need a service. You add the broadcast to the manifest ( so its already registered), implement the class, and define in onReceive method what you want to do when this intents or actions are "captured". Dont implement inside onReceive long running work, cause the system can kill the entire process after 10 seconds. Instead launch a notification, an activity, or a thread to run off the main thread the code you want. More info [here](https://developer.android.com/guide/components/broadcasts), and at the end of that page about what i said. – Pablo Rodriguez Aug 16 '18 at 16:55
  • I created a new class by extending it from BroadcastReceiver and specified the "android.intent.action.MEDIA_MOUNTED" action in its intent filter in manifest file. But I'm still not receiving the intent in OnReceive() when I plug in the USB. – androidFan Aug 16 '18 at 17:15
  • Maybe you need to specify permissions like ` ` ? – Pablo Rodriguez Aug 16 '18 at 19:08
  • 1
    Tried adding that permission, but still no luck. I think that permission is to actually mount and unmount filesystems from the App. I'm just trying to read the mount path – androidFan Aug 17 '18 at 15:11
  • Added source code of new approaches I tried, in Update 1 and Update 2. But still not working – androidFan Aug 17 '18 at 16:25
  • @androidFan Hey see my edit. Try using that code with the first edit. – Pablo Rodriguez Aug 18 '18 at 09:48
  • Hi @androidFan. Did you try it? – Pablo Rodriguez Aug 21 '18 at 18:02
  • So you tried using the code snippet from manifest with the implementation in your update 1? What do you mean not working? `onReceive()` not executing? – Pablo Rodriguez Aug 23 '18 at 06:51
  • Yes, I tried your code snippet in 'Edit' in my source code for 'Update 1'. onReceive() is not getting called when I plug in my USB flash drive – androidFan Aug 23 '18 at 19:04
  • This doesn't work on a Pixel 1 with Android 9. [Try this project](http://avn.cloud/LtxXjF) on that platform. – Rupert Rawnsley Oct 29 '18 at 12:01
  • 2
    I would suggest also checking that your device is actually firing the broadcast using `adb shell dumpsys activity broadcasts history`. I was having trouble with this issue and found that my device was not even firing the broadcast when plugging in the USB flash drive. – JacquesBauer Dec 30 '19 at 18:43
  • I had to add IntentFilter#addDataScheme("file"); to be able to receive android.intent.action.MEDIA_MOUNTED. Seems strange to me but hey... – JohnyTex Feb 02 '22 at 14:43
  • I also had to add a separate IntentFilter for it. – JohnyTex Feb 03 '22 at 07:40
2

Intent.ACTION_MEDIA_MOUNTED didn't work for me either. What did work is using a ContentObserver like this:

Uri uri = DocumentsContract.buildRootsUri("com.android.externalstorage.documents");

ContentObserver contentObserver = new ContentObserver(handler) {
    @Override
    public void onChange(boolean selfChange) {
        StorageManager sm = (StorageManager) requireContext().getSystemService(Context.STORAGE_SERVICE);
        // Check for new volumes here:
        for (StorageVolume storageVolume : sm.getStorageVolumes()) {
        }
    }
};

context.getContentResolver().registerContentObserver(uri, false, contentObserver);

(Here is the code in Android's source that triggers the change.)

robinst
  • 30,027
  • 10
  • 102
  • 108
1

I struggled with the same problem until finding this SO post: MEDIA_MOUNTED broadcast not being received

Apparently the MEDIA_MOUNTED filter MUST HAVE to be triggered...

Tompi
  • 218
  • 2
  • 10