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: +

+

+ +

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:

+ + + +Screenshot \ 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