diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/com/android/settings/SettingsActivity.java | 75 | ||||
-rw-r--r-- | src/com/android/settings/dashboard/DashboardSummary.java | 259 | ||||
-rw-r--r-- | src/com/android/settings/indexer/Index.java | 379 | ||||
-rw-r--r-- | src/com/android/settings/indexer/IndexDatabaseHelper.java | 156 | ||||
-rw-r--r-- | src/com/android/settings/indexer/IndexableData.java | 32 |
5 files changed, 899 insertions, 2 deletions
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index fa6f01a..6b7bdc9 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -94,6 +94,8 @@ import com.android.settings.dashboard.DashboardSummary; import com.android.settings.deviceinfo.Memory; import com.android.settings.deviceinfo.UsbSettings; import com.android.settings.fuelgauge.PowerUsageSummary; +import com.android.settings.indexer.Index; +import com.android.settings.indexer.IndexableData; import com.android.settings.inputmethod.InputMethodAndLanguageSettings; import com.android.settings.inputmethod.KeyboardLayoutPickerFragment; import com.android.settings.inputmethod.SpellCheckersSettings; @@ -159,6 +161,8 @@ public class SettingsActivity extends Activity */ public static final String EXTRA_NO_HEADERS = ":settings:no_headers"; + public static final String BACK_STACK_PREFS = ":settings:prefs"; + // extras that allow any preference activity to be launched as part of a wizard // show Back and Next buttons? takes boolean parameter @@ -180,8 +184,6 @@ public class SettingsActivity extends Activity */ protected static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title"; - private static final String BACK_STACK_PREFS = ":settings:prefs"; - private static final String META_DATA_KEY_HEADER_ID = "com.android.settings.TOP_LEVEL_HEADER_ID"; @@ -340,6 +342,71 @@ public class SettingsActivity extends Activity } }; + /** + * Searchable data description. + * + * Known restriction: we are only searching (for now) the first level of Settings. + */ + private static IndexableData[] INDEXABLE_DATA = new IndexableData[] { + new IndexableData(1, R.xml.wifi_settings, + "com.android.settings.wifi.WifiSettings", + R.drawable.ic_settings_wireless), + new IndexableData(2, R.xml.bluetooth_settings, + "com.android.settings.bluetooth.BluetoothSettings", + R.drawable.ic_settings_bluetooth2), + new IndexableData(3, R.xml.data_usage_metered_prefs, + "com.android.settings.net.DataUsageMeteredSettings", + R.drawable.ic_settings_data_usage), + new IndexableData(4, R.xml.wireless_settings, + "com.android.settings.WirelessSettings", + R.drawable.empty_icon), + new IndexableData(5, R.xml.home_selection, + "com.android.settings.HomeSettings", + R.drawable.ic_settings_home), + new IndexableData(6, R.xml.sound_settings, + "com.android.settings.SoundSettings", + R.drawable.ic_settings_sound), + new IndexableData(7, R.xml.display_settings, + "com.android.settings.DisplaySettings", + R.drawable.ic_settings_display), + new IndexableData(8, R.xml.device_info_memory, + "com.android.settings.deviceinfo.Memory", + R.drawable.ic_settings_storage), + new IndexableData(9, R.xml.power_usage_summary, + "com.android.settings.fuelgauge.PowerUsageSummary", + R.drawable.ic_settings_battery), + new IndexableData(10, R.xml.user_settings, + "com.android.settings.users.UserSettings", + R.drawable.ic_settings_multiuser), + new IndexableData(11, R.xml.location_settings, + "com.android.settings.location.LocationSettings", + R.drawable.ic_settings_location), + new IndexableData(12, R.xml.security_settings, + "com.android.settings.SecuritySettings", + R.drawable.ic_settings_security), + new IndexableData(13, R.xml.language_settings, + "com.android.settings.inputmethod.InputMethodAndLanguageSettings", + R.drawable.ic_settings_language), + new IndexableData(14, R.xml.privacy_settings, + "com.android.settings.PrivacySettings", + R.drawable.ic_settings_backup), + new IndexableData(15, R.xml.date_time_prefs, + "com.android.settings.DateTimeSettings", + R.drawable.ic_settings_date_time), + new IndexableData(16, R.xml.accessibility_settings, + "com.android.settings.accessibility.AccessibilitySettings", + R.drawable.ic_settings_accessibility), + new IndexableData(17, R.xml.print_settings, + "com.android.settings.print.PrintSettingsFragment", + com.android.internal.R.drawable.ic_print), + new IndexableData(18, R.xml.development_prefs, + "com.android.settings.DevelopmentSettings", + R.drawable.ic_settings_development), + new IndexableData(19, R.xml.device_info_settings, + "com.android.settings.DeviceInfoSettings", + R.drawable.ic_settings_about), + }; + @Override public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) { // Override the fragment title for Wallpaper settings @@ -463,6 +530,7 @@ public class SettingsActivity extends Activity public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mDrawerToggle.onConfigurationChanged(newConfig); + Index.getInstance(this).update(); } @Override @@ -479,6 +547,9 @@ public class SettingsActivity extends Activity getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0)); } + Index.getInstance(this).addIndexableData(INDEXABLE_DATA); + Index.getInstance(this).update(); + mAuthenticatorHelper = new AuthenticatorHelper(); mAuthenticatorHelper.updateAuthDescriptions(this); mAuthenticatorHelper.onAccountsUpdated(this, null); diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java index f5b47ae..ce3b0c0 100644 --- a/src/com/android/settings/dashboard/DashboardSummary.java +++ b/src/com/android/settings/dashboard/DashboardSummary.java @@ -17,17 +17,97 @@ package com.android.settings.dashboard; import android.app.Fragment; +import android.content.Context; +import android.database.Cursor; +import android.os.AsyncTask; import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.indexer.Index; public class DashboardSummary extends Fragment { + private static final String SAVE_KEY_QUERY = ":settings:query"; + + private EditText mEditText; + private ListView mListView; + + private SearchResultsAdapter mAdapter; + private Index mIndex; + private UpdateSearchResultsTask mUpdateSearchResultsTask; + + /** + * A basic AsyncTask for updating the query results cursor + */ + private class UpdateSearchResultsTask extends AsyncTask<String, Void, Cursor> { + @Override + protected Cursor doInBackground(String... params) { + return mIndex.search(params[0]); + } + + @Override + protected void onPostExecute(Cursor cursor) { + if (!isCancelled()) { + setCursor(cursor); + } else if (cursor != null) { + cursor.close(); + } + } + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + mIndex = Index.getInstance(getActivity()); + mAdapter = new SearchResultsAdapter(getActivity()); + } + + @Override + public void onStop() { + super.onStop(); + + clearResults(); + } + + @Override + public void onStart() { + super.onStart(); + + updateSearchResults(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if (outState != null) { + outState.putString(SAVE_KEY_QUERY, mEditText.getText().toString()); + } + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + if (savedInstanceState != null) { + final String query = savedInstanceState.getString(SAVE_KEY_QUERY); + if (query != null && !TextUtils.isEmpty(query)) { + mEditText.setText(query); + } + } } @Override @@ -36,6 +116,185 @@ public class DashboardSummary extends Fragment { final View view = inflater.inflate(R.layout.dashboard, container, false); + mEditText = (EditText)view.findViewById(R.id.edittext_query); + mEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + updateSearchResults(); + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + mEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + closeSoftKeyboard(); + } + } + }); + + mListView = (ListView) view.findViewById(R.id.list_results); + mListView.setAdapter(mAdapter); + mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + closeSoftKeyboard(); + final Cursor cursor = mAdapter.mCursor; + cursor.moveToPosition(position); + final String fragmentName = cursor.getString(Index.COLUMN_INDEX_FRAGMENT_NAME); + final String fragmentTitle = cursor.getString(Index.COLUMN_INDEX_FRAGMENT_TITLE); + + ((SettingsActivity) getActivity()).startPreferencePanel(fragmentName, null, 0, + fragmentTitle, null, 0); + } + }); + return view; } + + private void closeSoftKeyboard() { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null && imm.isActive(mEditText)) { + imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); + } + } + + private void clearResults() { + if (mUpdateSearchResultsTask != null) { + mUpdateSearchResultsTask.cancel(false); + mUpdateSearchResultsTask = null; + } + setCursor(null); + } + + private void setCursor(Cursor cursor) { + Cursor oldCursor = mAdapter.swapCursor(cursor); + if (oldCursor != null) { + oldCursor.close(); + } + } + + private void updateSearchResults() { + if (mUpdateSearchResultsTask != null) { + mUpdateSearchResultsTask.cancel(false); + mUpdateSearchResultsTask = null; + } + final String query = mEditText.getText().toString(); + if (TextUtils.isEmpty(query)) { + setCursor(null); + } else { + mUpdateSearchResultsTask = new UpdateSearchResultsTask(); + mUpdateSearchResultsTask.execute(query); + } + } + + private static class SearchResult { + public String title; + public String summary; + public int iconResId; + + public SearchResult(String title, String summary, int iconResId) { + this.title = title; + this.summary = summary; + this.iconResId = iconResId; + } + } + + private static class SearchResultsAdapter extends BaseAdapter { + + private Cursor mCursor; + private LayoutInflater mInflater; + private boolean mDataValid; + + public SearchResultsAdapter(Context context) { + mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mDataValid = false; + } + + public Cursor swapCursor(Cursor newCursor) { + if (newCursor == mCursor) { + return null; + } + Cursor oldCursor = mCursor; + mCursor = newCursor; + if (newCursor != null) { + mDataValid = true; + notifyDataSetChanged(); + } else { + mDataValid = false; + notifyDataSetInvalidated(); + } + return oldCursor; + } + + @Override + public int getCount() { + if (!mDataValid || mCursor == null || mCursor.isClosed()) return 0; + return mCursor.getCount(); + } + + @Override + public Object getItem(int position) { + if (mDataValid && mCursor.moveToPosition(position)) { + final String title = mCursor.getString(Index.COLUMN_INDEX_TITLE); + final String summary = mCursor.getString(Index.COLUMN_INDEX_SUMMARY); + final String iconResStr = mCursor.getString(Index.COLUMN_INDEX_ICON); + final int iconResId = + TextUtils.isEmpty(iconResStr) ? 0 : Integer.parseInt(iconResStr); + return new SearchResult(title, summary, iconResId); + } + return null; + } + + @Override + public long getItemId(int position) { + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (!mDataValid && convertView == null) { + throw new IllegalStateException( + "this should only be called when the cursor is valid"); + } + if (!mCursor.moveToPosition(position)) { + throw new IllegalStateException("couldn't move cursor to position " + position); + } + + View view; + TextView textTitle; + TextView textSummary; + ImageView imageView; + + if (convertView == null) { + view = mInflater.inflate(R.layout.search_result, parent, false); + } else { + view = convertView; + } + textTitle = (TextView) view.findViewById(R.id.title); + textSummary = (TextView) view.findViewById(R.id.summary); + imageView = (ImageView) view.findViewById(R.id.icon); + + SearchResult result = (SearchResult) getItem(position); + + textTitle.setText(result.title); + textSummary.setText(result.summary); + if (result.iconResId != R.drawable.empty_icon) { + imageView.setImageResource(result.iconResId); + imageView.setBackgroundResource(R.color.background_search_result_icon); + } else { + imageView.setImageDrawable(null); + imageView.setBackgroundResource(R.drawable.empty_icon); + } + + return view; + } + } } diff --git a/src/com/android/settings/indexer/Index.java b/src/com/android/settings/indexer/Index.java new file mode 100644 index 0000000..1a2eb48 --- /dev/null +++ b/src/com/android/settings/indexer/Index.java @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2014 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.android.settings.indexer; + +import android.content.ContentValues; +import android.content.Context; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.os.AsyncTask; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.util.Xml; +import com.android.settings.R; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.android.settings.indexer.IndexDatabaseHelper.Tables; +import static com.android.settings.indexer.IndexDatabaseHelper.IndexColumns; + +public class Index { + + private static final String LOG_TAG = "Indexer"; + + // Those indices should match the indices of SELECT_COLUMNS ! + public static final int COLUMN_INDEX_TITLE = 1; + public static final int COLUMN_INDEX_SUMMARY = 2; + public static final int COLUMN_INDEX_FRAGMENT_NAME = 4; + public static final int COLUMN_INDEX_FRAGMENT_TITLE = 5; + public static final int COLUMN_INDEX_ICON = 7; + + // If you change the order of columns here, you SHOULD change the COLUMN_INDEX_XXX values + private static final String[] SELECT_COLUMNS = new String[] { + IndexColumns.DATA_RANK, + IndexColumns.DATA_TITLE, + IndexColumns.DATA_SUMMARY, + IndexColumns.DATA_KEYWORDS, + IndexColumns.FRAGMENT_NAME, + IndexColumns.FRAGMENT_TITLE, + IndexColumns.INTENT, + IndexColumns.ICON + }; + + private static final String EMPTY = ""; + private static final String NON_BREAKING_HYPHEN = "\u2011"; + private static final String HYPHEN = "-"; + + private static Index sInstance; + + private final AtomicBoolean mIsAvailable = new AtomicBoolean(false); + private final List<IndexableData> mDataToIndex = new ArrayList<IndexableData>(); + + private final Context mContext; + + /** + * A basic singleton + */ + public static Index getInstance(Context context) { + if (sInstance == null) { + sInstance = new Index(context); + } + return sInstance; + } + + public Index(Context context) { + mContext = context; + } + + public boolean isAvailable() { + return mIsAvailable.get(); + } + + public Cursor search(String query) { + return getReadableDatabase().rawQuery(buildSQL(query), null); + } + + private String buildSQL(String query) { + StringBuilder sb = new StringBuilder(); + sb.append(buildSQLForColumn(query, IndexColumns.DATA_TITLE)); + sb.append(" UNION "); + sb.append(buildSQLForColumn(query, IndexColumns.DATA_SUMMARY)); + sb.append(" UNION "); + sb.append(buildSQLForColumn(query, IndexColumns.DATA_KEYWORDS)); + sb.append(" ORDER BY "); + sb.append(IndexColumns.DATA_RANK); + return sb.toString(); + } + + private String buildSQLForColumn(String query, String columnName) { + StringBuilder sb = new StringBuilder(); + sb.append("SELECT "); + for (int n = 0; n < SELECT_COLUMNS.length; n++) { + sb.append(SELECT_COLUMNS[n]); + if (n < SELECT_COLUMNS.length - 1) { + sb.append(", "); + } + } + sb.append(" FROM "); + sb.append(Tables.TABLE_PREFS_INDEX); + sb.append(" WHERE "); + sb.append(buildWhereStringForColumn(query, columnName)); + + return sb.toString(); + } + + private String buildWhereStringForColumn(String query, String columnName) { + final StringBuilder sb = new StringBuilder(columnName); + sb.append(" MATCH "); + DatabaseUtils.appendEscapedSQLString(sb, query + "*"); + sb.append(" AND "); + sb.append(IndexColumns.LOCALE); + sb.append(" = "); + DatabaseUtils.appendEscapedSQLString(sb, Locale.getDefault().toString()); + return sb.toString(); + } + + public void addIndexableData(IndexableData data) { + mDataToIndex.add(data); + } + + public void addIndexableData(IndexableData[] array) { + final int count = array.length; + for (int n = 0; n < count; n++) { + addIndexableData(array[n]); + } + } + + public boolean update() { + final IndexTask task = new IndexTask(); + task.execute(); + try { + return task.get(); + } catch (InterruptedException e) { + Log.e(LOG_TAG, "Cannot update index: " + e.getMessage()); + return false; + } catch (ExecutionException e) { + Log.e(LOG_TAG, "Cannot update index: " + e.getMessage()); + return false; + } + } + + private SQLiteDatabase getReadableDatabase() { + return IndexDatabaseHelper.getInstance(mContext).getReadableDatabase(); + } + + private SQLiteDatabase getWritableDatabase() { + return IndexDatabaseHelper.getInstance(mContext).getWritableDatabase(); + } + + /** + * A private class for updating the Index database + */ + private class IndexTask extends AsyncTask<Void, Integer, Boolean> { + + @Override + protected void onPreExecute() { + super.onPreExecute(); + mIsAvailable.set(false); + } + + @Override + protected void onPostExecute(Boolean aBoolean) { + super.onPostExecute(aBoolean); + mIsAvailable.set(true); + } + + @Override + protected Boolean doInBackground(Void... params) { + final SQLiteDatabase database = getWritableDatabase(); + boolean result = false; + final Locale locale = Locale.getDefault(); + final String localeStr = locale.toString(); + if (isLocaleAlreadyIndexed(database, locale)) { + Log.d(LOG_TAG, "Locale '" + localeStr + "' is already indexed"); + return true; + } + final long current = System.currentTimeMillis(); + try { + database.beginTransaction(); + final int count = mDataToIndex.size(); + for (int n = 0; n < count; n++) { + final IndexableData data = mDataToIndex.get(n); + indexFromResource(database, locale, data.xmlResId, data.fragmentName, + data.iconResId, data.rank); + } + database.setTransactionSuccessful(); + result = true; + } finally { + database.endTransaction(); + } + final long now = System.currentTimeMillis(); + Log.d(LOG_TAG, "Indexing locale '" + localeStr + "' took " + + (now - current) + " millis"); + return result; + } + + private boolean isLocaleAlreadyIndexed(SQLiteDatabase database, Locale locale) { + Cursor cursor = null; + boolean result = false; + final StringBuilder sb = new StringBuilder(IndexColumns.LOCALE); + sb.append(" = "); + DatabaseUtils.appendEscapedSQLString(sb, locale.toString()); + try { + // We care only for 1 row + cursor = database.query(Tables.TABLE_PREFS_INDEX, null, + sb.toString(), null, null, null, null, "1"); + final int count = cursor.getCount(); + result = (count >= 1); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return result; + } + + private void indexFromResource(SQLiteDatabase database, Locale locale, int xmlResId, + String fragmentName, int iconResId, int rank) { + XmlResourceParser parser = null; + final String localeStr = locale.toString(); + try { + parser = mContext.getResources().getXml(xmlResId); + + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + // Parse next until start tag is found + } + + String nodeName = parser.getName(); + if (!"PreferenceScreen".equals(nodeName)) { + throw new RuntimeException( + "XML document must start with <PreferenceScreen> tag; found" + + nodeName + " at " + parser.getPositionDescription()); + } + + final int outerDepth = parser.getDepth(); + final AttributeSet attrs = Xml.asAttributeSet(parser); + final String fragmentTitle = getData(attrs, + com.android.internal.R.styleable.Preference, com.android.internal.R.styleable.Preference_title); + + String title = getDataTitle(attrs); + String summary = getDataSummary(attrs); + String keywords = getDataKeywords(attrs); + + // Insert rows for the main PreferenceScreen node. Rewrite the data for removing + // hyphens. + inserOneRowWithFilteredData(database, localeStr, title, summary, fragmentName, + fragmentTitle, iconResId, rank, keywords, "\u2011", ""); + inserOneRowWithFilteredData(database, localeStr, title, summary, fragmentName, + fragmentTitle, iconResId, rank, keywords, "\u2011", "-"); + + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + title = getDataTitle(attrs); + summary = getDataSummary(attrs); + keywords = getDataKeywords(attrs); + + // Insert rows for the child nodes of PreferenceScreen + inserOneRowWithFilteredData(database, localeStr, title, summary, fragmentName, + fragmentTitle, iconResId, rank, keywords, NON_BREAKING_HYPHEN, EMPTY); + inserOneRowWithFilteredData(database, localeStr, title, summary, fragmentName, + fragmentTitle, iconResId, rank, keywords, NON_BREAKING_HYPHEN, HYPHEN); + } + + } catch (XmlPullParserException e) { + throw new RuntimeException("Error parsing PreferenceScreen", e); + } catch (IOException e) { + throw new RuntimeException("Error parsing PreferenceScreen", e); + } finally { + if (parser != null) parser.close(); + } + } + + private void inserOneRowWithFilteredData(SQLiteDatabase database, String locale, + String title, String summary, String fragmentName, String fragmentTitle, + int iconResId, int rank, String keywords, String seq, String replacement) { + + String updatedTitle; + String updateSummary; + if (title != null && title.contains(seq)) { + updatedTitle = title.replaceAll(seq, replacement); + } else { + updatedTitle = title; + } + if (summary != null && summary.contains(seq)) { + updateSummary = summary.replaceAll(seq, replacement); + } else { + updateSummary = summary; + } + insertOneRow(database, locale, + updatedTitle, updateSummary, + fragmentName, fragmentTitle, iconResId, rank, keywords); + } + + private void insertOneRow(SQLiteDatabase database, String locale, String title, + String summary, String fragmentName, String fragmentTitle, + int iconResId, int rank, String keywords) { + + if (TextUtils.isEmpty(title)) { + return; + } + ContentValues values = new ContentValues(); + values.put(IndexColumns.LOCALE, locale); + values.put(IndexColumns.DATA_RANK, rank); + values.put(IndexColumns.DATA_TITLE, title); + values.put(IndexColumns.DATA_SUMMARY, summary); + values.put(IndexColumns.DATA_KEYWORDS, keywords); + values.put(IndexColumns.FRAGMENT_NAME, fragmentName); + values.put(IndexColumns.FRAGMENT_TITLE, fragmentTitle); + values.put(IndexColumns.INTENT, ""); + values.put(IndexColumns.ICON, iconResId); + + database.insertOrThrow(Tables.TABLE_PREFS_INDEX, null, values); + } + + private String getDataTitle(AttributeSet attrs) { + return getData(attrs, + com.android.internal.R.styleable.Preference, + com.android.internal.R.styleable.Preference_title); + } + + private String getDataSummary(AttributeSet attrs) { + return getData(attrs, + com.android.internal.R.styleable.Preference, + com.android.internal.R.styleable.Preference_summary); + } + + private String getDataKeywords(AttributeSet attrs) { + return getData(attrs, + R.styleable.Preference, + R.styleable.Preference_keywords); + } + + private String getData(AttributeSet set, int[] attrs, int resId) { + final TypedArray sa = mContext.obtainStyledAttributes(set, attrs); + final TypedValue tv = sa.peekValue(resId); + + CharSequence data = null; + if (tv != null && tv.type == TypedValue.TYPE_STRING) { + if (tv.resourceId != 0) { + data = mContext.getText(tv.resourceId); + } else { + data = tv.string; + } + } + return (data != null) ? data.toString() : null; + } + } +} diff --git a/src/com/android/settings/indexer/IndexDatabaseHelper.java b/src/com/android/settings/indexer/IndexDatabaseHelper.java new file mode 100644 index 0000000..243f7b8 --- /dev/null +++ b/src/com/android/settings/indexer/IndexDatabaseHelper.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2014 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.android.settings.indexer; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.os.Build; +import android.util.Log; + +public class IndexDatabaseHelper extends SQLiteOpenHelper { + + private static final String TAG = "IndexDatabaseHelper"; + + private static final String DATABASE_NAME = "search_index.db"; + private static final int DATABASE_VERSION = 100; + + public interface Tables { + public static final String TABLE_PREFS_INDEX = "prefs_index"; + public static final String TABLE_META_INDEX = "meta_index"; + } + + public interface IndexColumns { + public static final String LOCALE = "locale"; + public static final String DATA_RANK = "data_rank"; + public static final String DATA_TITLE = "data_title"; + public static final String DATA_SUMMARY = "data_summary"; + public static final String DATA_KEYWORDS = "data_keywords"; + public static final String FRAGMENT_NAME = "fragment_name"; + public static final String FRAGMENT_TITLE = "fragment_title"; + public static final String INTENT = "intent"; + public static final String ICON = "icon"; + } + + public interface MetaColumns { + public static final String BUILD = "build"; + } + + private static final String CREATE_INDEX_TABLE = + "CREATE VIRTUAL TABLE " + Tables.TABLE_PREFS_INDEX + " USING fts4" + + "(" + + IndexColumns.LOCALE + + ", " + + IndexColumns.DATA_RANK + + ", " + + IndexColumns.DATA_TITLE + + ", " + + IndexColumns.DATA_SUMMARY + + ", " + + IndexColumns.DATA_KEYWORDS + + ", " + + IndexColumns.FRAGMENT_NAME + + ", " + + IndexColumns.FRAGMENT_TITLE + + ", " + + IndexColumns.INTENT + + ", " + + IndexColumns.ICON + + ");"; + + private static final String CREATE_META_TABLE = + "CREATE TABLE " + Tables.TABLE_META_INDEX + + "(" + + MetaColumns.BUILD + " VARCHAR(32) NOT NULL" + + ")"; + + private static final String INSERT_BUILD_VERSION = + "INSERT INTO " + Tables.TABLE_META_INDEX + + " VALUES ('" + Build.VERSION.INCREMENTAL + "');"; + + private static final String SELECT_BUILD_VERSION = + "SELECT " + MetaColumns.BUILD + " FROM " + Tables.TABLE_META_INDEX + " LIMIT 1;"; + + private static IndexDatabaseHelper sSingleton; + + public static synchronized IndexDatabaseHelper getInstance(Context context) { + if (sSingleton == null) { + sSingleton = new IndexDatabaseHelper(context); + } + return sSingleton; + } + + public IndexDatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + bootstrapDB(db); + } + + private void bootstrapDB(SQLiteDatabase db) { + db.execSQL(CREATE_INDEX_TABLE); + db.execSQL(CREATE_META_TABLE); + db.execSQL(INSERT_BUILD_VERSION); + Log.i(TAG, "Bootstrapped database"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + } + + private String getBuildVersion(SQLiteDatabase db) { + String version = null; + Cursor cursor = null; + try { + cursor = db.rawQuery(SELECT_BUILD_VERSION, null); + if (cursor.moveToFirst()) { + version = cursor.getString(0); + } + } + catch (Exception e) { + Log.e(TAG, "Cannot get build version from Index metadata"); + } + finally { + if (cursor != null) { + cursor.close(); + } + } + return version; + } + + private void dropTables(SQLiteDatabase db) { + db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_META_INDEX); + db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_PREFS_INDEX); + } + + @Override + public void onOpen(SQLiteDatabase db) { + super.onOpen(db); + + if (!Build.VERSION.INCREMENTAL.equals(getBuildVersion(db))) { + Log.w(TAG, "Index needs to be rebuilt"); + // We need to drop the tables and recreate them + dropTables(db); + bootstrapDB(db); + } else { + Log.i(TAG, "Index is fine"); + } + } +} diff --git a/src/com/android/settings/indexer/IndexableData.java b/src/com/android/settings/indexer/IndexableData.java new file mode 100644 index 0000000..61714a2 --- /dev/null +++ b/src/com/android/settings/indexer/IndexableData.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 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.android.settings.indexer; + +public class IndexableData { + + public int rank; + public int xmlResId; + public String fragmentName; + public int iconResId; + + public IndexableData(int rank, int dataResId, String name, int iconResId) { + this.rank = rank; + this.xmlResId = dataResId; + this.fragmentName = name; + this.iconResId = iconResId; + } +}
\ No newline at end of file |