summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/settings/SettingsActivity.java75
-rw-r--r--src/com/android/settings/dashboard/DashboardSummary.java259
-rw-r--r--src/com/android/settings/indexer/Index.java379
-rw-r--r--src/com/android/settings/indexer/IndexDatabaseHelper.java156
-rw-r--r--src/com/android/settings/indexer/IndexableData.java32
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