In the previous article I covered the various solutions that I tried to resolve an issue that had developed within TextClock. The time is actually updating anything up to a minute later than it should be, and the problem was caused by internal changes to AlarmManager which were introduced in KitKat. In this article in this we’ll implement the fix.
The first thing that we need to do to implement our fix is to convert TextClockService to TextClockReceiver. This only really requires us to extend BroadcastReceiver instead of IntentService, change the name of the class itself, and override onReceived(Intent intent)
instead of handleIntent(Intent intent)
. The other thing that we must do is change AlarmManager calls from setRepeating()
. There is no direct replacement for this to get an exact repeating alarm, so we must change things slightly. When we set the alarm it will be a one-shot alarm – meaning that it will only fire once. When it fires, we update the widget and then set a new one-shot alarm to go off at the next minute tick over:
@Override public void onReceive(Context context, Intent intent) { updateTime(context, Calendar.getInstance()); scheduleTimer(context); } . . . public static void cancelTimer(Context context) { PendingIntent pi = PendingIntent.getBroadcast(context, REQUEST_CODE, timer, PendingIntent.FLAG_NO_CREATE); if (pi != null) { AlarmManager am = (AlarmManager) context.getSystemService( Context.ALARM_SERVICE); am.cancel(pi); pi.cancel(); Log.d(TAG, "Alarm cancelled"); } } public static void scheduleTimer(Context context) { Calendar date = Calendar.getInstance(); date.set(Calendar.SECOND, 0); date.set(Calendar.MILLISECOND, 0); date.add(Calendar.MINUTE, 1); AlarmManager am = (AlarmManager) context.getSystemService( Context.ALARM_SERVICE); cancelTimer(context); PendingIntent pi = PendingIntent.getBroadcast(context, REQUEST_CODE, timer, PendingIntent.FLAG_ONE_SHOT); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { setKitkatAlarm(am, AlarmManager.RTC, date.getTimeInMillis(), pi); } else { am.set(AlarmManager.RTC, date.getTimeInMillis(), pi); } Log.d(TAG, "Alarm set for: " + TextClockService.DATE_FORMAT.format(date.getTime())); } @TargetApi(Build.VERSION_CODES.KITKAT) private static void setKitkatAlarm(AlarmManager am, int type, long timeInMillis, PendingIntent pi) { am.setExact(type, timeInMillis, pi); }
Note how we ned to set our alarm in the traditional manner for pre-KitKat devices, but using the new method for KitKat and later.
Next we need to change the Service declaration in our Manifest to our Receiver:
Finally we need to update our TextClockAppWidget to fire an initial update when the widget is initialised. We also need to cancel any outstanding alarms if the last widget instance is removed:
public class TextClockAppWidget extends AppWidgetProvider { private static final String TAG = "TextClockWidget"; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { Log.d(TAG, "onUpdate"); context.sendBroadcast(TextClockReceiver.timer); } @Override public void onDeleted(Context context, int[] appWidgetIds) { Log.d(TAG, "onDeleted"); AppWidgetManager mgr = AppWidgetManager.getInstance(context); if (mgr != null) { int[] remainingIds = mgr.getAppWidgetIds( new ComponentName(context, this.getClass())); if (remainingIds == null || remainingIds.length <= 0) { TextClockReceiver.cancelTimer(context); } } } }
Finally we have out widget updating correctly!
That concludes our revisit of the TextClock widget. I may, of course, re-visit it in the future to add new features. If you have any strong opinions either way of whether I should or should not revisit TextClock again, or have any feature requests (or indeed any other requests for topics that you'd like to see covered on Styling Android), then please let me know.
The source code for this article can be found here, and TextClock is available from Google Play.
© 2014, Mark Allison. All rights reserved.
Copyright © 2014 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.