am e412a0f4: Adding sample for collection widgets backed by content providers.
* commit 'e412a0f4a9ac5661ac3eaeaf160507e90642a249': Adding sample for collection widgets backed by content providers.
@@ -178,6 +178,7 @@ development/samples/SpinnerTest samples/${PLATFORM_NAME}/SpinnerTes
|
|||||||
development/samples/TicTacToeLib samples/${PLATFORM_NAME}/TicTacToeLib
|
development/samples/TicTacToeLib samples/${PLATFORM_NAME}/TicTacToeLib
|
||||||
development/samples/TicTacToeMain samples/${PLATFORM_NAME}/TicTacToeMain
|
development/samples/TicTacToeMain samples/${PLATFORM_NAME}/TicTacToeMain
|
||||||
development/samples/VoiceRecognitionService samples/${PLATFORM_NAME}/VoiceRecognitionService
|
development/samples/VoiceRecognitionService samples/${PLATFORM_NAME}/VoiceRecognitionService
|
||||||
|
development/samples/WeatherListWidget samples/${PLATFORM_NAME}/WeatherListWidget
|
||||||
development/apps/WidgetPreview samples/${PLATFORM_NAME}/WidgetPreview
|
development/apps/WidgetPreview samples/${PLATFORM_NAME}/WidgetPreview
|
||||||
development/samples/Wiktionary samples/${PLATFORM_NAME}/Wiktionary
|
development/samples/Wiktionary samples/${PLATFORM_NAME}/Wiktionary
|
||||||
development/samples/WiktionarySimple samples/${PLATFORM_NAME}/WiktionarySimple
|
development/samples/WiktionarySimple samples/${PLATFORM_NAME}/WiktionarySimple
|
||||||
|
|||||||
16
samples/WeatherListWidget/Android.mk
Normal file
@@ -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))
|
||||||
45
samples/WeatherListWidget/AndroidManifest.xml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- Declare the contents of this Android application. The namespace
|
||||||
|
attribute brings in the Android platform namespace, and the package
|
||||||
|
supplies a unique name for the application. When writing your
|
||||||
|
own application, the package name must be changed from "com.example.*"
|
||||||
|
to come from a domain that you own or have control over. -->
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.android.weatherlistwidget">
|
||||||
|
<uses-sdk android:minSdkVersion="11" />
|
||||||
|
<application android:label="Weather Widget Sample">
|
||||||
|
<!-- The widget provider -->
|
||||||
|
<receiver android:name="WeatherWidgetProvider">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
</intent-filter>
|
||||||
|
<!-- This specifies the widget provider info -->
|
||||||
|
<meta-data android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/widgetinfo" />
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<!-- The service serving the RemoteViews to the collection widget -->
|
||||||
|
<service android:name="WeatherWidgetService"
|
||||||
|
android:permission="android.permission.BIND_REMOTEVIEWS"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
<!-- The content provider serving the (fake) weather data -->
|
||||||
|
<provider android:name="WeatherDataProvider"
|
||||||
|
android:authorities="com.example.android.weatherlistwidget.provider" />
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
43
samples/WeatherListWidget/_index.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<p>This sample demonstrates how to create a list-based widget specifically backed by a content provider.<br/>
|
||||||
|
<em>Please make sure that you understand the earlier stack widget sample code before delving into this example
|
||||||
|
(see <a href="../StackWidget/index.html">StackWidget</a>).</em></p>
|
||||||
|
|
||||||
|
<p>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.</p>
|
||||||
|
|
||||||
|
<p>This sample uses the following classes:
|
||||||
|
<ul>
|
||||||
|
<li><a href="src/com/example/android/weatherlistwidget/WeatherDataProvider.html"><code>WeatherDataProvider</code></a> —
|
||||||
|
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.
|
||||||
|
</li>
|
||||||
|
<li><a href="src/com/example/android/weatherlistwidget/WeatherWidgetProvider.html"><code>WeatherWidgetProvider</code></a> —
|
||||||
|
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.
|
||||||
|
</li>
|
||||||
|
<li><a href="src/com/example/android/weatherlistwidget/WeatherWidgetService.html"><code>WeatherWidgetService</code></a> —
|
||||||
|
Our RemoteViewsService which manages the creation of new factories, which return the RemoteViews
|
||||||
|
for each item in the ListView.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>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:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><code><uses-sdk android:minSdkVersion="11" /></code>, which indicates
|
||||||
|
to Android Market and the platform that your application requires Android 3.0 or
|
||||||
|
higher. For more information, see the <a href="../../../guide/appendix/api-levels.html">API Levels</a>
|
||||||
|
and the documentation for the
|
||||||
|
<a href="../../../guide/topics/manifest/uses-sdk-element.html"><code><uses-sdk></code></a>
|
||||||
|
element.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<img alt="Screenshot" src="../images/WeatherListWidget.png" />
|
||||||
BIN
samples/WeatherListWidget/res/drawable-hdpi/body.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
samples/WeatherListWidget/res/drawable-hdpi/footer.png
Normal file
|
After Width: | Height: | Size: 332 B |
BIN
samples/WeatherListWidget/res/drawable-hdpi/header.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
samples/WeatherListWidget/res/drawable-hdpi/icon.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
samples/WeatherListWidget/res/drawable-hdpi/item_bg_dark.png
Normal file
|
After Width: | Height: | Size: 558 B |
BIN
samples/WeatherListWidget/res/drawable-hdpi/item_bg_light.png
Normal file
|
After Width: | Height: | Size: 567 B |
BIN
samples/WeatherListWidget/res/drawable-hdpi/refresh.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
samples/WeatherListWidget/res/drawable-hdpi/refresh_pressed.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
samples/WeatherListWidget/res/drawable-ldpi/body.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
samples/WeatherListWidget/res/drawable-ldpi/footer.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
samples/WeatherListWidget/res/drawable-ldpi/header.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
samples/WeatherListWidget/res/drawable-ldpi/icon.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
samples/WeatherListWidget/res/drawable-ldpi/item_bg_dark.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
samples/WeatherListWidget/res/drawable-ldpi/item_bg_light.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
samples/WeatherListWidget/res/drawable-ldpi/refresh.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
samples/WeatherListWidget/res/drawable-ldpi/refresh_pressed.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
samples/WeatherListWidget/res/drawable-mdpi/body.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
samples/WeatherListWidget/res/drawable-mdpi/footer.png
Normal file
|
After Width: | Height: | Size: 247 B |
BIN
samples/WeatherListWidget/res/drawable-mdpi/header.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
samples/WeatherListWidget/res/drawable-mdpi/icon.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
samples/WeatherListWidget/res/drawable-mdpi/item_bg_dark.png
Normal file
|
After Width: | Height: | Size: 399 B |
BIN
samples/WeatherListWidget/res/drawable-mdpi/item_bg_light.png
Normal file
|
After Width: | Height: | Size: 438 B |
BIN
samples/WeatherListWidget/res/drawable-mdpi/refresh.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
samples/WeatherListWidget/res/drawable-mdpi/refresh_pressed.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
samples/WeatherListWidget/res/drawable-nodpi/preview.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_pressed="true"
|
||||||
|
android:drawable="@drawable/refresh_pressed" /> <!-- pressed -->
|
||||||
|
<item android:drawable="@drawable/refresh" /> <!-- default -->
|
||||||
|
</selector>
|
||||||
24
samples/WeatherListWidget/res/layout/dark_widget_item.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/widget_item"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="46dp"
|
||||||
|
android:paddingLeft="25dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:background="@drawable/item_bg_dark"
|
||||||
|
android:textColor="#e5e5e1"
|
||||||
|
android:textSize="24sp" />
|
||||||
24
samples/WeatherListWidget/res/layout/light_widget_item.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/widget_item"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="46dp"
|
||||||
|
android:paddingLeft="25dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:background="@drawable/item_bg_light"
|
||||||
|
android:textColor="#e5e5e1"
|
||||||
|
android:textSize="24sp" />
|
||||||
61
samples/WeatherListWidget/res/layout/widget_layout.xml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="294dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/header" />
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/refresh"
|
||||||
|
android:layout_width="56dp"
|
||||||
|
android:layout_height="39dp"
|
||||||
|
android:layout_marginLeft="222dp"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:background="@drawable/refresh_button" />
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@drawable/body">
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/weather_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/empty_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:text="@string/empty_view_text"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
</FrameLayout>
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/footer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/footer" />
|
||||||
|
</LinearLayout>
|
||||||
20
samples/WeatherListWidget/res/values/strings.xml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
<string name="empty_view_text">No cities found...</string>
|
||||||
|
<string name="toast_format_string">%1$s says Hi!</string>
|
||||||
|
<string name="item_format_string">%1$d\u00B0 in %2$s</string>
|
||||||
|
</resources>
|
||||||
23
samples/WeatherListWidget/res/xml/widgetinfo.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
<appwidget-provider
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:minWidth="222dip"
|
||||||
|
android:minHeight="222dip"
|
||||||
|
android:updatePeriodMillis="1800000"
|
||||||
|
android:initialLayout="@layout/widget_layout"
|
||||||
|
android:previewImage="@drawable/preview">
|
||||||
|
</appwidget-provider>
|
||||||
@@ -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<WeatherDataPoint> sData = new ArrayList<WeatherDataPoint>();
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||