6

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);
    }
Community
  • 1
  • 1
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875
  • That's "by design". See [Unconfigured home screen widgets are left in limbo](https://code.google.com/p/android/issues/detail?id=2539) and [stop the widget from running in the background](http://stackoverflow.com/a/4710592/1893766) – ozbek Mar 24 '14 at 07:17

4 Answers4

1

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.

MKJParekh
  • 34,073
  • 11
  • 87
  • 98
  • My intention is to have 1 alarm manager for all instances of widgets. – Cheok Yan Cheng Mar 25 '14 at 14:06
  • Then in your above code it's being called only once. onEnabled() called for first instance only and same way onDisabled() is also called once. So what you are wanting is already done by you. @CheokYanCheng – MKJParekh Mar 26 '14 at 04:58
  • No. The problem is, `onDisabled` will not be called reliability. Sometimes it does called. Sometimes it doesn't. – Cheok Yan Cheng Mar 26 '14 at 05:23
  • According to me, `onDisabled` will and It is get called only when there is a single instance of widget and you try to remove it. – MKJParekh Mar 26 '14 at 05:25
  • According to my testing on Nexus S Android 4.1.2, I can confirm `onDisabled` will not be called always, even I remove the last instance of widget from screen. – Cheok Yan Cheng Mar 26 '14 at 05:30
  • Ok, I have only tested with Samsung S3,S4, Tab7,Tab10,Kindle,Zoom these devices and It was the same behavior so I insisted. – MKJParekh Mar 26 '14 at 05:32
1

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.

Scoup
  • 1,323
  • 8
  • 11
0

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);
}
Autocrab
  • 3,474
  • 1
  • 15
  • 15
  • I am afraid, this will not be sufficient to handle the problem reliably. – ozbek Mar 24 '14 at 07:17
  • 1
    I realize sometimes, appWidgetManager's `getAppWidgetIds` will return widget's Id which is already being removed from home screen. – Cheok Yan Cheng Mar 26 '14 at 05:24
  • 1
    This solution is closed. But, it will not work as it is blocked by this bug : https://code.google.com/p/android/issues/detail?id=2539 http://stackoverflow.com/questions/4393144/widget-not-deleted-when-passing-result-canceled-as-result-for-configuration-acti But, since this is the closest answer we can have, I will award to this answer. – Cheok Yan Cheng Mar 27 '14 at 16:52
0

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.

Jörg
  • 2,441
  • 1
  • 20
  • 18