development
修訂 | e412a0f4a9ac5661ac3eaeaf160507e90642a249 (tree) |
---|---|
時間 | 2011-02-12 07:50:06 |
作者 | Winson Chung <winsonc@goog...> |
Commiter | Winson Chung |
Adding sample for collection widgets backed by content providers.
Change-Id: If0005d88a19dfa05ddd7c60aba1a1d7d742158be
@@ -178,6 +178,7 @@ development/samples/SpinnerTest samples/${PLATFORM_NAME}/SpinnerTes | ||
178 | 178 | development/samples/TicTacToeLib samples/${PLATFORM_NAME}/TicTacToeLib |
179 | 179 | development/samples/TicTacToeMain samples/${PLATFORM_NAME}/TicTacToeMain |
180 | 180 | development/samples/VoiceRecognitionService samples/${PLATFORM_NAME}/VoiceRecognitionService |
181 | +development/samples/WeatherListWidget samples/${PLATFORM_NAME}/WeatherListWidget | |
181 | 182 | development/apps/WidgetPreview samples/${PLATFORM_NAME}/WidgetPreview |
182 | 183 | development/samples/Wiktionary samples/${PLATFORM_NAME}/Wiktionary |
183 | 184 | development/samples/WiktionarySimple samples/${PLATFORM_NAME}/WiktionarySimple |
@@ -0,0 +1,16 @@ | ||
1 | +LOCAL_PATH:= $(call my-dir) | |
2 | +include $(CLEAR_VARS) | |
3 | + | |
4 | +LOCAL_MODULE_TAGS := tests | |
5 | + | |
6 | +# Only compile source java files in this apk. | |
7 | +LOCAL_SRC_FILES := $(call all-java-files-under, src) | |
8 | + | |
9 | +LOCAL_PACKAGE_NAME := WeatherListWidget | |
10 | + | |
11 | +LOCAL_SDK_VERSION := current | |
12 | + | |
13 | +include $(BUILD_PACKAGE) | |
14 | + | |
15 | +# Use the following include to make our test apk. | |
16 | +include $(call all-makefiles-under,$(LOCAL_PATH)) |
@@ -0,0 +1,45 @@ | ||
1 | +<?xml version="1.0" encoding="utf-8"?> | |
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 | +<!-- Declare the contents of this Android application. The namespace | |
18 | + attribute brings in the Android platform namespace, and the package | |
19 | + supplies a unique name for the application. When writing your | |
20 | + own application, the package name must be changed from "com.example.*" | |
21 | + to come from a domain that you own or have control over. --> | |
22 | +<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |
23 | + package="com.example.android.weatherlistwidget"> | |
24 | + <uses-sdk android:minSdkVersion="11" /> | |
25 | + <application android:label="Weather Widget Sample"> | |
26 | + <!-- The widget provider --> | |
27 | + <receiver android:name="WeatherWidgetProvider"> | |
28 | + <intent-filter> | |
29 | + <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> | |
30 | + </intent-filter> | |
31 | + <!-- This specifies the widget provider info --> | |
32 | + <meta-data android:name="android.appwidget.provider" | |
33 | + android:resource="@xml/widgetinfo" /> | |
34 | + </receiver> | |
35 | + | |
36 | + <!-- The service serving the RemoteViews to the collection widget --> | |
37 | + <service android:name="WeatherWidgetService" | |
38 | + android:permission="android.permission.BIND_REMOTEVIEWS" | |
39 | + android:exported="false" /> | |
40 | + | |
41 | + <!-- The content provider serving the (fake) weather data --> | |
42 | + <provider android:name="WeatherDataProvider" | |
43 | + android:authorities="com.example.android.weatherlistwidget.provider" /> | |
44 | + </application> | |
45 | +</manifest> | |
\ No newline at end of file |
@@ -0,0 +1,43 @@ | ||
1 | +<p>This sample demonstrates how to create a list-based widget specifically backed by a content provider.<br/> | |
2 | +<em>Please make sure that you understand the earlier stack widget sample code before delving into this example | |
3 | + (see <a href="../StackWidget/index.html">StackWidget</a>).</em></p> | |
4 | + | |
5 | +<p>As in the StackWidget example, we will be using a collection view (the ListView in this case) to | |
6 | +present some mock weather data in a widget. In particular, we will be using a content provider to | |
7 | +demonstrate how the widget can retrieve data and update itself when you are using more complex data | |
8 | +sources. When working with external data, or data which must be fetched over high latency, it is | |
9 | +important to keep the data cached in a persistent location so that the widget feels responsive.</p> | |
10 | + | |
11 | +<p>This sample uses the following classes: | |
12 | + <ul> | |
13 | + <li><a href="src/com/example/android/weatherlistwidget/WeatherDataProvider.html"><code>WeatherDataProvider</code></a> — | |
14 | + Our ContentProvider which stores the weather data for this sample. Note that for simplicity, | |
15 | + it currently stores the data in memory as opposed to an external and persistent storage such | |
16 | + as a file, network location, database, or shared preference. | |
17 | + </li> | |
18 | + <li><a href="src/com/example/android/weatherlistwidget/WeatherWidgetProvider.html"><code>WeatherWidgetProvider</code></a> — | |
19 | + Our AppWidgetProvider which handles specific intent actions (such as when an item is clicked, | |
20 | + or the refresh button is pressed). It also sets up the RemoteViews for the widget and binds | |
21 | + the service to the collection view in the widget. | |
22 | + </li> | |
23 | + <li><a href="src/com/example/android/weatherlistwidget/WeatherWidgetService.html"><code>WeatherWidgetService</code></a> — | |
24 | + Our RemoteViewsService which manages the creation of new factories, which return the RemoteViews | |
25 | + for each item in the ListView. | |
26 | + </li> | |
27 | + </ul> | |
28 | +</p> | |
29 | + | |
30 | +<p>If you are writing collection-based widgets, remember that the feature is | |
31 | +supported only on Android 3.0 (API level 11) and higher versions of the platform. | |
32 | +Remember to add the following to the application's manifest publishing to Android Market:</p> | |
33 | + | |
34 | +<ul> | |
35 | +<li><code><uses-sdk android:minSdkVersion="11" /></code>, which indicates | |
36 | +to Android Market and the platform that your application requires Android 3.0 or | |
37 | +higher. For more information, see the <a href="../../../guide/appendix/api-levels.html">API Levels</a> | |
38 | +and the documentation for the | |
39 | +<a href="../../../guide/topics/manifest/uses-sdk-element.html"><code><uses-sdk></code></a> | |
40 | +element.</li> | |
41 | +</ul> | |
42 | + | |
43 | +<img alt="Screenshot" src="../images/WeatherListWidget.png" /> | |
\ No newline at end of file |
@@ -0,0 +1,6 @@ | ||
1 | +<?xml version="1.0" encoding="utf-8"?> | |
2 | +<selector xmlns:android="http://schemas.android.com/apk/res/android"> | |
3 | + <item android:state_pressed="true" | |
4 | + android:drawable="@drawable/refresh_pressed" /> <!-- pressed --> | |
5 | + <item android:drawable="@drawable/refresh" /> <!-- default --> | |
6 | +</selector> | |
\ No newline at end of file |
@@ -0,0 +1,24 @@ | ||
1 | +<?xml version="1.0" encoding="utf-8"?> | |
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 | +<TextView xmlns:android="http://schemas.android.com/apk/res/android" | |
17 | + android:id="@+id/widget_item" | |
18 | + android:layout_width="match_parent" | |
19 | + android:layout_height="46dp" | |
20 | + android:paddingLeft="25dp" | |
21 | + android:gravity="center_vertical" | |
22 | + android:background="@drawable/item_bg_dark" | |
23 | + android:textColor="#e5e5e1" | |
24 | + android:textSize="24sp" /> |
@@ -0,0 +1,24 @@ | ||
1 | +<?xml version="1.0" encoding="utf-8"?> | |
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 | +<TextView xmlns:android="http://schemas.android.com/apk/res/android" | |
17 | + android:id="@+id/widget_item" | |
18 | + android:layout_width="match_parent" | |
19 | + android:layout_height="46dp" | |
20 | + android:paddingLeft="25dp" | |
21 | + android:gravity="center_vertical" | |
22 | + android:background="@drawable/item_bg_light" | |
23 | + android:textColor="#e5e5e1" | |
24 | + android:textSize="24sp" /> |
@@ -0,0 +1,61 @@ | ||
1 | +<?xml version="1.0" encoding="utf-8"?> | |
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 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
17 | + android:layout_width="294dp" | |
18 | + android:layout_height="match_parent" | |
19 | + android:orientation="vertical"> | |
20 | + <FrameLayout | |
21 | + android:layout_width="match_parent" | |
22 | + android:layout_height="wrap_content"> | |
23 | + <ImageView | |
24 | + android:id="@+id/header" | |
25 | + android:layout_width="match_parent" | |
26 | + android:layout_height="wrap_content" | |
27 | + android:src="@drawable/header" /> | |
28 | + <ImageButton | |
29 | + android:id="@+id/refresh" | |
30 | + android:layout_width="56dp" | |
31 | + android:layout_height="39dp" | |
32 | + android:layout_marginLeft="222dp" | |
33 | + android:layout_marginTop="20dp" | |
34 | + android:background="@drawable/refresh_button" /> | |
35 | + </FrameLayout> | |
36 | + <FrameLayout | |
37 | + android:layout_width="match_parent" | |
38 | + android:layout_height="match_parent" | |
39 | + android:layout_weight="1" | |
40 | + android:layout_gravity="center" | |
41 | + android:background="@drawable/body"> | |
42 | + <ListView | |
43 | + android:id="@+id/weather_list" | |
44 | + android:layout_width="match_parent" | |
45 | + android:layout_height="match_parent" /> | |
46 | + <TextView | |
47 | + android:id="@+id/empty_view" | |
48 | + android:layout_width="match_parent" | |
49 | + android:layout_height="match_parent" | |
50 | + android:gravity="center" | |
51 | + android:visibility="gone" | |
52 | + android:textColor="#ffffff" | |
53 | + android:text="@string/empty_view_text" | |
54 | + android:textSize="20sp" /> | |
55 | + </FrameLayout> | |
56 | + <ImageView | |
57 | + android:id="@+id/footer" | |
58 | + android:layout_width="match_parent" | |
59 | + android:layout_height="wrap_content" | |
60 | + android:src="@drawable/footer" /> | |
61 | +</LinearLayout> |
@@ -0,0 +1,20 @@ | ||
1 | +<?xml version="1.0" encoding="utf-8"?> | |
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 | +<resources> | |
17 | + <string name="empty_view_text">No cities found...</string> | |
18 | + <string name="toast_format_string">%1$s says Hi!</string> | |
19 | + <string name="item_format_string">%1$d\u00B0 in %2$s</string> | |
20 | +</resources> |
@@ -0,0 +1,23 @@ | ||
1 | +<?xml version="1.0" encoding="utf-8"?> | |
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 | +<appwidget-provider | |
17 | + xmlns:android="http://schemas.android.com/apk/res/android" | |
18 | + android:minWidth="222dip" | |
19 | + android:minHeight="222dip" | |
20 | + android:updatePeriodMillis="1800000" | |
21 | + android:initialLayout="@layout/widget_layout" | |
22 | + android:previewImage="@drawable/preview"> | |
23 | +</appwidget-provider> | |
\ No newline at end of file |
@@ -0,0 +1,137 @@ | ||
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.appwidget.AppWidgetManager; | |
20 | +import android.appwidget.AppWidgetProvider; | |
21 | +import android.content.ContentProvider; | |
22 | +import android.content.ContentValues; | |
23 | +import android.content.Context; | |
24 | +import android.content.Intent; | |
25 | +import android.content.res.Resources; | |
26 | +import android.database.Cursor; | |
27 | +import android.database.MatrixCursor; | |
28 | +import android.net.Uri; | |
29 | + | |
30 | +import java.util.ArrayList; | |
31 | + | |
32 | +/** | |
33 | + * A dummy class that we are going to use internally to store weather data. Generally, this data | |
34 | + * will be stored in an external and persistent location (ie. File, Database, SharedPreferences) so | |
35 | + * that the data can persist if the process is ever killed. For simplicity, in this sample the | |
36 | + * data will only be stored in memory. | |
37 | + */ | |
38 | +class WeatherDataPoint { | |
39 | + String city; | |
40 | + int degrees; | |
41 | + | |
42 | + WeatherDataPoint(String c, int d) { | |
43 | + city = c; | |
44 | + degrees = d; | |
45 | + } | |
46 | +} | |
47 | + | |
48 | +/** | |
49 | + * The AppWidgetProvider for our sample weather widget. | |
50 | + */ | |
51 | +public class WeatherDataProvider extends ContentProvider { | |
52 | + public static final Uri CONTENT_URI = | |
53 | + Uri.parse("content://com.example.android.weatherlistwidget.provider"); | |
54 | + public static class Columns { | |
55 | + public static final String ID = "_id"; | |
56 | + public static final String CITY = "city"; | |
57 | + public static final String TEMPERATURE = "temperature"; | |
58 | + } | |
59 | + | |
60 | + /** | |
61 | + * Generally, this data will be stored in an external and persistent location (ie. File, | |
62 | + * Database, SharedPreferences) so that the data can persist if the process is ever killed. | |
63 | + * For simplicity, in this sample the data will only be stored in memory. | |
64 | + */ | |
65 | + private static final ArrayList<WeatherDataPoint> sData = new ArrayList<WeatherDataPoint>(); | |
66 | + | |
67 | + @Override | |
68 | + public boolean onCreate() { | |
69 | + // We are going to initialize the data provider with some default values | |
70 | + sData.add(new WeatherDataPoint("San Francisco", 13)); | |
71 | + sData.add(new WeatherDataPoint("New York", 1)); | |
72 | + sData.add(new WeatherDataPoint("Seattle", 7)); | |
73 | + sData.add(new WeatherDataPoint("Boston", 4)); | |
74 | + sData.add(new WeatherDataPoint("Miami", 22)); | |
75 | + sData.add(new WeatherDataPoint("Toronto", -10)); | |
76 | + sData.add(new WeatherDataPoint("Calgary", -13)); | |
77 | + sData.add(new WeatherDataPoint("Tokyo", 8)); | |
78 | + sData.add(new WeatherDataPoint("Kyoto", 11)); | |
79 | + sData.add(new WeatherDataPoint("London", -1)); | |
80 | + sData.add(new WeatherDataPoint("Nomanisan", 27)); | |
81 | + return true; | |
82 | + } | |
83 | + | |
84 | + @Override | |
85 | + public synchronized Cursor query(Uri uri, String[] projection, String selection, | |
86 | + String[] selectionArgs, String sortOrder) { | |
87 | + assert(uri.getPathSegments().isEmpty()); | |
88 | + | |
89 | + // In this sample, we only query without any parameters, so we can just return a cursor to | |
90 | + // all the weather data. | |
91 | + final MatrixCursor c = new MatrixCursor( | |
92 | + new String[]{ Columns.ID, Columns.CITY, Columns.TEMPERATURE }); | |
93 | + for (int i = 0; i < sData.size(); ++i) { | |
94 | + final WeatherDataPoint data = sData.get(i); | |
95 | + c.addRow(new Object[]{ new Integer(i), data.city, new Integer(data.degrees) }); | |
96 | + } | |
97 | + return c; | |
98 | + } | |
99 | + | |
100 | + @Override | |
101 | + public String getType(Uri uri) { | |
102 | + return "vnd.android.cursor.dir/vnd.weatherlistwidget.citytemperature"; | |
103 | + } | |
104 | + | |
105 | + @Override | |
106 | + public Uri insert(Uri uri, ContentValues values) { | |
107 | + // This example code does not support inserting | |
108 | + return null; | |
109 | + } | |
110 | + | |
111 | + @Override | |
112 | + public int delete(Uri uri, String selection, String[] selectionArgs) { | |
113 | + // This example code does not support deleting | |
114 | + return 0; | |
115 | + } | |
116 | + | |
117 | + @Override | |
118 | + public synchronized int update(Uri uri, ContentValues values, String selection, | |
119 | + String[] selectionArgs) { | |
120 | + assert(uri.getPathSegments().size() == 1); | |
121 | + | |
122 | + // In this sample, we only update the content provider individually for each row with new | |
123 | + // temperature values. | |
124 | + final int index = Integer.parseInt(uri.getPathSegments().get(0)); | |
125 | + final MatrixCursor c = new MatrixCursor( | |
126 | + new String[]{ Columns.ID, Columns.CITY, Columns.TEMPERATURE }); | |
127 | + assert(0 <= index && index < sData.size()); | |
128 | + final WeatherDataPoint data = sData.get(index); | |
129 | + data.degrees = values.getAsInteger(Columns.TEMPERATURE); | |
130 | + | |
131 | + // Notify any listeners that the data backing the content provider has changed, and return | |
132 | + // the number of rows affected. | |
133 | + getContext().getContentResolver().notifyChange(uri, null); | |
134 | + return 1; | |
135 | + } | |
136 | + | |
137 | +} | |
\ No newline at end of file |
@@ -0,0 +1,181 @@ | ||
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 | +} | |
\ No newline at end of file |
@@ -0,0 +1,126 @@ | ||
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 java.util.ArrayList; | |
20 | +import java.util.List; | |
21 | + | |
22 | +import android.appwidget.AppWidgetManager; | |
23 | +import android.content.Context; | |
24 | +import android.content.Intent; | |
25 | +import android.content.ContentUris; | |
26 | +import android.database.Cursor; | |
27 | +import android.net.Uri; | |
28 | +import android.os.Bundle; | |
29 | +import android.widget.RemoteViews; | |
30 | +import android.widget.RemoteViewsService; | |
31 | + | |
32 | +/** | |
33 | + * This is the service that provides the factory to be bound to the collection service. | |
34 | + */ | |
35 | +public class WeatherWidgetService extends RemoteViewsService { | |
36 | + @Override | |
37 | + public RemoteViewsFactory onGetViewFactory(Intent intent) { | |
38 | + return new StackRemoteViewsFactory(this.getApplicationContext(), intent); | |
39 | + } | |
40 | +} | |
41 | + | |
42 | +/** | |
43 | + * This is the factory that will provide data to the collection widget. | |
44 | + */ | |
45 | +class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { | |
46 | + private Context mContext; | |
47 | + private Cursor mCursor; | |
48 | + private int mAppWidgetId; | |
49 | + | |
50 | + public StackRemoteViewsFactory(Context context, Intent intent) { | |
51 | + mContext = context; | |
52 | + mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, | |
53 | + AppWidgetManager.INVALID_APPWIDGET_ID); | |
54 | + } | |
55 | + | |
56 | + public void onCreate() { | |
57 | + // Since we reload the cursor in onDataSetChanged() which gets called immediately after | |
58 | + // onCreate(), we do nothing here. | |
59 | + } | |
60 | + | |
61 | + public void onDestroy() { | |
62 | + if (mCursor != null) { | |
63 | + mCursor.close(); | |
64 | + } | |
65 | + } | |
66 | + | |
67 | + public int getCount() { | |
68 | + return mCursor.getCount(); | |
69 | + } | |
70 | + | |
71 | + public RemoteViews getViewAt(int position) { | |
72 | + // Get the data for this position from the content provider | |
73 | + String city = "Unknown City"; | |
74 | + int temp = 0; | |
75 | + if (mCursor.moveToPosition(position)) { | |
76 | + final int cityColIndex = mCursor.getColumnIndex(WeatherDataProvider.Columns.CITY); | |
77 | + final int tempColIndex = mCursor.getColumnIndex( | |
78 | + WeatherDataProvider.Columns.TEMPERATURE); | |
79 | + city = mCursor.getString(cityColIndex); | |
80 | + temp = mCursor.getInt(tempColIndex); | |
81 | + } | |
82 | + | |
83 | + // Return a proper item with the proper city and temperature. Just for fun, we alternate | |
84 | + // the items to make the list easier to read. | |
85 | + final String formatStr = mContext.getResources().getString(R.string.item_format_string); | |
86 | + final int itemId = (position % 2 == 0 ? R.layout.light_widget_item | |
87 | + : R.layout.dark_widget_item); | |
88 | + RemoteViews rv = new RemoteViews(mContext.getPackageName(), itemId); | |
89 | + rv.setTextViewText(R.id.widget_item, String.format(formatStr, temp, city)); | |
90 | + | |
91 | + // Set the click intent so that we can handle it and show a toast message | |
92 | + final Intent fillInIntent = new Intent(); | |
93 | + final Bundle extras = new Bundle(); | |
94 | + extras.putString(WeatherWidgetProvider.EXTRA_CITY_ID, city); | |
95 | + fillInIntent.putExtras(extras); | |
96 | + rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent); | |
97 | + | |
98 | + return rv; | |
99 | + } | |
100 | + public RemoteViews getLoadingView() { | |
101 | + // We aren't going to return a default loading view in this sample | |
102 | + return null; | |
103 | + } | |
104 | + | |
105 | + public int getViewTypeCount() { | |
106 | + // Technically, we have two types of views (the dark and light background views) | |
107 | + return 2; | |
108 | + } | |
109 | + | |
110 | + public long getItemId(int position) { | |
111 | + return position; | |
112 | + } | |
113 | + | |
114 | + public boolean hasStableIds() { | |
115 | + return true; | |
116 | + } | |
117 | + | |
118 | + public void onDataSetChanged() { | |
119 | + // Refresh the cursor | |
120 | + if (mCursor != null) { | |
121 | + mCursor.close(); | |
122 | + } | |
123 | + mCursor = mContext.getContentResolver().query(WeatherDataProvider.CONTENT_URI, null, null, | |
124 | + null, null); | |
125 | + } | |
126 | +} | |
\ No newline at end of file |