Importing Wikitionary/SimpleWiktionary sample code from http://code.google.com/p/wiktionary-android/.
Original code by Jeffrey Sharkey <jsharkey@android.com>.
Modifications:
- Remove .classpath, .project, and default.properties.
- Remove generated files (/gen).
- Create Android.mk and _index.html.
- Cleaned up whitespace and converted to match AOSP style guide.
- Renamed SimpleWiktionary to WiktionarySimple to keep both samples next to each other in the
directory listing.
- Removed the android:text attribute in the BulletPoint due to localization issues.
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..eb7cb96
--- /dev/null
+++ b/samples/Wiktionary/_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" 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 custom WebView to render the
+definition.</p>
+
+<img alt="" src="../images/wiktionary.png"/>
+<img alt="" src="../images/wiktionary_detail.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..cf8142e
--- /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/wiktionary.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;
+ }
+ }
+}