diff --git a/build/sdk.atree b/build/sdk.atree
index e0689091736..641862640d5 100644
--- a/build/sdk.atree
+++ b/build/sdk.atree
@@ -178,6 +178,7 @@ development/samples/SpinnerTest samples/${PLATFORM_NAME}/SpinnerTes
development/samples/TicTacToeLib samples/${PLATFORM_NAME}/TicTacToeLib
development/samples/TicTacToeMain samples/${PLATFORM_NAME}/TicTacToeMain
development/samples/VoiceRecognitionService samples/${PLATFORM_NAME}/VoiceRecognitionService
+development/samples/WeatherListWidget samples/${PLATFORM_NAME}/WeatherListWidget
development/apps/WidgetPreview samples/${PLATFORM_NAME}/WidgetPreview
development/samples/Wiktionary samples/${PLATFORM_NAME}/Wiktionary
development/samples/WiktionarySimple samples/${PLATFORM_NAME}/WiktionarySimple
diff --git a/samples/WeatherListWidget/Android.mk b/samples/WeatherListWidget/Android.mk
new file mode 100644
index 00000000000..95d233fa80c
--- /dev/null
+++ b/samples/WeatherListWidget/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := WeatherListWidget
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/samples/WeatherListWidget/AndroidManifest.xml b/samples/WeatherListWidget/AndroidManifest.xml
new file mode 100644
index 00000000000..cfb23722cc7
--- /dev/null
+++ b/samples/WeatherListWidget/AndroidManifest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/WeatherListWidget/_index.html b/samples/WeatherListWidget/_index.html
new file mode 100644
index 00000000000..730f27caa7a
--- /dev/null
+++ b/samples/WeatherListWidget/_index.html
@@ -0,0 +1,43 @@
+
This sample demonstrates how to create a list-based widget specifically backed by a content provider.
+Please make sure that you understand the earlier stack widget sample code before delving into this example
+ (see StackWidget).
+
+As in the StackWidget example, we will be using a collection view (the ListView in this case) to
+present some mock weather data in a widget. In particular, we will be using a content provider to
+demonstrate how the widget can retrieve data and update itself when you are using more complex data
+sources. When working with external data, or data which must be fetched over high latency, it is
+important to keep the data cached in a persistent location so that the widget feels responsive.
+
+This sample uses the following classes:
+
+ WeatherDataProvider
—
+ Our ContentProvider which stores the weather data for this sample. Note that for simplicity,
+ it currently stores the data in memory as opposed to an external and persistent storage such
+ as a file, network location, database, or shared preference.
+
+ WeatherWidgetProvider
—
+ Our AppWidgetProvider which handles specific intent actions (such as when an item is clicked,
+ or the refresh button is pressed). It also sets up the RemoteViews for the widget and binds
+ the service to the collection view in the widget.
+
+ WeatherWidgetService
—
+ Our RemoteViewsService which manages the creation of new factories, which return the RemoteViews
+ for each item in the ListView.
+
+
+
+
+If you are writing collection-based widgets, remember that the feature is
+supported only on Android 3.0 (API level 11) and higher versions of the platform.
+Remember to add the following to the application's manifest publishing to Android Market:
+
+
+<uses-sdk android:minSdkVersion="11" />
, which indicates
+to Android Market and the platform that your application requires Android 3.0 or
+higher. For more information, see the API Levels
+and the documentation for the
+<uses-sdk>
+element.
+
+
+
\ No newline at end of file
diff --git a/samples/WeatherListWidget/res/drawable-hdpi/body.png b/samples/WeatherListWidget/res/drawable-hdpi/body.png
new file mode 100644
index 00000000000..0a2c1d1303d
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-hdpi/body.png differ
diff --git a/samples/WeatherListWidget/res/drawable-hdpi/footer.png b/samples/WeatherListWidget/res/drawable-hdpi/footer.png
new file mode 100644
index 00000000000..73cc95c26fa
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-hdpi/footer.png differ
diff --git a/samples/WeatherListWidget/res/drawable-hdpi/header.png b/samples/WeatherListWidget/res/drawable-hdpi/header.png
new file mode 100644
index 00000000000..e659aee2350
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-hdpi/header.png differ
diff --git a/samples/WeatherListWidget/res/drawable-hdpi/icon.png b/samples/WeatherListWidget/res/drawable-hdpi/icon.png
new file mode 100644
index 00000000000..8074c4c571b
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-hdpi/icon.png differ
diff --git a/samples/WeatherListWidget/res/drawable-hdpi/item_bg_dark.png b/samples/WeatherListWidget/res/drawable-hdpi/item_bg_dark.png
new file mode 100644
index 00000000000..5097ab70cf2
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-hdpi/item_bg_dark.png differ
diff --git a/samples/WeatherListWidget/res/drawable-hdpi/item_bg_light.png b/samples/WeatherListWidget/res/drawable-hdpi/item_bg_light.png
new file mode 100644
index 00000000000..019f36ce039
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-hdpi/item_bg_light.png differ
diff --git a/samples/WeatherListWidget/res/drawable-hdpi/refresh.png b/samples/WeatherListWidget/res/drawable-hdpi/refresh.png
new file mode 100644
index 00000000000..284777318e3
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-hdpi/refresh.png differ
diff --git a/samples/WeatherListWidget/res/drawable-hdpi/refresh_pressed.png b/samples/WeatherListWidget/res/drawable-hdpi/refresh_pressed.png
new file mode 100644
index 00000000000..820cc36063d
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-hdpi/refresh_pressed.png differ
diff --git a/samples/WeatherListWidget/res/drawable-ldpi/body.png b/samples/WeatherListWidget/res/drawable-ldpi/body.png
new file mode 100644
index 00000000000..3ce427611e4
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-ldpi/body.png differ
diff --git a/samples/WeatherListWidget/res/drawable-ldpi/footer.png b/samples/WeatherListWidget/res/drawable-ldpi/footer.png
new file mode 100644
index 00000000000..ab89bf3ded0
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-ldpi/footer.png differ
diff --git a/samples/WeatherListWidget/res/drawable-ldpi/header.png b/samples/WeatherListWidget/res/drawable-ldpi/header.png
new file mode 100644
index 00000000000..ff29577d63e
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-ldpi/header.png differ
diff --git a/samples/WeatherListWidget/res/drawable-ldpi/icon.png b/samples/WeatherListWidget/res/drawable-ldpi/icon.png
new file mode 100644
index 00000000000..1095584ec21
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-ldpi/icon.png differ
diff --git a/samples/WeatherListWidget/res/drawable-ldpi/item_bg_dark.png b/samples/WeatherListWidget/res/drawable-ldpi/item_bg_dark.png
new file mode 100644
index 00000000000..c945b5f94b9
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-ldpi/item_bg_dark.png differ
diff --git a/samples/WeatherListWidget/res/drawable-ldpi/item_bg_light.png b/samples/WeatherListWidget/res/drawable-ldpi/item_bg_light.png
new file mode 100644
index 00000000000..a14b9a649a0
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-ldpi/item_bg_light.png differ
diff --git a/samples/WeatherListWidget/res/drawable-ldpi/refresh.png b/samples/WeatherListWidget/res/drawable-ldpi/refresh.png
new file mode 100644
index 00000000000..d08343c07e6
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-ldpi/refresh.png differ
diff --git a/samples/WeatherListWidget/res/drawable-ldpi/refresh_pressed.png b/samples/WeatherListWidget/res/drawable-ldpi/refresh_pressed.png
new file mode 100644
index 00000000000..3da3ae6b3a5
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-ldpi/refresh_pressed.png differ
diff --git a/samples/WeatherListWidget/res/drawable-mdpi/body.png b/samples/WeatherListWidget/res/drawable-mdpi/body.png
new file mode 100644
index 00000000000..5331a9a3ca9
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-mdpi/body.png differ
diff --git a/samples/WeatherListWidget/res/drawable-mdpi/footer.png b/samples/WeatherListWidget/res/drawable-mdpi/footer.png
new file mode 100644
index 00000000000..ca8b7ec93cd
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-mdpi/footer.png differ
diff --git a/samples/WeatherListWidget/res/drawable-mdpi/header.png b/samples/WeatherListWidget/res/drawable-mdpi/header.png
new file mode 100644
index 00000000000..5452abf8e70
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-mdpi/header.png differ
diff --git a/samples/WeatherListWidget/res/drawable-mdpi/icon.png b/samples/WeatherListWidget/res/drawable-mdpi/icon.png
new file mode 100644
index 00000000000..a07c69fa5a0
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-mdpi/icon.png differ
diff --git a/samples/WeatherListWidget/res/drawable-mdpi/item_bg_dark.png b/samples/WeatherListWidget/res/drawable-mdpi/item_bg_dark.png
new file mode 100644
index 00000000000..5ae5480420c
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-mdpi/item_bg_dark.png differ
diff --git a/samples/WeatherListWidget/res/drawable-mdpi/item_bg_light.png b/samples/WeatherListWidget/res/drawable-mdpi/item_bg_light.png
new file mode 100644
index 00000000000..56969443501
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-mdpi/item_bg_light.png differ
diff --git a/samples/WeatherListWidget/res/drawable-mdpi/refresh.png b/samples/WeatherListWidget/res/drawable-mdpi/refresh.png
new file mode 100644
index 00000000000..569b36012cd
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-mdpi/refresh.png differ
diff --git a/samples/WeatherListWidget/res/drawable-mdpi/refresh_pressed.png b/samples/WeatherListWidget/res/drawable-mdpi/refresh_pressed.png
new file mode 100644
index 00000000000..5f1066267ef
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-mdpi/refresh_pressed.png differ
diff --git a/samples/WeatherListWidget/res/drawable-nodpi/preview.png b/samples/WeatherListWidget/res/drawable-nodpi/preview.png
new file mode 100644
index 00000000000..f0cbdaff095
Binary files /dev/null and b/samples/WeatherListWidget/res/drawable-nodpi/preview.png differ
diff --git a/samples/WeatherListWidget/res/drawable/refresh_button.xml b/samples/WeatherListWidget/res/drawable/refresh_button.xml
new file mode 100644
index 00000000000..1c0017e1924
--- /dev/null
+++ b/samples/WeatherListWidget/res/drawable/refresh_button.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/WeatherListWidget/res/layout/dark_widget_item.xml b/samples/WeatherListWidget/res/layout/dark_widget_item.xml
new file mode 100644
index 00000000000..1f920a2d940
--- /dev/null
+++ b/samples/WeatherListWidget/res/layout/dark_widget_item.xml
@@ -0,0 +1,24 @@
+
+
+
diff --git a/samples/WeatherListWidget/res/layout/light_widget_item.xml b/samples/WeatherListWidget/res/layout/light_widget_item.xml
new file mode 100644
index 00000000000..bb2946ff660
--- /dev/null
+++ b/samples/WeatherListWidget/res/layout/light_widget_item.xml
@@ -0,0 +1,24 @@
+
+
+
diff --git a/samples/WeatherListWidget/res/layout/widget_layout.xml b/samples/WeatherListWidget/res/layout/widget_layout.xml
new file mode 100644
index 00000000000..4b09efcdf57
--- /dev/null
+++ b/samples/WeatherListWidget/res/layout/widget_layout.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/WeatherListWidget/res/values/strings.xml b/samples/WeatherListWidget/res/values/strings.xml
new file mode 100644
index 00000000000..6542545f095
--- /dev/null
+++ b/samples/WeatherListWidget/res/values/strings.xml
@@ -0,0 +1,20 @@
+
+
+
+ No cities found...
+ %1$s says Hi!
+ %1$d\u00B0 in %2$s
+
diff --git a/samples/WeatherListWidget/res/xml/widgetinfo.xml b/samples/WeatherListWidget/res/xml/widgetinfo.xml
new file mode 100644
index 00000000000..a0932085d68
--- /dev/null
+++ b/samples/WeatherListWidget/res/xml/widgetinfo.xml
@@ -0,0 +1,23 @@
+
+
+
+
\ No newline at end of file
diff --git a/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherDataProvider.java b/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherDataProvider.java
new file mode 100644
index 00000000000..92a1cb352f4
--- /dev/null
+++ b/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherDataProvider.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.weatherlistwidget;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+
+import java.util.ArrayList;
+
+/**
+ * A dummy class that we are going to use internally to store weather data. Generally, this data
+ * will be stored in an external and persistent location (ie. File, Database, SharedPreferences) so
+ * that the data can persist if the process is ever killed. For simplicity, in this sample the
+ * data will only be stored in memory.
+ */
+class WeatherDataPoint {
+ String city;
+ int degrees;
+
+ WeatherDataPoint(String c, int d) {
+ city = c;
+ degrees = d;
+ }
+}
+
+/**
+ * The AppWidgetProvider for our sample weather widget.
+ */
+public class WeatherDataProvider extends ContentProvider {
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://com.example.android.weatherlistwidget.provider");
+ public static class Columns {
+ public static final String ID = "_id";
+ public static final String CITY = "city";
+ public static final String TEMPERATURE = "temperature";
+ }
+
+ /**
+ * Generally, this data will be stored in an external and persistent location (ie. File,
+ * Database, SharedPreferences) so that the data can persist if the process is ever killed.
+ * For simplicity, in this sample the data will only be stored in memory.
+ */
+ private static final ArrayList sData = new ArrayList();
+
+ @Override
+ public boolean onCreate() {
+ // We are going to initialize the data provider with some default values
+ sData.add(new WeatherDataPoint("San Francisco", 13));
+ sData.add(new WeatherDataPoint("New York", 1));
+ sData.add(new WeatherDataPoint("Seattle", 7));
+ sData.add(new WeatherDataPoint("Boston", 4));
+ sData.add(new WeatherDataPoint("Miami", 22));
+ sData.add(new WeatherDataPoint("Toronto", -10));
+ sData.add(new WeatherDataPoint("Calgary", -13));
+ sData.add(new WeatherDataPoint("Tokyo", 8));
+ sData.add(new WeatherDataPoint("Kyoto", 11));
+ sData.add(new WeatherDataPoint("London", -1));
+ sData.add(new WeatherDataPoint("Nomanisan", 27));
+ return true;
+ }
+
+ @Override
+ public synchronized Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ assert(uri.getPathSegments().isEmpty());
+
+ // In this sample, we only query without any parameters, so we can just return a cursor to
+ // all the weather data.
+ final MatrixCursor c = new MatrixCursor(
+ new String[]{ Columns.ID, Columns.CITY, Columns.TEMPERATURE });
+ for (int i = 0; i < sData.size(); ++i) {
+ final WeatherDataPoint data = sData.get(i);
+ c.addRow(new Object[]{ new Integer(i), data.city, new Integer(data.degrees) });
+ }
+ return c;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return "vnd.android.cursor.dir/vnd.weatherlistwidget.citytemperature";
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ // This example code does not support inserting
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ // This example code does not support deleting
+ return 0;
+ }
+
+ @Override
+ public synchronized int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ assert(uri.getPathSegments().size() == 1);
+
+ // In this sample, we only update the content provider individually for each row with new
+ // temperature values.
+ final int index = Integer.parseInt(uri.getPathSegments().get(0));
+ final MatrixCursor c = new MatrixCursor(
+ new String[]{ Columns.ID, Columns.CITY, Columns.TEMPERATURE });
+ assert(0 <= index && index < sData.size());
+ final WeatherDataPoint data = sData.get(index);
+ data.degrees = values.getAsInteger(Columns.TEMPERATURE);
+
+ // Notify any listeners that the data backing the content provider has changed, and return
+ // the number of rows affected.
+ getContext().getContentResolver().notifyChange(uri, null);
+ return 1;
+ }
+
+}
\ No newline at end of file
diff --git a/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherWidgetProvider.java b/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherWidgetProvider.java
new file mode 100644
index 00000000000..2f2b3475095
--- /dev/null
+++ b/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherWidgetProvider.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.weatherlistwidget;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.database.Cursor;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import java.util.Random;
+
+/**
+ * Our data observer just notifies an update for all weather widgets when it detects a change.
+ */
+class WeatherDataProviderObserver extends ContentObserver {
+ private AppWidgetManager mAppWidgetManager;
+ private ComponentName mComponentName;
+
+ WeatherDataProviderObserver(AppWidgetManager mgr, ComponentName cn, Handler h) {
+ super(h);
+ mAppWidgetManager = mgr;
+ mComponentName = cn;
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ // The data has changed, so notify the widget that the collection view needs to be updated.
+ // In response, the factory's onDataSetChanged() will be called which will requery the
+ // cursor for the new data.
+ mAppWidgetManager.notifyAppWidgetViewDataChanged(
+ mAppWidgetManager.getAppWidgetIds(mComponentName), R.id.weather_list);
+ }
+}
+
+/**
+ * The weather widget's AppWidgetProvider.
+ */
+public class WeatherWidgetProvider extends AppWidgetProvider {
+ public static String CLICK_ACTION = "com.example.android.weatherlistwidget.CLICK";
+ public static String REFRESH_ACTION = "com.example.android.weatherlistwidget.REFRESH";
+ public static String EXTRA_CITY_ID = "com.example.android.weatherlistwidget.city";
+
+ private static HandlerThread sWorkerThread;
+ private static Handler sWorkerQueue;
+ private static WeatherDataProviderObserver sDataObserver;
+
+ public WeatherWidgetProvider() {
+ // Start the worker thread
+ sWorkerThread = new HandlerThread("WeatherWidgetProvider-worker");
+ sWorkerThread.start();
+ sWorkerQueue = new Handler(sWorkerThread.getLooper());
+ }
+
+ @Override
+ public void onEnabled(Context context) {
+ // Register for external updates to the data to trigger an update of the widget. When using
+ // content providers, the data is often updated via a background service, or in response to
+ // user interaction in the main app. To ensure that the widget always reflects the current
+ // state of the data, we must listen for changes and update ourselves accordingly.
+ final ContentResolver r = context.getContentResolver();
+ if (sDataObserver == null) {
+ final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
+ final ComponentName cn = new ComponentName(context, WeatherWidgetProvider.class);
+ sDataObserver = new WeatherDataProviderObserver(mgr, cn, sWorkerQueue);
+ r.registerContentObserver(WeatherDataProvider.CONTENT_URI, true, sDataObserver);
+ }
+ }
+
+ @Override
+ public void onReceive(Context ctx, Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(REFRESH_ACTION)) {
+ // BroadcastReceivers have a limited amount of time to do work, so for this sample, we
+ // are triggering an update of the data on another thread. In practice, this update
+ // can be triggered from a background service, or perhaps as a result of user actions
+ // inside the main application.
+ final Context context = ctx;
+ sWorkerQueue.removeMessages(0);
+ sWorkerQueue.post(new Runnable() {
+ @Override
+ public void run() {
+ final ContentResolver r = context.getContentResolver();
+ final Cursor c = r.query(WeatherDataProvider.CONTENT_URI, null, null, null,
+ null);
+ final int count = c.getCount();
+ final int maxDegrees = 96;
+
+ // We disable the data changed observer temporarily since each of the updates
+ // will trigger an onChange() in our data observer.
+ r.unregisterContentObserver(sDataObserver);
+ for (int i = 0; i < count; ++i) {
+ final Uri uri = ContentUris.withAppendedId(WeatherDataProvider.CONTENT_URI, i);
+ final ContentValues values = new ContentValues();
+ values.put(WeatherDataProvider.Columns.TEMPERATURE,
+ new Random().nextInt(maxDegrees));
+ r.update(uri, values, null, null);
+ }
+ r.registerContentObserver(WeatherDataProvider.CONTENT_URI, true, sDataObserver);
+
+ final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
+ final ComponentName cn = new ComponentName(context, WeatherWidgetProvider.class);
+ mgr.notifyAppWidgetViewDataChanged(mgr.getAppWidgetIds(cn), R.id.weather_list);
+ }
+ });
+ } else if (action.equals(CLICK_ACTION)) {
+ // Show a toast
+ final int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
+ AppWidgetManager.INVALID_APPWIDGET_ID);
+ final String city = intent.getStringExtra(EXTRA_CITY_ID);
+ final String formatStr = ctx.getResources().getString(R.string.toast_format_string);
+ Toast.makeText(ctx, String.format(formatStr, city), Toast.LENGTH_SHORT).show();
+ }
+
+ super.onReceive(ctx, intent);
+ }
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ // Update each of the widgets with the remote adapter
+ for (int i = 0; i < appWidgetIds.length; ++i) {
+ // Specify the service to provide data for the collection widget. Note that we need to
+ // embed the appWidgetId via the data otherwise it will be ignored.
+ final Intent intent = new Intent(context, WeatherWidgetService.class);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
+ intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
+ final RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
+ rv.setRemoteAdapter(appWidgetIds[i], R.id.weather_list, intent);
+
+ // Set the empty view to be displayed if the collection is empty. It must be a sibling
+ // view of the collection view.
+ rv.setEmptyView(R.id.weather_list, R.id.empty_view);
+
+ // Bind a click listener template for the contents of the weather list. Note that we
+ // need to update the intent's data if we set an extra, since the extras will be
+ // ignored otherwise.
+ final Intent onClickIntent = new Intent(context, WeatherWidgetProvider.class);
+ onClickIntent.setAction(WeatherWidgetProvider.CLICK_ACTION);
+ onClickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
+ onClickIntent.setData(Uri.parse(onClickIntent.toUri(Intent.URI_INTENT_SCHEME)));
+ final PendingIntent onClickPendingIntent = PendingIntent.getBroadcast(context, 0,
+ onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ rv.setPendingIntentTemplate(R.id.weather_list, onClickPendingIntent);
+
+ // Bind the click intent for the refresh button on the widget
+ final Intent refreshIntent = new Intent(context, WeatherWidgetProvider.class);
+ refreshIntent.setAction(WeatherWidgetProvider.REFRESH_ACTION);
+ final PendingIntent refreshPendingIntent = PendingIntent.getBroadcast(context, 0,
+ refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ rv.setOnClickPendingIntent(R.id.refresh, refreshPendingIntent);
+
+ appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
+ }
+ super.onUpdate(context, appWidgetManager, appWidgetIds);
+ }
+}
\ No newline at end of file
diff --git a/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherWidgetService.java b/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherWidgetService.java
new file mode 100644
index 00000000000..e0bc682761e
--- /dev/null
+++ b/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherWidgetService.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.weatherlistwidget;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ContentUris;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+/**
+ * This is the service that provides the factory to be bound to the collection service.
+ */
+public class WeatherWidgetService extends RemoteViewsService {
+ @Override
+ public RemoteViewsFactory onGetViewFactory(Intent intent) {
+ return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
+ }
+}
+
+/**
+ * This is the factory that will provide data to the collection widget.
+ */
+class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
+ private Context mContext;
+ private Cursor mCursor;
+ private int mAppWidgetId;
+
+ public StackRemoteViewsFactory(Context context, Intent intent) {
+ mContext = context;
+ mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
+ AppWidgetManager.INVALID_APPWIDGET_ID);
+ }
+
+ public void onCreate() {
+ // Since we reload the cursor in onDataSetChanged() which gets called immediately after
+ // onCreate(), we do nothing here.
+ }
+
+ public void onDestroy() {
+ if (mCursor != null) {
+ mCursor.close();
+ }
+ }
+
+ public int getCount() {
+ return mCursor.getCount();
+ }
+
+ public RemoteViews getViewAt(int position) {
+ // Get the data for this position from the content provider
+ String city = "Unknown City";
+ int temp = 0;
+ if (mCursor.moveToPosition(position)) {
+ final int cityColIndex = mCursor.getColumnIndex(WeatherDataProvider.Columns.CITY);
+ final int tempColIndex = mCursor.getColumnIndex(
+ WeatherDataProvider.Columns.TEMPERATURE);
+ city = mCursor.getString(cityColIndex);
+ temp = mCursor.getInt(tempColIndex);
+ }
+
+ // Return a proper item with the proper city and temperature. Just for fun, we alternate
+ // the items to make the list easier to read.
+ final String formatStr = mContext.getResources().getString(R.string.item_format_string);
+ final int itemId = (position % 2 == 0 ? R.layout.light_widget_item
+ : R.layout.dark_widget_item);
+ RemoteViews rv = new RemoteViews(mContext.getPackageName(), itemId);
+ rv.setTextViewText(R.id.widget_item, String.format(formatStr, temp, city));
+
+ // Set the click intent so that we can handle it and show a toast message
+ final Intent fillInIntent = new Intent();
+ final Bundle extras = new Bundle();
+ extras.putString(WeatherWidgetProvider.EXTRA_CITY_ID, city);
+ fillInIntent.putExtras(extras);
+ rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);
+
+ return rv;
+ }
+ public RemoteViews getLoadingView() {
+ // We aren't going to return a default loading view in this sample
+ return null;
+ }
+
+ public int getViewTypeCount() {
+ // Technically, we have two types of views (the dark and light background views)
+ return 2;
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ public void onDataSetChanged() {
+ // Refresh the cursor
+ if (mCursor != null) {
+ mCursor.close();
+ }
+ mCursor = mContext.getContentResolver().query(WeatherDataProvider.CONTENT_URI, null, null,
+ null, null);
+ }
+}
\ No newline at end of file