1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.weatherlistwidget; 18 19 import android.app.PendingIntent; 20 import android.appwidget.AppWidgetManager; 21 import android.appwidget.AppWidgetProvider; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.ComponentName; 25 import android.content.ContentValues; 26 import android.content.ContentResolver; 27 import android.content.ContentUris; 28 import android.database.Cursor; 29 import android.database.ContentObserver; 30 import android.net.Uri; 31 import android.os.Handler; 32 import android.os.HandlerThread; 33 import android.widget.RemoteViews; 34 import android.widget.Toast; 35 36 import java.util.Random; 37 38 /** 39 * Our data observer just notifies an update for all weather widgets when it detects a change. 40 */ 41 class WeatherDataProviderObserver extends ContentObserver { 42 private AppWidgetManager mAppWidgetManager; 43 private ComponentName mComponentName; 44 45 WeatherDataProviderObserver(AppWidgetManager mgr, ComponentName cn, Handler h) { 46 super(h); 47 mAppWidgetManager = mgr; 48 mComponentName = cn; 49 } 50 51 @Override 52 public void onChange(boolean selfChange) { 53 // The data has changed, so notify the widget that the collection view needs to be updated. 54 // In response, the factory's onDataSetChanged() will be called which will requery the 55 // cursor for the new data. 56 mAppWidgetManager.notifyAppWidgetViewDataChanged( 57 mAppWidgetManager.getAppWidgetIds(mComponentName), R.id.weather_list); 58 } 59 } 60 61 /** 62 * The weather widget's AppWidgetProvider. 63 */ 64 public class WeatherWidgetProvider extends AppWidgetProvider { 65 public static String CLICK_ACTION = "com.example.android.weatherlistwidget.CLICK"; 66 public static String REFRESH_ACTION = "com.example.android.weatherlistwidget.REFRESH"; 67 public static String EXTRA_CITY_ID = "com.example.android.weatherlistwidget.city"; 68 69 private static HandlerThread sWorkerThread; 70 private static Handler sWorkerQueue; 71 private static WeatherDataProviderObserver sDataObserver; 72 73 public WeatherWidgetProvider() { 74 // Start the worker thread 75 sWorkerThread = new HandlerThread("WeatherWidgetProvider-worker"); 76 sWorkerThread.start(); 77 sWorkerQueue = new Handler(sWorkerThread.getLooper()); 78 } 79 80 @Override 81 public void onEnabled(Context context) { 82 // Register for external updates to the data to trigger an update of the widget. When using 83 // content providers, the data is often updated via a background service, or in response to 84 // user interaction in the main app. To ensure that the widget always reflects the current 85 // state of the data, we must listen for changes and update ourselves accordingly. 86 final ContentResolver r = context.getContentResolver(); 87 if (sDataObserver == null) { 88 final AppWidgetManager mgr = AppWidgetManager.getInstance(context); 89 final ComponentName cn = new ComponentName(context, WeatherWidgetProvider.class); 90 sDataObserver = new WeatherDataProviderObserver(mgr, cn, sWorkerQueue); 91 r.registerContentObserver(WeatherDataProvider.CONTENT_URI, true, sDataObserver); 92 } 93 } 94 95 @Override 96 public void onReceive(Context ctx, Intent intent) { 97 final String action = intent.getAction(); 98 if (action.equals(REFRESH_ACTION)) { 99 // BroadcastReceivers have a limited amount of time to do work, so for this sample, we 100 // are triggering an update of the data on another thread. In practice, this update 101 // can be triggered from a background service, or perhaps as a result of user actions 102 // inside the main application. 103 final Context context = ctx; 104 sWorkerQueue.removeMessages(0); 105 sWorkerQueue.post(new Runnable() { 106 @Override 107 public void run() { 108 final ContentResolver r = context.getContentResolver(); 109 final Cursor c = r.query(WeatherDataProvider.CONTENT_URI, null, null, null, 110 null); 111 final int count = c.getCount(); 112 final int maxDegrees = 96; 113 114 // We disable the data changed observer temporarily since each of the updates 115 // will trigger an onChange() in our data observer. 116 r.unregisterContentObserver(sDataObserver); 117 for (int i = 0; i < count; ++i) { 118 final Uri uri = ContentUris.withAppendedId(WeatherDataProvider.CONTENT_URI, i); 119 final ContentValues values = new ContentValues(); 120 values.put(WeatherDataProvider.Columns.TEMPERATURE, 121 new Random().nextInt(maxDegrees)); 122 r.update(uri, values, null, null); 123 } 124 r.registerContentObserver(WeatherDataProvider.CONTENT_URI, true, sDataObserver); 125 126 final AppWidgetManager mgr = AppWidgetManager.getInstance(context); 127 final ComponentName cn = new ComponentName(context, WeatherWidgetProvider.class); 128 mgr.notifyAppWidgetViewDataChanged(mgr.getAppWidgetIds(cn), R.id.weather_list); 129 } 130 }); 131 } else if (action.equals(CLICK_ACTION)) { 132 // Show a toast 133 final int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 134 AppWidgetManager.INVALID_APPWIDGET_ID); 135 final String city = intent.getStringExtra(EXTRA_CITY_ID); 136 final String formatStr = ctx.getResources().getString(R.string.toast_format_string); 137 Toast.makeText(ctx, String.format(formatStr, city), Toast.LENGTH_SHORT).show(); 138 } 139 140 super.onReceive(ctx, intent); 141 } 142 143 @Override 144 public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { 145 // Update each of the widgets with the remote adapter 146 for (int i = 0; i < appWidgetIds.length; ++i) { 147 // Specify the service to provide data for the collection widget. Note that we need to 148 // embed the appWidgetId via the data otherwise it will be ignored. 149 final Intent intent = new Intent(context, WeatherWidgetService.class); 150 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]); 151 intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); 152 final RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout); 153 rv.setRemoteAdapter(appWidgetIds[i], R.id.weather_list, intent); 154 155 // Set the empty view to be displayed if the collection is empty. It must be a sibling 156 // view of the collection view. 157 rv.setEmptyView(R.id.weather_list, R.id.empty_view); 158 159 // Bind a click listener template for the contents of the weather list. Note that we 160 // need to update the intent's data if we set an extra, since the extras will be 161 // ignored otherwise. 162 final Intent onClickIntent = new Intent(context, WeatherWidgetProvider.class); 163 onClickIntent.setAction(WeatherWidgetProvider.CLICK_ACTION); 164 onClickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]); 165 onClickIntent.setData(Uri.parse(onClickIntent.toUri(Intent.URI_INTENT_SCHEME))); 166 final PendingIntent onClickPendingIntent = PendingIntent.getBroadcast(context, 0, 167 onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT); 168 rv.setPendingIntentTemplate(R.id.weather_list, onClickPendingIntent); 169 170 // Bind the click intent for the refresh button on the widget 171 final Intent refreshIntent = new Intent(context, WeatherWidgetProvider.class); 172 refreshIntent.setAction(WeatherWidgetProvider.REFRESH_ACTION); 173 final PendingIntent refreshPendingIntent = PendingIntent.getBroadcast(context, 0, 174 refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT); 175 rv.setOnClickPendingIntent(R.id.refresh, refreshPendingIntent); 176 177 appWidgetManager.updateAppWidget(appWidgetIds[i], rv); 178 } 179 super.onUpdate(context, appWidgetManager, appWidgetIds); 180 } 181 }