am d71605b0: am 8c63b97f: Rename framework-common testdef to android-common.
diff --git a/samples/SearchableDictionary/AndroidManifest.xml b/samples/SearchableDictionary/AndroidManifest.xml
index 93cd47b..ec82449 100644
--- a/samples/SearchableDictionary/AndroidManifest.xml
+++ b/samples/SearchableDictionary/AndroidManifest.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
-** Copyright 2009, The Android Open Source Project
+** Copyright 2010, 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.
@@ -22,12 +22,11 @@
<uses-sdk android:minSdkVersion="4" />
<application android:label="@string/app_name"
- android:icon="@drawable/ic_dictionary">
+ android:icon="@drawable/ic_dictionary">
- <!-- The default activity of the app. Can also display search results. -->
+ <!-- The default activity of the app; displays search results. -->
<activity android:name=".SearchableDictionary"
- android:label="@string/app_name"
- android:theme="@android:style/Theme.NoTitleBar">
+ android:theme="@android:style/Theme.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -37,22 +36,26 @@
<!-- Receives the search request. -->
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
- <category android:name="android.intent.category.DEFAULT" />
+ <!-- No category needed, because the Intent will specify this class component-->
</intent-filter>
<!-- Points to searchable meta data. -->
<meta-data android:name="android.app.searchable"
- android:resource="@xml/searchable"/>
+ android:resource="@xml/searchable" />
+
</activity>
<!-- Displays the definition of a word. -->
<activity android:name=".WordActivity"
- android:theme="@android:style/Theme.NoTitleBar"/>
+ android:theme="@android:style/Theme.NoTitleBar" />
<!-- Provides search suggestions for words and their definitions. -->
- <provider android:name="DictionaryProvider"
- android:authorities="dictionary"
- android:syncable="false" />
+ <provider android:name=".DictionaryProvider"
+ android:authorities="com.example.android.searchabledict.DictionaryProvider" />
+
+ <!-- Points to searchable activity so the whole app can invoke search. -->
+ <meta-data android:name="android.app.default_searchable"
+ android:value=".SearchableDictionary" />
</application>
</manifest>
diff --git a/samples/SearchableDictionary/_index.html b/samples/SearchableDictionary/_index.html
index de3345e..6f9d997 100644
--- a/samples/SearchableDictionary/_index.html
+++ b/samples/SearchableDictionary/_index.html
@@ -1,21 +1,32 @@
<p>A sample application that demonstrates Android's search framework.</p>
-<p>This application includes a dictionary of words. By invoking a search inside the app
-(via the device search button or Menu > Search), a local search will be performed
+<p>This application includes a dictionary of words. By invoking the Android search dialog inside the
+app (via the device search button or Menu > Search), you can perform a search
across the dictionary. As you type, suggestions will appear, which you can select
-to view the full definition. You can also execute the search to view all word definitions
+to view the complete definition. You can also execute the search to view all word definitions
that match the entered text.</p>
<p>The application also grants content provider privileges to
Quick Search Box, Android's system-wide search tool. This means that the
dictionary definitions can be offered as search suggestions outside of the application,
when text is entered into Quick Search Box.</p>
-
+
+<p>The code in this application demonstrates how to:</p>
+<ul>
+ <li>Implement a search interface using Android's search framework</li>
+ <li>Provide custom search suggestions and offer them in Quick Search Box</li>
+ <li>Create an FTS3 table in SQLite and perform full-text search</li>
+ <li>Create a content provider to perform all searches and queries to the dictionary</li>
+ <li>Use <a
+href="../../../reference/android/widget/SimpleCursorAdapter.html">SimpleCursorAdapter</a> to bind
+data from a Cursor to a ListView.</li>
+</ul>
+
<p>See also:</p>
<ul>
- <li><a href="../../../reference/android/app/SearchManager.html">SearchManager</a></li>
- <li><a href="../../../reference/android/content/ContentProvider.html">ContentProvider</a></li>
+ <li><a href="../../../guide/topics/search/index.html">Search Developer Guide</a></li>
</ul>
+
<img src="../images/SearchableDictionary1.png" />
<img src="../images/SearchableDictionary2.png" />
\ No newline at end of file
diff --git a/samples/SearchableDictionary/res/drawable-hdpi/ic_menu_search.png b/samples/SearchableDictionary/res/drawable-hdpi/ic_menu_search.png
new file mode 100644
index 0000000..f78234e
--- /dev/null
+++ b/samples/SearchableDictionary/res/drawable-hdpi/ic_menu_search.png
Binary files differ
diff --git a/samples/SearchableDictionary/res/drawable-mdpi/ic_menu_search.png b/samples/SearchableDictionary/res/drawable-mdpi/ic_menu_search.png
new file mode 100644
index 0000000..94446db
--- /dev/null
+++ b/samples/SearchableDictionary/res/drawable-mdpi/ic_menu_search.png
Binary files differ
diff --git a/samples/SearchableDictionary/res/layout/main.xml b/samples/SearchableDictionary/res/layout/main.xml
index 666416d..5a7e969 100644
--- a/samples/SearchableDictionary/res/layout/main.xml
+++ b/samples/SearchableDictionary/res/layout/main.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
-** Copyright 2009, The Android Open Source Project
+** Copyright 2010, 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.
@@ -16,22 +16,25 @@
** limitations under the License.
*/
-->
+<!-- Layout for SearchableActivity.
+ -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
<TextView
- android:id="@+id/textField"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/search_instructions"/>
-
+ android:id="@+id/text"
+ android:textColor="?android:textColorPrimary"
+ android:textSize="17dp"
+ android:text="@string/search_instructions"
+ android:background="@android:drawable/title_bar"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
<ListView
android:id="@+id/list"
- android:layout_width="match_parent"
- android:layout_height="0dip"
- android:layout_weight="1"/>
+ android:layout_width="fill_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
</LinearLayout>
diff --git a/samples/SearchableDictionary/res/layout/result.xml b/samples/SearchableDictionary/res/layout/result.xml
new file mode 100644
index 0000000..a0bbd59
--- /dev/null
+++ b/samples/SearchableDictionary/res/layout/result.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2010, 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.
+*/
+-->
+<!-- Layout for list items in the search results.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:padding="5dp">
+ <TextView
+ android:id="@+id/word"
+ style="@android:style/TextAppearance.Large"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/definition"
+ style="@android:style/TextAppearance.Small"
+ android:singleLine="true"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/samples/SearchableDictionary/res/layout/word.xml b/samples/SearchableDictionary/res/layout/word.xml
index 21a5ed4..8db8449 100644
--- a/samples/SearchableDictionary/res/layout/word.xml
+++ b/samples/SearchableDictionary/res/layout/word.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
-** Copyright 2009, The Android Open Source Project
+** Copyright 2010, 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.
@@ -16,24 +16,24 @@
** limitations under the License.
*/
-->
+<!-- Layout for WordActivity.
+ -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:padding="5dp">
<TextView
android:id="@+id/word"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textSize="30sp"
+ android:textSize="35dp"
+ android:textColor="?android:textColorPrimary"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/search_instructions"
- android:layout_marginLeft="10dip"
- android:layout_marginTop="5dip" />
+ android:layout_height="wrap_content" />
<TextView
android:id="@+id/definition"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/search_instructions"
- android:layout_marginLeft="10dip" />
+ android:textSize="18dp"
+ android:textColor="?android:textColorSecondary"
+ android:paddingTop="10dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
</LinearLayout>
diff --git a/samples/SearchableDictionary/res/menu/options_menu.xml b/samples/SearchableDictionary/res/menu/options_menu.xml
new file mode 100644
index 0000000..2aa7cf2
--- /dev/null
+++ b/samples/SearchableDictionary/res/menu/options_menu.xml
@@ -0,0 +1,24 @@
+<!--
+/*
+** Copyright 2010, 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.
+*/
+-->
+<!-- Options Menu for SearchableActivity and WordActivity.
+ -->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/search"
+ android:title="@string/menu_search"
+ android:icon="@drawable/ic_menu_search" />
+</menu>
diff --git a/samples/SearchableDictionary/res/raw/definitions.txt b/samples/SearchableDictionary/res/raw/definitions.txt
index e4e1cbd..703a5c9 100644
--- a/samples/SearchableDictionary/res/raw/definitions.txt
+++ b/samples/SearchableDictionary/res/raw/definitions.txt
@@ -56,6 +56,7 @@
analyst - n. someone who is skilled at analyzing data
analyze - v. break down into components or essential features
anchor - v. fix firmly and stably
+android - n. a robot designed to look and behave like a human being
annual - j. occurring or payable every year
anonymous - j. having no known name or identity or known source
antichrist - n. the adversary of Christ (or Christianity) mentioned in the New Testament
diff --git a/samples/SearchableDictionary/res/values/strings.xml b/samples/SearchableDictionary/res/values/strings.xml
index a39ba75..569e1d9 100644
--- a/samples/SearchableDictionary/res/values/strings.xml
+++ b/samples/SearchableDictionary/res/values/strings.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
-** Copyright 2009, The Android Open Source Project
+** Copyright 2010, 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.
@@ -16,14 +16,17 @@
** limitations under the License.
*/
-->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
<!-- The name of the application. -->
<string name="app_name">Searchable Dictionary</string>
- <!-- The label for the search results for this app (see searchable.xml for more details). -->
+ <!-- The label for use as a searchable item -->
<string name="search_label">Dictionary</string>
+ <!-- The hint text that appears in the search box. -->
+ <string name="search_hint">Search the dictionary</string>
+
<!-- The menu entry that invokes search. -->
<string name="menu_search">Search</string>
@@ -34,5 +37,12 @@
<string name="search_instructions">Press the search key to look up a word</string>
<!-- Shown above search results when we receive a search request. -->
- <string name="search_results">Search results for \'<xliff:g id="string">%s</xliff:g>\': </string>
+ <plurals name="search_results">
+ <item quantity="one">%d result for \"%s\": </item>
+ <item quantity="other">%d results for \"%s\": </item>
+ </plurals>
+
+ <!-- Search failure message. -->
+ <string name="no_results">No results found for \"%s\"</string>
+
</resources>
diff --git a/samples/SearchableDictionary/res/xml/searchable.xml b/samples/SearchableDictionary/res/xml/searchable.xml
index 1edb57c..1faa4e1 100644
--- a/samples/SearchableDictionary/res/xml/searchable.xml
+++ b/samples/SearchableDictionary/res/xml/searchable.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
-** Copyright 2009, The Android Open Source Project
+** Copyright 2010, 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.
@@ -17,24 +17,20 @@
*/
-->
-<!-- The attributes below configure how the search results will work:
- - the 'label' points to a short description used when searching within the application if
- 'badge mode' is used as specified with android:searchMode="useLabelAsBadge" (which it is not for
- this application).
- - the 'searchSettingsDescription' points to a string that will be displayed underneath the
- name of this application in the search settings to describe what content will be searched.
- - 'includeInGlobalSearch' will include this app's search suggestions in Quick Search Box.
- - 'searchSuggestAuthority' specifies the authority matching the authority of the
- "DictionaryProvider" specified in the manifest. This means the DictionaryProvider will be
- queried for search suggestions.
- - 'searchSuggestIntentAction' the default intent action used in the intent that is launched based
- on a user cilcking on a search suggestion. This saves us from manually having to fill in the
- SUGGEST_COLUMN_INTENT_ACTION column for each suggestion returned by the provider.
+<!-- The attributes below configure the Android search box appearance
+ and the search suggestions settings.
+ See the Developer Guide for more information
+ http://developer.android.com/guide/topics/search/
-->
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/search_label"
+ android:hint="@string/search_hint"
android:searchSettingsDescription="@string/settings_description"
+ android:searchSuggestAuthority="com.example.android.searchabledict.DictionaryProvider"
+ android:searchSuggestIntentAction="android.intent.action.VIEW"
+ android:searchSuggestIntentData="content://com.example.android.searchabledict.DictionaryProvider/dictionary"
+ android:searchSuggestSelection=" ?"
+ android:searchSuggestThreshold="1"
android:includeInGlobalSearch="true"
- android:searchSuggestAuthority="dictionary"
- android:searchSuggestIntentAction="android.intent.action.VIEW">
-</searchable>
+ >
+ </searchable>
diff --git a/samples/SearchableDictionary/src/com/example/android/searchabledict/Dictionary.java b/samples/SearchableDictionary/src/com/example/android/searchabledict/Dictionary.java
deleted file mode 100644
index 59e735b..0000000
--- a/samples/SearchableDictionary/src/com/example/android/searchabledict/Dictionary.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * 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.searchabledict;
-
-import android.content.res.Resources;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Contains logic to load the word of words and definitions and find a list of matching words
- * given a query. Everything is held in memory; this is not a robust way to serve lots of
- * words and is only for demo purposes.
- *
- * You may want to consider using an SQLite database. In practice, you'll want to make sure your
- * suggestion provider is as efficient as possible, as the system will be taxed while performing
- * searches across many sources for each keystroke the user enters into Quick Search Box.
- */
-public class Dictionary {
-
- public static class Word {
- public final String word;
- public final String definition;
-
- public Word(String word, String definition) {
- this.word = word;
- this.definition = definition;
- }
- }
-
- private static final Dictionary sInstance = new Dictionary();
-
- public static Dictionary getInstance() {
- return sInstance;
- }
-
- private final Map<String, List<Word>> mDict = new ConcurrentHashMap<String, List<Word>>();
-
- private Dictionary() {
- }
-
- private boolean mLoaded = false;
-
- /**
- * Loads the words and definitions if they haven't been loaded already.
- *
- * @param resources Used to load the file containing the words and definitions.
- */
- public synchronized void ensureLoaded(final Resources resources) {
- if (mLoaded) return;
-
- new Thread(new Runnable() {
- public void run() {
- try {
- loadWords(resources);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- }).start();
- }
-
- private synchronized void loadWords(Resources resources) throws IOException {
- if (mLoaded) return;
-
- Log.d("dict", "loading words");
- InputStream inputStream = resources.openRawResource(R.raw.definitions);
- BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
-
- try {
- String line;
- while((line = reader.readLine()) != null) {
- String[] strings = TextUtils.split(line, "-");
- if (strings.length < 2) continue;
- addWord(strings[0].trim(), strings[1].trim());
- }
- } finally {
- reader.close();
- }
- mLoaded = true;
- }
-
-
- public List<Word> getMatches(String query) {
- List<Word> list = mDict.get(query);
- return list == null ? Collections.EMPTY_LIST : list;
- }
-
- private void addWord(String word, String definition) {
- final Word theWord = new Word(word, definition);
-
- final int len = word.length();
- for (int i = 0; i < len; i++) {
- final String prefix = word.substring(0, len - i);
- addMatch(prefix, theWord);
- }
- }
-
- private void addMatch(String query, Word word) {
- List<Word> matches = mDict.get(query);
- if (matches == null) {
- matches = new ArrayList<Word>();
- mDict.put(query, matches);
- }
- matches.add(word);
- }
-}
diff --git a/samples/SearchableDictionary/src/com/example/android/searchabledict/DictionaryDatabase.java b/samples/SearchableDictionary/src/com/example/android/searchabledict/DictionaryDatabase.java
new file mode 100644
index 0000000..0f854c3
--- /dev/null
+++ b/samples/SearchableDictionary/src/com/example/android/searchabledict/DictionaryDatabase.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2010 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.searchabledict;
+
+import android.app.SearchManager;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.provider.BaseColumns;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+
+/**
+ * Contains logic to return specific words from the dictionary, and
+ * load the dictionary table when it needs to be created.
+ */
+public class DictionaryDatabase {
+ private static final String TAG = "DictionaryDatabase";
+
+ //The columns we'll include in the dictionary table
+ public static final String KEY_WORD = SearchManager.SUGGEST_COLUMN_TEXT_1;
+ public static final String KEY_DEFINITION = SearchManager.SUGGEST_COLUMN_TEXT_2;
+
+ private static final String DATABASE_NAME = "dictionary";
+ private static final String FTS_VIRTUAL_TABLE = "FTSdictionary";
+ private static final int DATABASE_VERSION = 2;
+
+ private final DictionaryOpenHelper mDatabaseOpenHelper;
+ private static final HashMap<String,String> mColumnMap = buildColumnMap();
+
+ /**
+ * Constructor
+ * @param context The Context within which to work, used to create the DB
+ */
+ public DictionaryDatabase(Context context) {
+ mDatabaseOpenHelper = new DictionaryOpenHelper(context);
+ }
+
+ /**
+ * Builds a map for all columns that may be requested, which will be given to the
+ * SQLiteQueryBuilder. This is a good way to define aliases for column names, but must include
+ * all columns, even if the value is the key. This allows the ContentProvider to request
+ * columns w/o the need to know real column names and create the alias itself.
+ */
+ private static HashMap<String,String> buildColumnMap() {
+ HashMap<String,String> map = new HashMap<String,String>();
+ map.put(KEY_WORD, KEY_WORD);
+ map.put(KEY_DEFINITION, KEY_DEFINITION);
+ map.put(BaseColumns._ID, "rowid AS " +
+ BaseColumns._ID);
+ map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, "rowid AS " +
+ SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
+ map.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, "rowid AS " +
+ SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
+ return map;
+ }
+
+ /**
+ * Returns a Cursor positioned at the word specified by rowId
+ *
+ * @param rowId id of word to retrieve
+ * @param columns The columns to include, if null then all are included
+ * @return Cursor positioned to matching word, or null if not found.
+ */
+ public Cursor getWord(String rowId, String[] columns) {
+ String selection = "rowid = ?";
+ String[] selectionArgs = new String[] {rowId};
+
+ return query(selection, selectionArgs, columns);
+
+ /* This builds a query that looks like:
+ * SELECT <columns> FROM <table> WHERE rowid = <rowId>
+ */
+ }
+
+ /**
+ * Returns a Cursor over all words that match the given query
+ *
+ * @param query The string to search for
+ * @param columns The columns to include, if null then all are included
+ * @return Cursor over all words that match, or null if none found.
+ */
+ public Cursor getWordMatches(String query, String[] columns) {
+ String selection = KEY_WORD + " MATCH ?";
+ String[] selectionArgs = new String[] {query+"*"};
+
+ return query(selection, selectionArgs, columns);
+
+ /* This builds a query that looks like:
+ * SELECT <columns> FROM <table> WHERE <KEY_WORD> MATCH 'query*'
+ * which is an FTS3 search for the query text (plus a wildcard) inside the word column.
+ *
+ * - "rowid" is the unique id for all rows but we need this value for the "_id" column in
+ * order for the Adapters to work, so the columns need to make "_id" an alias for "rowid"
+ * - "rowid" also needs to be used by the SUGGEST_COLUMN_INTENT_DATA alias in order
+ * for suggestions to carry the proper intent data.
+ * These aliases are defined in the DictionaryProvider when queries are made.
+ * - This can be revised to also search the definition text with FTS3 by changing
+ * the selection clause to use FTS_VIRTUAL_TABLE instead of KEY_WORD (to search across
+ * the entire table, but sorting the relevance could be difficult.
+ */
+ }
+
+ /**
+ * Performs a database query.
+ * @param selection The selection clause
+ * @param selectionArgs Selection arguments for "?" components in the selection
+ * @param columns The columns to return
+ * @return A Cursor over all rows matching the query
+ */
+ private Cursor query(String selection, String[] selectionArgs, String[] columns) {
+ /* The SQLiteBuilder provides a map for all possible columns requested to
+ * actual columns in the database, creating a simple column alias mechanism
+ * by which the ContentProvider does not need to know the real column names
+ */
+ SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
+ builder.setTables(FTS_VIRTUAL_TABLE);
+ builder.setProjectionMap(mColumnMap);
+
+ Cursor cursor = builder.query(mDatabaseOpenHelper.getReadableDatabase(),
+ columns, selection, selectionArgs, null, null, null);
+
+ if (cursor == null) {
+ return null;
+ } else if (!cursor.moveToFirst()) {
+ cursor.close();
+ return null;
+ }
+ return cursor;
+ }
+
+
+ /**
+ * This creates/opens the database.
+ */
+ private static class DictionaryOpenHelper extends SQLiteOpenHelper {
+
+ private final Context mHelperContext;
+ private SQLiteDatabase mDatabase;
+
+ /* Note that FTS3 does not support column constraints and thus, you cannot
+ * declare a primary key. However, "rowid" is automatically used as a unique
+ * identifier, so when making requests, we will use "_id" as an alias for "rowid"
+ */
+ private static final String FTS_TABLE_CREATE =
+ "CREATE VIRTUAL TABLE " + FTS_VIRTUAL_TABLE +
+ " USING fts3 (" +
+ KEY_WORD + ", " +
+ KEY_DEFINITION + ");";
+
+ DictionaryOpenHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ mHelperContext = context;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ mDatabase = db;
+ mDatabase.execSQL(FTS_TABLE_CREATE);
+ loadDictionary();
+ }
+
+ /**
+ * Starts a thread to load the database table with words
+ */
+ private void loadDictionary() {
+ new Thread(new Runnable() {
+ public void run() {
+ try {
+ loadWords();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }).start();
+ }
+
+ private void loadWords() throws IOException {
+ Log.d(TAG, "Loading words...");
+ final Resources resources = mHelperContext.getResources();
+ InputStream inputStream = resources.openRawResource(R.raw.definitions);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+
+ try {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ String[] strings = TextUtils.split(line, "-");
+ if (strings.length < 2) continue;
+ long id = addWord(strings[0].trim(), strings[1].trim());
+ if (id < 0) {
+ Log.e(TAG, "unable to add word: " + strings[0].trim());
+ }
+ }
+ } finally {
+ reader.close();
+ }
+ Log.d(TAG, "DONE loading words.");
+ }
+
+ /**
+ * Add a word to the dictionary.
+ * @return rowId or -1 if failed
+ */
+ public long addWord(String word, String definition) {
+ ContentValues initialValues = new ContentValues();
+ initialValues.put(KEY_WORD, word);
+ initialValues.put(KEY_DEFINITION, definition);
+
+ return mDatabase.insert(FTS_VIRTUAL_TABLE, null, initialValues);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ + newVersion + ", which will destroy all old data");
+ db.execSQL("DROP TABLE IF EXISTS " + FTS_VIRTUAL_TABLE);
+ onCreate(db);
+ }
+ }
+
+}
diff --git a/samples/SearchableDictionary/src/com/example/android/searchabledict/DictionaryProvider.java b/samples/SearchableDictionary/src/com/example/android/searchabledict/DictionaryProvider.java
index 586fddb..c510e92 100644
--- a/samples/SearchableDictionary/src/com/example/android/searchabledict/DictionaryProvider.java
+++ b/samples/SearchableDictionary/src/com/example/android/searchabledict/DictionaryProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2010 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.
@@ -18,144 +18,186 @@
import android.app.SearchManager;
import android.content.ContentProvider;
+import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.UriMatcher;
-import android.content.res.Resources;
import android.database.Cursor;
-import android.database.MatrixCursor;
import android.net.Uri;
-import android.text.TextUtils;
-
-import java.util.List;
+import android.provider.BaseColumns;
/**
- * Provides search suggestions for a list of words and their definitions.
+ * Provides access to the dictionary database.
*/
public class DictionaryProvider extends ContentProvider {
+ String TAG = "DictionaryProvider";
- public static String AUTHORITY = "dictionary";
+ public static String AUTHORITY = "com.example.android.searchabledict.DictionaryProvider";
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/dictionary");
- private static final int SEARCH_SUGGEST = 0;
- private static final int SHORTCUT_REFRESH = 1;
+ // MIME types used for searching words or looking up a single definition
+ public static final String WORDS_MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE +
+ "/vnd.example.android.searchabledict";
+ public static final String DEFINITION_MIME_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE +
+ "/vnd.example.android.searchabledict";
+
+ private DictionaryDatabase mDictionary;
+
+ // UriMatcher stuff
+ private static final int SEARCH_WORDS = 0;
+ private static final int GET_WORD = 1;
+ private static final int SEARCH_SUGGEST = 2;
+ private static final int REFRESH_SHORTCUT = 3;
private static final UriMatcher sURIMatcher = buildUriMatcher();
/**
- * The columns we'll include in our search suggestions. There are others that could be used
- * to further customize the suggestions, see the docs in {@link SearchManager} for the details
- * on additional columns that are supported.
- */
- private static final String[] COLUMNS = {
- "_id", // must include this column
- SearchManager.SUGGEST_COLUMN_TEXT_1,
- SearchManager.SUGGEST_COLUMN_TEXT_2,
- SearchManager.SUGGEST_COLUMN_INTENT_DATA,
- };
-
-
- /**
- * Sets up a uri matcher for search suggestion and shortcut refresh queries.
+ * Builds up a UriMatcher for search suggestion and shortcut refresh queries.
*/
private static UriMatcher buildUriMatcher() {
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
+ // to get definitions...
+ matcher.addURI(AUTHORITY, "dictionary", SEARCH_WORDS);
+ matcher.addURI(AUTHORITY, "dictionary/#", GET_WORD);
+ // to get suggestions...
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
- matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT, SHORTCUT_REFRESH);
- matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*", SHORTCUT_REFRESH);
+
+ /* The following are unused in this implementation, but if we include
+ * {@link SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} as a column in our suggestions table, we
+ * could expect to receive refresh queries when a shortcutted suggestion is displayed in
+ * Quick Search Box, in which case, the following Uris would be provided and we
+ * would return a cursor with a single item representing the refreshed suggestion data.
+ */
+ matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT, REFRESH_SHORTCUT);
+ matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*", REFRESH_SHORTCUT);
return matcher;
}
@Override
public boolean onCreate() {
- Resources resources = getContext().getResources();
- Dictionary.getInstance().ensureLoaded(resources);
+ mDictionary = new DictionaryDatabase(getContext());
return true;
}
+ /**
+ * Handles all the dictionary searches and suggestion queries from the Search Manager.
+ * When requesting a specific word, the uri alone is required.
+ * When searching all of the dictionary for matches, the selectionArgs argument must carry
+ * the search query as the first element.
+ * All other arguments are ignored.
+ */
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- if (!TextUtils.isEmpty(selection)) {
- throw new IllegalArgumentException("selection not allowed for " + uri);
- }
- if (selectionArgs != null && selectionArgs.length != 0) {
- throw new IllegalArgumentException("selectionArgs not allowed for " + uri);
- }
- if (!TextUtils.isEmpty(sortOrder)) {
- throw new IllegalArgumentException("sortOrder not allowed for " + uri);
- }
+ String sortOrder) {
+
+ // Use the UriMatcher to see what kind of query we have and format the db query accordingly
switch (sURIMatcher.match(uri)) {
case SEARCH_SUGGEST:
- String query = null;
- if (uri.getPathSegments().size() > 1) {
- query = uri.getLastPathSegment().toLowerCase();
+ if (selectionArgs == null) {
+ throw new IllegalArgumentException(
+ "selectionArgs must be provided for the Uri: " + uri);
}
- return getSuggestions(query, projection);
- case SHORTCUT_REFRESH:
- String shortcutId = null;
- if (uri.getPathSegments().size() > 1) {
- shortcutId = uri.getLastPathSegment();
+ return getSuggestions(selectionArgs[0]);
+ case SEARCH_WORDS:
+ if (selectionArgs == null) {
+ throw new IllegalArgumentException(
+ "selectionArgs must be provided for the Uri: " + uri);
}
- return refreshShortcut(shortcutId, projection);
+ return search(selectionArgs[0]);
+ case GET_WORD:
+ return getWord(uri);
+ case REFRESH_SHORTCUT:
+ return refreshShortcut(uri);
default:
- throw new IllegalArgumentException("Unknown URL " + uri);
+ throw new IllegalArgumentException("Unknown Uri: " + uri);
}
}
- private Cursor getSuggestions(String query, String[] projection) {
- String processedQuery = query == null ? "" : query.toLowerCase();
- List<Dictionary.Word> words = Dictionary.getInstance().getMatches(processedQuery);
+ private Cursor getSuggestions(String query) {
+ query = query.toLowerCase();
+ String[] columns = new String[] {
+ BaseColumns._ID,
+ DictionaryDatabase.KEY_WORD,
+ DictionaryDatabase.KEY_DEFINITION,
+ /* SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
+ (only if you want to refresh shortcuts) */
+ SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID};
- MatrixCursor cursor = new MatrixCursor(COLUMNS);
- long id = 0;
- for (Dictionary.Word word : words) {
- cursor.addRow(columnValuesOfWord(id++, word));
- }
-
- return cursor;
+ return mDictionary.getWordMatches(query, columns);
}
- private Object[] columnValuesOfWord(long id, Dictionary.Word word) {
- return new Object[] {
- id, // _id
- word.word, // text1
- word.definition, // text2
- word.word, // intent_data (included when clicking on item)
- };
+ private Cursor search(String query) {
+ query = query.toLowerCase();
+ String[] columns = new String[] {
+ BaseColumns._ID,
+ DictionaryDatabase.KEY_WORD,
+ DictionaryDatabase.KEY_DEFINITION};
+
+ return mDictionary.getWordMatches(query, columns);
+ }
+
+ private Cursor getWord(Uri uri) {
+ String rowId = uri.getLastPathSegment();
+ String[] columns = new String[] {
+ DictionaryDatabase.KEY_WORD,
+ DictionaryDatabase.KEY_DEFINITION};
+
+ return mDictionary.getWord(rowId, columns);
+ }
+
+ private Cursor refreshShortcut(Uri uri) {
+ /* This won't be called with the current implementation, but if we include
+ * {@link SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} as a column in our suggestions table, we
+ * could expect to receive refresh queries when a shortcutted suggestion is displayed in
+ * Quick Search Box. In which case, this method will query the table for the specific
+ * word, using the given item Uri and provide all the columns originally provided with the
+ * suggestion query.
+ */
+ String rowId = uri.getLastPathSegment();
+ String[] columns = new String[] {
+ BaseColumns._ID,
+ DictionaryDatabase.KEY_WORD,
+ DictionaryDatabase.KEY_DEFINITION,
+ SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
+ SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID};
+
+ return mDictionary.getWord(rowId, columns);
}
/**
- * Note: this is unused as is, but if we included
- * {@link SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} as a column in our results, we
- * could expect to receive refresh queries on this uri for the id provided, in which case we
- * would return a cursor with a single item representing the refreshed suggestion data.
+ * This method is required in order to query the supported types.
+ * It's also useful in our own query() method to determine the type of Uri received.
*/
- private Cursor refreshShortcut(String shortcutId, String[] projection) {
- return null;
- }
-
- /**
- * All queries for this provider are for the search suggestion and shortcut refresh mime type.
- */
+ @Override
public String getType(Uri uri) {
switch (sURIMatcher.match(uri)) {
+ case SEARCH_WORDS:
+ return WORDS_MIME_TYPE;
+ case GET_WORD:
+ return DEFINITION_MIME_TYPE;
case SEARCH_SUGGEST:
return SearchManager.SUGGEST_MIME_TYPE;
- case SHORTCUT_REFRESH:
+ case REFRESH_SHORTCUT:
return SearchManager.SHORTCUT_MIME_TYPE;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
}
+ // Other required implementations...
+
+ @Override
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException();
}
+ @Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
+ @Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
+
}
diff --git a/samples/SearchableDictionary/src/com/example/android/searchabledict/SearchableDictionary.java b/samples/SearchableDictionary/src/com/example/android/searchabledict/SearchableDictionary.java
index 4d27470..ef938f8 100644
--- a/samples/SearchableDictionary/src/com/example/android/searchabledict/SearchableDictionary.java
+++ b/samples/SearchableDictionary/src/com/example/android/searchabledict/SearchableDictionary.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2010 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.
@@ -18,138 +18,114 @@
import android.app.Activity;
import android.app.SearchManager;
-import android.content.Context;
import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.View;
import android.widget.AdapterView;
-import android.widget.BaseAdapter;
import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
-import android.widget.TwoLineListItem;
-
-import java.util.List;
+import android.widget.AdapterView.OnItemClickListener;
/**
- * The main activity for the dictionary. Also displays search results triggered by the search
- * dialog.
+ * The main activity for the dictionary.
+ * Displays search results triggered by the search dialog and handles
+ * actions from search suggestions.
*/
public class SearchableDictionary extends Activity {
- private static final int MENU_SEARCH = 1;
-
private TextView mTextView;
- private ListView mList;
+ private ListView mListView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ mTextView = (TextView) findViewById(R.id.text);
+ mListView = (ListView) findViewById(R.id.list);
Intent intent = getIntent();
- setContentView(R.layout.main);
- mTextView = (TextView) findViewById(R.id.textField);
- mList = (ListView) findViewById(R.id.list);
-
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
- // from click on search results
- Dictionary.getInstance().ensureLoaded(getResources());
- String word = intent.getDataString();
- Dictionary.Word theWord = Dictionary.getInstance().getMatches(word).get(0);
- launchWord(theWord);
+ // handles a click on a search suggestion; launches activity to show word
+ Intent wordIntent = new Intent(this, WordActivity.class);
+ wordIntent.setData(intent.getData());
+ startActivity(wordIntent);
finish();
} else if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
+ // handles a search query
String query = intent.getStringExtra(SearchManager.QUERY);
- mTextView.setText(getString(R.string.search_results, query));
- WordAdapter wordAdapter = new WordAdapter(Dictionary.getInstance().getMatches(query));
- mList.setAdapter(wordAdapter);
- mList.setOnItemClickListener(wordAdapter);
+ showResults(query);
}
+ }
- Log.d("dict", intent.toString());
- if (intent.getExtras() != null) {
- Log.d("dict", intent.getExtras().keySet().toString());
+ /**
+ * Searches the dictionary and displays results for the given query.
+ * @param query The search query
+ */
+ private void showResults(String query) {
+
+ Cursor cursor = managedQuery(DictionaryProvider.CONTENT_URI, null, null,
+ new String[] {query}, null);
+
+ if (cursor == null) {
+ // There are no results
+ mTextView.setText(getString(R.string.no_results, new Object[] {query}));
+ } else {
+ // Display the number of results
+ int count = cursor.getCount();
+ String countString = getResources().getQuantityString(R.plurals.search_results,
+ count, new Object[] {count, query});
+ mTextView.setText(countString);
+
+ // Specify the columns we want to display in the result
+ String[] from = new String[] { DictionaryDatabase.KEY_WORD,
+ DictionaryDatabase.KEY_DEFINITION };
+
+ // Specify the corresponding layout elements where we want the columns to go
+ int[] to = new int[] { R.id.word,
+ R.id.definition };
+
+ // Create a simple cursor adapter for the definitions and apply them to the ListView
+ SimpleCursorAdapter words = new SimpleCursorAdapter(this,
+ R.layout.result, cursor, from, to);
+ mListView.setAdapter(words);
+
+ // Define the on-click listener for the list items
+ mListView.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ // Build the Intent used to open WordActivity with a specific word Uri
+ Intent wordIntent = new Intent(getApplicationContext(), WordActivity.class);
+ Uri data = Uri.withAppendedPath(DictionaryProvider.CONTENT_URI,
+ String.valueOf(id));
+ wordIntent.setData(data);
+ startActivity(wordIntent);
+ }
+ });
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(0, MENU_SEARCH, 0, R.string.menu_search)
- .setIcon(android.R.drawable.ic_search_category_default)
- .setAlphabeticShortcut(SearchManager.MENU_KEY);
-
- return super.onCreateOptionsMenu(menu);
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.options_menu, menu);
+ return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
- case MENU_SEARCH:
+ case R.id.search:
onSearchRequested();
return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- private void launchWord(Dictionary.Word theWord) {
- Intent next = new Intent();
- next.setClass(this, WordActivity.class);
- next.putExtra("word", theWord.word);
- next.putExtra("definition", theWord.definition);
- startActivity(next);
- }
-
- class WordAdapter extends BaseAdapter implements AdapterView.OnItemClickListener {
-
- private final List<Dictionary.Word> mWords;
- private final LayoutInflater mInflater;
-
- public WordAdapter(List<Dictionary.Word> words) {
- mWords = words;
- mInflater = (LayoutInflater) SearchableDictionary.this.getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- }
-
- public int getCount() {
- return mWords.size();
- }
-
- public Object getItem(int position) {
- return position;
- }
-
- public long getItemId(int position) {
- return position;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- TwoLineListItem view = (convertView != null) ? (TwoLineListItem) convertView :
- createView(parent);
- bindView(view, mWords.get(position));
- return view;
- }
-
- private TwoLineListItem createView(ViewGroup parent) {
- TwoLineListItem item = (TwoLineListItem) mInflater.inflate(
- android.R.layout.simple_list_item_2, parent, false);
- item.getText2().setSingleLine();
- item.getText2().setEllipsize(TextUtils.TruncateAt.END);
- return item;
- }
-
- private void bindView(TwoLineListItem view, Dictionary.Word word) {
- view.getText1().setText(word.word);
- view.getText2().setText(word.definition);
- }
-
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- launchWord(mWords.get(position));
+ default:
+ return false;
}
}
}
diff --git a/samples/SearchableDictionary/src/com/example/android/searchabledict/WordActivity.java b/samples/SearchableDictionary/src/com/example/android/searchabledict/WordActivity.java
index 1c4b8b4..00dc270 100644
--- a/samples/SearchableDictionary/src/com/example/android/searchabledict/WordActivity.java
+++ b/samples/SearchableDictionary/src/com/example/android/searchabledict/WordActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2010 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.
@@ -17,33 +17,58 @@
package com.example.android.searchabledict;
import android.app.Activity;
+import android.database.Cursor;
+import android.net.Uri;
import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.widget.TextView;
-import android.content.Intent;
/**
* Displays a word and its definition.
*/
public class WordActivity extends Activity {
- private TextView mWord;
- private TextView mDefinition;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
setContentView(R.layout.word);
- mWord = (TextView) findViewById(R.id.word);
- mDefinition = (TextView) findViewById(R.id.definition);
+ Uri uri = getIntent().getData();
+ Cursor cursor = managedQuery(uri, null, null, null, null);
- Intent intent = getIntent();
+ if (cursor == null) {
+ finish();
+ } else {
+ cursor.moveToFirst();
- String word = intent.getStringExtra("word");
- String definition = intent.getStringExtra("definition");
+ TextView word = (TextView) findViewById(R.id.word);
+ TextView definition = (TextView) findViewById(R.id.definition);
- mWord.setText(word);
- mDefinition.setText(definition);
+ int wIndex = cursor.getColumnIndexOrThrow(DictionaryDatabase.KEY_WORD);
+ int dIndex = cursor.getColumnIndexOrThrow(DictionaryDatabase.KEY_DEFINITION);
+
+ word.setText(cursor.getString(wIndex));
+ definition.setText(cursor.getString(dIndex));
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.options_menu, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.search:
+ onSearchRequested();
+ return true;
+ default:
+ return false;
+ }
}
}
diff --git a/samples/XmlAdapters/Android.mk b/samples/XmlAdapters/Android.mk
new file mode 100644
index 0000000..24a3327
--- /dev/null
+++ b/samples/XmlAdapters/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 := XmlAdaptersSample
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/samples/XmlAdapters/AndroidManifest.xml b/samples/XmlAdapters/AndroidManifest.xml
new file mode 100644
index 0000000..af040ad
--- /dev/null
+++ b/samples/XmlAdapters/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.xmladapters">
+
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+ <application android:label="@string/app_name">
+ <activity android:name="ContactsListActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/samples/XmlAdapters/res/drawable-hdpi/ic_contact_picture.png b/samples/XmlAdapters/res/drawable-hdpi/ic_contact_picture.png
new file mode 100644
index 0000000..a60565a
--- /dev/null
+++ b/samples/XmlAdapters/res/drawable-hdpi/ic_contact_picture.png
Binary files differ
diff --git a/samples/XmlAdapters/res/layout/contact_item.xml b/samples/XmlAdapters/res/layout/contact_item.xml
new file mode 100644
index 0000000..6fcb109
--- /dev/null
+++ b/samples/XmlAdapters/res/layout/contact_item.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeight">
+
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="0px"
+ android:layout_weight="1.0"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:gravity="center_vertical"
+ android:drawablePadding="6dip"
+ android:paddingLeft="6dip"
+ android:paddingRight="6dip" />
+
+ <ImageView
+ android:id="@+id/star"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/samples/XmlAdapters/res/layout/contacts_list.xml b/samples/XmlAdapters/res/layout/contacts_list.xml
new file mode 100644
index 0000000..973fe4b
--- /dev/null
+++ b/samples/XmlAdapters/res/layout/contacts_list.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <ListView
+ android:id="@android:id/list"
+ android:adapter="@xml/contacts"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <TextView android:id="@android:id/empty"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:text="@string/no_contacts"
+ android:visibility="gone" />
+
+</merge>
diff --git a/samples/XmlAdapters/res/values/strings.xml b/samples/XmlAdapters/res/values/strings.xml
new file mode 100644
index 0000000..3a0b5fe
--- /dev/null
+++ b/samples/XmlAdapters/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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" BASI
+ 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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">Xml Contacts Adapter</string>
+ <string name="no_contacts">No contacts available</string>
+</resources>
diff --git a/samples/XmlAdapters/res/xml/contacts.xml b/samples/XmlAdapters/res/xml/contacts.xml
new file mode 100644
index 0000000..b33d948
--- /dev/null
+++ b/samples/XmlAdapters/res/xml/contacts.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<cursor-adapter xmlns:android="http://schemas.android.com/apk/res/android"
+ android:uri="content://com.android.contacts/contacts"
+ android:selection="has_phone_number=1"
+ android:layout="@layout/contact_item">
+
+ <bind android:from="display_name" android:to="@id/name" android:as="string" />
+ <bind android:from="starred" android:to="@id/star" android:as="drawable">
+ <map android:fromValue="0" android:toValue="@android:drawable/star_big_off" />
+ <map android:fromValue="1" android:toValue="@android:drawable/star_big_on" />
+ </bind>
+ <bind android:from="_id" android:to="@id/name"
+ android:as="com.example.android.xmladapters.ContactPhotoBinder" />
+
+</cursor-adapter>
diff --git a/samples/XmlAdapters/src/com/example/android/xmladapters/ContactPhotoBinder.java b/samples/XmlAdapters/src/com/example/android/xmladapters/ContactPhotoBinder.java
new file mode 100644
index 0000000..947eb2a
--- /dev/null
+++ b/samples/XmlAdapters/src/com/example/android/xmladapters/ContactPhotoBinder.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2010 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.xmladapters;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.view.View;
+import android.widget.Adapters;
+import android.widget.TextView;
+
+import java.io.InputStream;
+import java.util.HashMap;
+
+/**
+ * This custom cursor binder is used by the adapter defined in res/xml to
+ * bin contacts photos to their respective list item. This binder simply
+ * queries a contact's photo based on the contact's id and sets the
+ * photo as a compound drawable on the TextView used to display the contact's
+ * name.
+ */
+public class ContactPhotoBinder extends Adapters.CursorBinder {
+ private static final int PHOTO_SIZE_DIP = 54;
+
+ private final Drawable mDefault;
+ private final HashMap<Long, Drawable> mCache;
+ private final Resources mResources;
+ private final int mPhotoSize;
+
+ public ContactPhotoBinder(Context context, Adapters.CursorTransformation transformation) {
+ super(context, transformation);
+
+ mResources = mContext.getResources();
+ // Default picture used when a contact does not provide one
+ mDefault = mResources.getDrawable(R.drawable.ic_contact_picture);
+ // Cache used to avoid requerying contacts photos every time
+ mCache = new HashMap<Long, Drawable>();
+ // Compute the size of the photo based on the display's density
+ mPhotoSize = (int) (PHOTO_SIZE_DIP * mResources.getDisplayMetrics().density + 0.5f);
+ }
+
+ @Override
+ public boolean bind(View view, Cursor cursor, int columnIndex) {
+ final long id = cursor.getLong(columnIndex);
+
+ // First check whether we have already cached the contact's photo
+ Drawable d = mCache.get(id);
+
+ if (d == null) {
+ // If the photo wasn't in the cache, ask the contacts provider for
+ // an input stream we can use to load the photo
+ Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id);
+ InputStream stream = ContactsContract.Contacts.openContactPhotoInputStream(
+ mContext.getContentResolver(), uri);
+
+ // Creates the drawable for the contact's photo or use our fallback drawable
+ if (stream != null) {
+ Bitmap bitmap = BitmapFactory.decodeStream(stream);
+ d = new BitmapDrawable(mResources, bitmap);
+ } else {
+ d = mDefault;
+ }
+
+ d.setBounds(0, 0, mPhotoSize, mPhotoSize);
+ ((TextView) view).setCompoundDrawables(d, null, null, null);
+
+ // Remember the photo associated with this contact
+ mCache.put(id, d);
+ } else {
+ ((TextView) view).setCompoundDrawables(d, null, null, null);
+ }
+
+ return true;
+ }
+}
diff --git a/samples/XmlAdapters/src/com/example/android/xmladapters/ContactsListActivity.java b/samples/XmlAdapters/src/com/example/android/xmladapters/ContactsListActivity.java
new file mode 100644
index 0000000..bf5ab58
--- /dev/null
+++ b/samples/XmlAdapters/src/com/example/android/xmladapters/ContactsListActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 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.xmladapters;
+
+import android.app.ListActivity;
+import android.os.Bundle;
+
+/**
+ * This activity demonstrates how to create a complex UI using a ListView
+ * and an adapter defined in XML.
+ *
+ * The following activity shows a list of contacts, their starred status
+ * and their photos, using the adapter defined in res/xml.
+ */
+public class ContactsListActivity extends ListActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.contacts_list);
+ }
+}
diff --git a/simulator/app/MessageStream.cpp b/simulator/app/MessageStream.cpp
index 2397c63..c52e7c4 100644
--- a/simulator/app/MessageStream.cpp
+++ b/simulator/app/MessageStream.cpp
@@ -8,6 +8,7 @@
#include "utils/Log.h"
+#include <stdint.h>
#include <string.h>
#include <assert.h>
@@ -338,7 +339,7 @@
* and capability flags.
*/
if (initiateHello) {
- long data = kHelloMsg;
+ int32_t data = kHelloMsg;
Message msg;
/* send hello */
@@ -357,14 +358,15 @@
return false;
}
- const long* pAck;
- pAck = (const long*) msg.getData();
+ const int32_t* pAck;
+ pAck = (const int32_t*) msg.getData();
if (pAck == NULL || *pAck != kHelloAckMsg) {
- LOG(LOG_WARN, "", "hello ack was bad\n");
+ LOG(LOG_WARN, "", "hello ack was bad (%08x vs %08x)\n",
+ *pAck, kHelloAckMsg);
return false;
}
} else {
- long data = kHelloAckMsg;
+ int32_t data = kHelloAckMsg;
Message msg;
LOG(LOG_DEBUG, "", "waiting for hello from peer\n");
@@ -375,8 +377,8 @@
return false;
}
- const long* pAck;
- pAck = (const long*) msg.getData();
+ const int32_t* pAck;
+ pAck = (const int32_t*) msg.getData();
if (pAck == NULL || *pAck != kHelloMsg) {
LOG(LOG_WARN, "", "hello was bad\n");
return false;
diff --git a/testrunner/test_defs.xml b/testrunner/test_defs.xml
index 4cc91c8..8c6a0fe 100644
--- a/testrunner/test_defs.xml
+++ b/testrunner/test_defs.xml
@@ -380,7 +380,8 @@
<test name="browser"
build_path="packages/apps/Browser"
package="com.android.browser.tests"
- coverage_target="Browser" />
+ coverage_target="Browser"
+ continuous="true" />
<test name="calendar"
build_path="packages/apps/Calendar"