diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2010-01-12 15:18:14 -0800 |
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2010-01-12 15:18:14 -0800 |
| commit | 2b6482773eb251df1f205bf239b94aa0a563a82e (patch) | |
| tree | 9406d79234d787d825e8085e11f0e8f0d4eb3e43 /samples/Wiktionary | |
| parent | 2c8ead32c7ebf08db568e2e9d5dbb3738ac69797 (diff) | |
| download | android_development-2b6482773eb251df1f205bf239b94aa0a563a82e.tar.gz android_development-2b6482773eb251df1f205bf239b94aa0a563a82e.tar.bz2 android_development-2b6482773eb251df1f205bf239b94aa0a563a82e.zip | |
android-2.1_r1 snapshot
Diffstat (limited to 'samples/Wiktionary')
29 files changed, 1610 insertions, 0 deletions
diff --git a/samples/Wiktionary/Android.mk b/samples/Wiktionary/Android.mk new file mode 100644 index 000000000..d6ce1f16c --- /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 000000000..1641a8b0a --- /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 000000000..507dda331 --- /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 000000000..3da074e09 --- /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 000000000..ec21f521e --- /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 Binary files differnew file mode 100644 index 000000000..2b1417aad --- /dev/null +++ b/samples/Wiktionary/res/drawable/app_icon.png diff --git a/samples/Wiktionary/res/drawable/ic_menu_shuffle.png b/samples/Wiktionary/res/drawable/ic_menu_shuffle.png Binary files differnew file mode 100755 index 000000000..cb7009dea --- /dev/null +++ b/samples/Wiktionary/res/drawable/ic_menu_shuffle.png diff --git a/samples/Wiktionary/res/drawable/logo_overlay.9.png b/samples/Wiktionary/res/drawable/logo_overlay.9.png Binary files differnew file mode 100644 index 000000000..851ceb15b --- /dev/null +++ b/samples/Wiktionary/res/drawable/logo_overlay.9.png diff --git a/samples/Wiktionary/res/drawable/lookup_bg.xml b/samples/Wiktionary/res/drawable/lookup_bg.xml new file mode 100644 index 000000000..46d76eb9b --- /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 000000000..4594a181b --- /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 Binary files differnew file mode 100644 index 000000000..b32d1756c --- /dev/null +++ b/samples/Wiktionary/res/drawable/star_logo.png diff --git a/samples/Wiktionary/res/drawable/widget_bg.xml b/samples/Wiktionary/res/drawable/widget_bg.xml new file mode 100644 index 000000000..c2b846277 --- /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 Binary files differnew file mode 100644 index 000000000..314eb8ef9 --- /dev/null +++ b/samples/Wiktionary/res/drawable/widget_bg_normal.9.png diff --git a/samples/Wiktionary/res/drawable/widget_bg_pressed.9.png b/samples/Wiktionary/res/drawable/widget_bg_pressed.9.png Binary files differnew file mode 100644 index 000000000..cc23e787b --- /dev/null +++ b/samples/Wiktionary/res/drawable/widget_bg_pressed.9.png diff --git a/samples/Wiktionary/res/drawable/widget_bg_selected.9.png b/samples/Wiktionary/res/drawable/widget_bg_selected.9.png Binary files differnew file mode 100644 index 000000000..ef0cdc066 --- /dev/null +++ b/samples/Wiktionary/res/drawable/widget_bg_selected.9.png diff --git a/samples/Wiktionary/res/layout/about.xml b/samples/Wiktionary/res/layout/about.xml new file mode 100644 index 000000000..3b25b3274 --- /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 000000000..43cffaa1a --- /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 000000000..ba9471447 --- /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 000000000..0e76f0b80 --- /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 000000000..741ca9ab2 --- /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 000000000..38d993768 --- /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 000000000..45fc8f5a4 --- /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 000000000..c4d7630f6 --- /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 000000000..02ee31fc5 --- /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 000000000..46d31c321 --- /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 000000000..3a3917248 --- /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 000000000..6cc231b8c --- /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 000000000..1c71d7e8b --- /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 000000000..e80eaf92a --- /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; + } + } +} |
