11

I am trying to update a Widget more frequently than the 30 minute restriction imposed by the 1.6docs. After reading nearly every post in SO, and the developer docs, and various other sources, I thought I had got to a point where i could implement it. And so, I tried, and failed. Since then, I have trawled yet more forums and solutions, and I cannot seem to get it to update.

I have an Update class that sets the AlarmManager:

public class Update extends Service{

    @Override
    public void onStart(Intent intent, int startId) {
          String currentTemp = Battery.outputTemp;
          String currentLevel = Battery.outputLevel;
          String currentCard = Battery.outputCard;
          String currentInternal = Battery.memory;
          String currentRam = String.valueOf(Battery.outputRam).substring(0, 3) + "MB";

          // Change the text in the widget
          RemoteViews updateViews = new RemoteViews( 
          this.getPackageName(), R.layout.main);
          //update temp
          updateViews.setTextViewText(R.id.batteryTemp, currentTemp); 
          //update %
          updateViews.setTextViewText(R.id.batteryLevel, currentLevel);     
          //update level
          updateViews.setTextViewText(R.id.sdCard, currentCard);
          //update internal memory
          updateViews.setTextViewText(R.id.internal, currentInternal);
          //update ram
          updateViews.setTextViewText(R.id.ram, currentRam);

          ComponentName thisWidget = new ComponentName(this, Widget.class);
          AppWidgetManager manager = AppWidgetManager.getInstance(this);
          manager.updateAppWidget(thisWidget, updateViews);

    }
    @Override
    public IBinder onBind(Intent intent) {
        // no need to bind
        return null;
    }

}

This has caused my onReceive in my widget class to fire frequently (i have a toast to see when it fires), yet it carries no intent (the toast is meant to display this as they are received but it is blank).

I cannot figure it out (i'm a relative newb-2 months of slow android dev), and appreciate any insight you guys have.

heres my widget class for reference:

    public class Widget extends AppWidgetProvider {


    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
            int[] appWidgetIds) {

        AlarmManager alarmManager;
        Intent intent = new Intent(context, Update.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
                intent, PendingIntent.FLAG_UPDATE_CURRENT);
        alarmManager = (AlarmManager) context
                .getSystemService(Context.ALARM_SERVICE);
        Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(System.currentTimeMillis());
        cal.add(Calendar.SECOND, 10);
        alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, cal
                .getTimeInMillis(), 5 * 1000, pendingIntent);

        String currentTemp = Battery.outputTemp;
        String currentLevel = Battery.outputLevel;
        String currentCard = Battery.outputCard;
        String currentInternal = Battery.memory;
        String currentRam = String.valueOf(Battery.outputRam).substring(0, 3)
                + "MB";

        // Change the text in the widget
        RemoteViews updateViews = new RemoteViews(context.getPackageName(),
                R.layout.main);
        // update temp
        updateViews.setTextViewText(R.id.batteryTemp, currentTemp);
        appWidgetManager.updateAppWidget(appWidgetIds, updateViews);
        // update %
        updateViews.setTextViewText(R.id.batteryLevel, currentLevel);
        appWidgetManager.updateAppWidget(appWidgetIds, updateViews);
        // update level
        updateViews.setTextViewText(R.id.sdCard, currentCard);
        appWidgetManager.updateAppWidget(appWidgetIds, updateViews);
        // update internal memory
        updateViews.setTextViewText(R.id.internal, currentInternal);
        appWidgetManager.updateAppWidget(appWidgetIds, updateViews);
        // update ram
        updateViews.setTextViewText(R.id.ram, currentRam);
        appWidgetManager.updateAppWidget(appWidgetIds, updateViews);
        super.onUpdate(context, appWidgetManager, appWidgetIds);

    }

    public void onReceive(Context context, Intent intent) {

        super.onReceive(context, intent);
        Toast
                .makeText(context, intent.getAction() + context,
                        Toast.LENGTH_LONG).show();
        Bundle extras = intent.getExtras();
        if (extras != null) {
            AppWidgetManager appWidgetManager = AppWidgetManager
                    .getInstance(context);
            ComponentName thisAppWidget = new ComponentName(context
                    .getPackageName(), Widget.class.getName());
            int[] appWidgetIds = appWidgetManager
                    .getAppWidgetIds(thisAppWidget);

            onUpdate(context, appWidgetManager, appWidgetIds);
        }

    }
}
doydoy
  • 4,021
  • 3
  • 20
  • 33
  • 3
    This is my question from a few years back, regarding my first ever android program (and indeed one of my first programming project). Oh how it makes me cringe to look at my previous code. – doydoy Oct 30 '13 at 11:04

4 Answers4

27

This is my solution, how to automatically update widget more frequently than the 30 minutes. I use AlarmManager. Before you use AlarmManager for refreshing appwidget, make sure you know what you do, because this technique could drain the device's battery.

Read more about widget update in Android doc - especially about updatePeriodMillis parameter.

This is part of my Manifest.xml. I define custom action AUTO_UPDATE.

<receiver android:name=".appwidget.AppWidget" >
    <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/appwidget_info" />
</receiver>

This is part of my AppWidget.java. In onReceive method, I handle my custom action AUTO_UPDATE. In onEnabled and onDisabled methods, I start/stop alarm.

public class AppWidget extends AppWidgetProvider
{
    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))
        {
            // DO SOMETHING
        }

        ...
    }

    @Override
    public void onEnabled(Context context)
    {
        // start alarm
        AppWidgetAlarm appWidgetAlarm = new AppWidgetAlarm(context.getApplicationContext());
        appWidgetAlarm.startAlarm();
    }

    @Override
    public void onDisabled(Context context)
    {
        // stop alarm only if all widgets have been disabled
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
        ComponentName thisAppWidgetComponentName = new ComponentName(context.getPackageName(),getClass().getName());
        int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidgetComponentName);
        if (appWidgetIds.length == 0) {
            // stop alarm
            AppWidgetAlarm appWidgetAlarm = new AppWidgetAlarm(context.getApplicationContext());
            appWidgetAlarm.stopAlarm();
    }

    }

    ...
}

This is my AppWidgetAlarm.java, which starts/stops alarm. Alarm manager sends broadcast to AppWidget.

public class AppWidgetAlarm
{
    private final int ALARM_ID = 0;
    private final int INTERVAL_MILLIS = 10000;

    private Context mContext;


    public AppWidgetAlarm(Context context)
    {
        mContext = context;
    }


    public void startAlarm()
    {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MILLISECOND, INTERVAL_MILLIS);

        Intent alarmIntent=new Intent(mContext, AppWidget.class);
        alarmIntent.setAction(AppWidget.ACTION_AUTO_UPDATE);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, ALARM_ID, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);

        AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        // RTC does not wake the device up
        alarmManager.setRepeating(AlarmManager.RTC, calendar.getTimeInMillis(), INTERVAL_MILLIS, pendingIntent);
    }


    public void stopAlarm()
    {
        Intent alarmIntent = new Intent(AppWidget.ACTION_AUTO_UPDATE);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, ALARM_ID, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);

        AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(pendingIntent);
    }
}
Robert
  • 1,357
  • 15
  • 26
petrnohejl
  • 7,581
  • 3
  • 51
  • 63
  • Hello - I try your alarm example however onReceive() never gets the ACTION_AUTO_UPDATE broadcast. Any ideas why? – wufoo Mar 27 '13 at 20:58
  • Look at my AppWidgetAlarm.startAlarm() method. I create pendingIntent, which should send ACTION_AUTO_UPDATE broadcast. That intent is executed by alarm event. Check if your alarm works. – petrnohejl Mar 28 '13 at 09:32
  • Still not working. If you would be so kind, please see my code post on pastebin (http://pastebin.com/mr4Gih0i). ---- EDIT: just noticed a problem with the code. I will fix it but I forgot to paste in the appWidgetAlarm class :/ – wufoo Mar 28 '13 at 14:20
  • Hi I updated the paste (http://pastebin.com/uVu5Dtwt). I think part of the problem is I'm not sure if AppWidget.java is supposed to be replaced with Widget.java? Anyway the only logcat messages I get are from onReceive() and onUpdate(). AlarmManager is setting the alarm in onUpdate() however it never fires back in onRecieve(). Perhaps a Manifest setting or widget_provider.xml ? – wufoo Mar 28 '13 at 14:48
  • 1
    Ok - nevermind. I had the intent-filter for AUTO_UPDATE inside the application stanza. After moving it inside the reciever stanza, my battery is draining correctly ;) – wufoo Mar 28 '13 at 15:38
  • 1
    Simple basic! Thanks :) – narb Jun 09 '18 at 07:12
  • also have a look at this, i had this problem on Oreo: ``W/BroadcastQueue: Background execution not allowed: receiving Intent....`` https://stackoverflow.com/questions/50894567/widget-case-that-doesnt-work-with-oreo-8-1-message-received-w-broadcastqueue/50955019#50955019 – desperateCoder Feb 16 '19 at 15:42
14

I have an Update class that sets the AlarmManager:

No, you don't. AlarmManager appears nowhere in the code snippet.

You do have a reference to AlarmManager in the second code snippet. Problems there include:

  • You are setting a new repeating alarm every time the app widget updates

  • You are setting a 5 second frequency on the alarm, which is utter insanity

  • You are setting a 5 second frequency on a _WAKEUP alarm, which I think is grounds for your arrest in some jurisdictions

  • You have a pointless onReceive() method, even ignoring the temporary Toast

  • You are assuming that there will be an action string on the Intent in your Toast, but you do not specify an action string when you create the Intent that you put in the PendingIntent for the alarm

  • Your code refers to what I presume are static data members on a Battery class, but it is rather likely those are all empty/null... or at least they would be, if you had a sane frequency on the alarm

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Thanks for the response. Firstly, I a 5 second frequency just for testing, when I finally get this working will decrease the frequency to the 1 minute frequency I would like. You mention that using an '_WAKEUP' alarm is not appropriate. Could you elaborate? Why is it not, and maybe why something else would be better? – doydoy Mar 30 '11 at 10:06
  • 2
    @Ben Bullock: A `_WAKEUP` alarm wakes up the phone. Even at a one-minute polling cycle, you will *seriously* drain the user's battery. – CommonsWare Mar 30 '11 at 10:39
  • Oh, okay I misunderstood, I thought it meant it only worked when the phone was awake. – doydoy Mar 30 '11 at 11:27
  • Your final point haunts me more than it should. Seriously so many bad practices, so much ignorance and general poor development highlighted by one action. – doydoy Oct 30 '13 at 11:12
  • @CommonsWare You have mentioned that "You are setting a 5 second frequency on the alarm, which is utter insanity". Is it, even when the alarm is not _WAKEUP? And what if I put in the users hand to choose the update interval? Should I let him/her choose a low interval like 5 or even 2 seconds? Thanks a lot – Milad.Nozari Mar 24 '15 at 06:22
  • 2
    @mnVoh: "Is it, even when the alarm is not _WAKEUP?" -- considering that [Google is no longer supporting sub-minute polling periods starting with Android 5.1](http://commonsware.com/blog/2015/03/23/alarmmanager-regression-android-5p1.html), yes. "Should I let him/her choose a low interval like 5 or even 2 seconds?" -- before, I would have said "sure, perhaps with some additional warning dialog or something". Now, I wouldn't, as it will look like your app is broken on 5.1. – CommonsWare Mar 24 '15 at 11:24
  • @CommonsWare So basically if I wanna stick to AlarmManager I'v gotta change my unit from seconds to minutes. Really good to know, thanks. – Milad.Nozari Mar 24 '15 at 12:23
4

Thanks for this example - I also had problems using a later Android version.

This post made it work for me: widget case that doesn't work (see the answer from Larry Schiefer).

So substituting for this from the code above:

Intent alarmIntent = new Intent(AppWidget.ACTION_AUTO_UPDATE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, ALARM_ID, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);

with this from the ref:

Intent alarmIntent=new Intent(mContext, MyWidget.class);
alarmIntent.setAction(AppWidget.ACTION_AUTO_UPDATE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, ALARM_ID, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);

did the job.

marzetti
  • 369
  • 4
  • 8
2

A little bit modified version of petrnohejl's solution. This one is working in my project. (written in kotlin):

This is part of the Manifest.xml. I added the following actions: AUTO_UPDATE, APPWIDGET_UPDATE, APPWIDGET_ENABLED, APWIDGET_DISABLED.

                <receiver android:name=".AppWidget">
                    <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_DISABLED"/>
                    </intent-filter>
                    <intent-filter>
                        <action android:name="ACTION_AUTO_UPDATE" />
                    </intent-filter>

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

This is part of the AppWidget.kt. Here I implemented the onUpdate(), onEnabled(), onDisabled(), onReceive() functions.

class AppWidget: AppWidgetProvider() {

    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {

        // There may be multiple widgets active, so update all of them
        for (appWidgetId in appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId)
        }
    }

    override fun onEnabled(context: Context) { // Enter relevant functionality for when the first widget is created

        // start alarm
        val appWidgetAlarm = AppWidgetAlarm(context.applicationContext)
        appWidgetAlarm.startAlarm()
    }

    override fun onDisabled(context: Context) { // Enter relevant functionality for when the last widget is disabled

        // stop alarm only if all widgets have been disabled
        val appWidgetManager = AppWidgetManager.getInstance(context)

        if (appWidgetIds.isEmpty()) {
            // stop alarm
            val appWidgetAlarm = AppWidgetAlarm(context.getApplicationContext())
            appWidgetAlarm.stopAlarm()
        }
    }

    companion object {

        val ACTION_AUTO_UPDATE = "AUTO_UPDATE"

        fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {

        val widgetText = Random.nextInt(0, 100).toString()

            // Construct the RemoteViews object
            val views = RemoteViews(context.packageName, R.layout.appwidget)
            views.setTextViewText(R.id.widget_text, widgetText)

            // Instruct the widget manager to update the widget
            appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_text)
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }

    override fun onReceive(context: Context?, intent: Intent?) {
        super.onReceive(context, intent)

        // Do something

        /*if (intent!!.action == ACTION_AUTO_UPDATE) {
            // DO SOMETHING
        }*/
    }
}

And this is the AppWidgetAlarm.kt. Here it is my main modification. The answers didn't help me, but it is working. I set here a repeating alarm. (https://developer.android.com/training/scheduling/alarms)

class AppWidgetAlarm(private val context: Context?) {
        private val ALARM_ID = 0
        private val INTERVAL_MILLIS : Long = 10000

    fun startAlarm() {
        val calendar: Calendar = Calendar.getInstance()
        calendar.add(Calendar.MILLISECOND, INTERVAL_MILLIS.toInt())        

        val alarmIntent = Intent(context, AppWidget::class.java).let { intent ->
            //intent.action = AppWidget.ACTION_AUTO_UPDATE
            PendingIntent.getBroadcast(context, 0, intent, 0)
        }
        with(context!!.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
           setRepeating(AlarmManager.RTC,calendar.timeInMillis, INTERVAL_MILLIS ,alarmIntent)
        }
    }

    fun stopAlarm() {
        val alarmIntent = Intent(AppWidget.ACTION_AUTO_UPDATE)
        val pendingIntent = PendingIntent.getBroadcast(context, ALARM_ID, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT)
        val alarmManager = context!!.getSystemService(Context.ALARM_SERVICE) as AlarmManager
        alarmManager.cancel(pendingIntent)
    }
}
Marci
  • 899
  • 10
  • 13