/* * 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); } } }