diff --git a/samples/SearchableDictionary/AndroidManifest.xml b/samples/SearchableDictionary/AndroidManifest.xml
index 93cd47beb..ec82449e3 100644
--- a/samples/SearchableDictionary/AndroidManifest.xml
+++ b/samples/SearchableDictionary/AndroidManifest.xml
@@ -1,7 +1,7 @@
+
+ android:theme="@android:style/Theme.NoTitleBar">
@@ -37,22 +36,26 @@
-
+
+ android:resource="@xml/searchable" />
+
+ android:theme="@android:style/Theme.NoTitleBar" />
-
+
+
+
+
diff --git a/samples/SearchableDictionary/_index.html b/samples/SearchableDictionary/_index.html
index de3345e3f..6f9d99762 100644
--- a/samples/SearchableDictionary/_index.html
+++ b/samples/SearchableDictionary/_index.html
@@ -1,21 +1,32 @@
A sample application that demonstrates Android's search framework.
-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
+
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.
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.
-
+
+The code in this application demonstrates how to:
+
+ - Implement a search interface using Android's search framework
+ - Provide custom search suggestions and offer them in Quick Search Box
+ - Create an FTS3 table in SQLite and perform full-text search
+ - Create a content provider to perform all searches and queries to the dictionary
+ - Use SimpleCursorAdapter to bind
+data from a Cursor to a ListView.
+
+
See also:
+
\ 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 000000000..f78234e47
Binary files /dev/null and b/samples/SearchableDictionary/res/drawable-hdpi/ic_menu_search.png 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 000000000..94446db97
Binary files /dev/null and b/samples/SearchableDictionary/res/drawable-mdpi/ic_menu_search.png differ
diff --git a/samples/SearchableDictionary/res/layout/main.xml b/samples/SearchableDictionary/res/layout/main.xml
index 666416d4d..5a7e969c6 100644
--- a/samples/SearchableDictionary/res/layout/main.xml
+++ b/samples/SearchableDictionary/res/layout/main.xml
@@ -1,7 +1,7 @@
+
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
-
+ 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" />
+ android:layout_width="fill_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
diff --git a/samples/SearchableDictionary/res/layout/result.xml b/samples/SearchableDictionary/res/layout/result.xml
new file mode 100644
index 000000000..a0bbd5913
--- /dev/null
+++ b/samples/SearchableDictionary/res/layout/result.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
diff --git a/samples/SearchableDictionary/res/layout/word.xml b/samples/SearchableDictionary/res/layout/word.xml
index 21a5ed4cb..8db844975 100644
--- a/samples/SearchableDictionary/res/layout/word.xml
+++ b/samples/SearchableDictionary/res/layout/word.xml
@@ -1,7 +1,7 @@
+
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:padding="5dp">
+ android:layout_height="wrap_content" />
+ android:textSize="18dp"
+ android:textColor="?android:textColorSecondary"
+ android:paddingTop="10dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
diff --git a/samples/SearchableDictionary/res/menu/options_menu.xml b/samples/SearchableDictionary/res/menu/options_menu.xml
new file mode 100644
index 000000000..2aa7cf211
--- /dev/null
+++ b/samples/SearchableDictionary/res/menu/options_menu.xml
@@ -0,0 +1,24 @@
+
+
+
diff --git a/samples/SearchableDictionary/res/raw/definitions.txt b/samples/SearchableDictionary/res/raw/definitions.txt
index e4e1cbda6..703a5c945 100644
--- a/samples/SearchableDictionary/res/raw/definitions.txt
+++ b/samples/SearchableDictionary/res/raw/definitions.txt
@@ -56,6 +56,7 @@ analogy - n. drawing a comparison in order to show a similarity in some respect
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 a39ba75b9..569e1d92c 100644
--- a/samples/SearchableDictionary/res/values/strings.xml
+++ b/samples/SearchableDictionary/res/values/strings.xml
@@ -1,7 +1,7 @@
-
+
Searchable Dictionary
-
+
Dictionary
+
+ Search the dictionary
+
Search
@@ -34,5 +37,12 @@
Press the search key to look up a word
- Search results for \'%s\':
+
+ - %d result for \"%s\":
+ - %d results for \"%s\":
+
+
+
+ No results found for \"%s\"
+
diff --git a/samples/SearchableDictionary/res/xml/searchable.xml b/samples/SearchableDictionary/res/xml/searchable.xml
index 1edb57c94..1faa4e189 100644
--- a/samples/SearchableDictionary/res/xml/searchable.xml
+++ b/samples/SearchableDictionary/res/xml/searchable.xml
@@ -1,7 +1,7 @@
-
-
+ >
+
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 59e735b3d..000000000
--- 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> mDict = new ConcurrentHashMap>();
-
- 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 getMatches(String query) {
- List 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 matches = mDict.get(query);
- if (matches == null) {
- matches = new ArrayList();
- 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 000000000..0f854c373
--- /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 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 buildColumnMap() {
+ HashMap map = new HashMap();
+ 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 FROM WHERE 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 FROM WHERE 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 586fddb4a..c510e92d9 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 @@ package com.example.android.searchabledict;
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 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.
- */
- private Cursor refreshShortcut(String shortcutId, String[] projection) {
- return null;
- }
-
- /**
- * All queries for this provider are for the search suggestion and shortcut refresh mime type.
+ * 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.
*/
+ @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 4d2747025..ef938f8a6 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 @@ package com.example.android.searchabledict;
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 mWords;
- private final LayoutInflater mInflater;
-
- public WordAdapter(List 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 1c4b8b4aa..00dc270c4 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;
+ }
}
}