4

Why would the following Ordered Broadcast fail in Android Oreo, unless I specifically set the package name?

final Intent vrIntent = new Intent(RecognizerIntent.ACTION_GET_LANGUAGE_DETAILS);

// Setting the package it will work. Omitting, it will fail
// vrIntent.setPackage("com.google.android.googlequicksearchbox");

getContext().sendOrderedBroadcast(vrIntent, null, new BroadcastReceiver() {

    @Override
    public void onReceive(final Context context, final Intent intent) {

                // final Bundle bundle = intent.getExtras();
                final Bundle bundle = getResultExtras(true);

                if (bundle != null) {

                    if (bundle.containsKey(RecognizerIntent.EXTRA_SUPPORTED_LANGUAGES)) {
                        Log.i("TAG", "onReceive: EXTRA_SUPPORTED_LANGUAGES present");

                        final ArrayList<String> vrStringLocales = bundle.getStringArrayList(
                                RecognizerIntent.EXTRA_SUPPORTED_LANGUAGES);

                        Log.i("TAG", "onReceive: EXTRA_SUPPORTED_LANGUAGES size: " + vrStringLocales.size());

                    } else {
                        Log.w("TAG", "onReceive: missing EXTRA_SUPPORTED_LANGUAGES");
                    }

                } else {
                    Log.w("TAG", "onReceive: Bundle null");
                }

}, null, 1234, null, null);

If the package name is not set, EXTRA_SUPPORTED_LANGUAGES will be missing.

I recently asked a bounty question where my 'legacy code', which didn't set the package name, was failing in Oreo, but worked successfully on previous Android versions.

Having checked all of the behavioural changes in API 26 I can see nothing that would explain this.

Can anyone shed some light on the possible cause please?

Note: The sample code and issue assumes the device has Google's 'Now' application installed to provide the RecognitionService

brandall
  • 6,094
  • 4
  • 49
  • 103
  • What exactly does "fail" mean in this context? Bear in mind that you cannot register for implicit broadcasts in the manifest anymore, so if "fail" means "nothing seems to be triggered by the `sendOrderedBroadcast()` call", then that would be a problem in whatever portion of Android is supposed to respond to `ACTION_GET_LANGUAGE_DETAILS`. – CommonsWare Feb 06 '18 at 23:36
  • @CommonsWare I'll clarify in the question. I mean the `EXTRA_SUPPORTED_LANGUAGES` will be missing. – brandall Feb 06 '18 at 23:43
  • Is it just that extra that is missing, or is the entire `Intent` empty? Is the result code still 1234 (not sure where you find out...)? I haven't played with ordered broadcasts much recently, let alone on Android 8.0+. But I can see where Android might wind up just invoking your final `BroadcastReceiver`, after finding no valid receivers for an implicit broadcast. – CommonsWare Feb 06 '18 at 23:49
  • @CommonsWare the `Intent` is empty. The result code is still 1234. Although setting a package name would suggest it becomes 'explicit', the receiver would still be implicit in nature, given the multiple `SpeechRecognizers` that could be present on a device? – brandall Feb 06 '18 at 23:53
  • Well, `setPackage()` tends to make an `Intent` "explicit enough" for many purposes, including for getting around the ban on implicit `Intents`. Rather than hard-coding that package name, though, you could try an ordered variant of the `sendImplicitBroadcast()` workaround method that I have towards the end of [this blog post on the ban](https://commonsware.com/blog/2017/04/11/android-o-implicit-broadcast-ban.html). BTW, since I'm not finding it in the docs... where does that result code get delivered to you? I'd like to run some experiments. – CommonsWare Feb 06 '18 at 23:57
  • @CommonsWare Agreed on the implicit vs explicit. I always find your blogs very useful! You can get the result code by calling `getResultCode()` inside the receiver. I'll have a read of your blog and check `sendImplicitBroadcast()` which I'm not familiar with. Thank you for the advice. BTW, off-topic, if you're ever bored over a coffee, I'd love to see what you make of my project https://github.com/brandall76/Saiy-PS :) – brandall Feb 07 '18 at 00:07
  • "You can get the result code by calling getResultCode() inside the receiver" -- ah! I was looking at `Intent` and trying to figure out where it was stored, and not finding anything. Many thanks! With regards to your project, kudos on the documentation. I am deeply concerned about virtual assistants as a category, so I'm not a particularly good person to provide feedback on the project itself. And thanks for the kind words! – CommonsWare Feb 07 '18 at 00:11
  • @CommonsWare well if the singularity is coming regardless, it would surely be best to have at least submitted a commit to make it happen! :) – brandall Feb 07 '18 at 00:14

1 Answers1

3

OK, I reproduced the problem. The 1234 result code was a red herring — it looks like the process behind RecognizerIntent does not set a result code, so you get the initial code.

However, you do get this error message on Android 8.1 (and, presumably, 8.0):

W/BroadcastQueue: Background execution not allowed: receiving Intent { act=android.speech.action.GET_LANGUAGE_DETAILS flg=0x10 } to com.google.android.googlequicksearchbox/com.google.android.voicesearch.intentapi.IntentApiReceiver

That's the "you registered a receiver in the manifest, and we're not going to give you the broadcast, because you are in the background" error.

This lightly-tested sendImplicitOrderedBroadcast() method works around the problem, while in principle maintaining the order of receivers (descending by priority):

  private void sendImplicitOrderedBroadcast(Intent i, String receiverPermission,
                                            BroadcastReceiver resultReceiver,
                                            Handler scheduler, int initialCode,
                                            String initialData,
                                            Bundle initialExtras) {
    PackageManager pm=getPackageManager();
    List<ResolveInfo> matches=pm.queryBroadcastReceivers(i, 0);

    Collections.sort(matches,
      (left, right) -> right.filter.getPriority()-left.filter.getPriority());

    for (ResolveInfo resolveInfo : matches) {
      Intent explicit=new Intent(i);
      ComponentName cn=
        new ComponentName(resolveInfo.activityInfo.applicationInfo.packageName,
          resolveInfo.activityInfo.name);

      explicit.setComponent(cn);
      sendOrderedBroadcast(explicit, receiverPermission, resultReceiver,
        scheduler, initialCode, initialData, initialExtras);
    }
  }

I took the liberty of filing an issue.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Thank you so much. I couldn't have written that filed issue any better myself! I made some incorrect assumptions - first being that this would obviously be whitelisted *sigh* and secondly that implicit intents declared in the Manifest aren't completely ignored now (i.e. run time registrations only accepted). It now makes sense to me what you said in your comment, regarding declaring a package name changing the implicit to explicit. I shall be using your `sendImplicitOrderedBroadcast()` aplenty! Thanks again. – brandall Feb 08 '18 at 01:03
  • 1
    Ok, so I really don't understand what's happening there, but it appears you just saved both my life, my bosse's life, our integrator's life and the final customer's life. Thanks. – dim Jan 08 '19 at 14:08
  • On Android 11 (Galaxy S10e) I'm getting an NPE due to a null filter. So I needed to adapt the comparator accordingly. – Mark Jul 04 '21 at 04:41
  • Even using this workaround, the Pixel 7A (Android 13) is reporting empty Bundle. – Mark Jul 29 '23 at 09:16