Question

Since I'm using AlarmManager to perform periodical widget update, I need to ensure onEnabled & onDisabled will work reliably.

However, I realize they will not be triggered sometimes. I'm not the only one who is facing this problem.

Android appWidgetProvider onEnabled never called on tablet

  1. Is there any official bug ticket submitted to Google Android team?
  2. Is there any workaround, especially onDisabled? As I do not want AlarmManager still being triggered repeatably, after the last widget had been removed.

AndroidManifest.xml

    <receiver android:name="org.yccheok.MyAppWidgetProvider"
        android:exported="true" >
        <intent-filter >
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            <action android:name="android.appwidget.action.APPWIDGET_ENABLED" />
            <action android:name="android.appwidget.action.APPWIDGET_DELETED" />
            <action android:name="android.appwidget.action.APPWIDGET_DISABLED" />
        </intent-filter>

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

public class MyAppWidgetProvider extends AppWidgetProvider {

    private static PendingIntent createAlarmUpdatePendingIntent(Context context) {
        Intent intent = new Intent(context, JStockAppWidgetProvider.class);
        intent.setAction(JStockAppWidgetProvider.ALARM_UPDATE_ACTION);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        return pendingIntent;
    }

    @Override
    public void onEnabled(Context context)
    {        
        super.onEnabled(context);

        PendingIntent pendingIntent = createAlarmUpdatePendingIntent(context);
        AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
        int scanSpeed = JStockApplication.instance().getJStockOptions().getScanSpeed();
        alarmManager.setRepeating(AlarmManager.RTC, System.currentTimeMillis() + scanSpeed, scanSpeed, pendingIntent);
    }

    @Override
    public void onDisabled(Context context)
    {
        super.onDisabled(context);

        PendingIntent pendingIntent = createAlarmUpdatePendingIntent(context);
        AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(pendingIntent);
    }
Was it helpful?

Solution

you can check in method a number of instances that are currently running.

private boolean hasInstances(Context context) {
    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    int[] appWidgetIds = appWidgetManager.getAppWidgetIds(
            new ComponentName(context, this.getClass()));
    return (appWidgetIds.length > 0);
}

OTHER TIPS

I afraid you have misunderstood the methods of AppWidgetProvider.

onDeleted(Context context, int[] appWidgetIds)
    Called in response to the ACTION_APPWIDGET_DELETED broadcast **when one or more** AppWidget instances have been deleted. Override this method to implement your own AppWidget functionality.

onDisabled(Context context)
    Called in response to the ACTION_APPWIDGET_DISABLED broadcast, which is sent when the last AppWidget instance for this provider is deleted. Override this method to implement your own AppWidget functionality.

In short, If you have two or more AppWidget instances then if you remove any of them at that time only onDeleted() method for particular widget will be called. If you have only single AppWidget instance then if you remove that time onDesabled() and onDeleted() both will be called.


So you will have to move your code from onDesabled() method to onDeleted() method and it will get called every time.!

Also take care that onEnabled() will be called only for the first instance and not for every next instance you create.

For what I understood you just need one alarm, and I suppose all widgets will be all the same. So, the idea for your alarm is run on service, not on widget. You shouldn't run long time actions on BroadcastReceiver.

We will use the onUpdate and not onEnable (because the design of this demo). Then aways we get a new widget we can perform the service.

AppWidgetProvider:

public class MyAppWidgetProvider extends AppWidgetProvider {
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
              int[] appWidgetIds)
    {
        ComponentName thisWidget = new ComponentName(context, MyAppWidgetProvider.class);
        int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);

        Intent intent = new Intent(context.getApplicationContext(), JStockAppWidgetService.class);
        intent.setAction(JStockAppWidgetService.ALARM_UPDATE_ACTION);

        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds);

        context.startService(intent);
    }

    @Override
    public void onDisabled(Context context)
    {
        super.onDisabled(context);

        Intent intent = new Intent(context.getApplicationContext(), JStockAppWidgetService.class);
        intent.setAction(JStockAppWidgetService.ALARM_STOP_ACTION);
        // I kept this just in case you wanna keep running your alarm without widget. You can just stopService here too.
        context.startService(intent);
    }
}

And here the Service:

public class JStockAppWidgetService extends Service {

    public static final String ALARM_UPDATE_ACTION = "ALARM_UPDATE_ACTION";
    public static final String ALARM_STOP_ACTION = "ALARM_STOP_ACTION";

    //delay to refresh your widget
    private int delay = 10000; //10 secs

    private Thread myThread;

    @Override
    public void onStart(Intent intent, int startId) {
        final int[] allWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);

        final Handler handler = new Handler();
        Runnable runnable = new Runnable() {

            @Override
            public void run() {
                try {
                    //running your timeout while is not interrupted
                    while(!Thread.currentThread().isInterrupted()) {
                        //we need to back to GUI thread
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                runInMyGuiThread(allWidgetIds);
                            }
                        });
                        //everybody needs to sleep sometime =p
                        Thread.sleep(delay);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }
            }
        };

        String action = intent.getAction();
        if(action == ALARM_UPDATE_ACTION) {
            if(myThread != null)
                myThread.interrupt();
            myThread = new Thread(runnable);
            myThread.start();
        } else if(action == ALARM_STOP_ACTION && myThread != null) {
            myThread.interrupt();
        }       
    }

    //here you are in GUI thread with all your widgets id
    public void runInMyGuiThread(int[] allWidgetIds) {
        for(int widgetId : allWidgetIds){
            //do what you want to update each widget
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

}

This is just a little demo, you can stop the service or keep it running without widget, I will let with you.

I was confused by the following situation.

If you have added the widget to the lock screen and then you are testing by adding and removing the widget to the home screen, then you never see onEnabled/onDisabled called. The reason is that there is still a widget added - the lock screen widget.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top