Mastering Android Widget Development - Part4


SDK Version: 
M3

As described in the previous part, we will use a Service to update the appWidget.
So we will have the Service below, which gets the command (right now we have only the update command), ant the appwidgetId, reads the date from sharedPreferences and updates the widget.

  1. package com.helloandroid.countdownexample;
  2.  
  3. import java.util.Date;
  4.  
  5. import android.app.Service;
  6. import android.appwidget.AppWidgetManager;
  7. import android.content.Intent;
  8. import android.content.SharedPreferences;
  9. import android.os.IBinder;
  10. import android.widget.RemoteViews;
  11.  
  12. public class CountdownService extends Service {
  13.         // command strings to send to service
  14.         public static final String UPDATE = "update";
  15.  
  16.         @Override
  17.         public void onStart(Intent intent, int startId) {
  18.                 //a command, to define what to do, will be important only in the next tutorial part, now there is only update command
  19.                 String command = intent.getAction();
  20.                 int appWidgetId = intent.getExtras().getInt(
  21.                                 AppWidgetManager.EXTRA_APPWIDGET_ID);
  22.                 RemoteViews remoteView = new RemoteViews(getApplicationContext()
  23.                                 .getPackageName(), R.layout.countdownwidget);
  24.                 AppWidgetManager appWidgetManager = AppWidgetManager
  25.                                 .getInstance(getApplicationContext());
  26.  
  27.                 SharedPreferences prefs = getApplicationContext().getSharedPreferences(
  28.                                 "prefs", 0);
  29.                 long goal = prefs.getLong("goal" + appWidgetId, 0);
  30.                 //compute the time left
  31.                 long left = goal - new Date().getTime();
  32.                 int days = (int) Math.floor(left / (long) (60 * 60 * 24 * 1000));
  33.                 left = left - days * (long) (60 * 60 * 24 * 1000);
  34.                 int hours = (int) Math.floor(left / (60 * 60 * 1000));
  35.                 left = left - hours * (long) (60 * 60 * 1000);
  36.                 int mins = (int) Math.floor(left / (60 * 1000));
  37.                 left = left - mins * (long) (60 * 1000);
  38.                 int secs = (int) Math.floor(left / (1000));
  39.                 //put the text into the textView
  40.                 remoteView.setTextViewText(R.id.TextView01, days + " days\n" + hours
  41.                                 + " hours " + mins + " mins " + secs + " secs left");
  42.  
  43.                 // apply changes to widget
  44.                 appWidgetManager.updateAppWidget(appWidgetId, remoteView);
  45.                 super.onStart(intent, startId);
  46.         }
  47.  
  48.         @Override
  49.         public IBinder onBind(Intent arg0) {
  50.                 // TODO Auto-generated method stub
  51.                 return null;
  52.         }
  53. }

Services must be registered in the AndroidManifest:

  1. <service android:name=".CountdownService"></service>

The service is started automatically when you call it, but don~t forget to stop it in the AppWidgetProvider onDisable (When all widget instances are deleted):

  1. public void onDisabled(Context context) {
  2.         context.stopService(new Intent(context,CountdownService.class));
  3.         super.onDisabled(context);
  4. }

Now all we have to do is to call this Service when we want to update. The following function constructs a PendingIntent with the given parameters to do this. Insert it into the CountdownConfiguration class.

  1. public static PendingIntent makeControlPendingIntent(Context context, String command, int appWidgetId) {
  2.     Intent active = new Intent(context,CountdownService.class);
  3.     active.setAction(command);
  4.     active.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
  5.     //this Uri data is to make the PendingIntent unique, so it wont be updated by FLAG_UPDATE_CURRENT
  6.     //so if there are multiple widget instances they wont override each other
  7.     Uri data = Uri.withAppendedPath(Uri.parse("countdownwidget://widget/id/#"+command+appWidgetId), String.valueOf(appWidgetId));
  8.     active.setData(data);
  9.     return(PendingIntent.getService(context, 0, active, PendingIntent.FLAG_UPDATE_CURRENT));
  10. }

To update the time left every second we will use the AlarmManager class. Insert following method into the CountdownConfiguration class too. This will start and stop the updating of an AppWidget with the given appWidgetId.

  1. public static void setAlarm(Context context, int appWidgetId, int updateRate) {
  2.     PendingIntent newPending = makeControlPendingIntent(context,CountdownService.UPDATE,appWidgetId);
  3.     AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
  4.     if (updateRate >= 0) {
  5.         alarms.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), updateRate, newPending);
  6.     } else {
  7.         // on a negative updateRate stop the refreshing
  8.         alarms.cancel(newPending);
  9.     }
  10. }

Using this methods, we
- refresh the appWidget when configuration is done (in ok button onclick)

  1. PendingIntent updatepending = CountdownWidget
  2.         .makeControlPendingIntent(self,
  3.         CountdownService.UPDATE, appWidgetId);
  4. try {
  5.         updatepending.send();
  6. } catch (CanceledException e) {
  7.         e.printStackTrace();
  8. }

- start the Alarmanager in the AppWidgetProvider onUpdate method (for example at first update, or after phone reboot)
  1. public void onUpdate(Context context, AppWidgetManager appWidgetManager,
  2.                         int[] appWidgetIds) {
  3.         for (int appWidgetId : appWidgetIds) {
  4.                 setAlarm(context, appWidgetId, UPDATE_RATE);
  5.         }
  6.         super.onUpdate(context, appWidgetManager, appWidgetIds);
  7.  
  8. }

- stop the Alarmanager on the AppWidgetProvider onDelete
  1. public void onDeleted(Context context, int[] appWidgetIds) {
  2.     for (int appWidgetId : appWidgetIds) {      
  3.         setAlarm(context, appWidgetId, -1);
  4.     }
  5.     super.onDeleted(context, appWidgetIds);
  6. }