Merge change I0d9344aa into eclair

* changes:
  Fixed
diff --git a/build/sdk.atree b/build/sdk.atree
index 6696deb..f03d354 100644
--- a/build/sdk.atree
+++ b/build/sdk.atree
@@ -83,6 +83,10 @@
 development/samples/SoftKeyboard platforms/${PLATFORM_NAME}/samples/SoftKeyboard
 development/samples/JetBoy platforms/${PLATFORM_NAME}/samples/JetBoy
 development/samples/SearchableDictionary platforms/${PLATFORM_NAME}/samples/SearchableDictionary
+development/samples/ContactManager platforms/${PLATFORM_NAME}/samples/ContactManager
+development/samples/MultiResolution platforms/${PLATFORM_NAME}/samples/MultiResolution
+development/samples/Wiktionary platforms/${PLATFORM_NAME}/samples/Wiktionary
+development/samples/WiktionarySimple platforms/${PLATFORM_NAME}/samples/WiktionarySimple
 
 # dx
 bin/dx platforms/${PLATFORM_NAME}/tools/dx
diff --git a/ndk/docs/CHANGES.TXT b/ndk/docs/CHANGES.TXT
index 145933c..c6db521 100644
--- a/ndk/docs/CHANGES.TXT
+++ b/ndk/docs/CHANGES.TXT
@@ -56,6 +56,10 @@
   OpenGL ES 2.0 is currently *not* available from Java, and must be used
   through native code exclusively.
 
+  IMPORTANT: OpenGL ES 2.0 is not supported in the Android emulator at this
+             time. Running/testing any native code that depends on it thus
+             requires a real device.
+
 - The NDK build script will now remove installed binaries from the application
   project's path before starting the build. This ensures that:
 
diff --git a/ndk/docs/STABLE-APIS.TXT b/ndk/docs/STABLE-APIS.TXT
index 700bb88..48e6b5a 100644
--- a/ndk/docs/STABLE-APIS.TXT
+++ b/ndk/docs/STABLE-APIS.TXT
@@ -157,8 +157,7 @@
 ----------------------------------
 
 All the APIs listed below are available for developing native code that runs
-on the Eclair experimental branch, which will be used to make the next official
-platform system images.
+on Android 2.0 system images and above.
 
 
 The OpenGL ES 2.0 Library:
@@ -183,3 +182,8 @@
 
 The "hello-gl2" sample application demonstrate this. It is used to draw a very
 simple triangle with the help of a vertex and fragment shaders.
+
+IMPORTANT NOTE:
+    The Android emulator does not support OpenGL ES 2.0 hardware emulation
+    at this time. Running and testing code that uses this API requires a
+    real device with such capabilities.
diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index aba330a..a6eff96 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -543,6 +543,15 @@
             </intent-filter>
         </activity>
 
+        <!-- Text-To-Speech Samples -->
+
+        <activity android:name=".app.TextToSpeechActivity" android:label="@string/text_to_speech">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
         <!-- ************************************* -->
         <!--        CONTENT PACKAGE SAMPLES        -->
         <!-- ************************************* -->
diff --git a/samples/ApiDemos/res/layout/text_to_speech.xml b/samples/ApiDemos/res/layout/text_to_speech.xml
new file mode 100644
index 0000000..0ba60e1
--- /dev/null
+++ b/samples/ApiDemos/res/layout/text_to_speech.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    >
+    <Button android:id="@+id/again_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:enabled="false"
+        android:text="@string/again" />
+</LinearLayout>
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index 6f91b34..b19504b 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -221,6 +221,9 @@
 
     <string name="voice_recognition">App/Voice Recognition</string>
 
+    <string name="text_to_speech">App/Text-To-Speech</string>
+    <string name="again">Again</string>
+
     <!-- ============================== -->
     <!--  app/content examples strings  -->
     <!-- ============================== -->
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/TextToSpeechActivity.java b/samples/ApiDemos/src/com/example/android/apis/app/TextToSpeechActivity.java
new file mode 100644
index 0000000..a693e80
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/TextToSpeechActivity.java
@@ -0,0 +1,136 @@
+ /*
+ * Copyright (C) 2009 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.apis.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.speech.tts.TextToSpeech;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+
+import com.example.android.apis.R;
+
+import java.util.Locale;
+import java.util.Random;
+
+/**
+ * <p>Demonstrates text-to-speech (TTS). Please note the following steps:</p>
+ *
+ * <ol>
+ * <li>Construct the TextToSpeech object.</li>
+ * <li>Handle initialization callback in the onInit method.
+ * The activity implements TextToSpeech.OnInitListener for this purpose.</li>
+ * <li>Call TextToSpeech.speak to synthesize speech.</li>
+ * <li>Shutdown TextToSpeech in onDestroy.</li>
+ * </ol>
+ *
+ * <p>Documentation:
+ * http://developer.android.com/reference/android/speech/tts/package-summary.html
+ * </p>
+ * <ul>
+ */
+public class TextToSpeechActivity extends Activity implements TextToSpeech.OnInitListener {
+
+    private static final String TAG = "TextToSpeechDemo";
+
+    private TextToSpeech mTts;
+    private Button mAgainButton;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.text_to_speech);
+
+        // Initialize text-to-speech. This is an asynchronous operation.
+        // The OnInitListener (second argument) is called after initialization completes.
+        mTts = new TextToSpeech(this,
+            this  // TextToSpeech.OnInitListener
+            );
+
+        // The button is disabled in the layout.
+        // It will be enabled upon initialization of the TTS engine.
+        mAgainButton = (Button) findViewById(R.id.again_button);
+
+        mAgainButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                sayHello();
+            }
+        });
+    }
+
+    @Override
+    public void onDestroy() {
+        // Don't forget to shutdown!
+        if (mTts != null) {
+            mTts.stop();
+            mTts.shutdown();
+        }
+
+        super.onDestroy();
+    }
+
+    // Implements TextToSpeech.OnInitListener.
+    public void onInit(int status) {
+        // status can be either TextToSpeech.SUCCESS or TextToSpeech.ERROR.
+        if (status == TextToSpeech.SUCCESS) {
+            // Set preferred language to US english.
+            // Note that a language may not be available, and the result will indicate this.
+            int result = mTts.setLanguage(Locale.US);
+            // Try this someday for some interesting results.
+            // int result mTts.setLanguage(Locale.FRANCE);
+            if (result == TextToSpeech.LANG_MISSING_DATA ||
+                result == TextToSpeech.LANG_NOT_SUPPORTED) {
+               // Lanuage data is missing or the language is not supported.
+                Log.e(TAG, "Language is not available.");
+            } else {
+                // Check the documentation for other possible result codes.
+                // For example, the language may be available for the locale,
+                // but not for the specified country and variant.
+
+                // The TTS engine has been successfully initialized.
+                // Allow the user to press the button for the app to speak again.
+                mAgainButton.setEnabled(true);
+                // Greet the user.
+                sayHello();
+            }
+        } else {
+            // Initialization failed.
+            Log.e(TAG, "Could not initialize TextToSpeech.");
+        }
+    }
+
+    private static final Random RANDOM = new Random();
+    private static final String[] HELLOS = {
+      "Hello",
+      "Salutations",
+      "Greetings",
+      "Howdy",
+      "What's crack-a-lackin?",
+      "That explains the stench!"
+    };
+
+    private void sayHello() {
+        // Select a random hello.
+        int helloLength = HELLOS.length;
+        String hello = HELLOS[RANDOM.nextInt(helloLength)];
+        mTts.speak(hello,
+            TextToSpeech.QUEUE_FLUSH,  // Drop all pending entries in the playback queue.
+            null);
+    }
+
+}
diff --git a/samples/BluetoothChat/src/com/example/android/BluetoothChat/BluetoothChat.java b/samples/BluetoothChat/src/com/example/android/BluetoothChat/BluetoothChat.java
index e4b9d52..d05bbd6 100644
--- a/samples/BluetoothChat/src/com/example/android/BluetoothChat/BluetoothChat.java
+++ b/samples/BluetoothChat/src/com/example/android/BluetoothChat/BluetoothChat.java
@@ -50,14 +50,13 @@
     // Message types sent from the BluetoothChatService Handler
     public static final int MESSAGE_STATE_CHANGE = 1;
     public static final int MESSAGE_READ = 2;
-    public static final int MESSAGE_OUTGOING_STRING = 3;
+    public static final int MESSAGE_WRITE = 3;
     public static final int MESSAGE_DEVICE_NAME = 4;
     public static final int MESSAGE_TOAST = 5;
 
     // Key names received from the BluetoothChatService Handler
     public static final String DEVICE_NAME = "device_name";
     public static final String TOAST = "toast";
-    public static final String MESSAGE_WRITE = "message_write";
 
     // Intent request codes
     private static final int REQUEST_CONNECT_DEVICE = 1;
@@ -192,9 +191,10 @@
 
     private void ensureDiscoverable() {
         if(D) Log.d(TAG, "ensure discoverable");
-        if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
+        if (mBluetoothAdapter.getScanMode() !=
+            BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
             Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
-            discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); // set max duration
+            discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
             startActivity(discoverableIntent);
         }
     }
@@ -223,7 +223,8 @@
     }
 
     // The action listener for the EditText widget, to listen for the return key
-    private TextView.OnEditorActionListener mWriteListener = new TextView.OnEditorActionListener() {
+    private TextView.OnEditorActionListener mWriteListener =
+        new TextView.OnEditorActionListener() {
         public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
             // If the action is a key-up event on the return key, send the message
             if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_UP) {
@@ -257,20 +258,27 @@
                     break;
                 }
                 break;
-            case MESSAGE_OUTGOING_STRING:
-                mConversationArrayAdapter.add("Me:   " + new String(msg.getData().getByteArray(MESSAGE_WRITE)).trim());
-                mOutEditText.setText(mOutStringBuffer);
+            case MESSAGE_WRITE:
+                byte[] writeBuf = (byte[]) msg.obj;
+                // construct a string from the buffer
+                String writeMessage = new String(writeBuf);
+                mConversationArrayAdapter.add("Me:  " + writeMessage);
                 break;
             case MESSAGE_READ:
-                byte[] buf = (byte[]) msg.obj;
-                mConversationArrayAdapter.add(mConnectedDeviceName+":   " + new String(buf).trim());
+                byte[] readBuf = (byte[]) msg.obj;
+                // construct a string from the valid bytes in the buffer
+                String readMessage = new String(readBuf, 0, msg.arg1);
+                mConversationArrayAdapter.add(mConnectedDeviceName+":  " + readMessage);
                 break;
             case MESSAGE_DEVICE_NAME:
-                mConnectedDeviceName = msg.getData().getString(DEVICE_NAME); // save the connected device's name
-                Toast.makeText(getApplicationContext(), "Connected to " + mConnectedDeviceName, Toast.LENGTH_SHORT).show();
+                // save the connected device's name
+                mConnectedDeviceName = msg.getData().getString(DEVICE_NAME);
+                Toast.makeText(getApplicationContext(), "Connected to "
+                               + mConnectedDeviceName, Toast.LENGTH_SHORT).show();
                 break;
             case MESSAGE_TOAST:
-                Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST), Toast.LENGTH_SHORT).show();
+                Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST),
+                               Toast.LENGTH_SHORT).show();
                 break;
             }
         }
@@ -283,7 +291,8 @@
             // When DeviceListActivity returns with a device to connect
             if (resultCode == Activity.RESULT_OK) {
                 // Get the device MAC address
-                String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
+                String address = data.getExtras()
+                                     .getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
                 // Get the BLuetoothDevice object
                 BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
                 // Attempt to connect to the device
diff --git a/samples/BluetoothChat/src/com/example/android/BluetoothChat/BluetoothChatService.java b/samples/BluetoothChat/src/com/example/android/BluetoothChat/BluetoothChatService.java
index 8d29c6a..d0c1d43 100644
--- a/samples/BluetoothChat/src/com/example/android/BluetoothChat/BluetoothChatService.java
+++ b/samples/BluetoothChat/src/com/example/android/BluetoothChat/BluetoothChatService.java
@@ -57,7 +57,7 @@
     private int mState;
 
     // Constants that indicate the current connection state
-    public static final int STATE_NONE = 0;       // we're doing nothing. only valid during setup/shutdown
+    public static final int STATE_NONE = 0;       // we're doing nothing
     public static final int STATE_LISTEN = 1;     // now listening for incoming connections
     public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
     public static final int STATE_CONNECTED = 3;  // now connected to a remote device
@@ -397,10 +397,8 @@
                     bytes = mmInStream.read(buffer);
 
                     // Send the obtained bytes to the UI Activity
-                    mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer).sendToTarget();
-
-                    // Reload the buffer to clear extra bytes from the previous read
-                    buffer = new byte[1024];
+                    mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer)
+                            .sendToTarget();
                 } catch (IOException e) {
                     Log.e(TAG, "disconnected", e);
                     connectionLost();
@@ -411,18 +409,15 @@
 
         /**
          * Write to the connected OutStream.
-         * @param b  The bytes to write
+         * @param buffer  The bytes to write
          */
-        public void write(byte[] b) {
+        public void write(byte[] buffer) {
             try {
-                mmOutStream.write(b);
+                mmOutStream.write(buffer);
 
                 // Share the sent message back to the UI Activity
-                Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_OUTGOING_STRING);
-                Bundle bundle = new Bundle();
-                bundle.putByteArray(BluetoothChat.MESSAGE_WRITE, b);
-                msg.setData(bundle);
-                mHandler.sendMessage(msg);
+                mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer)
+                        .sendToTarget();
             } catch (IOException e) {
                 Log.e(TAG, "Exception during write", e);
             }
diff --git a/samples/ContactManager/Android.mk b/samples/ContactManager/Android.mk
new file mode 100644
index 0000000..c585716
--- /dev/null
+++ b/samples/ContactManager/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := ContactManager
+
+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/ContactManager/AndroidManifest.xml b/samples/ContactManager/AndroidManifest.xml
new file mode 100644
index 0000000..c0a01cd
--- /dev/null
+++ b/samples/ContactManager/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.example.android.contactmanager"
+    android:versionCode="1" android:versionName="1.0">
+    <application android:label="@string/app_name" android:icon="@drawable/icon">
+        <activity android:name=".ContactManager" android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name="ContactAdder" android:label="@string/addContactTitle">
+        </activity>
+
+    </application>
+    <uses-sdk android:minSdkVersion="5" android:targetSdkVersion="5" />
+    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+</manifest>
diff --git a/samples/ContactManager/_index.html b/samples/ContactManager/_index.html
new file mode 100644
index 0000000..1fd51f0
--- /dev/null
+++ b/samples/ContactManager/_index.html
@@ -0,0 +1,10 @@
+<p>A sample application that demonstrates how to manually query the system contacts provider using
+the new <code><a
+href="../../../reference/android/provider/ContactsContract.html">ContactsContract</a></code> API, as
+well as manually insert contacts into a specific account.</p>
+
+<p>In most cases, you will want to ask the existing Contacts activity to handle these tasks for you,
+resulting in a more consistent user experience.</p>
+
+<img alt="Screenshot 1" src="../images/ContactManager1.png" />
+<img alt="Screenshot 2" src="../images/ContactManager2.png" />
\ No newline at end of file
diff --git a/samples/ContactManager/res/drawable-hdpi/icon.png b/samples/ContactManager/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/samples/ContactManager/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/samples/ContactManager/res/drawable-ldpi/icon.png b/samples/ContactManager/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/samples/ContactManager/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/samples/ContactManager/res/drawable-mdpi/icon.png b/samples/ContactManager/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/samples/ContactManager/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/samples/ContactManager/res/layout/account_entry.xml b/samples/ContactManager/res/layout/account_entry.xml
new file mode 100644
index 0000000..435e737
--- /dev/null
+++ b/samples/ContactManager/res/layout/account_entry.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:padding="6dip">
+    <ImageView
+        android:id="@+id/accountIcon"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentBottom="true"
+        android:layout_marginRight="6dip" />
+    <TextView
+        android:id="@+id/secondAccountLine"
+        android:layout_width="fill_parent"
+        android:layout_height="26dip"
+        android:layout_toRightOf="@id/accountIcon"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textColor="@android:color/secondary_text_light" />
+    <TextView
+        android:id="@+id/firstAccountLine"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_toRightOf="@id/accountIcon"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentTop="true"
+        android:layout_above="@id/secondAccountLine"
+        android:layout_alignWithParentIfMissing="true"
+        android:gravity="center_vertical"
+        android:textColor="@android:color/primary_text_light"/>
+</RelativeLayout>
diff --git a/samples/ContactManager/res/layout/contact_adder.xml b/samples/ContactManager/res/layout/contact_adder.xml
new file mode 100644
index 0000000..92d06f3
--- /dev/null
+++ b/samples/ContactManager/res/layout/contact_adder.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent">
+    <TableLayout android:layout_width="fill_parent"
+                 android:layout_height="fill_parent">
+        <TableRow>
+            <TextView android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:text="@string/targetAccountLabel"/>
+        </TableRow>
+        <TableRow>
+            <Spinner android:layout_height="wrap_content"
+                     android:layout_width="fill_parent"
+                     android:layout_weight="1"
+                     android:id="@+id/accountSpinner"/>
+        </TableRow>
+        <TableRow>
+            <TextView android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:text="@string/contactNameLabel"/>
+        </TableRow>
+        <TableRow>
+            <EditText android:id="@+id/contactNameEditText"
+                      android:layout_height="wrap_content"
+                      android:layout_width="wrap_content"
+                      android:layout_weight="1"/>
+        </TableRow>
+        <TableRow>
+            <TextView android:text="@string/contactPhoneLabel"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"/>
+        </TableRow>
+        <TableRow>
+            <EditText android:id="@+id/contactPhoneEditText"
+                      android:layout_height="wrap_content"
+                      android:layout_width="wrap_content"
+                      android:layout_weight="1"/>
+            <Spinner android:id="@+id/contactPhoneTypeSpinner"
+                     android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"/>
+        </TableRow>
+        <TableRow>
+            <TextView android:text="@string/contactEmailLabel"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"/>
+        </TableRow>
+        <TableRow>
+            <EditText android:id="@+id/contactEmailEditText"
+                      android:layout_height="wrap_content"
+                      android:layout_width="wrap_content"
+                      android:layout_weight="1"/>
+            <Spinner android:id="@+id/contactEmailTypeSpinner"
+                     android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"/>
+        </TableRow>
+        <TableRow>
+            <Button android:layout_height="wrap_content"
+                    android:text="@string/save"
+                    android:id="@+id/contactSaveButton"
+                    android:layout_width="fill_parent"
+                    android:layout_weight="1"/>
+        </TableRow>
+    </TableLayout>
+</ScrollView>
diff --git a/samples/ContactManager/res/layout/contact_entry.xml b/samples/ContactManager/res/layout/contact_entry.xml
new file mode 100644
index 0000000..9909025
--- /dev/null
+++ b/samples/ContactManager/res/layout/contact_entry.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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="wrap_content"
+              android:layout_height="wrap_content">
+    <TextView android:text="@+id/contactEntryText"
+              android:id="@+id/contactEntryText"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"/>
+</LinearLayout>
diff --git a/samples/ContactManager/res/layout/contact_manager.xml b/samples/ContactManager/res/layout/contact_manager.xml
new file mode 100644
index 0000000..73f6f81
--- /dev/null
+++ b/samples/ContactManager/res/layout/contact_manager.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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:orientation="vertical"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent">
+    <ListView android:layout_width="fill_parent"
+              android:id="@+id/contactList"
+              android:layout_height="wrap_content"
+              android:layout_weight="1"/>
+    <CheckBox android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:id="@+id/showInvisible"
+              android:text="@string/showInvisible"/>
+    <Button android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/addContactButton"
+            android:text="@string/addContactButtonLabel"/>
+</LinearLayout>
diff --git a/samples/ContactManager/res/values/strings.xml b/samples/ContactManager/res/values/strings.xml
new file mode 100644
index 0000000..c7a65b4
--- /dev/null
+++ b/samples/ContactManager/res/values/strings.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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="accountSpinnerLabel">Account</string>
+    <string name="addContactButtonLabel">Add Contact</string>
+    <string name="addContactTitle">Add Contact</string>
+    <string name="allAccounts">All Accounts</string>
+    <string name="app_name">Contact Manager</string>
+    <string name="contactCreationFailure">Contact creation failed, check logs.</string>
+    <string name="contactEmailLabel">Contact Email</string>
+    <string name="contactNameLabel">Contact Name</string>
+    <string name="contactPhoneLabel">Contact Phone</string>
+    <string name="save">Save</string>
+    <string name="selectAccountLabel">Select</string>
+    <string name="selectLabel">Select label</string>
+    <string name="showInvisible">Show Invisible Contacts (Only)</string>
+    <string name="targetAccountLabel">Target Account</string>
+    <string name="undefinedTypeLabel">(Undefined)</string>
+</resources>
diff --git a/samples/ContactManager/src/com/google/example/android/contactmanager/ContactAdder.java b/samples/ContactManager/src/com/google/example/android/contactmanager/ContactAdder.java
new file mode 100644
index 0000000..92ad5a2
--- /dev/null
+++ b/samples/ContactManager/src/com/google/example/android/contactmanager/ContactAdder.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2009 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.google.example.android.contactmanager;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.accounts.OnAccountsUpdateListener;
+import android.app.Activity;
+import android.content.ContentProviderOperation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.AdapterView.OnItemSelectedListener;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public final class ContactAdder extends Activity implements OnAccountsUpdateListener
+{
+    public static final String TAG = "ContactsAdder";
+    public static final String ACCOUNT_NAME =
+            "com.google.example.android.contactmanager.ContactsAdder.ACCOUNT_NAME";
+    public static final String ACCOUNT_TYPE =
+            "com.google.example.android.contactmanager.ContactsAdder.ACCOUNT_TYPE";
+
+    private ArrayList<AccountData> mAccounts;
+    private AccountAdapter mAccountAdapter;
+    private Spinner mAccountSpinner;
+    private EditText mContactEmailEditText;
+    private ArrayList<Integer> mContactEmailTypes;
+    private Spinner mContactEmailTypeSpinner;
+    private EditText mContactNameEditText;
+    private EditText mContactPhoneEditText;
+    private ArrayList<Integer> mContactPhoneTypes;
+    private Spinner mContactPhoneTypeSpinner;
+    private Button mContactSaveButton;
+    private AccountData mSelectedAccount;
+
+    /**
+     * Called when the activity is first created. Responsible for initializing the UI.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        Log.v(TAG, "Activity State: onCreate()");
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.contact_adder);
+
+        // Obtain handles to UI objects
+        mAccountSpinner = (Spinner) findViewById(R.id.accountSpinner);
+        mContactNameEditText = (EditText) findViewById(R.id.contactNameEditText);
+        mContactPhoneEditText = (EditText) findViewById(R.id.contactPhoneEditText);
+        mContactEmailEditText = (EditText) findViewById(R.id.contactEmailEditText);
+        mContactPhoneTypeSpinner = (Spinner) findViewById(R.id.contactPhoneTypeSpinner);
+        mContactEmailTypeSpinner = (Spinner) findViewById(R.id.contactEmailTypeSpinner);
+        mContactSaveButton = (Button) findViewById(R.id.contactSaveButton);
+
+        // Prepare list of supported account types
+        // Note: Other types are available in ContactsContract.CommonDataKinds
+        //       Also, be aware that type IDs differ between Phone and Email, and MUST be computed
+        //       separately.
+        mContactPhoneTypes = new ArrayList<Integer>();
+        mContactPhoneTypes.add(ContactsContract.CommonDataKinds.Phone.TYPE_HOME);
+        mContactPhoneTypes.add(ContactsContract.CommonDataKinds.Phone.TYPE_WORK);
+        mContactPhoneTypes.add(ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
+        mContactPhoneTypes.add(ContactsContract.CommonDataKinds.Phone.TYPE_OTHER);
+        mContactEmailTypes = new ArrayList<Integer>();
+        mContactEmailTypes.add(ContactsContract.CommonDataKinds.Email.TYPE_HOME);
+        mContactEmailTypes.add(ContactsContract.CommonDataKinds.Email.TYPE_WORK);
+        mContactEmailTypes.add(ContactsContract.CommonDataKinds.Email.TYPE_MOBILE);
+        mContactEmailTypes.add(ContactsContract.CommonDataKinds.Email.TYPE_OTHER);
+
+        // Prepare model for account spinner
+        mAccounts = new ArrayList<AccountData>();
+        mAccountAdapter = new AccountAdapter(this, mAccounts);
+        mAccountSpinner.setAdapter(mAccountAdapter);
+
+        // Populate list of account types for phone
+        ArrayAdapter<String> adapter;
+        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item);
+        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        Iterator<Integer> iter;
+        iter = mContactPhoneTypes.iterator();
+        while (iter.hasNext()) {
+            adapter.add(ContactsContract.CommonDataKinds.Phone.getTypeLabel(
+                    this.getResources(),
+                    iter.next(),
+                    getString(R.string.undefinedTypeLabel)).toString());
+        }
+        mContactPhoneTypeSpinner.setAdapter(adapter);
+        mContactPhoneTypeSpinner.setPrompt(getString(R.string.selectLabel));
+
+        // Populate list of account types for email
+        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item);
+        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        iter = mContactEmailTypes.iterator();
+        while (iter.hasNext()) {
+            adapter.add(ContactsContract.CommonDataKinds.Email.getTypeLabel(
+                    this.getResources(),
+                    iter.next(),
+                    getString(R.string.undefinedTypeLabel)).toString());
+        }
+        mContactEmailTypeSpinner.setAdapter(adapter);
+        mContactEmailTypeSpinner.setPrompt(getString(R.string.selectLabel));
+
+        // Prepare the system account manager. On registering the listener below, we also ask for
+        // an initial callback to pre-populate the account list.
+        AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
+
+        // Register handlers for UI elements
+        mAccountSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+            public void onItemSelected(AdapterView<?> parent, View view, int position, long i) {
+                updateAccountSelection();
+            }
+
+            public void onNothingSelected(AdapterView<?> parent) {
+                // We don't need to worry about nothing being selected, since Spinners don't allow
+                // this.
+            }
+        });
+        mContactSaveButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                onSaveButtonClicked();
+            }
+        });
+    }
+
+    /**
+     * Actions for when the Save button is clicked. Creates a contact entry and terminates the
+     * activity.
+     */
+    private void onSaveButtonClicked() {
+        Log.v(TAG, "Save button clicked");
+        createContactEntry();
+        finish();
+    }
+
+    /**
+     * Creates a contact entry from the current UI values in the account named by mSelectedAccount.
+     */
+    protected void createContactEntry() {
+        // Get values from UI
+        String name = mContactNameEditText.getText().toString();
+        String phone = mContactPhoneEditText.getText().toString();
+        String email = mContactEmailEditText.getText().toString();
+        int phoneType = mContactPhoneTypes.get(
+                mContactPhoneTypeSpinner.getSelectedItemPosition());
+        int emailType = mContactEmailTypes.get(
+                mContactEmailTypeSpinner.getSelectedItemPosition());;
+
+        // Prepare contact creation request
+        //
+        // Note: We use RawContacts because this data must be associated with a particular account.
+        //       The system will aggregate this with any other data for this contact and create a
+        //       coresponding entry in the ContactsContract.Contacts provider for us.
+        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+        ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
+                .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
+                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName())
+                .build());
+        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+                .withValue(ContactsContract.Data.MIMETYPE,
+                        ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
+                .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)
+                .build());
+        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+                .withValue(ContactsContract.Data.MIMETYPE,
+                        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+                .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
+                .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType)
+                .build());
+        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+                .withValue(ContactsContract.Data.MIMETYPE,
+                        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
+                .withValue(ContactsContract.CommonDataKinds.Email.DATA, email)
+                .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType)
+                .build());
+
+        // Ask the Contact provider to create a new contact
+        Log.i(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
+                mSelectedAccount.getType() + ")");
+        Log.i(TAG,"Creating contact: " + name);
+        try {
+            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
+        } catch (Exception e) {
+            // Display warning
+            Context ctx = getApplicationContext();
+            CharSequence txt = getString(R.string.contactCreationFailure);
+            int duration = Toast.LENGTH_SHORT;
+            Toast toast = Toast.makeText(ctx, txt, duration);
+            toast.show();
+
+            // Log exception
+            Log.e(TAG, "Exceptoin encoutered while inserting contact: " + e);
+        }
+    }
+
+    /**
+     * Called when this activity is about to be destroyed by the system.
+     */
+    @Override
+    public void onDestroy() {
+        // Remove AccountManager callback
+        AccountManager.get(this).removeOnAccountsUpdatedListener(this);
+        super.onDestroy();
+    }
+
+    /**
+     * Updates account list spinner when the list of Accounts on the system changes. Satisfies
+     * OnAccountsUpdateListener implementation.
+     */
+    public void onAccountsUpdated(Account[] a) {
+        Log.i(TAG, "Account list update detected");
+        // Clear out any old data to prevent duplicates
+        mAccounts.clear();
+
+        // Get account data from system
+        AuthenticatorDescription[] accountTypes = AccountManager.get(this).getAuthenticatorTypes();
+
+        // Populate tables
+        for (int i = 0; i < a.length; i++) {
+            // The user may have multiple accounts with the same name, so we need to construct a
+            // meaningful display name for each.
+            String systemAccountType = a[i].type;
+            AuthenticatorDescription ad = getAuthenticatorDescription(systemAccountType,
+                    accountTypes);
+            AccountData data = new AccountData(a[i].name, ad);
+            mAccounts.add(data);
+        }
+
+        // Update the account spinner
+        mAccountAdapter.notifyDataSetChanged();
+    }
+
+    /**
+     * Obtain the AuthenticatorDescription for a given account type.
+     * @param type The account type to locate.
+     * @param dictionary An array of AuthenticatorDescriptions, as returned by AccountManager.
+     * @return The description for the specified account type.
+     */
+    private static AuthenticatorDescription getAuthenticatorDescription(String type,
+            AuthenticatorDescription[] dictionary) {
+        for (int i = 0; i < dictionary.length; i++) {
+            if (dictionary[i].type.equals(type)) {
+                return dictionary[i];
+            }
+        }
+        // No match found
+        throw new RuntimeException("Unable to find matching authenticator");
+    }
+
+    /**
+     * Update account selection. If NO_ACCOUNT is selected, then we prohibit inserting new contacts.
+     */
+    private void updateAccountSelection() {
+        // Read current account selection
+        mSelectedAccount = (AccountData) mAccountSpinner.getSelectedItem();
+    }
+
+    /**
+     * A container class used to repreresent all known information about an account.
+     */
+    private class AccountData {
+        private String mName;
+        private String mType;
+        private CharSequence mTypeLabel;
+        private Drawable mIcon;
+
+        /**
+         * @param name The name of the account. This is usually the user's email address or
+         *        username.
+         * @param description The description for this account. This will be dictated by the
+         *        type of account returned, and can be obtained from the system AccountManager.
+         */
+        public AccountData(String name, AuthenticatorDescription description) {
+            mName = name;
+            if (description != null) {
+                mType = description.type;
+
+                // The type string is stored in a resource, so we need to convert it into something
+                // human readable.
+                String packageName = description.packageName;
+                PackageManager pm = getPackageManager();
+
+                if (description.labelId != 0) {
+                    mTypeLabel = pm.getText(packageName, description.labelId, null);
+                    if (mTypeLabel == null) {
+                        throw new IllegalArgumentException("LabelID provided, but label not found");
+                    }
+                } else {
+                    mTypeLabel = "";
+                }
+
+                if (description.iconId != 0) {
+                    mIcon = pm.getDrawable(packageName, description.iconId, null);
+                    if (mIcon == null) {
+                        throw new IllegalArgumentException("IconID provided, but drawable not " +
+                                "found");
+                    }
+                } else {
+                    mIcon = getResources().getDrawable(android.R.drawable.sym_def_app_icon);
+                }
+            }
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public String getType() {
+            return mType;
+        }
+
+        public CharSequence getTypeLabel() {
+            return mTypeLabel;
+        }
+
+        public Drawable getIcon() {
+            return mIcon;
+        }
+
+        public String toString() {
+            return mName;
+        }
+    }
+
+    /**
+     * Custom adapter used to display account icons and descriptions in the account spinner.
+     */
+    private class AccountAdapter extends ArrayAdapter<AccountData> {
+        public AccountAdapter(Context context, ArrayList<AccountData> accountData) {
+            super(context, android.R.layout.simple_spinner_item, accountData);
+            setDropDownViewResource(R.layout.account_entry);
+        }
+
+        public View getDropDownView(int position, View convertView, ViewGroup parent) {
+            // Inflate a view template
+            if (convertView == null) {
+                LayoutInflater layoutInflater = getLayoutInflater();
+                convertView = layoutInflater.inflate(R.layout.account_entry, parent, false);
+            }
+            TextView firstAccountLine = (TextView) convertView.findViewById(R.id.firstAccountLine);
+            TextView secondAccountLine = (TextView) convertView.findViewById(R.id.secondAccountLine);
+            ImageView accountIcon = (ImageView) convertView.findViewById(R.id.accountIcon);
+
+            // Populate template
+            AccountData data = getItem(position);
+            firstAccountLine.setText(data.getName());
+            secondAccountLine.setText(data.getTypeLabel());
+            Drawable icon = data.getIcon();
+            if (icon == null) {
+                icon = getResources().getDrawable(android.R.drawable.ic_menu_search);
+            }
+            accountIcon.setImageDrawable(icon);
+            return convertView;
+        }
+    }
+}
diff --git a/samples/ContactManager/src/com/google/example/android/contactmanager/ContactManager.java b/samples/ContactManager/src/com/google/example/android/contactmanager/ContactManager.java
new file mode 100644
index 0000000..7b416b0
--- /dev/null
+++ b/samples/ContactManager/src/com/google/example/android/contactmanager/ContactManager.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2009 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.google.example.android.contactmanager;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+
+public final class ContactManager extends Activity
+{
+
+    public static final String TAG = "ContactManager";
+
+    private Button mAddAccountButton;
+    private ListView mContactList;
+    private boolean mShowInvisible;
+    private CheckBox mShowInvisibleControl;
+
+    /**
+     * Called when the activity is first created. Responsible for initializing the UI.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        Log.v(TAG, "Activity State: onCreate()");
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.contact_manager);
+
+        // Obtain handles to UI objects
+        mAddAccountButton = (Button) findViewById(R.id.addContactButton);
+        mContactList = (ListView) findViewById(R.id.contactList);
+        mShowInvisibleControl = (CheckBox) findViewById(R.id.showInvisible);
+
+        // Initialize class properties
+        mShowInvisible = false;
+        mShowInvisibleControl.setChecked(mShowInvisible);
+
+        // Register handler for UI elements
+        mAddAccountButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                Log.d(TAG, "mAddAccountButton clicked");
+                launchContactAdder();
+            }
+        });
+        mShowInvisibleControl.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                Log.d(TAG, "mShowInvisibleControl changed: " + isChecked);
+                mShowInvisible = isChecked;
+                populateContactList();
+            }
+        });
+
+        // Populate the contact list
+        populateContactList();
+    }
+
+    /**
+     * Populate the contact list based on account currently selected in the account spinner.
+     */
+    private void populateContactList() {
+        // Build adapter with contact entries
+        Cursor cursor = getContacts();
+        String[] fields = new String[] {
+                ContactsContract.Data.DISPLAY_NAME
+        };
+        SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.contact_entry, cursor,
+                fields, new int[] {R.id.contactEntryText});
+        mContactList.setAdapter(adapter);
+    }
+
+    /**
+     * Obtains the contact list for the currently selected account.
+     *
+     * @return A cursor for for accessing the contact list.
+     */
+    private Cursor getContacts()
+    {
+        // Run query
+        Uri uri = ContactsContract.Contacts.CONTENT_URI;
+        String[] projection = new String[] {
+                ContactsContract.Contacts._ID,
+                ContactsContract.Contacts.DISPLAY_NAME
+        };
+        String selection = ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '" +
+                (mShowInvisible ? "0" : "1") + "'";
+        String[] selectionArgs = null;
+        String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
+
+        return managedQuery(uri, projection, selection, selectionArgs, sortOrder);
+    }
+
+    /**
+     * Launches the ContactAdder activity to add a new contact to the selected accont.
+     */
+    protected void launchContactAdder() {
+        Intent i = new Intent(this, ContactAdder.class);
+        startActivity(i);
+    }
+}
diff --git a/samples/MultiResolution/_index.html b/samples/MultiResolution/_index.html
index 73089ae..825ea4d 100644
--- a/samples/MultiResolution/_index.html
+++ b/samples/MultiResolution/_index.html
@@ -17,4 +17,4 @@
 </ul>
 </p>
 
-<img alt="" src="../images/sample_multires.png"/>
+<img alt="" src="../images/MultiResolution.png"/>
diff --git a/samples/Wiktionary/Android.mk b/samples/Wiktionary/Android.mk
new file mode 100644
index 0000000..d6ce1f1
--- /dev/null
+++ b/samples/Wiktionary/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := Wiktionary
+
+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/Wiktionary/AndroidManifest.xml b/samples/Wiktionary/AndroidManifest.xml
new file mode 100644
index 0000000..1641a8b
--- /dev/null
+++ b/samples/Wiktionary/AndroidManifest.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.wiktionary"
+    android:versionCode="1"
+    android:versionName="1.0">
+    <application android:icon="@drawable/app_icon" android:label="@string/app_name"
+        android:description="@string/app_descrip">
+
+        <!-- Browser-like Activity to navigate dictionary definitions  -->
+        <activity
+            android:name=".LookupActivity"
+            android:theme="@style/LookupTheme"
+            android:launchMode="singleTop"
+            android:configChanges="orientation|keyboardHidden">
+
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="wiktionary" android:host="lookup" />
+            </intent-filter>
+
+            <intent-filter>
+                <action android:name="android.intent.action.SEARCH" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <meta-data android:name="android.app.searchable" android:resource="@xml/searchable" />
+        </activity>
+
+        <!-- Broadcast Receiver that will process AppWidget updates -->
+        <receiver android:name=".WordWidget" android:label="@string/widget_name">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+            </intent-filter>
+            <meta-data android:name="android.appwidget.provider"
+                android:resource="@xml/widget_word" />
+        </receiver>
+
+        <!-- Service to perform web API queries -->
+        <service android:name=".WordWidget$UpdateService" />
+
+    </application>
+
+    <meta-data android:name="android.app.default_searchable" android:value=".LookupActivity" />
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="4" />
+
+</manifest>
diff --git a/samples/Wiktionary/_index.html b/samples/Wiktionary/_index.html
new file mode 100644
index 0000000..507dda3
--- /dev/null
+++ b/samples/Wiktionary/_index.html
@@ -0,0 +1,10 @@
+<p>A sample application that demonstrates how to create an interactive Android
+home screen widget that launches an application activity.</p>
+
+<p>When installed, this adds a "Wiktionary" option to the widget installation
+menu. The word of the day is downloaded from Wiktionary and displayed in a
+frame. Touching the widget will launch a custom application activity showing
+more details.</p>
+
+<img alt="" src="../images/WiktionarySimple.png"/>
+<img alt="" src="../images/Wiktionary.png"/>
diff --git a/samples/Wiktionary/res/anim/slide_in.xml b/samples/Wiktionary/res/anim/slide_in.xml
new file mode 100644
index 0000000..3da074e
--- /dev/null
+++ b/samples/Wiktionary/res/anim/slide_in.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:fillAfter="true">
+    <translate
+        android:fromXDelta="-26"
+        android:toXDelta="0"
+        android:duration="400" />
+</set>
diff --git a/samples/Wiktionary/res/anim/slide_out.xml b/samples/Wiktionary/res/anim/slide_out.xml
new file mode 100644
index 0000000..ec21f52
--- /dev/null
+++ b/samples/Wiktionary/res/anim/slide_out.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:fillAfter="true">
+    <translate
+        android:fromXDelta="0"
+        android:toXDelta="-26"
+        android:duration="400" />
+</set>
diff --git a/samples/Wiktionary/res/drawable/app_icon.png b/samples/Wiktionary/res/drawable/app_icon.png
new file mode 100644
index 0000000..2b1417a
--- /dev/null
+++ b/samples/Wiktionary/res/drawable/app_icon.png
Binary files differ
diff --git a/samples/Wiktionary/res/drawable/ic_menu_shuffle.png b/samples/Wiktionary/res/drawable/ic_menu_shuffle.png
new file mode 100755
index 0000000..cb7009d
--- /dev/null
+++ b/samples/Wiktionary/res/drawable/ic_menu_shuffle.png
Binary files differ
diff --git a/samples/Wiktionary/res/drawable/logo_overlay.9.png b/samples/Wiktionary/res/drawable/logo_overlay.9.png
new file mode 100644
index 0000000..851ceb1
--- /dev/null
+++ b/samples/Wiktionary/res/drawable/logo_overlay.9.png
Binary files differ
diff --git a/samples/Wiktionary/res/drawable/lookup_bg.xml b/samples/Wiktionary/res/drawable/lookup_bg.xml
new file mode 100644
index 0000000..46d76eb
--- /dev/null
+++ b/samples/Wiktionary/res/drawable/lookup_bg.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+    <item android:drawable="@drawable/logo_overlay" />
+</layer-list>
diff --git a/samples/Wiktionary/res/drawable/progress_spin.xml b/samples/Wiktionary/res/drawable/progress_spin.xml
new file mode 100644
index 0000000..4594a18
--- /dev/null
+++ b/samples/Wiktionary/res/drawable/progress_spin.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<rotate xmlns:android="http://schemas.android.com/apk/res/android"
+        android:pivotX="50%"
+        android:pivotY="50%"
+        android:fromDegrees="0"
+        android:toDegrees="360">
+
+    <shape
+        android:shape="ring"
+        android:innerRadiusRatio="4"
+        android:thicknessRatio="5.333"
+        android:useLevel="false">
+
+        <size
+            android:width="18dip"
+            android:height="18dip" />
+
+        <gradient
+            android:type="sweep"
+            android:useLevel="false"
+            android:startColor="#006688cc"
+            android:centerColor="#886688cc"
+            android:endColor="#ff6688cc"
+            android:centerY="0.50" />
+
+    </shape>
+
+</rotate>
diff --git a/samples/Wiktionary/res/drawable/star_logo.png b/samples/Wiktionary/res/drawable/star_logo.png
new file mode 100644
index 0000000..b32d175
--- /dev/null
+++ b/samples/Wiktionary/res/drawable/star_logo.png
Binary files differ
diff --git a/samples/Wiktionary/res/drawable/widget_bg.xml b/samples/Wiktionary/res/drawable/widget_bg.xml
new file mode 100644
index 0000000..c2b8462
--- /dev/null
+++ b/samples/Wiktionary/res/drawable/widget_bg.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_window_focused="false" android:drawable="@drawable/widget_bg_normal" />
+    <item android:state_pressed="true" android:drawable="@drawable/widget_bg_pressed" />
+    <item android:state_focused="true" android:drawable="@drawable/widget_bg_selected" />
+    <item android:drawable="@drawable/widget_bg_normal" />
+</selector>
diff --git a/samples/Wiktionary/res/drawable/widget_bg_normal.9.png b/samples/Wiktionary/res/drawable/widget_bg_normal.9.png
new file mode 100644
index 0000000..314eb8e
--- /dev/null
+++ b/samples/Wiktionary/res/drawable/widget_bg_normal.9.png
Binary files differ
diff --git a/samples/Wiktionary/res/drawable/widget_bg_pressed.9.png b/samples/Wiktionary/res/drawable/widget_bg_pressed.9.png
new file mode 100644
index 0000000..cc23e78
--- /dev/null
+++ b/samples/Wiktionary/res/drawable/widget_bg_pressed.9.png
Binary files differ
diff --git a/samples/Wiktionary/res/drawable/widget_bg_selected.9.png b/samples/Wiktionary/res/drawable/widget_bg_selected.9.png
new file mode 100644
index 0000000..ef0cdc0
--- /dev/null
+++ b/samples/Wiktionary/res/drawable/widget_bg_selected.9.png
Binary files differ
diff --git a/samples/Wiktionary/res/layout/about.xml b/samples/Wiktionary/res/layout/about.xml
new file mode 100644
index 0000000..3b25b32
--- /dev/null
+++ b/samples/Wiktionary/res/layout/about.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical"
+    android:padding="20dip">
+
+    <TextView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:textSize="16sp"
+        android:text="@string/app_descrip"
+        android:textColor="?android:attr/textColorPrimaryInverse" />
+
+    <TextView
+        android:id="@+id/about_credits"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="20dip"
+        android:textSize="16sp"
+        android:text="@string/app_credits"
+        android:autoLink="web"
+        android:textColor="?android:attr/textColorPrimaryInverse" />
+
+</LinearLayout>
diff --git a/samples/Wiktionary/res/layout/lookup.xml b/samples/Wiktionary/res/layout/lookup.xml
new file mode 100644
index 0000000..43cffaa
--- /dev/null
+++ b/samples/Wiktionary/res/layout/lookup.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/title_bar"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:gravity="center_vertical">
+
+        <ProgressBar
+            android:id="@+id/progress"
+            android:layout_width="18dip"
+            android:layout_height="18dip"
+            android:layout_marginLeft="10dip"
+            android:visibility="invisible"
+            android:indeterminateOnly="true"
+            android:indeterminateDrawable="@drawable/progress_spin"
+            android:indeterminateBehavior="repeat"
+            android:indeterminateDuration="3500" />
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:padding="10dip"
+            style="@style/LookupTitle" />
+
+    </LinearLayout>
+
+    <WebView
+        android:id="@+id/webview"
+        android:layout_width="fill_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1" />
+
+</LinearLayout>
diff --git a/samples/Wiktionary/res/layout/widget_message.xml b/samples/Wiktionary/res/layout/widget_message.xml
new file mode 100644
index 0000000..ba94714
--- /dev/null
+++ b/samples/Wiktionary/res/layout/widget_message.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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:id="@+id/widget"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    style="@style/WidgetBackground">
+
+    <TextView
+        android:id="@+id/message"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="12dip"
+        android:padding="10dip"
+        android:gravity="center"
+        android:text="@string/widget_loading"
+        style="@style/Text.Loading" />
+
+</LinearLayout>
diff --git a/samples/Wiktionary/res/layout/widget_word.xml b/samples/Wiktionary/res/layout/widget_word.xml
new file mode 100644
index 0000000..0e76f0b
--- /dev/null
+++ b/samples/Wiktionary/res/layout/widget_word.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/widget"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:focusable="true"
+    style="@style/WidgetBackground">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:src="@drawable/star_logo" />
+
+    <TextView
+        android:id="@+id/word_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="14dip"
+        android:layout_marginBottom="1dip"
+        android:includeFontPadding="false"
+        android:singleLine="true"
+        android:ellipsize="end"
+        style="@style/Text.WordTitle" />
+
+    <TextView
+        android:id="@+id/word_type"
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_toRightOf="@id/word_title"
+        android:layout_toLeftOf="@id/icon"
+        android:layout_alignBaseline="@id/word_title"
+        android:paddingLeft="4dip"
+        android:includeFontPadding="false"
+        android:singleLine="true"
+        android:ellipsize="end"
+        style="@style/Text.WordType" />
+
+    <TextView
+        android:id="@+id/bullet"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/word_title"
+        android:paddingRight="4dip"
+        android:includeFontPadding="false"
+        android:singleLine="true"
+        style="@style/BulletPoint" />
+
+    <TextView
+        android:id="@+id/definition"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/word_title"
+        android:layout_toRightOf="@id/bullet"
+        android:paddingRight="5dip"
+        android:paddingBottom="4dip"
+        android:includeFontPadding="false"
+        android:lineSpacingMultiplier="0.9"
+        android:maxLines="4"
+        android:fadingEdge="vertical"
+        style="@style/Text.Definition" />
+
+</RelativeLayout>
diff --git a/samples/Wiktionary/res/menu/lookup.xml b/samples/Wiktionary/res/menu/lookup.xml
new file mode 100644
index 0000000..741ca9a
--- /dev/null
+++ b/samples/Wiktionary/res/menu/lookup.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item
+        android:id="@+id/lookup_search"
+        android:title="@string/lookup_search"
+        android:icon="@android:drawable/ic_menu_search" />
+
+    <item
+        android:id="@+id/lookup_random"
+        android:title="@string/lookup_random"
+        android:icon="@drawable/ic_menu_shuffle" />
+
+    <item
+        android:id="@+id/lookup_about"
+        android:title="@string/lookup_about"
+        android:icon="@android:drawable/ic_menu_help" />
+
+</menu>
diff --git a/samples/Wiktionary/res/values/strings.xml b/samples/Wiktionary/res/values/strings.xml
new file mode 100644
index 0000000..38d9937
--- /dev/null
+++ b/samples/Wiktionary/res/values/strings.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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="app_name">Wiktionary example</string>
+    <string name="app_descrip">Example of a fast Wiktionary browser and Word-of-day widget</string>
+    <string name="app_credits">"All dictionary content provided by Wiktionary under a GFDL license.  http://en.wiktionary.org\n\nIcon derived from Tango Desktop Project under a public domain license.  http://tango.freedesktop.org"</string>
+
+    <string name="template_user_agent">"%s/%s (Linux; Android)"</string>
+    <string name="template_wotd_title">"Wiktionary:Word of the day/%s %s"</string>
+    <string name="template_define_url">"http://en.wiktionary.org/wiki/%s"</string>
+
+    <string name="widget_name">Wiktionary</string>
+
+    <string name="widget_loading">"Loading word\nof day\u2026"</string>
+    <string name="widget_error">No word of day found</string>
+
+    <string-array name="month_names">
+        <item>January</item>
+        <item>February</item>
+        <item>March</item>
+        <item>April</item>
+        <item>May</item>
+        <item>June</item>
+        <item>July</item>
+        <item>August</item>
+        <item>September</item>
+        <item>October</item>
+        <item>November</item>
+        <item>December</item>
+    </string-array>
+
+
+    <string name="search_label">Wiktionary search</string>
+    <string name="search_hint">Define word</string>
+
+    <string name="lookup_search">Search</string>
+    <string name="lookup_random">Random</string>
+    <string name="lookup_about">About</string>
+
+    <string name="empty_result">No entry found for this word, or problem reading data.</string>
+
+</resources>
diff --git a/samples/Wiktionary/res/values/styles.xml b/samples/Wiktionary/res/values/styles.xml
new file mode 100644
index 0000000..45fc8f5
--- /dev/null
+++ b/samples/Wiktionary/res/values/styles.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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>
+
+    <style name="WidgetBackground">
+        <item name="android:background">@drawable/widget_bg</item>
+    </style>
+
+    <style name="BulletPoint">
+        <item name="android:textSize">13sp</item>
+        <item name="android:textColor">@android:color/black</item>
+    </style>
+
+    <style name="Text" />
+
+    <style name="Text.Loading">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">@android:color/black</item>
+    </style>
+
+    <style name="Text.WordTitle">
+        <item name="android:textSize">16sp</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:textColor">@android:color/black</item>
+    </style>
+
+    <style name="Text.WordType">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">italic</item>
+        <item name="android:textColor">@android:color/black</item>
+    </style>
+
+    <style name="Text.Definition">
+        <item name="android:textSize">13sp</item>
+        <item name="android:textColor">@android:color/black</item>
+    </style>
+
+
+    <style name="LookupProgress">
+        <item name="android:indeterminateOnly">true</item>
+        <item name="android:indeterminateDrawable">@drawable/progress_spin</item>
+        <item name="android:indeterminateBehavior">repeat</item>
+        <item name="android:indeterminateDuration">3500</item>
+    </style>
+
+    <style name="LookupTitle">
+        <item name="android:textSize">30sp</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+</resources>
diff --git a/samples/Wiktionary/res/values/themes.xml b/samples/Wiktionary/res/values/themes.xml
new file mode 100644
index 0000000..c4d7630
--- /dev/null
+++ b/samples/Wiktionary/res/values/themes.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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>
+    <style name="LookupTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">@drawable/lookup_bg</item>
+    </style>
+</resources>
diff --git a/samples/Wiktionary/res/xml/searchable.xml b/samples/Wiktionary/res/xml/searchable.xml
new file mode 100644
index 0000000..02ee31f
--- /dev/null
+++ b/samples/Wiktionary/res/xml/searchable.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<searchable xmlns:android="http://schemas.android.com/apk/res/android"
+    android:label="@string/search_label"
+    android:hint="@string/search_hint" />
diff --git a/samples/Wiktionary/res/xml/widget_word.xml b/samples/Wiktionary/res/xml/widget_word.xml
new file mode 100644
index 0000000..46d31c3
--- /dev/null
+++ b/samples/Wiktionary/res/xml/widget_word.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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="146dip"
+    android:minHeight="72dip"
+    android:updatePeriodMillis="86400000"
+    android:initialLayout="@layout/widget_message" />
diff --git a/samples/Wiktionary/src/com/example/android/wiktionary/ExtendedWikiHelper.java b/samples/Wiktionary/src/com/example/android/wiktionary/ExtendedWikiHelper.java
new file mode 100644
index 0000000..3a39172
--- /dev/null
+++ b/samples/Wiktionary/src/com/example/android/wiktionary/ExtendedWikiHelper.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2009 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.wiktionary;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.net.Uri;
+import android.text.TextUtils;
+import android.webkit.WebView;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Extended version of {@link SimpleWikiHelper}. This version adds methods to
+ * pick a random word, and to format generic wiki-style text into HTML.
+ */
+public class ExtendedWikiHelper extends SimpleWikiHelper {
+    /**
+     * HTML style sheet to include with any {@link #formatWikiText(String)} HTML
+     * results. It formats nicely for a mobile screen, and hides some content
+     * boxes to keep things tidy.
+     */
+    private static final String STYLE_SHEET = "<style>h2 {font-size:1.2em;font-weight:normal;} " +
+            "a {color:#6688cc;} ol {padding-left:1.5em;} blockquote {margin-left:0em;} " +
+            ".interProject, .noprint {display:none;} " +
+            "li, blockquote {margin-top:0.5em;margin-bottom:0.5em;}</style>";
+
+    /**
+     * Pattern of section titles we're interested in showing. This trims out
+     * extra sections that can clutter things up on a mobile screen.
+     */
+    private static final Pattern sValidSections =
+        Pattern.compile("(verb|noun|adjective|pronoun|interjection)", Pattern.CASE_INSENSITIVE);
+
+    /**
+     * Pattern that can be used to split a returned wiki page into its various
+     * sections. Doesn't treat children sections differently.
+     */
+    private static final Pattern sSectionSplit =
+        Pattern.compile("^=+(.+?)=+.+?(?=^=)", Pattern.MULTILINE | Pattern.DOTALL);
+
+    /**
+     * When picking random words in {@link #getRandomWord()}, we sometimes
+     * encounter special articles or templates. This pattern ignores any words
+     * like those, usually because they have ":" or other punctuation.
+     */
+    private static final Pattern sInvalidWord = Pattern.compile("[^A-Za-z0-9 ]");
+
+    /**
+     * {@link Uri} authority to use when creating internal links.
+     */
+    public static final String WIKI_AUTHORITY = "wiktionary";
+
+    /**
+     * {@link Uri} host to use when creating internal links.
+     */
+    public static final String WIKI_LOOKUP_HOST = "lookup";
+
+    /**
+     * Mime-type to use when showing parsed results in a {@link WebView}.
+     */
+    public static final String MIME_TYPE = "text/html";
+
+    /**
+     * Encoding to use when showing parsed results in a {@link WebView}.
+     */
+    public static final String ENCODING = "utf-8";
+
+    /**
+     * {@link Uri} to use when requesting a random page.
+     */
+    private static final String WIKTIONARY_RANDOM =
+        "http://en.wiktionary.org/w/api.php?action=query&list=random&format=json";
+
+    /**
+     * Fake section to insert at the bottom of a wiki response before parsing.
+     * This ensures that {@link #sSectionSplit} will always catch the last
+     * section, as it uses section headers in its searching.
+     */
+    private static final String STUB_SECTION = "\n=Stub section=";
+
+    /**
+     * Number of times to try finding a random word in {@link #getRandomWord()}.
+     * These failures are usually when the found word fails the
+     * {@link #sInvalidWord} test, or when a network error happens.
+     */
+    private static final int RANDOM_TRIES = 3;
+
+    /**
+     * Internal class to hold a wiki formatting rule. It's mostly a wrapper to
+     * simplify {@link Matcher#replaceAll(String)}.
+     */
+    private static class FormatRule {
+        private Pattern mPattern;
+        private String mReplaceWith;
+
+        /**
+         * Create a wiki formatting rule.
+         *
+         * @param pattern Search string to be compiled into a {@link Pattern}.
+         * @param replaceWith String to replace any found occurances with. This
+         *            string can also include back-references into the given
+         *            pattern.
+         * @param flags Any flags to compile the {@link Pattern} with.
+         */
+        public FormatRule(String pattern, String replaceWith, int flags) {
+            mPattern = Pattern.compile(pattern, flags);
+            mReplaceWith = replaceWith;
+        }
+
+        /**
+         * Create a wiki formatting rule.
+         *
+         * @param pattern Search string to be compiled into a {@link Pattern}.
+         * @param replaceWith String to replace any found occurances with. This
+         *            string can also include back-references into the given
+         *            pattern.
+         */
+        public FormatRule(String pattern, String replaceWith) {
+            this(pattern, replaceWith, 0);
+        }
+
+        /**
+         * Apply this formatting rule to the given input string, and return the
+         * resulting new string.
+         */
+        public String apply(String input) {
+            Matcher m = mPattern.matcher(input);
+            return m.replaceAll(mReplaceWith);
+        }
+
+    }
+
+    /**
+     * List of internal formatting rules to apply when parsing wiki text. These
+     * include indenting various bullets, apply italic and bold styles, and
+     * adding internal linking.
+     */
+    private static final List<FormatRule> sFormatRules = new ArrayList<FormatRule>();
+
+    static {
+        // Format header blocks and wrap outside content in ordered list
+        sFormatRules.add(new FormatRule("^=+(.+?)=+", "</ol><h2>$1</h2><ol>",
+                Pattern.MULTILINE));
+
+        // Indent quoted blocks, handle ordered and bullet lists
+        sFormatRules.add(new FormatRule("^#+\\*?:(.+?)$", "<blockquote>$1</blockquote>",
+                Pattern.MULTILINE));
+        sFormatRules.add(new FormatRule("^#+:?\\*(.+?)$", "<ul><li>$1</li></ul>",
+                Pattern.MULTILINE));
+        sFormatRules.add(new FormatRule("^#+(.+?)$", "<li>$1</li>",
+                Pattern.MULTILINE));
+
+        // Add internal links
+        sFormatRules.add(new FormatRule("\\[\\[([^:\\|\\]]+)\\]\\]",
+                String.format("<a href=\"%s://%s/$1\">$1</a>", WIKI_AUTHORITY, WIKI_LOOKUP_HOST)));
+        sFormatRules.add(new FormatRule("\\[\\[([^:\\|\\]]+)\\|([^\\]]+)\\]\\]",
+                String.format("<a href=\"%s://%s/$1\">$2</a>", WIKI_AUTHORITY, WIKI_LOOKUP_HOST)));
+
+        // Add bold and italic formatting
+        sFormatRules.add(new FormatRule("'''(.+?)'''", "<b>$1</b>"));
+        sFormatRules.add(new FormatRule("([^'])''([^'].*?[^'])''([^'])", "$1<i>$2</i>$3"));
+
+        // Remove odd category links and convert remaining links into flat text
+        sFormatRules.add(new FormatRule("(\\{+.+?\\}+|\\[\\[[^:]+:[^\\\\|\\]]+\\]\\]|" +
+                "\\[http.+?\\]|\\[\\[Category:.+?\\]\\])", "", Pattern.MULTILINE | Pattern.DOTALL));
+        sFormatRules.add(new FormatRule("\\[\\[([^\\|\\]]+\\|)?(.+?)\\]\\]", "$2",
+                Pattern.MULTILINE));
+
+    }
+
+    /**
+     * Query the Wiktionary API to pick a random dictionary word. Will try
+     * multiple times to find a valid word before giving up.
+     *
+     * @return Random dictionary word, or null if no valid word was found.
+     * @throws ApiException If any connection or server error occurs.
+     * @throws ParseException If there are problems parsing the response.
+     */
+    public static String getRandomWord() throws ApiException, ParseException {
+        // Keep trying a few times until we find a valid word
+        int tries = 0;
+        while (tries++ < RANDOM_TRIES) {
+            // Query the API for a random word
+            String content = getUrlContent(WIKTIONARY_RANDOM);
+            try {
+                // Drill into the JSON response to find the returned word
+                JSONObject response = new JSONObject(content);
+                JSONObject query = response.getJSONObject("query");
+                JSONArray random = query.getJSONArray("random");
+                JSONObject word = random.getJSONObject(0);
+                String foundWord = word.getString("title");
+
+                // If we found an actual word, and it wasn't rejected by our invalid
+                // filter, then accept and return it.
+                if (foundWord != null &&
+                        !sInvalidWord.matcher(foundWord).find()) {
+                    return foundWord;
+                }
+            } catch (JSONException e) {
+                throw new ParseException("Problem parsing API response", e);
+            }
+        }
+
+        // No valid word found in number of tries, so return null
+        return null;
+    }
+
+    /**
+     * Format the given wiki-style text into formatted HTML content. This will
+     * create headers, lists, internal links, and style formatting for any wiki
+     * markup found.
+     *
+     * @param wikiText The raw text to format, with wiki-markup included.
+     * @return HTML formatted content, ready for display in {@link WebView}.
+     */
+    public static String formatWikiText(String wikiText) {
+        if (wikiText == null) {
+            return null;
+        }
+
+        // Insert a fake last section into the document so our section splitter
+        // can correctly catch the last section.
+        wikiText = wikiText.concat(STUB_SECTION);
+
+        // Read through all sections, keeping only those matching our filter,
+        // and only including the first entry for each title.
+        HashSet<String> foundSections = new HashSet<String>();
+        StringBuilder builder = new StringBuilder();
+
+        Matcher sectionMatcher = sSectionSplit.matcher(wikiText);
+        while (sectionMatcher.find()) {
+            String title = sectionMatcher.group(1);
+            if (!foundSections.contains(title) &&
+                    sValidSections.matcher(title).matches()) {
+                String sectionContent = sectionMatcher.group();
+                foundSections.add(title);
+                builder.append(sectionContent);
+            }
+        }
+
+        // Our new wiki text is the selected sections only
+        wikiText = builder.toString();
+
+        // Apply all formatting rules, in order, to the wiki text
+        for (FormatRule rule : sFormatRules) {
+            wikiText = rule.apply(wikiText);
+        }
+
+        // Return the resulting HTML with style sheet, if we have content left
+        if (!TextUtils.isEmpty(wikiText)) {
+            return STYLE_SHEET + wikiText;
+        } else {
+            return null;
+        }
+    }
+
+}
diff --git a/samples/Wiktionary/src/com/example/android/wiktionary/LookupActivity.java b/samples/Wiktionary/src/com/example/android/wiktionary/LookupActivity.java
new file mode 100644
index 0000000..6cc231b
--- /dev/null
+++ b/samples/Wiktionary/src/com/example/android/wiktionary/LookupActivity.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2009 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.wiktionary;
+
+import com.example.android.wiktionary.SimpleWikiHelper.ApiException;
+import com.example.android.wiktionary.SimpleWikiHelper.ParseException;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.SearchManager;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Animation.AnimationListener;
+import android.webkit.WebView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import java.util.Stack;
+
+/**
+ * Activity that lets users browse through Wiktionary content. This is just the
+ * user interface, and all API communication and parsing is handled in
+ * {@link ExtendedWikiHelper}.
+ */
+public class LookupActivity extends Activity implements AnimationListener {
+    private static final String TAG = "LookupActivity";
+
+    private View mTitleBar;
+    private TextView mTitle;
+    private ProgressBar mProgress;
+    private WebView mWebView;
+
+    private Animation mSlideIn;
+    private Animation mSlideOut;
+
+    /**
+     * History stack of previous words browsed in this session. This is
+     * referenced when the user taps the "back" key, to possibly intercept and
+     * show the last-visited entry, instead of closing the activity.
+     */
+    private Stack<String> mHistory = new Stack<String>();
+
+    private String mEntryTitle;
+
+    /**
+     * Keep track of last time user tapped "back" hard key. When pressed more
+     * than once within {@link #BACK_THRESHOLD}, we treat let the back key fall
+     * through and close the app.
+     */
+    private long mLastPress = -1;
+
+    private static final long BACK_THRESHOLD = DateUtils.SECOND_IN_MILLIS / 2;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.lookup);
+
+        // Load animations used to show/hide progress bar
+        mSlideIn = AnimationUtils.loadAnimation(this, R.anim.slide_in);
+        mSlideOut = AnimationUtils.loadAnimation(this, R.anim.slide_out);
+
+        // Listen for the "in" animation so we make the progress bar visible
+        // only after the sliding has finished.
+        mSlideIn.setAnimationListener(this);
+
+        mTitleBar = findViewById(R.id.title_bar);
+        mTitle = (TextView) findViewById(R.id.title);
+        mProgress = (ProgressBar) findViewById(R.id.progress);
+        mWebView = (WebView) findViewById(R.id.webview);
+
+        // Make the view transparent to show background
+        mWebView.setBackgroundColor(0);
+
+        // Prepare User-Agent string for wiki actions
+        ExtendedWikiHelper.prepareUserAgent(this);
+
+        // Handle incoming intents as possible searches or links
+        onNewIntent(getIntent());
+    }
+
+    /**
+     * Intercept the back-key to try walking backwards along our word history
+     * stack. If we don't have any remaining history, the key behaves normally
+     * and closes this activity.
+     */
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        // Handle back key as long we have a history stack
+        if (keyCode == KeyEvent.KEYCODE_BACK && !mHistory.empty()) {
+
+            // Compare against last pressed time, and if user hit multiple times
+            // in quick succession, we should consider bailing out early.
+            long currentPress = SystemClock.uptimeMillis();
+            if (currentPress - mLastPress < BACK_THRESHOLD) {
+                return super.onKeyDown(keyCode, event);
+            }
+            mLastPress = currentPress;
+
+            // Pop last entry off stack and start loading
+            String lastEntry = mHistory.pop();
+            startNavigating(lastEntry, false);
+
+            return true;
+        }
+
+        // Otherwise fall through to parent
+        return super.onKeyDown(keyCode, event);
+    }
+
+    /**
+     * Start navigating to the given word, pushing any current word onto the
+     * history stack if requested. The navigation happens on a background thread
+     * and updates the GUI when finished.
+     *
+     * @param word The dictionary word to navigate to.
+     * @param pushHistory If true, push the current word onto history stack.
+     */
+    private void startNavigating(String word, boolean pushHistory) {
+        // Push any current word onto the history stack
+        if (!TextUtils.isEmpty(mEntryTitle) && pushHistory) {
+            mHistory.add(mEntryTitle);
+        }
+
+        // Start lookup for new word in background
+        new LookupTask().execute(word);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.lookup, menu);
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.lookup_search: {
+                onSearchRequested();
+                return true;
+            }
+            case R.id.lookup_random: {
+                startNavigating(null, true);
+                return true;
+            }
+            case R.id.lookup_about: {
+                showAbout();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Show an about dialog that cites data sources.
+     */
+    protected void showAbout() {
+        // Inflate the about message contents
+        View messageView = getLayoutInflater().inflate(R.layout.about, null, false);
+
+        // When linking text, force to always use default color. This works
+        // around a pressed color state bug.
+        TextView textView = (TextView) messageView.findViewById(R.id.about_credits);
+        int defaultColor = textView.getTextColors().getDefaultColor();
+        textView.setTextColor(defaultColor);
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setIcon(R.drawable.app_icon);
+        builder.setTitle(R.string.app_name);
+        builder.setView(messageView);
+        builder.create();
+        builder.show();
+    }
+
+    /**
+     * Because we're singleTop, we handle our own new intents. These usually
+     * come from the {@link SearchManager} when a search is requested, or from
+     * internal links the user clicks on.
+     */
+    @Override
+    public void onNewIntent(Intent intent) {
+        final String action = intent.getAction();
+        if (Intent.ACTION_SEARCH.equals(action)) {
+            // Start query for incoming search request
+            String query = intent.getStringExtra(SearchManager.QUERY);
+            startNavigating(query, true);
+
+        } else if (Intent.ACTION_VIEW.equals(action)) {
+            // Treat as internal link only if valid Uri and host matches
+            Uri data = intent.getData();
+            if (data != null && ExtendedWikiHelper.WIKI_LOOKUP_HOST
+                    .equals(data.getHost())) {
+                String query = data.getPathSegments().get(0);
+                startNavigating(query, true);
+            }
+
+        } else {
+            // If not recognized, then start showing random word
+            startNavigating(null, true);
+        }
+    }
+
+    /**
+     * Set the title for the current entry.
+     */
+    protected void setEntryTitle(String entryText) {
+        mEntryTitle = entryText;
+        mTitle.setText(mEntryTitle);
+    }
+
+    /**
+     * Set the content for the current entry. This will update our
+     * {@link WebView} to show the requested content.
+     */
+    protected void setEntryContent(String entryContent) {
+        mWebView.loadDataWithBaseURL(ExtendedWikiHelper.WIKI_AUTHORITY, entryContent,
+                ExtendedWikiHelper.MIME_TYPE, ExtendedWikiHelper.ENCODING, null);
+    }
+
+    /**
+     * Background task to handle Wiktionary lookups. This correctly shows and
+     * hides the loading animation from the GUI thread before starting a
+     * background query to the Wiktionary API. When finished, it transitions
+     * back to the GUI thread where it updates with the newly-found entry.
+     */
+    private class LookupTask extends AsyncTask<String, String, String> {
+        /**
+         * Before jumping into background thread, start sliding in the
+         * {@link ProgressBar}. We'll only show it once the animation finishes.
+         */
+        @Override
+        protected void onPreExecute() {
+            mTitleBar.startAnimation(mSlideIn);
+        }
+
+        /**
+         * Perform the background query using {@link ExtendedWikiHelper}, which
+         * may return an error message as the result.
+         */
+        @Override
+        protected String doInBackground(String... args) {
+            String query = args[0];
+            String parsedText = null;
+
+            try {
+                // If query word is null, assume request for random word
+                if (query == null) {
+                    query = ExtendedWikiHelper.getRandomWord();
+                }
+
+                if (query != null) {
+                    // Push our requested word to the title bar
+                    publishProgress(query);
+                    String wikiText = ExtendedWikiHelper.getPageContent(query, true);
+                    parsedText = ExtendedWikiHelper.formatWikiText(wikiText);
+                }
+            } catch (ApiException e) {
+                Log.e(TAG, "Problem making wiktionary request", e);
+            } catch (ParseException e) {
+                Log.e(TAG, "Problem making wiktionary request", e);
+            }
+
+            if (parsedText == null) {
+                parsedText = getString(R.string.empty_result);
+            }
+
+            return parsedText;
+        }
+
+        /**
+         * Our progress update pushes a title bar update.
+         */
+        @Override
+        protected void onProgressUpdate(String... args) {
+            String searchWord = args[0];
+            setEntryTitle(searchWord);
+        }
+
+        /**
+         * When finished, push the newly-found entry content into our
+         * {@link WebView} and hide the {@link ProgressBar}.
+         */
+        @Override
+        protected void onPostExecute(String parsedText) {
+            mTitleBar.startAnimation(mSlideOut);
+            mProgress.setVisibility(View.INVISIBLE);
+
+            setEntryContent(parsedText);
+        }
+    }
+
+    /**
+     * Make the {@link ProgressBar} visible when our in-animation finishes.
+     */
+    public void onAnimationEnd(Animation animation) {
+        mProgress.setVisibility(View.VISIBLE);
+    }
+
+    public void onAnimationRepeat(Animation animation) {
+        // Not interested if the animation repeats
+    }
+
+    public void onAnimationStart(Animation animation) {
+        // Not interested when the animation starts
+    }
+}
diff --git a/samples/Wiktionary/src/com/example/android/wiktionary/SimpleWikiHelper.java b/samples/Wiktionary/src/com/example/android/wiktionary/SimpleWikiHelper.java
new file mode 100644
index 0000000..1c71d7e
--- /dev/null
+++ b/samples/Wiktionary/src/com/example/android/wiktionary/SimpleWikiHelper.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2009 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.wiktionary;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Helper methods to simplify talking with and parsing responses from a
+ * lightweight Wiktionary API. Before making any requests, you should call
+ * {@link #prepareUserAgent(Context)} to generate a User-Agent string based on
+ * your application package name and version.
+ */
+public class SimpleWikiHelper {
+    private static final String TAG = "SimpleWikiHelper";
+
+    /**
+     * Partial URL to use when requesting the detailed entry for a specific
+     * Wiktionary page. Use {@link String#format(String, Object...)} to insert
+     * the desired page title after escaping it as needed.
+     */
+    private static final String WIKTIONARY_PAGE =
+            "http://en.wiktionary.org/w/api.php?action=query&prop=revisions&titles=%s&" +
+            "rvprop=content&format=json%s";
+
+    /**
+     * Partial URL to append to {@link #WIKTIONARY_PAGE} when you want to expand
+     * any templates found on the requested page. This is useful when browsing
+     * full entries, but may use more network bandwidth.
+     */
+    private static final String WIKTIONARY_EXPAND_TEMPLATES =
+            "&rvexpandtemplates=true";
+
+    /**
+     * {@link StatusLine} HTTP status code when no server error has occurred.
+     */
+    private static final int HTTP_STATUS_OK = 200;
+
+    /**
+     * Shared buffer used by {@link #getUrlContent(String)} when reading results
+     * from an API request.
+     */
+    private static byte[] sBuffer = new byte[512];
+
+    /**
+     * User-agent string to use when making requests. Should be filled using
+     * {@link #prepareUserAgent(Context)} before making any other calls.
+     */
+    private static String sUserAgent = null;
+
+    /**
+     * Thrown when there were problems contacting the remote API server, either
+     * because of a network error, or the server returned a bad status code.
+     */
+    public static class ApiException extends Exception {
+        public ApiException(String detailMessage, Throwable throwable) {
+            super(detailMessage, throwable);
+        }
+
+        public ApiException(String detailMessage) {
+            super(detailMessage);
+        }
+    }
+
+    /**
+     * Thrown when there were problems parsing the response to an API call,
+     * either because the response was empty, or it was malformed.
+     */
+    public static class ParseException extends Exception {
+        public ParseException(String detailMessage, Throwable throwable) {
+            super(detailMessage, throwable);
+        }
+    }
+
+    /**
+     * Prepare the internal User-Agent string for use. This requires a
+     * {@link Context} to pull the package name and version number for this
+     * application.
+     */
+    public static void prepareUserAgent(Context context) {
+        try {
+            // Read package name and version number from manifest
+            PackageManager manager = context.getPackageManager();
+            PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
+            sUserAgent = String.format(context.getString(R.string.template_user_agent),
+                    info.packageName, info.versionName);
+
+        } catch(NameNotFoundException e) {
+            Log.e(TAG, "Couldn't find package information in PackageManager", e);
+        }
+    }
+
+    /**
+     * Read and return the content for a specific Wiktionary page. This makes a
+     * lightweight API call, and trims out just the page content returned.
+     * Because this call blocks until results are available, it should not be
+     * run from a UI thread.
+     *
+     * @param title The exact title of the Wiktionary page requested.
+     * @param expandTemplates If true, expand any wiki templates found.
+     * @return Exact content of page.
+     * @throws ApiException If any connection or server error occurs.
+     * @throws ParseException If there are problems parsing the response.
+     */
+    public static String getPageContent(String title, boolean expandTemplates)
+            throws ApiException, ParseException {
+        // Encode page title and expand templates if requested
+        String encodedTitle = Uri.encode(title);
+        String expandClause = expandTemplates ? WIKTIONARY_EXPAND_TEMPLATES : "";
+
+        // Query the API for content
+        String content = getUrlContent(String.format(WIKTIONARY_PAGE,
+                encodedTitle, expandClause));
+        try {
+            // Drill into the JSON response to find the content body
+            JSONObject response = new JSONObject(content);
+            JSONObject query = response.getJSONObject("query");
+            JSONObject pages = query.getJSONObject("pages");
+            JSONObject page = pages.getJSONObject((String) pages.keys().next());
+            JSONArray revisions = page.getJSONArray("revisions");
+            JSONObject revision = revisions.getJSONObject(0);
+            return revision.getString("*");
+        } catch (JSONException e) {
+            throw new ParseException("Problem parsing API response", e);
+        }
+    }
+
+    /**
+     * Pull the raw text content of the given URL. This call blocks until the
+     * operation has completed, and is synchronized because it uses a shared
+     * buffer {@link #sBuffer}.
+     *
+     * @param url The exact URL to request.
+     * @return The raw content returned by the server.
+     * @throws ApiException If any connection or server error occurs.
+     */
+    protected static synchronized String getUrlContent(String url) throws ApiException {
+        if (sUserAgent == null) {
+            throw new ApiException("User-Agent string must be prepared");
+        }
+
+        // Create client and set our specific user-agent string
+        HttpClient client = new DefaultHttpClient();
+        HttpGet request = new HttpGet(url);
+        request.setHeader("User-Agent", sUserAgent);
+
+        try {
+            HttpResponse response = client.execute(request);
+
+            // Check if server response is valid
+            StatusLine status = response.getStatusLine();
+            if (status.getStatusCode() != HTTP_STATUS_OK) {
+                throw new ApiException("Invalid response from server: " +
+                        status.toString());
+            }
+
+            // Pull content stream from response
+            HttpEntity entity = response.getEntity();
+            InputStream inputStream = entity.getContent();
+
+            ByteArrayOutputStream content = new ByteArrayOutputStream();
+
+            // Read response into a buffered stream
+            int readBytes = 0;
+            while ((readBytes = inputStream.read(sBuffer)) != -1) {
+                content.write(sBuffer, 0, readBytes);
+            }
+
+            // Return result from buffered stream
+            return new String(content.toByteArray());
+        } catch (IOException e) {
+            throw new ApiException("Problem communicating with API", e);
+        }
+    }
+
+}
diff --git a/samples/Wiktionary/src/com/example/android/wiktionary/WordWidget.java b/samples/Wiktionary/src/com/example/android/wiktionary/WordWidget.java
new file mode 100644
index 0000000..e80eaf9
--- /dev/null
+++ b/samples/Wiktionary/src/com/example/android/wiktionary/WordWidget.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2009 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.wiktionary;
+
+import com.example.android.wiktionary.SimpleWikiHelper.ApiException;
+import com.example.android.wiktionary.SimpleWikiHelper.ParseException;
+
+import android.app.PendingIntent;
+import android.app.Service;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.IBinder;
+import android.text.format.Time;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Define a simple widget that shows the Wiktionary "Word of the day." To build
+ * an update we spawn a background {@link Service} to perform the API queries.
+ */
+public class WordWidget extends AppWidgetProvider {
+    @Override
+    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+        // To prevent any ANR timeouts, we perform the update in a service
+        context.startService(new Intent(context, UpdateService.class));
+    }
+
+    public static class UpdateService extends Service {
+        @Override
+        public void onStart(Intent intent, int startId) {
+            // Build the widget update for today
+            RemoteViews updateViews = buildUpdate(this);
+
+            // Push update for this widget to the home screen
+            ComponentName thisWidget = new ComponentName(this, WordWidget.class);
+            AppWidgetManager manager = AppWidgetManager.getInstance(this);
+            manager.updateAppWidget(thisWidget, updateViews);
+        }
+
+        @Override
+        public IBinder onBind(Intent intent) {
+            return null;
+        }
+
+        /**
+         * Regular expression that splits "Word of the day" entry into word
+         * name, word type, and the first description bullet point.
+         */
+        private static final String WOTD_PATTERN =
+            "(?s)\\{\\{wotd\\|(.+?)\\|(.+?)\\|([^#\\|]+).*?\\}\\}";
+
+        /**
+         * Build a widget update to show the current Wiktionary
+         * "Word of the day." Will block until the online API returns.
+         */
+        public RemoteViews buildUpdate(Context context) {
+            // Pick out month names from resources
+            Resources res = context.getResources();
+            String[] monthNames = res.getStringArray(R.array.month_names);
+
+            // Find current month and day
+            Time today = new Time();
+            today.setToNow();
+
+            // Build the page title for today, such as "March 21"
+            String pageName = res.getString(R.string.template_wotd_title,
+                    monthNames[today.month], today.monthDay);
+            String pageContent = null;
+
+            try {
+                // Try querying the Wiktionary API for today's word
+                SimpleWikiHelper.prepareUserAgent(context);
+                pageContent = SimpleWikiHelper.getPageContent(pageName, false);
+            } catch (ApiException e) {
+                Log.e("WordWidget", "Couldn't contact API", e);
+            } catch (ParseException e) {
+                Log.e("WordWidget", "Couldn't parse API response", e);
+            }
+
+            RemoteViews views = null;
+            Matcher matcher = Pattern.compile(WOTD_PATTERN).matcher(pageContent);
+            if (matcher.find()) {
+                // Build an update that holds the updated widget contents
+                views = new RemoteViews(context.getPackageName(), R.layout.widget_word);
+
+                String wordTitle = matcher.group(1);
+                views.setTextViewText(R.id.word_title, wordTitle);
+                views.setTextViewText(R.id.word_type, matcher.group(2));
+                views.setTextViewText(R.id.definition, matcher.group(3).trim());
+
+                // When user clicks on widget, launch to Wiktionary definition page
+                String definePage = String.format("%s://%s/%s", ExtendedWikiHelper.WIKI_AUTHORITY,
+                        ExtendedWikiHelper.WIKI_LOOKUP_HOST, wordTitle);
+                Intent defineIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(definePage));
+                PendingIntent pendingIntent = PendingIntent.getActivity(context,
+                        0 /* no requestCode */, defineIntent, 0 /* no flags */);
+                views.setOnClickPendingIntent(R.id.widget, pendingIntent);
+
+            } else {
+                // Didn't find word of day, so show error message
+                views = new RemoteViews(context.getPackageName(), R.layout.widget_message);
+                views.setTextViewText(R.id.message, context.getString(R.string.widget_error));
+            }
+            return views;
+        }
+    }
+}
diff --git a/samples/WiktionarySimple/Android.mk b/samples/WiktionarySimple/Android.mk
new file mode 100644
index 0000000..a5a1423
--- /dev/null
+++ b/samples/WiktionarySimple/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := WiktionarySimple
+
+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/WiktionarySimple/AndroidManifest.xml b/samples/WiktionarySimple/AndroidManifest.xml
new file mode 100644
index 0000000..c6b8724
--- /dev/null
+++ b/samples/WiktionarySimple/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.simplewiktionary"
+    android:versionCode="1"
+    android:versionName="1.0">
+
+    <application android:icon="@drawable/app_icon" android:label="@string/app_name">
+
+        <!-- Broadcast Receiver that will process AppWidget updates -->
+        <receiver android:name=".WordWidget" android:label="@string/widget_name">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+            </intent-filter>
+            <meta-data android:name="android.appwidget.provider"
+                android:resource="@xml/widget_word" />
+        </receiver>
+
+        <!-- Service to perform web API queries -->
+        <service android:name=".WordWidget$UpdateService" />
+
+    </application>
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="4" />
+
+</manifest>
diff --git a/samples/WiktionarySimple/_index.html b/samples/WiktionarySimple/_index.html
new file mode 100644
index 0000000..3980e33
--- /dev/null
+++ b/samples/WiktionarySimple/_index.html
@@ -0,0 +1,10 @@
+<p>A sample application that demonstrates how to create an interactive widget for display on the Android home screen.</p>
+
+<p>When installed, this adds a "Wiktionary simple" option to the widget
+installation menu. The word of the day is downloaded from Wiktionary and
+displayed in a frame. Touching the widget will open a new browser session and
+load the word's Wiktionary entry.</p>
+
+<p>A more advanced version of this sample is available in the Wiktionary directory.</p>
+
+<img alt="" src="../images/WiktionarySimple.png"/>
diff --git a/samples/WiktionarySimple/res/drawable/app_icon.png b/samples/WiktionarySimple/res/drawable/app_icon.png
new file mode 100644
index 0000000..2b1417a
--- /dev/null
+++ b/samples/WiktionarySimple/res/drawable/app_icon.png
Binary files differ
diff --git a/samples/WiktionarySimple/res/drawable/star_logo.png b/samples/WiktionarySimple/res/drawable/star_logo.png
new file mode 100644
index 0000000..b32d175
--- /dev/null
+++ b/samples/WiktionarySimple/res/drawable/star_logo.png
Binary files differ
diff --git a/samples/WiktionarySimple/res/drawable/widget_bg.xml b/samples/WiktionarySimple/res/drawable/widget_bg.xml
new file mode 100644
index 0000000..692a13d
--- /dev/null
+++ b/samples/WiktionarySimple/res/drawable/widget_bg.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- The stateful background drawable for a widget -->
+    <item android:state_window_focused="false" android:drawable="@drawable/widget_bg_normal" />
+    <item android:state_pressed="true" android:drawable="@drawable/widget_bg_pressed" />
+    <item android:state_focused="true" android:drawable="@drawable/widget_bg_selected" />
+    <item android:drawable="@drawable/widget_bg_normal" />
+</selector>
diff --git a/samples/WiktionarySimple/res/drawable/widget_bg_normal.9.png b/samples/WiktionarySimple/res/drawable/widget_bg_normal.9.png
new file mode 100644
index 0000000..314eb8e
--- /dev/null
+++ b/samples/WiktionarySimple/res/drawable/widget_bg_normal.9.png
Binary files differ
diff --git a/samples/WiktionarySimple/res/drawable/widget_bg_pressed.9.png b/samples/WiktionarySimple/res/drawable/widget_bg_pressed.9.png
new file mode 100644
index 0000000..cc23e78
--- /dev/null
+++ b/samples/WiktionarySimple/res/drawable/widget_bg_pressed.9.png
Binary files differ
diff --git a/samples/WiktionarySimple/res/drawable/widget_bg_selected.9.png b/samples/WiktionarySimple/res/drawable/widget_bg_selected.9.png
new file mode 100644
index 0000000..ef0cdc0
--- /dev/null
+++ b/samples/WiktionarySimple/res/drawable/widget_bg_selected.9.png
Binary files differ
diff --git a/samples/WiktionarySimple/res/layout/widget_message.xml b/samples/WiktionarySimple/res/layout/widget_message.xml
new file mode 100644
index 0000000..ba94714
--- /dev/null
+++ b/samples/WiktionarySimple/res/layout/widget_message.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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:id="@+id/widget"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    style="@style/WidgetBackground">
+
+    <TextView
+        android:id="@+id/message"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="12dip"
+        android:padding="10dip"
+        android:gravity="center"
+        android:text="@string/widget_loading"
+        style="@style/Text.Loading" />
+
+</LinearLayout>
diff --git a/samples/WiktionarySimple/res/layout/widget_word.xml b/samples/WiktionarySimple/res/layout/widget_word.xml
new file mode 100644
index 0000000..0e76f0b
--- /dev/null
+++ b/samples/WiktionarySimple/res/layout/widget_word.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/widget"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:focusable="true"
+    style="@style/WidgetBackground">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:src="@drawable/star_logo" />
+
+    <TextView
+        android:id="@+id/word_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="14dip"
+        android:layout_marginBottom="1dip"
+        android:includeFontPadding="false"
+        android:singleLine="true"
+        android:ellipsize="end"
+        style="@style/Text.WordTitle" />
+
+    <TextView
+        android:id="@+id/word_type"
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_toRightOf="@id/word_title"
+        android:layout_toLeftOf="@id/icon"
+        android:layout_alignBaseline="@id/word_title"
+        android:paddingLeft="4dip"
+        android:includeFontPadding="false"
+        android:singleLine="true"
+        android:ellipsize="end"
+        style="@style/Text.WordType" />
+
+    <TextView
+        android:id="@+id/bullet"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/word_title"
+        android:paddingRight="4dip"
+        android:includeFontPadding="false"
+        android:singleLine="true"
+        style="@style/BulletPoint" />
+
+    <TextView
+        android:id="@+id/definition"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/word_title"
+        android:layout_toRightOf="@id/bullet"
+        android:paddingRight="5dip"
+        android:paddingBottom="4dip"
+        android:includeFontPadding="false"
+        android:lineSpacingMultiplier="0.9"
+        android:maxLines="4"
+        android:fadingEdge="vertical"
+        style="@style/Text.Definition" />
+
+</RelativeLayout>
diff --git a/samples/WiktionarySimple/res/values/strings.xml b/samples/WiktionarySimple/res/values/strings.xml
new file mode 100644
index 0000000..65e44cb
--- /dev/null
+++ b/samples/WiktionarySimple/res/values/strings.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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="app_name">Wiktionary simple example</string>
+
+    <string name="template_user_agent">"%s/%s (Linux; Android)"</string>
+    <string name="template_wotd_title">"Wiktionary:Word of the day/%s %s"</string>
+    <string name="template_define_url">"http://en.wiktionary.org/wiki/%s"</string>
+
+    <string name="widget_name">Wiktionary simple</string>
+
+    <string name="widget_loading">Loading word\nof day\u2026</string>
+    <string name="widget_error">No word of\nday found</string>
+
+    <string-array name="month_names">
+        <item>January</item>
+        <item>February</item>
+        <item>March</item>
+        <item>April</item>
+        <item>May</item>
+        <item>June</item>
+        <item>July</item>
+        <item>August</item>
+        <item>September</item>
+        <item>October</item>
+        <item>November</item>
+        <item>December</item>
+    </string-array>
+
+</resources>
diff --git a/samples/WiktionarySimple/res/values/styles.xml b/samples/WiktionarySimple/res/values/styles.xml
new file mode 100644
index 0000000..42d679c
--- /dev/null
+++ b/samples/WiktionarySimple/res/values/styles.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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>
+
+    <style name="WidgetBackground">
+        <item name="android:background">@drawable/widget_bg</item>
+    </style>
+
+    <style name="BulletPoint">
+        <item name="android:textSize">13sp</item>
+        <item name="android:textColor">@android:color/black</item>
+    </style>
+
+    <style name="Text" />
+
+    <style name="Text.Loading">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">@android:color/black</item>
+    </style>
+
+    <style name="Text.WordTitle">
+        <item name="android:textSize">16sp</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:textColor">@android:color/black</item>
+    </style>
+
+    <style name="Text.WordType">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">italic</item>
+        <item name="android:textColor">@android:color/black</item>
+    </style>
+
+    <style name="Text.Definition">
+        <item name="android:textSize">13sp</item>
+        <item name="android:textColor">@android:color/black</item>
+    </style>
+
+</resources>
diff --git a/samples/WiktionarySimple/res/xml/widget_word.xml b/samples/WiktionarySimple/res/xml/widget_word.xml
new file mode 100644
index 0000000..46d31c3
--- /dev/null
+++ b/samples/WiktionarySimple/res/xml/widget_word.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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="146dip"
+    android:minHeight="72dip"
+    android:updatePeriodMillis="86400000"
+    android:initialLayout="@layout/widget_message" />
diff --git a/samples/WiktionarySimple/src/com/example/android/simplewiktionary/SimpleWikiHelper.java b/samples/WiktionarySimple/src/com/example/android/simplewiktionary/SimpleWikiHelper.java
new file mode 100644
index 0000000..bb39d7b
--- /dev/null
+++ b/samples/WiktionarySimple/src/com/example/android/simplewiktionary/SimpleWikiHelper.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2009 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.simplewiktionary;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Helper methods to simplify talking with and parsing responses from a
+ * lightweight Wiktionary API. Before making any requests, you should call
+ * {@link #prepareUserAgent(Context)} to generate a User-Agent string based on
+ * your application package name and version.
+ */
+public class SimpleWikiHelper {
+    private static final String TAG = "SimpleWikiHelper";
+
+    /**
+     * Regular expression that splits "Word of the day" entry into word
+     * name, word type, and the first description bullet point.
+     */
+    public static final String WORD_OF_DAY_REGEX =
+            "(?s)\\{\\{wotd\\|(.+?)\\|(.+?)\\|([^#\\|]+).*?\\}\\}";
+
+    /**
+     * Partial URL to use when requesting the detailed entry for a specific
+     * Wiktionary page. Use {@link String#format(String, Object...)} to insert
+     * the desired page title after escaping it as needed.
+     */
+    private static final String WIKTIONARY_PAGE =
+            "http://en.wiktionary.org/w/api.php?action=query&prop=revisions&titles=%s&" +
+            "rvprop=content&format=json%s";
+
+    /**
+     * Partial URL to append to {@link #WIKTIONARY_PAGE} when you want to expand
+     * any templates found on the requested page. This is useful when browsing
+     * full entries, but may use more network bandwidth.
+     */
+    private static final String WIKTIONARY_EXPAND_TEMPLATES =
+            "&rvexpandtemplates=true";
+
+    /**
+     * {@link StatusLine} HTTP status code when no server error has occurred.
+     */
+    private static final int HTTP_STATUS_OK = 200;
+
+    /**
+     * Shared buffer used by {@link #getUrlContent(String)} when reading results
+     * from an API request.
+     */
+    private static byte[] sBuffer = new byte[512];
+
+    /**
+     * User-agent string to use when making requests. Should be filled using
+     * {@link #prepareUserAgent(Context)} before making any other calls.
+     */
+    private static String sUserAgent = null;
+
+    /**
+     * Thrown when there were problems contacting the remote API server, either
+     * because of a network error, or the server returned a bad status code.
+     */
+    public static class ApiException extends Exception {
+        public ApiException(String detailMessage, Throwable throwable) {
+            super(detailMessage, throwable);
+        }
+
+        public ApiException(String detailMessage) {
+            super(detailMessage);
+        }
+    }
+
+    /**
+     * Thrown when there were problems parsing the response to an API call,
+     * either because the response was empty, or it was malformed.
+     */
+    public static class ParseException extends Exception {
+        public ParseException(String detailMessage, Throwable throwable) {
+            super(detailMessage, throwable);
+        }
+    }
+
+    /**
+     * Prepare the internal User-Agent string for use. This requires a
+     * {@link Context} to pull the package name and version number for this
+     * application.
+     */
+    public static void prepareUserAgent(Context context) {
+        try {
+            // Read package name and version number from manifest
+            PackageManager manager = context.getPackageManager();
+            PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
+            sUserAgent = String.format(context.getString(R.string.template_user_agent),
+                    info.packageName, info.versionName);
+
+        } catch(NameNotFoundException e) {
+            Log.e(TAG, "Couldn't find package information in PackageManager", e);
+        }
+    }
+
+    /**
+     * Read and return the content for a specific Wiktionary page. This makes a
+     * lightweight API call, and trims out just the page content returned.
+     * Because this call blocks until results are available, it should not be
+     * run from a UI thread.
+     *
+     * @param title The exact title of the Wiktionary page requested.
+     * @param expandTemplates If true, expand any wiki templates found.
+     * @return Exact content of page.
+     * @throws ApiException If any connection or server error occurs.
+     * @throws ParseException If there are problems parsing the response.
+     */
+    public static String getPageContent(String title, boolean expandTemplates)
+            throws ApiException, ParseException {
+        // Encode page title and expand templates if requested
+        String encodedTitle = Uri.encode(title);
+        String expandClause = expandTemplates ? WIKTIONARY_EXPAND_TEMPLATES : "";
+
+        // Query the API for content
+        String content = getUrlContent(String.format(WIKTIONARY_PAGE,
+                encodedTitle, expandClause));
+        try {
+            // Drill into the JSON response to find the content body
+            JSONObject response = new JSONObject(content);
+            JSONObject query = response.getJSONObject("query");
+            JSONObject pages = query.getJSONObject("pages");
+            JSONObject page = pages.getJSONObject((String) pages.keys().next());
+            JSONArray revisions = page.getJSONArray("revisions");
+            JSONObject revision = revisions.getJSONObject(0);
+            return revision.getString("*");
+        } catch (JSONException e) {
+            throw new ParseException("Problem parsing API response", e);
+        }
+    }
+
+    /**
+     * Pull the raw text content of the given URL. This call blocks until the
+     * operation has completed, and is synchronized because it uses a shared
+     * buffer {@link #sBuffer}.
+     *
+     * @param url The exact URL to request.
+     * @return The raw content returned by the server.
+     * @throws ApiException If any connection or server error occurs.
+     */
+    protected static synchronized String getUrlContent(String url) throws ApiException {
+        if (sUserAgent == null) {
+            throw new ApiException("User-Agent string must be prepared");
+        }
+
+        // Create client and set our specific user-agent string
+        HttpClient client = new DefaultHttpClient();
+        HttpGet request = new HttpGet(url);
+        request.setHeader("User-Agent", sUserAgent);
+
+        try {
+            HttpResponse response = client.execute(request);
+
+            // Check if server response is valid
+            StatusLine status = response.getStatusLine();
+            if (status.getStatusCode() != HTTP_STATUS_OK) {
+                throw new ApiException("Invalid response from server: " +
+                        status.toString());
+            }
+
+            // Pull content stream from response
+            HttpEntity entity = response.getEntity();
+            InputStream inputStream = entity.getContent();
+
+            ByteArrayOutputStream content = new ByteArrayOutputStream();
+
+            // Read response into a buffered stream
+            int readBytes = 0;
+            while ((readBytes = inputStream.read(sBuffer)) != -1) {
+                content.write(sBuffer, 0, readBytes);
+            }
+
+            // Return result from buffered stream
+            return new String(content.toByteArray());
+        } catch (IOException e) {
+            throw new ApiException("Problem communicating with API", e);
+        }
+    }
+}
diff --git a/samples/WiktionarySimple/src/com/example/android/simplewiktionary/WordWidget.java b/samples/WiktionarySimple/src/com/example/android/simplewiktionary/WordWidget.java
new file mode 100644
index 0000000..d005faa
--- /dev/null
+++ b/samples/WiktionarySimple/src/com/example/android/simplewiktionary/WordWidget.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2009 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.simplewiktionary;
+
+import com.example.android.simplewiktionary.SimpleWikiHelper.ApiException;
+import com.example.android.simplewiktionary.SimpleWikiHelper.ParseException;
+
+import android.app.PendingIntent;
+import android.app.Service;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.IBinder;
+import android.text.format.Time;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Define a simple widget that shows the Wiktionary "Word of the day." To build
+ * an update we spawn a background {@link Service} to perform the API queries.
+ */
+public class WordWidget extends AppWidgetProvider {
+    @Override
+    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
+            int[] appWidgetIds) {
+        // To prevent any ANR timeouts, we perform the update in a service
+        context.startService(new Intent(context, UpdateService.class));
+    }
+
+    public static class UpdateService extends Service {
+        @Override
+        public void onStart(Intent intent, int startId) {
+            // Build the widget update for today
+            RemoteViews updateViews = buildUpdate(this);
+
+            // Push update for this widget to the home screen
+            ComponentName thisWidget = new ComponentName(this, WordWidget.class);
+            AppWidgetManager manager = AppWidgetManager.getInstance(this);
+            manager.updateAppWidget(thisWidget, updateViews);
+        }
+
+        /**
+         * Build a widget update to show the current Wiktionary
+         * "Word of the day." Will block until the online API returns.
+         */
+        public RemoteViews buildUpdate(Context context) {
+            // Pick out month names from resources
+            Resources res = context.getResources();
+            String[] monthNames = res.getStringArray(R.array.month_names);
+
+            // Find current month and day
+            Time today = new Time();
+            today.setToNow();
+
+            // Build today's page title, like "Wiktionary:Word of the day/March 21"
+            String pageName = res.getString(R.string.template_wotd_title,
+                    monthNames[today.month], today.monthDay);
+            RemoteViews updateViews = null;
+            String pageContent = "";
+
+            try {
+                // Try querying the Wiktionary API for today's word
+                SimpleWikiHelper.prepareUserAgent(context);
+                pageContent = SimpleWikiHelper.getPageContent(pageName, false);
+            } catch (ApiException e) {
+                Log.e("WordWidget", "Couldn't contact API", e);
+            } catch (ParseException e) {
+                Log.e("WordWidget", "Couldn't parse API response", e);
+            }
+
+            // Use a regular expression to parse out the word and its definition
+            Pattern pattern = Pattern.compile(SimpleWikiHelper.WORD_OF_DAY_REGEX);
+            Matcher matcher = pattern.matcher(pageContent);
+            if (matcher.find()) {
+                // Build an update that holds the updated widget contents
+                updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_word);
+
+                String wordTitle = matcher.group(1);
+                updateViews.setTextViewText(R.id.word_title, wordTitle);
+                updateViews.setTextViewText(R.id.word_type, matcher.group(2));
+                updateViews.setTextViewText(R.id.definition, matcher.group(3).trim());
+
+                // When user clicks on widget, launch to Wiktionary definition page
+                String definePage = res.getString(R.string.template_define_url,
+                        Uri.encode(wordTitle));
+                Intent defineIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(definePage));
+                PendingIntent pendingIntent = PendingIntent.getActivity(context,
+                        0 /* no requestCode */, defineIntent, 0 /* no flags */);
+                updateViews.setOnClickPendingIntent(R.id.widget, pendingIntent);
+
+            } else {
+                // Didn't find word of day, so show error message
+                updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_message);
+                CharSequence errorMessage = context.getText(R.string.widget_error);
+                updateViews.setTextViewText(R.id.message, errorMessage);
+            }
+            return updateViews;
+        }
+
+        @Override
+        public IBinder onBind(Intent intent) {
+            // We don't need to bind to this service
+            return null;
+        }
+    }
+}