8

My widget app is running fine on all android version except 8 Oreo. I get a W/BroadcastQueue: Background execution not allowed: receiving Intent message.

There is an interesting blog from CommonsWare but I don't fully understand why it applies to my case. https://commonsware.com/blog/2017/04/11/android-o-implicit-broadcast-ban.html

My case looks pretty simple: I have a widget with a button and I want to change the text's button when it is clicked.

What is the right way to fix this issue?

TestWidget.java

public class TestWidget extends AppWidgetProvider {
    private static RemoteViews views;
    private static boolean buttonClicked = false;
    public static final String ACTION_AUTO_UPDATE = "AUTO_UPDATE";

    @Override
    public void onReceive(Context context, Intent intent)
    {
        super.onReceive(context, intent);

        if(intent.getAction().equals(ACTION_AUTO_UPDATE))
        {
                Log.i("TESTWID", "get onReceive");
        }
    }

    static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
                                int appWidgetId) {
        views = new RemoteViews(context.getPackageName(), R.layout.test_widget);
        views.setOnClickPendingIntent(R.id.wid_btn_tst, setButton(context));

        appWidgetManager.updateAppWidget(appWidgetId, views);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        Log.i("TESTWID", "onupdate ");

        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

    public static PendingIntent setButton(Context context) {
        Intent intent = new Intent();
        intent.setAction("TEST");
        return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }

    public static void pushWidgetUpdate(Context context, RemoteViews remoteViews) {
        ComponentName myWidget = new ComponentName(context, TestWidget.class);
        AppWidgetManager manager = AppWidgetManager.getInstance(context);
        manager.updateAppWidget(myWidget, remoteViews);
    }

}

TestWidgetReceiver.java

public class TestWidgetReceiver extends BroadcastReceiver{
    private static boolean isButtonON = false;

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i("TESTWID", "onReceive "+intent.getAction());

        if(intent.getAction().equals("TEST")){
            updateWidgetButton(context, 2);
        }
    }

    private void updateWidgetButton(Context context, int index) {
        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.test_widget);
        if(index == 2) {
            if(isButtonON) {
                remoteViews.setTextViewText(R.id.wid_btn_tst, "Test Off");
                isButtonON = false;
            }
            else{
                remoteViews.setTextViewText(R.id.wid_btn_tst, "Test On");
                isButtonON = true;
            }
        }

        TestWidget.pushWidgetUpdate(context.getApplicationContext(), remoteViews);
    }

}

Manifest.xml:

<application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="Test"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver android:name=".TestWidget">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <intent-filter>
                <action android:name="AUTO_UPDATE" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/test_widget_info" />
        </receiver>

        <receiver
            android:name=".TestWidgetReceiver"
            android:label="widgetBroadcastReceiver" >
            <intent-filter>
                <action android:name="TEST" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/test_widget_info" />
        </receiver>

    </application>
narb
  • 958
  • 1
  • 13
  • 39

2 Answers2

16

It's subtle, but it is because of the implicit broadcast being used to trigger your TestWidgetReceiver. It is implicit because it is only specifying the action portion of the Intent. Make the broadcast Intent explicit by specifying the receiver class in the constructor:

public static PendingIntent setButton(Context context) {
    Intent intent = new Intent(context, TestWidgetReceiver.class);
    intent.setAction("TEST");
    return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
Larry Schiefer
  • 15,687
  • 2
  • 27
  • 33
  • You are my hero! How did you learn all that? This implicit broadcast notion is nebulous: I need to read about it. Many thanks! – narb Jun 21 '18 at 09:13
  • And why did Android dev do that: breaking backward compatibility? Was my code poorly written? – narb Jun 21 '18 at 09:16
  • It was done mainly to reduce process thrashing and battery consumption. Since an app process can be started by a broadcast, this could cause lots of processes to spin up frequently, even when they really aren't needed. – Larry Schiefer Jun 21 '18 at 09:32
  • It's not that your code was poorly written, just that Google/AOSP continues to refine and tighten up trouble spots over time and this was found to be a big one. You should also be able to better secure your app's handling using the explicit `Intent` and removing the intent filter and action completely. That will make your receiver private to your app, so nothing else can send it an `Intent`. – Larry Schiefer Jun 21 '18 at 09:37
  • how do you stay on top of such changes? if this is not a secret ;) – narb Jun 21 '18 at 09:39
  • 1
    LOL. It can be overwhelming, but reviewing release notes / doc updates with new releases, and often by learning the hard way like you have! – Larry Schiefer Jun 21 '18 at 10:57
1

Things are changed now a days in Android. For the security & battery consumption google introduced so many ways & increased some problem for developers. You haven't passed the Context of the TestWidgetReceiver.class to you intent.

You can do it like that in java

Intent intent = new Intent(context, TestWidgetReceiver.class);

or in kotlin

val intent = Intent(context, TestWidgetReceiver.class);

You can read more changes here

https://developer.android.com/about/versions/oreo/android-8.0-changes

Regards

Shivam Mathur
  • 111
  • 1
  • 3