Home | History | Annotate | Download | only in weatherlistwidget
      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.Bundle;
     32 import android.os.Handler;
     33 import android.os.HandlerThread;
     34 import android.widget.RemoteViews;
     35 import android.widget.Toast;
     36 
     37 import java.util.Random;
     38 
     39 /**
     40  * Our data observer just notifies an update for all weather widgets when it detects a change.
     41  */
     42 class WeatherDataProviderObserver extends ContentObserver {
     43     private AppWidgetManager mAppWidgetManager;
     44     private ComponentName mComponentName;
     45 
     46     WeatherDataProviderObserver(AppWidgetManager mgr, ComponentName cn, Handler h) {
     47         super(h);
     48         mAppWidgetManager = mgr;
     49         mComponentName = cn;
     50     }
     51 
     52     @Override
     53     public void onChange(boolean selfChange) {
     54         // The data has changed, so notify the widget that the collection view needs to be updated.
     55         // In response, the factory's onDataSetChanged() will be called which will requery the
     56         // cursor for the new data.
     57         mAppWidgetManager.notifyAppWidgetViewDataChanged(
     58                 mAppWidgetManager.getAppWidgetIds(mComponentName), R.id.weather_list);
     59     }
     60 }
     61 
     62 /**
     63  * The weather widget's AppWidgetProvider.
     64  */
     65 public class WeatherWidgetProvider extends AppWidgetProvider {
     66     public static String CLICK_ACTION = "com.example.android.weatherlistwidget.CLICK";
     67     public static String REFRESH_ACTION = "com.example.android.weatherlistwidget.REFRESH";
     68     public static String EXTRA_DAY_ID = "com.example.android.weatherlistwidget.day";
     69 
     70     private static HandlerThread sWorkerThread;
     71     private static Handler sWorkerQueue;
     72     private static WeatherDataProviderObserver sDataObserver;
     73     private static final int sMaxDegrees = 96;
     74 
     75     private boolean mIsLargeLayout = true;
     76     private int mHeaderWeatherState = 0;
     77 
     78     public WeatherWidgetProvider() {
     79         // Start the worker thread
     80         sWorkerThread = new HandlerThread("WeatherWidgetProvider-worker");
     81         sWorkerThread.start();
     82         sWorkerQueue = new Handler(sWorkerThread.getLooper());
     83     }
     84 
     85     // XXX: clear the worker queue if we are destroyed?
     86 
     87     @Override
     88     public void onEnabled(Context context) {
     89         // Register for external updates to the data to trigger an update of the widget.  When using
     90         // content providers, the data is often updated via a background service, or in response to
     91         // user interaction in the main app.  To ensure that the widget always reflects the current
     92         // state of the data, we must listen for changes and update ourselves accordingly.
     93         final ContentResolver r = context.getContentResolver();
     94         if (sDataObserver == null) {
     95             final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
     96             final ComponentName cn = new ComponentName(context, WeatherWidgetProvider.class);
     97             sDataObserver = new WeatherDataProviderObserver(mgr, cn, sWorkerQueue);
     98             r.registerContentObserver(WeatherDataProvider.CONTENT_URI, true, sDataObserver);
     99         }
    100     }
    101 
    102     @Override
    103     public void onReceive(Context ctx, Intent intent) {
    104         final String action = intent.getAction();
    105         if (action.equals(REFRESH_ACTION)) {
    106             // BroadcastReceivers have a limited amount of time to do work, so for this sample, we
    107             // are triggering an update of the data on another thread.  In practice, this update
    108             // can be triggered from a background service, or perhaps as a result of user actions
    109             // inside the main application.
    110             final Context context = ctx;
    111             sWorkerQueue.removeMessages(0);
    112             sWorkerQueue.post(new Runnable() {
    113                 @Override
    114                 public void run() {
    115                     final ContentResolver r = context.getContentResolver();
    116                     final Cursor c = r.query(WeatherDataProvider.CONTENT_URI, null, null, null,
    117                             null);
    118                     final int count = c.getCount();
    119 
    120                     // We disable the data changed observer temporarily since each of the updates
    121                     // will trigger an onChange() in our data observer.
    122                     r.unregisterContentObserver(sDataObserver);
    123                     for (int i = 0; i < count; ++i) {
    124                         final Uri uri = ContentUris.withAppendedId(WeatherDataProvider.CONTENT_URI, i);
    125                         final ContentValues values = new ContentValues();
    126                         values.put(WeatherDataProvider.Columns.TEMPERATURE,
    127                                 new Random().nextInt(sMaxDegrees));
    128                         r.update(uri, values, null, null);
    129                     }
    130                     r.registerContentObserver(WeatherDataProvider.CONTENT_URI, true, sDataObserver);
    131 
    132                     final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
    133                     final ComponentName cn = new ComponentName(context, WeatherWidgetProvider.class);
    134                     mgr.notifyAppWidgetViewDataChanged(mgr.getAppWidgetIds(cn), R.id.weather_list);
    135                 }
    136             });
    137 
    138             final int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
    139                     AppWidgetManager.INVALID_APPWIDGET_ID);
    140         } else if (action.equals(CLICK_ACTION)) {
    141             // Show a toast
    142             final int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
    143                     AppWidgetManager.INVALID_APPWIDGET_ID);
    144             final String day = intent.getStringExtra(EXTRA_DAY_ID);
    145             final String formatStr = ctx.getResources().getString(R.string.toast_format_string);
    146             Toast.makeText(ctx, String.format(formatStr, day), Toast.LENGTH_SHORT).show();
    147         }
    148 
    149         super.onReceive(ctx, intent);
    150     }
    151 
    152     private RemoteViews buildLayout(Context context, int appWidgetId, boolean largeLayout) {
    153         RemoteViews rv;
    154         if (largeLayout) {
    155             // Specify the service to provide data for the collection widget.  Note that we need to
    156             // embed the appWidgetId via the data otherwise it will be ignored.
    157             final Intent intent = new Intent(context, WeatherWidgetService.class);
    158             intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
    159             intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
    160             rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
    161             rv.setRemoteAdapter(appWidgetId, R.id.weather_list, intent);
    162 
    163             // Set the empty view to be displayed if the collection is empty.  It must be a sibling
    164             // view of the collection view.
    165             rv.setEmptyView(R.id.weather_list, R.id.empty_view);
    166 
    167             // Bind a click listener template for the contents of the weather list.  Note that we
    168             // need to update the intent's data if we set an extra, since the extras will be
    169             // ignored otherwise.
    170             final Intent onClickIntent = new Intent(context, WeatherWidgetProvider.class);
    171             onClickIntent.setAction(WeatherWidgetProvider.CLICK_ACTION);
    172             onClickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
    173             onClickIntent.setData(Uri.parse(onClickIntent.toUri(Intent.URI_INTENT_SCHEME)));
    174             final PendingIntent onClickPendingIntent = PendingIntent.getBroadcast(context, 0,
    175                     onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    176             rv.setPendingIntentTemplate(R.id.weather_list, onClickPendingIntent);
    177 
    178             // Bind the click intent for the refresh button on the widget
    179             final Intent refreshIntent = new Intent(context, WeatherWidgetProvider.class);
    180             refreshIntent.setAction(WeatherWidgetProvider.REFRESH_ACTION);
    181             final PendingIntent refreshPendingIntent = PendingIntent.getBroadcast(context, 0,
    182                     refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    183             rv.setOnClickPendingIntent(R.id.refresh, refreshPendingIntent);
    184 
    185             // Restore the minimal header
    186             rv.setTextViewText(R.id.city_name, context.getString(R.string.city_name));
    187         } else {
    188             rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout_small);
    189 
    190             // Update the header to reflect the weather for "today"
    191             Cursor c = context.getContentResolver().query(WeatherDataProvider.CONTENT_URI, null,
    192                     null, null, null);
    193             if (c.moveToPosition(0)) {
    194                 int tempColIndex = c.getColumnIndex(WeatherDataProvider.Columns.TEMPERATURE);
    195                 int temp = c.getInt(tempColIndex);
    196                 String formatStr = context.getResources().getString(R.string.header_format_string);
    197                 String header = String.format(formatStr, temp,
    198                         context.getString(R.string.city_name));
    199                 rv.setTextViewText(R.id.city_name, header);
    200             }
    201             c.close();
    202         }
    203         return rv;
    204     }
    205 
    206     @Override
    207     public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    208         // Update each of the widgets with the remote adapter
    209         for (int i = 0; i < appWidgetIds.length; ++i) {
    210             RemoteViews layout = buildLayout(context, appWidgetIds[i], mIsLargeLayout);
    211             appWidgetManager.updateAppWidget(appWidgetIds[i], layout);
    212         }
    213         super.onUpdate(context, appWidgetManager, appWidgetIds);
    214     }
    215 
    216     @Override
    217     public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
    218             int appWidgetId, Bundle newOptions) {
    219 
    220         int minWidth = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
    221         int maxWidth = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH);
    222         int minHeight = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
    223         int maxHeight = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT);
    224 
    225         RemoteViews layout;
    226         if (minHeight < 100) {
    227             mIsLargeLayout = false;
    228         } else {
    229             mIsLargeLayout = true;
    230         }
    231         layout = buildLayout(context, appWidgetId, mIsLargeLayout);
    232         appWidgetManager.updateAppWidget(appWidgetId, layout);
    233     }
    234 }