summaryrefslogtreecommitdiffstats
path: root/src/com/android/settings/search
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/settings/search')
-rw-r--r--src/com/android/settings/search/BaseSearchIndexProvider.java49
-rw-r--r--src/com/android/settings/search/DynamicIndexableContentMonitor.java307
-rw-r--r--src/com/android/settings/search/Index.java1320
-rw-r--r--src/com/android/settings/search/IndexDatabaseHelper.java223
-rw-r--r--src/com/android/settings/search/Indexable.java69
-rw-r--r--src/com/android/settings/search/Ranking.java180
-rw-r--r--src/com/android/settings/search/SearchIndexableRaw.java64
-rw-r--r--src/com/android/settings/search/SearchIndexableResources.java281
-rw-r--r--src/com/android/settings/search/SettingsSearchIndexablesProvider.java75
9 files changed, 2568 insertions, 0 deletions
diff --git a/src/com/android/settings/search/BaseSearchIndexProvider.java b/src/com/android/settings/search/BaseSearchIndexProvider.java
new file mode 100644
index 0000000..0fe1944
--- /dev/null
+++ b/src/com/android/settings/search/BaseSearchIndexProvider.java
@@ -0,0 +1,49 @@
+/*
+ * 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.search;
+
+import android.content.Context;
+import android.provider.SearchIndexableResource;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A basic SearchIndexProvider that returns no data to index.
+ */
+public class BaseSearchIndexProvider implements Indexable.SearchIndexProvider {
+
+ private static final List<String> EMPTY_LIST = Collections.<String>emptyList();
+
+ public BaseSearchIndexProvider() {
+ }
+
+ @Override
+ public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled) {
+ return null;
+ }
+
+ @Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+ return null;
+ }
+
+ @Override
+ public List<String> getNonIndexableKeys(Context context) {
+ return EMPTY_LIST;
+ }
+}
diff --git a/src/com/android/settings/search/DynamicIndexableContentMonitor.java b/src/com/android/settings/search/DynamicIndexableContentMonitor.java
new file mode 100644
index 0000000..12bb6ef
--- /dev/null
+++ b/src/com/android/settings/search/DynamicIndexableContentMonitor.java
@@ -0,0 +1,307 @@
+/*
+ * 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.search;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.database.ContentObserver;
+import android.hardware.input.InputManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.print.PrintManager;
+import android.printservice.PrintService;
+import android.printservice.PrintServiceInfo;
+import android.provider.UserDictionary;
+import android.view.accessibility.AccessibilityManager;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import com.android.internal.content.PackageMonitor;
+import com.android.settings.accessibility.AccessibilitySettings;
+import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
+import com.android.settings.print.PrintSettingsFragment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class DynamicIndexableContentMonitor extends PackageMonitor implements
+ InputManager.InputDeviceListener {
+
+ private static final long DELAY_PROCESS_PACKAGE_CHANGE = 2000;
+
+ private static final int MSG_PACKAGE_AVAILABLE = 1;
+ private static final int MSG_PACKAGE_UNAVAILABLE = 2;
+
+ private final List<String> mAccessibilityServices = new ArrayList<String>();
+ private final List<String> mPrintServices = new ArrayList<String>();
+ private final List<String> mImeServices = new ArrayList<String>();
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_PACKAGE_AVAILABLE: {
+ String packageName = (String) msg.obj;
+ handlePackageAvailable(packageName);
+ } break;
+
+ case MSG_PACKAGE_UNAVAILABLE: {
+ String packageName = (String) msg.obj;
+ handlePackageUnavailable(packageName);
+ } break;
+ }
+ }
+ };
+
+ private final ContentObserver mUserDictionaryContentObserver =
+ new UserDictionaryContentObserver(mHandler);
+
+ private Context mContext;
+ private boolean mHasFeaturePrinting;
+ private boolean mHasFeatureIme;
+
+ private static Intent getAccessibilityServiceIntent(String packageName) {
+ final Intent intent = new Intent(AccessibilityService.SERVICE_INTERFACE);
+ intent.setPackage(packageName);
+ return intent;
+ }
+
+ private static Intent getPrintServiceIntent(String packageName) {
+ final Intent intent = new Intent(PrintService.SERVICE_INTERFACE);
+ intent.setPackage(packageName);
+ return intent;
+ }
+
+ private static Intent getIMEServiceIntent(String packageName) {
+ final Intent intent = new Intent("android.view.InputMethod");
+ intent.setPackage(packageName);
+ return intent;
+ }
+
+ public void register(Context context) {
+ mContext = context;
+
+ mHasFeaturePrinting = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_PRINTING);
+ mHasFeatureIme = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_INPUT_METHODS);
+
+ // Cache accessibility service packages to know when they go away.
+ AccessibilityManager accessibilityManager = (AccessibilityManager)
+ mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ List<AccessibilityServiceInfo> accessibilityServices = accessibilityManager
+ .getInstalledAccessibilityServiceList();
+ final int accessibilityServiceCount = accessibilityServices.size();
+ for (int i = 0; i < accessibilityServiceCount; i++) {
+ AccessibilityServiceInfo accessibilityService = accessibilityServices.get(i);
+ ResolveInfo resolveInfo = accessibilityService.getResolveInfo();
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ continue;
+ }
+ mAccessibilityServices.add(resolveInfo.serviceInfo.packageName);
+ }
+
+ if (mHasFeaturePrinting) {
+ // Cache print service packages to know when they go away.
+ PrintManager printManager = (PrintManager)
+ mContext.getSystemService(Context.PRINT_SERVICE);
+ List<PrintServiceInfo> printServices = printManager.getInstalledPrintServices();
+ final int serviceCount = printServices.size();
+ for (int i = 0; i < serviceCount; i++) {
+ PrintServiceInfo printService = printServices.get(i);
+ ResolveInfo resolveInfo = printService.getResolveInfo();
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ continue;
+ }
+ mPrintServices.add(resolveInfo.serviceInfo.packageName);
+ }
+ }
+
+ // Cache IME service packages to know when they go away.
+ if (mHasFeatureIme) {
+ InputMethodManager imeManager = (InputMethodManager)
+ mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+ List<InputMethodInfo> inputMethods = imeManager.getInputMethodList();
+ final int inputMethodCount = inputMethods.size();
+ for (int i = 0; i < inputMethodCount; i++) {
+ InputMethodInfo inputMethod = inputMethods.get(i);
+ ServiceInfo serviceInfo = inputMethod.getServiceInfo();
+ if (serviceInfo == null) continue;
+ mImeServices.add(serviceInfo.packageName);
+ }
+
+ // Watch for related content URIs.
+ mContext.getContentResolver().registerContentObserver(
+ UserDictionary.Words.CONTENT_URI, true, mUserDictionaryContentObserver);
+ }
+
+ // Watch for input device changes.
+ InputManager inputManager = (InputManager) context.getSystemService(
+ Context.INPUT_SERVICE);
+ inputManager.registerInputDeviceListener(this, mHandler);
+
+ // Start tracking packages.
+ register(context, Looper.getMainLooper(), UserHandle.CURRENT, false);
+ }
+
+ public void unregister() {
+ super.unregister();
+
+ InputManager inputManager = (InputManager) mContext.getSystemService(
+ Context.INPUT_SERVICE);
+ inputManager.unregisterInputDeviceListener(this);
+
+ if (mHasFeatureIme) {
+ mContext.getContentResolver().unregisterContentObserver(
+ mUserDictionaryContentObserver);
+ }
+
+ mAccessibilityServices.clear();
+ mPrintServices.clear();
+ mImeServices.clear();
+ }
+
+ // Covers installed, appeared external storage with the package, upgraded.
+ @Override
+ public void onPackageAppeared(String packageName, int uid) {
+ postMessage(MSG_PACKAGE_AVAILABLE, packageName);
+ }
+
+ // Covers uninstalled, removed external storage with the package.
+ @Override
+ public void onPackageDisappeared(String packageName, int uid) {
+ postMessage(MSG_PACKAGE_UNAVAILABLE, packageName);
+ }
+
+ // Covers enabled, disabled.
+ @Override
+ public void onPackageModified(String packageName) {
+ super.onPackageModified(packageName);
+ final int state = mContext.getPackageManager().getApplicationEnabledSetting(
+ packageName);
+ if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+ || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ postMessage(MSG_PACKAGE_AVAILABLE, packageName);
+ } else {
+ postMessage(MSG_PACKAGE_UNAVAILABLE, packageName);
+ }
+ }
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ Index.getInstance(mContext).updateFromClassNameResource(
+ InputMethodAndLanguageSettings.class.getName(), false, true);
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ onInputDeviceChanged(deviceId);
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ Index.getInstance(mContext).updateFromClassNameResource(
+ InputMethodAndLanguageSettings.class.getName(), true, true);
+ }
+
+ private void postMessage(int what, String packageName) {
+ Message message = mHandler.obtainMessage(what, packageName);
+ mHandler.sendMessageDelayed(message, DELAY_PROCESS_PACKAGE_CHANGE);
+ }
+
+ private void handlePackageAvailable(String packageName) {
+ if (!mAccessibilityServices.contains(packageName)) {
+ final Intent intent = getAccessibilityServiceIntent(packageName);
+ if (!mContext.getPackageManager().queryIntentServices(intent, 0).isEmpty()) {
+ mAccessibilityServices.add(packageName);
+ Index.getInstance(mContext).updateFromClassNameResource(
+ AccessibilitySettings.class.getName(), false, true);
+ }
+ }
+
+ if (mHasFeaturePrinting) {
+ if (!mPrintServices.contains(packageName)) {
+ final Intent intent = getPrintServiceIntent(packageName);
+ if (!mContext.getPackageManager().queryIntentServices(intent, 0).isEmpty()) {
+ mPrintServices.add(packageName);
+ Index.getInstance(mContext).updateFromClassNameResource(
+ PrintSettingsFragment.class.getName(), false, true);
+ }
+ }
+ }
+
+ if (mHasFeatureIme) {
+ if (!mImeServices.contains(packageName)) {
+ Intent intent = getIMEServiceIntent(packageName);
+ if (!mContext.getPackageManager().queryIntentServices(intent, 0).isEmpty()) {
+ mImeServices.add(packageName);
+ Index.getInstance(mContext).updateFromClassNameResource(
+ InputMethodAndLanguageSettings.class.getName(), false, true);
+ }
+ }
+ }
+ }
+
+ private void handlePackageUnavailable(String packageName) {
+ final int accessibilityIndex = mAccessibilityServices.indexOf(packageName);
+ if (accessibilityIndex >= 0) {
+ mAccessibilityServices.remove(accessibilityIndex);
+ Index.getInstance(mContext).updateFromClassNameResource(
+ AccessibilitySettings.class.getName(), true, true);
+ }
+
+ if (mHasFeaturePrinting) {
+ final int printIndex = mPrintServices.indexOf(packageName);
+ if (printIndex >= 0) {
+ mPrintServices.remove(printIndex);
+ Index.getInstance(mContext).updateFromClassNameResource(
+ PrintSettingsFragment.class.getName(), true, true);
+ }
+ }
+
+ if (mHasFeatureIme) {
+ final int imeIndex = mImeServices.indexOf(packageName);
+ if (imeIndex >= 0) {
+ mImeServices.remove(imeIndex);
+ Index.getInstance(mContext).updateFromClassNameResource(
+ InputMethodAndLanguageSettings.class.getName(), true, true);
+ }
+ }
+ }
+
+ private final class UserDictionaryContentObserver extends ContentObserver {
+
+ public UserDictionaryContentObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (UserDictionary.Words.CONTENT_URI.equals(uri)) {
+ Index.getInstance(mContext).updateFromClassNameResource(
+ InputMethodAndLanguageSettings.class.getName(), true, true);
+ }
+ };
+ }
+}
diff --git a/src/com/android/settings/search/Index.java b/src/com/android/settings/search/Index.java
new file mode 100644
index 0000000..3957cf6
--- /dev/null
+++ b/src/com/android/settings/search/Index.java
@@ -0,0 +1,1320 @@
+/*
+ * 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.search;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.MergeCursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.SearchIndexableData;
+import android.provider.SearchIndexableResource;
+import android.provider.SearchIndexablesContract;
+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.lang.reflect.Field;
+import java.text.Normalizer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Pattern;
+
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_RANK;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_TITLE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_ON;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_OFF;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ENTRIES;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_KEYWORDS;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SCREEN_TITLE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_CLASS_NAME;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ICON_RESID;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_ACTION;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_TARGET_CLASS;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_KEY;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_USER_ID;
+
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RANK;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RESID;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_CLASS_NAME;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_ICON_RESID;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_ACTION;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS;
+
+import static com.android.settings.search.IndexDatabaseHelper.Tables;
+import static com.android.settings.search.IndexDatabaseHelper.IndexColumns;
+
+public class Index {
+
+ private static final String LOG_TAG = "Index";
+
+ // Those indices should match the indices of SELECT_COLUMNS !
+ public static final int COLUMN_INDEX_RANK = 0;
+ public static final int COLUMN_INDEX_TITLE = 1;
+ public static final int COLUMN_INDEX_SUMMARY_ON = 2;
+ public static final int COLUMN_INDEX_SUMMARY_OFF = 3;
+ public static final int COLUMN_INDEX_ENTRIES = 4;
+ public static final int COLUMN_INDEX_KEYWORDS = 5;
+ public static final int COLUMN_INDEX_CLASS_NAME = 6;
+ public static final int COLUMN_INDEX_SCREEN_TITLE = 7;
+ public static final int COLUMN_INDEX_ICON = 8;
+ public static final int COLUMN_INDEX_INTENT_ACTION = 9;
+ public static final int COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE = 10;
+ public static final int COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS = 11;
+ public static final int COLUMN_INDEX_ENABLED = 12;
+ public static final int COLUMN_INDEX_KEY = 13;
+ public static final int COLUMN_INDEX_USER_ID = 14;
+
+ public static final String ENTRIES_SEPARATOR = "|";
+
+ // 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, // 0
+ IndexColumns.DATA_TITLE, // 1
+ IndexColumns.DATA_SUMMARY_ON, // 2
+ IndexColumns.DATA_SUMMARY_OFF, // 3
+ IndexColumns.DATA_ENTRIES, // 4
+ IndexColumns.DATA_KEYWORDS, // 5
+ IndexColumns.CLASS_NAME, // 6
+ IndexColumns.SCREEN_TITLE, // 7
+ IndexColumns.ICON, // 8
+ IndexColumns.INTENT_ACTION, // 9
+ IndexColumns.INTENT_TARGET_PACKAGE, // 10
+ IndexColumns.INTENT_TARGET_CLASS, // 11
+ IndexColumns.ENABLED, // 12
+ IndexColumns.DATA_KEY_REF // 13
+ };
+
+ private static final String[] MATCH_COLUMNS_PRIMARY = {
+ IndexColumns.DATA_TITLE,
+ IndexColumns.DATA_TITLE_NORMALIZED,
+ IndexColumns.DATA_KEYWORDS
+ };
+
+ private static final String[] MATCH_COLUMNS_SECONDARY = {
+ IndexColumns.DATA_SUMMARY_ON,
+ IndexColumns.DATA_SUMMARY_ON_NORMALIZED,
+ IndexColumns.DATA_SUMMARY_OFF,
+ IndexColumns.DATA_SUMMARY_OFF_NORMALIZED,
+ IndexColumns.DATA_ENTRIES
+ };
+
+ // Max number of saved search queries (who will be used for proposing suggestions)
+ private static long MAX_SAVED_SEARCH_QUERY = 64;
+ // Max number of proposed suggestions
+ private static final int MAX_PROPOSED_SUGGESTIONS = 5;
+
+ private static final String BASE_AUTHORITY = "com.android.settings";
+
+ private static final String EMPTY = "";
+ private static final String NON_BREAKING_HYPHEN = "\u2011";
+ private static final String HYPHEN = "-";
+
+ private static final String FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER =
+ "SEARCH_INDEX_DATA_PROVIDER";
+
+ private static final String NODE_NAME_PREFERENCE_SCREEN = "PreferenceScreen";
+ private static final String NODE_NAME_CHECK_BOX_PREFERENCE = "CheckBoxPreference";
+ private static final String NODE_NAME_LIST_PREFERENCE = "ListPreference";
+
+ private static final List<String> EMPTY_LIST = Collections.<String>emptyList();
+
+ private static Index sInstance;
+
+ private static final Pattern REMOVE_DIACRITICALS_PATTERN
+ = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
+
+ /**
+ * A private class to describe the update data for the Index database
+ */
+ private static class UpdateData {
+ public List<SearchIndexableData> dataToUpdate;
+ public List<SearchIndexableData> dataToDelete;
+ public Map<String, List<String>> nonIndexableKeys;
+
+ public boolean forceUpdate = false;
+
+ public UpdateData() {
+ dataToUpdate = new ArrayList<SearchIndexableData>();
+ dataToDelete = new ArrayList<SearchIndexableData>();
+ nonIndexableKeys = new HashMap<String, List<String>>();
+ }
+
+ public UpdateData(UpdateData other) {
+ dataToUpdate = new ArrayList<SearchIndexableData>(other.dataToUpdate);
+ dataToDelete = new ArrayList<SearchIndexableData>(other.dataToDelete);
+ nonIndexableKeys = new HashMap<String, List<String>>(other.nonIndexableKeys);
+ forceUpdate = other.forceUpdate;
+ }
+
+ public UpdateData copy() {
+ return new UpdateData(this);
+ }
+
+ public void clear() {
+ dataToUpdate.clear();
+ dataToDelete.clear();
+ nonIndexableKeys.clear();
+ forceUpdate = false;
+ }
+ }
+
+ private final AtomicBoolean mIsAvailable = new AtomicBoolean(false);
+ private final UpdateData mDataToProcess = new UpdateData();
+ private Context mContext;
+ private final String mBaseAuthority;
+
+ /**
+ * A basic singleton
+ */
+ public static Index getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new Index(context, BASE_AUTHORITY);
+ } else {
+ sInstance.setContext(context);
+ }
+ return sInstance;
+ }
+
+ public Index(Context context, String baseAuthority) {
+ mContext = context;
+ mBaseAuthority = baseAuthority;
+ }
+
+ public void setContext(Context context) {
+ mContext = context;
+ }
+
+ public boolean isAvailable() {
+ return mIsAvailable.get();
+ }
+
+ public Cursor search(String query) {
+ final SQLiteDatabase database = getReadableDatabase();
+ final Cursor[] cursors = new Cursor[2];
+
+ final String primarySql = buildSearchSQL(query, MATCH_COLUMNS_PRIMARY, true);
+ Log.d(LOG_TAG, "Search primary query: " + primarySql);
+ cursors[0] = database.rawQuery(primarySql, null);
+
+ // We need to use an EXCEPT operator as negate MATCH queries do not work.
+ StringBuilder sql = new StringBuilder(
+ buildSearchSQL(query, MATCH_COLUMNS_SECONDARY, false));
+ sql.append(" EXCEPT ");
+ sql.append(primarySql);
+
+ final String secondarySql = sql.toString();
+ Log.d(LOG_TAG, "Search secondary query: " + secondarySql);
+ cursors[1] = database.rawQuery(secondarySql, null);
+
+ return new MergeCursor(cursors);
+ }
+
+ public Cursor getSuggestions(String query) {
+ final String sql = buildSuggestionsSQL(query);
+ Log.d(LOG_TAG, "Suggestions query: " + sql);
+ return getReadableDatabase().rawQuery(sql, null);
+ }
+
+ private String buildSuggestionsSQL(String query) {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("SELECT ");
+ sb.append(IndexDatabaseHelper.SavedQueriesColums.QUERY);
+ sb.append(" FROM ");
+ sb.append(Tables.TABLE_SAVED_QUERIES);
+
+ if (TextUtils.isEmpty(query)) {
+ sb.append(" ORDER BY rowId DESC");
+ } else {
+ sb.append(" WHERE ");
+ sb.append(IndexDatabaseHelper.SavedQueriesColums.QUERY);
+ sb.append(" LIKE ");
+ sb.append("'");
+ sb.append(query);
+ sb.append("%");
+ sb.append("'");
+ }
+
+ sb.append(" LIMIT ");
+ sb.append(MAX_PROPOSED_SUGGESTIONS);
+
+ return sb.toString();
+ }
+
+ public long addSavedQuery(String query){
+ final SaveSearchQueryTask task = new SaveSearchQueryTask();
+ task.execute(query);
+ try {
+ return task.get();
+ } catch (InterruptedException e) {
+ Log.e(LOG_TAG, "Cannot insert saved query: " + query, e);
+ return -1 ;
+ } catch (ExecutionException e) {
+ Log.e(LOG_TAG, "Cannot insert saved query: " + query, e);
+ return -1;
+ }
+ }
+
+ public void update() {
+ final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE);
+ List<ResolveInfo> list =
+ mContext.getPackageManager().queryIntentContentProviders(intent, 0);
+
+ final int size = list.size();
+ for (int n = 0; n < size; n++) {
+ final ResolveInfo info = list.get(n);
+ if (!isWellKnownProvider(info)) {
+ continue;
+ }
+ final String authority = info.providerInfo.authority;
+ final String packageName = info.providerInfo.packageName;
+
+ addIndexablesFromRemoteProvider(packageName, authority);
+ addNonIndexablesKeysFromRemoteProvider(packageName, authority);
+ }
+
+ updateInternal();
+ }
+
+ private boolean addIndexablesFromRemoteProvider(String packageName, String authority) {
+ try {
+ final int baseRank = Ranking.getBaseRankForAuthority(authority);
+
+ final Context context = mBaseAuthority.equals(authority) ?
+ mContext : mContext.createPackageContext(packageName, 0);
+
+ final Uri uriForResources = buildUriForXmlResources(authority);
+ addIndexablesForXmlResourceUri(context, packageName, uriForResources,
+ SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS, baseRank);
+
+ final Uri uriForRawData = buildUriForRawData(authority);
+ addIndexablesForRawDataUri(context, packageName, uriForRawData,
+ SearchIndexablesContract.INDEXABLES_RAW_COLUMNS, baseRank);
+ return true;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(LOG_TAG, "Could not create context for " + packageName + ": "
+ + Log.getStackTraceString(e));
+ return false;
+ }
+ }
+
+ private void addNonIndexablesKeysFromRemoteProvider(String packageName,
+ String authority) {
+ final List<String> keys =
+ getNonIndexablesKeysFromRemoteProvider(packageName, authority);
+ addNonIndexableKeys(packageName, keys);
+ }
+
+ private List<String> getNonIndexablesKeysFromRemoteProvider(String packageName,
+ String authority) {
+ try {
+ final Context packageContext = mContext.createPackageContext(packageName, 0);
+
+ final Uri uriForNonIndexableKeys = buildUriForNonIndexableKeys(authority);
+ return getNonIndexablesKeys(packageContext, uriForNonIndexableKeys,
+ SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(LOG_TAG, "Could not create context for " + packageName + ": "
+ + Log.getStackTraceString(e));
+ return EMPTY_LIST;
+ }
+ }
+
+ private List<String> getNonIndexablesKeys(Context packageContext, Uri uri,
+ String[] projection) {
+
+ final ContentResolver resolver = packageContext.getContentResolver();
+ final Cursor cursor = resolver.query(uri, projection, null, null, null);
+
+ if (cursor == null) {
+ Log.w(LOG_TAG, "Cannot add index data for Uri: " + uri.toString());
+ return EMPTY_LIST;
+ }
+
+ List<String> result = new ArrayList<String>();
+ try {
+ final int count = cursor.getCount();
+ if (count > 0) {
+ while (cursor.moveToNext()) {
+ final String key = cursor.getString(COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE);
+ result.add(key);
+ }
+ }
+ return result;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ public void addIndexableData(SearchIndexableData data) {
+ synchronized (mDataToProcess) {
+ mDataToProcess.dataToUpdate.add(data);
+ }
+ }
+
+ public void addIndexableData(SearchIndexableResource[] array) {
+ synchronized (mDataToProcess) {
+ final int count = array.length;
+ for (int n = 0; n < count; n++) {
+ mDataToProcess.dataToUpdate.add(array[n]);
+ }
+ }
+ }
+
+ public void deleteIndexableData(SearchIndexableData data) {
+ synchronized (mDataToProcess) {
+ mDataToProcess.dataToDelete.add(data);
+ }
+ }
+
+ public void addNonIndexableKeys(String authority, List<String> keys) {
+ synchronized (mDataToProcess) {
+ mDataToProcess.nonIndexableKeys.put(authority, keys);
+ }
+ }
+
+ /**
+ * Only allow a "well known" SearchIndexablesProvider. The provider should:
+ *
+ * - have read/write {@link android.Manifest.permission#READ_SEARCH_INDEXABLES}
+ * - be from a privileged package
+ */
+ private boolean isWellKnownProvider(ResolveInfo info) {
+ final String authority = info.providerInfo.authority;
+ final String packageName = info.providerInfo.applicationInfo.packageName;
+
+ if (TextUtils.isEmpty(authority) || TextUtils.isEmpty(packageName)) {
+ return false;
+ }
+
+ final String readPermission = info.providerInfo.readPermission;
+ final String writePermission = info.providerInfo.writePermission;
+
+ if (TextUtils.isEmpty(readPermission) || TextUtils.isEmpty(writePermission)) {
+ return false;
+ }
+
+ if (!android.Manifest.permission.READ_SEARCH_INDEXABLES.equals(readPermission) ||
+ !android.Manifest.permission.READ_SEARCH_INDEXABLES.equals(writePermission)) {
+ return false;
+ }
+
+ return isPrivilegedPackage(packageName);
+ }
+
+ private boolean isPrivilegedPackage(String packageName) {
+ final PackageManager pm = mContext.getPackageManager();
+ try {
+ PackageInfo packInfo = pm.getPackageInfo(packageName, 0);
+ return ((packInfo.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ private void updateFromRemoteProvider(String packageName, String authority) {
+ if (addIndexablesFromRemoteProvider(packageName, authority)) {
+ updateInternal();
+ }
+ }
+
+ /**
+ * Update the Index for a specific class name resources
+ *
+ * @param className the class name (typically a fragment name).
+ * @param rebuild true means that you want to delete the data from the Index first.
+ * @param includeInSearchResults true means that you want the bit "enabled" set so that the
+ * data will be seen included into the search results
+ */
+ public void updateFromClassNameResource(String className, boolean rebuild,
+ boolean includeInSearchResults) {
+ if (className == null) {
+ throw new IllegalArgumentException("class name cannot be null!");
+ }
+ final SearchIndexableResource res = SearchIndexableResources.getResourceByName(className);
+ if (res == null ) {
+ Log.e(LOG_TAG, "Cannot find SearchIndexableResources for class name: " + className);
+ return;
+ }
+ res.context = mContext;
+ res.enabled = includeInSearchResults;
+ if (rebuild) {
+ deleteIndexableData(res);
+ }
+ addIndexableData(res);
+ mDataToProcess.forceUpdate = true;
+ updateInternal();
+ res.enabled = false;
+ }
+
+ public void updateFromSearchIndexableData(SearchIndexableData data) {
+ addIndexableData(data);
+ mDataToProcess.forceUpdate = true;
+ updateInternal();
+ }
+
+ private SQLiteDatabase getReadableDatabase() {
+ return IndexDatabaseHelper.getInstance(mContext).getReadableDatabase();
+ }
+
+ private SQLiteDatabase getWritableDatabase() {
+ return IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
+ }
+
+ private static Uri buildUriForXmlResources(String authority) {
+ return Uri.parse("content://" + authority + "/" +
+ SearchIndexablesContract.INDEXABLES_XML_RES_PATH);
+ }
+
+ private static Uri buildUriForRawData(String authority) {
+ return Uri.parse("content://" + authority + "/" +
+ SearchIndexablesContract.INDEXABLES_RAW_PATH);
+ }
+
+ private static Uri buildUriForNonIndexableKeys(String authority) {
+ return Uri.parse("content://" + authority + "/" +
+ SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH);
+ }
+
+ private void updateInternal() {
+ synchronized (mDataToProcess) {
+ final UpdateIndexTask task = new UpdateIndexTask();
+ UpdateData copy = mDataToProcess.copy();
+ task.execute(copy);
+ mDataToProcess.clear();
+ }
+ }
+
+ private void addIndexablesForXmlResourceUri(Context packageContext, String packageName,
+ Uri uri, String[] projection, int baseRank) {
+
+ final ContentResolver resolver = packageContext.getContentResolver();
+ final Cursor cursor = resolver.query(uri, projection, null, null, null);
+
+ if (cursor == null) {
+ Log.w(LOG_TAG, "Cannot add index data for Uri: " + uri.toString());
+ return;
+ }
+
+ try {
+ final int count = cursor.getCount();
+ if (count > 0) {
+ while (cursor.moveToNext()) {
+ final int providerRank = cursor.getInt(COLUMN_INDEX_XML_RES_RANK);
+ final int rank = (providerRank > 0) ? baseRank + providerRank : baseRank;
+
+ final int xmlResId = cursor.getInt(COLUMN_INDEX_XML_RES_RESID);
+
+ final String className = cursor.getString(COLUMN_INDEX_XML_RES_CLASS_NAME);
+ final int iconResId = cursor.getInt(COLUMN_INDEX_XML_RES_ICON_RESID);
+
+ final String action = cursor.getString(COLUMN_INDEX_XML_RES_INTENT_ACTION);
+ final String targetPackage = cursor.getString(
+ COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE);
+ final String targetClass = cursor.getString(
+ COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS);
+
+ SearchIndexableResource sir = new SearchIndexableResource(packageContext);
+ sir.rank = rank;
+ sir.xmlResId = xmlResId;
+ sir.className = className;
+ sir.packageName = packageName;
+ sir.iconResId = iconResId;
+ sir.intentAction = action;
+ sir.intentTargetPackage = targetPackage;
+ sir.intentTargetClass = targetClass;
+
+ addIndexableData(sir);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private void addIndexablesForRawDataUri(Context packageContext, String packageName,
+ Uri uri, String[] projection, int baseRank) {
+
+ final ContentResolver resolver = packageContext.getContentResolver();
+ final Cursor cursor = resolver.query(uri, projection, null, null, null);
+
+ if (cursor == null) {
+ Log.w(LOG_TAG, "Cannot add index data for Uri: " + uri.toString());
+ return;
+ }
+
+ try {
+ final int count = cursor.getCount();
+ if (count > 0) {
+ while (cursor.moveToNext()) {
+ final int providerRank = cursor.getInt(COLUMN_INDEX_RAW_RANK);
+ final int rank = (providerRank > 0) ? baseRank + providerRank : baseRank;
+
+ final String title = cursor.getString(COLUMN_INDEX_RAW_TITLE);
+ final String summaryOn = cursor.getString(COLUMN_INDEX_RAW_SUMMARY_ON);
+ final String summaryOff = cursor.getString(COLUMN_INDEX_RAW_SUMMARY_OFF);
+ final String entries = cursor.getString(COLUMN_INDEX_RAW_ENTRIES);
+ final String keywords = cursor.getString(COLUMN_INDEX_RAW_KEYWORDS);
+
+ final String screenTitle = cursor.getString(COLUMN_INDEX_RAW_SCREEN_TITLE);
+
+ final String className = cursor.getString(COLUMN_INDEX_RAW_CLASS_NAME);
+ final int iconResId = cursor.getInt(COLUMN_INDEX_RAW_ICON_RESID);
+
+ final String action = cursor.getString(COLUMN_INDEX_RAW_INTENT_ACTION);
+ final String targetPackage = cursor.getString(
+ COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE);
+ final String targetClass = cursor.getString(
+ COLUMN_INDEX_RAW_INTENT_TARGET_CLASS);
+
+ final String key = cursor.getString(COLUMN_INDEX_RAW_KEY);
+ final int userId = cursor.getInt(COLUMN_INDEX_RAW_USER_ID);
+
+ SearchIndexableRaw data = new SearchIndexableRaw(packageContext);
+ data.rank = rank;
+ data.title = title;
+ data.summaryOn = summaryOn;
+ data.summaryOff = summaryOff;
+ data.entries = entries;
+ data.keywords = keywords;
+ data.screenTitle = screenTitle;
+ data.className = className;
+ data.packageName = packageName;
+ data.iconResId = iconResId;
+ data.intentAction = action;
+ data.intentTargetPackage = targetPackage;
+ data.intentTargetClass = targetClass;
+ data.key = key;
+ data.userId = userId;
+
+ addIndexableData(data);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private String buildSearchSQL(String query, String[] colums, boolean withOrderBy) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(buildSearchSQLForColumn(query, colums));
+ if (withOrderBy) {
+ sb.append(" ORDER BY ");
+ sb.append(IndexColumns.DATA_RANK);
+ }
+ return sb.toString();
+ }
+
+ private String buildSearchSQLForColumn(String query, String[] columnNames) {
+ 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(buildSearchWhereStringForColumns(query, columnNames));
+
+ return sb.toString();
+ }
+
+ private String buildSearchWhereStringForColumns(String query, String[] columnNames) {
+ final StringBuilder sb = new StringBuilder(Tables.TABLE_PREFS_INDEX);
+ sb.append(" MATCH ");
+ DatabaseUtils.appendEscapedSQLString(sb,
+ buildSearchMatchStringForColumns(query, columnNames));
+ sb.append(" AND ");
+ sb.append(IndexColumns.LOCALE);
+ sb.append(" = ");
+ DatabaseUtils.appendEscapedSQLString(sb, Locale.getDefault().toString());
+ sb.append(" AND ");
+ sb.append(IndexColumns.ENABLED);
+ sb.append(" = 1");
+ return sb.toString();
+ }
+
+ private String buildSearchMatchStringForColumns(String query, String[] columnNames) {
+ final String value = query + "*";
+ StringBuilder sb = new StringBuilder();
+ final int count = columnNames.length;
+ for (int n = 0; n < count; n++) {
+ sb.append(columnNames[n]);
+ sb.append(":");
+ sb.append(value);
+ if (n < count - 1) {
+ sb.append(" OR ");
+ }
+ }
+ return sb.toString();
+ }
+
+ private void indexOneSearchIndexableData(SQLiteDatabase database, String localeStr,
+ SearchIndexableData data, Map<String, List<String>> nonIndexableKeys) {
+ if (data instanceof SearchIndexableResource) {
+ indexOneResource(database, localeStr, (SearchIndexableResource) data, nonIndexableKeys);
+ } else if (data instanceof SearchIndexableRaw) {
+ indexOneRaw(database, localeStr, (SearchIndexableRaw) data);
+ }
+ }
+
+ private void indexOneRaw(SQLiteDatabase database, String localeStr,
+ SearchIndexableRaw raw) {
+ // Should be the same locale as the one we are processing
+ if (!raw.locale.toString().equalsIgnoreCase(localeStr)) {
+ return;
+ }
+
+ updateOneRowWithFilteredData(database, localeStr,
+ raw.title,
+ raw.summaryOn,
+ raw.summaryOff,
+ raw.entries,
+ raw.className,
+ raw.screenTitle,
+ raw.iconResId,
+ raw.rank,
+ raw.keywords,
+ raw.intentAction,
+ raw.intentTargetPackage,
+ raw.intentTargetClass,
+ raw.enabled,
+ raw.key,
+ raw.userId);
+ }
+
+ private static boolean isIndexableClass(final Class<?> clazz) {
+ return (clazz != null) && Indexable.class.isAssignableFrom(clazz);
+ }
+
+ private static Class<?> getIndexableClass(String className) {
+ final Class<?> clazz;
+ try {
+ clazz = Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ Log.d(LOG_TAG, "Cannot find class: " + className);
+ return null;
+ }
+ return isIndexableClass(clazz) ? clazz : null;
+ }
+
+ private void indexOneResource(SQLiteDatabase database, String localeStr,
+ SearchIndexableResource sir, Map<String, List<String>> nonIndexableKeysFromResource) {
+
+ if (sir == null) {
+ Log.e(LOG_TAG, "Cannot index a null resource!");
+ return;
+ }
+
+ final List<String> nonIndexableKeys = new ArrayList<String>();
+
+ if (sir.xmlResId > SearchIndexableResources.NO_DATA_RES_ID) {
+ List<String> resNonIndxableKeys = nonIndexableKeysFromResource.get(sir.packageName);
+ if (resNonIndxableKeys != null && resNonIndxableKeys.size() > 0) {
+ nonIndexableKeys.addAll(resNonIndxableKeys);
+ }
+
+ indexFromResource(sir.context, database, localeStr,
+ sir.xmlResId, sir.className, sir.iconResId, sir.rank,
+ sir.intentAction, sir.intentTargetPackage, sir.intentTargetClass,
+ nonIndexableKeys);
+ } else {
+ if (TextUtils.isEmpty(sir.className)) {
+ Log.w(LOG_TAG, "Cannot index an empty Search Provider name!");
+ return;
+ }
+
+ final Class<?> clazz = getIndexableClass(sir.className);
+ if (clazz == null) {
+ Log.d(LOG_TAG, "SearchIndexableResource '" + sir.className +
+ "' should implement the " + Indexable.class.getName() + " interface!");
+ return;
+ }
+
+ // Will be non null only for a Local provider implementing a
+ // SEARCH_INDEX_DATA_PROVIDER field
+ final Indexable.SearchIndexProvider provider = getSearchIndexProvider(clazz);
+ if (provider != null) {
+ List<String> providerNonIndexableKeys = provider.getNonIndexableKeys(sir.context);
+ if (providerNonIndexableKeys != null && providerNonIndexableKeys.size() > 0) {
+ nonIndexableKeys.addAll(providerNonIndexableKeys);
+ }
+
+ indexFromProvider(mContext, database, localeStr, provider, sir.className,
+ sir.iconResId, sir.rank, sir.enabled, nonIndexableKeys);
+ }
+ }
+ }
+
+ private Indexable.SearchIndexProvider getSearchIndexProvider(final Class<?> clazz) {
+ try {
+ final Field f = clazz.getField(FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER);
+ return (Indexable.SearchIndexProvider) f.get(null);
+ } catch (NoSuchFieldException e) {
+ Log.d(LOG_TAG, "Cannot find field '" + FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'");
+ } catch (SecurityException se) {
+ Log.d(LOG_TAG,
+ "Security exception for field '" + FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'");
+ } catch (IllegalAccessException e) {
+ Log.d(LOG_TAG,
+ "Illegal access to field '" + FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'");
+ } catch (IllegalArgumentException e) {
+ Log.d(LOG_TAG,
+ "Illegal argument when accessing field '" +
+ FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'");
+ }
+ return null;
+ }
+
+ private void indexFromResource(Context context, SQLiteDatabase database, String localeStr,
+ int xmlResId, String fragmentName, int iconResId, int rank,
+ String intentAction, String intentTargetPackage, String intentTargetClass,
+ List<String> nonIndexableKeys) {
+
+ XmlResourceParser parser = null;
+ try {
+ parser = context.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 (!NODE_NAME_PREFERENCE_SCREEN.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 screenTitle = getDataTitle(context, attrs);
+
+ String key = getDataKey(context, attrs);
+
+ String title;
+ String summary;
+ String keywords;
+
+ // Insert rows for the main PreferenceScreen node. Rewrite the data for removing
+ // hyphens.
+ if (!nonIndexableKeys.contains(key)) {
+ title = getDataTitle(context, attrs);
+ summary = getDataSummary(context, attrs);
+ keywords = getDataKeywords(context, attrs);
+
+ updateOneRowWithFilteredData(database, localeStr, title, summary, null, null,
+ fragmentName, screenTitle, iconResId, rank,
+ keywords, intentAction, intentTargetPackage, intentTargetClass, true,
+ key, -1 /* default user id */);
+ }
+
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ nodeName = parser.getName();
+
+ key = getDataKey(context, attrs);
+ if (nonIndexableKeys.contains(key)) {
+ continue;
+ }
+
+ title = getDataTitle(context, attrs);
+ keywords = getDataKeywords(context, attrs);
+
+ if (!nodeName.equals(NODE_NAME_CHECK_BOX_PREFERENCE)) {
+ summary = getDataSummary(context, attrs);
+
+ String entries = null;
+
+ if (nodeName.endsWith(NODE_NAME_LIST_PREFERENCE)) {
+ entries = getDataEntries(context, attrs);
+ }
+
+ // Insert rows for the child nodes of PreferenceScreen
+ updateOneRowWithFilteredData(database, localeStr, title, summary, null, entries,
+ fragmentName, screenTitle, iconResId, rank,
+ keywords, intentAction, intentTargetPackage, intentTargetClass,
+ true, key, -1 /* default user id */);
+ } else {
+ String summaryOn = getDataSummaryOn(context, attrs);
+ String summaryOff = getDataSummaryOff(context, attrs);
+
+ if (TextUtils.isEmpty(summaryOn) && TextUtils.isEmpty(summaryOff)) {
+ summaryOn = getDataSummary(context, attrs);
+ }
+
+ updateOneRowWithFilteredData(database, localeStr, title, summaryOn, summaryOff,
+ null, fragmentName, screenTitle, iconResId, rank,
+ keywords, intentAction, intentTargetPackage, intentTargetClass,
+ true, key, -1 /* default user id */);
+ }
+ }
+
+ } 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 indexFromProvider(Context context, SQLiteDatabase database, String localeStr,
+ Indexable.SearchIndexProvider provider, String className, int iconResId, int rank,
+ boolean enabled, List<String> nonIndexableKeys) {
+
+ if (provider == null) {
+ Log.w(LOG_TAG, "Cannot find provider: " + className);
+ return;
+ }
+
+ final List<SearchIndexableRaw> rawList = provider.getRawDataToIndex(context, enabled);
+
+ if (rawList != null) {
+ final int rawSize = rawList.size();
+ for (int i = 0; i < rawSize; i++) {
+ SearchIndexableRaw raw = rawList.get(i);
+
+ // Should be the same locale as the one we are processing
+ if (!raw.locale.toString().equalsIgnoreCase(localeStr)) {
+ continue;
+ }
+
+ if (nonIndexableKeys.contains(raw.key)) {
+ continue;
+ }
+
+ updateOneRowWithFilteredData(database, localeStr,
+ raw.title,
+ raw.summaryOn,
+ raw.summaryOff,
+ raw.entries,
+ className,
+ raw.screenTitle,
+ iconResId,
+ rank,
+ raw.keywords,
+ raw.intentAction,
+ raw.intentTargetPackage,
+ raw.intentTargetClass,
+ raw.enabled,
+ raw.key,
+ raw.userId);
+ }
+ }
+
+ final List<SearchIndexableResource> resList =
+ provider.getXmlResourcesToIndex(context, enabled);
+ if (resList != null) {
+ final int resSize = resList.size();
+ for (int i = 0; i < resSize; i++) {
+ SearchIndexableResource item = resList.get(i);
+
+ // Should be the same locale as the one we are processing
+ if (!item.locale.toString().equalsIgnoreCase(localeStr)) {
+ continue;
+ }
+
+ final int itemIconResId = (item.iconResId == 0) ? iconResId : item.iconResId;
+ final int itemRank = (item.rank == 0) ? rank : item.rank;
+ String itemClassName = (TextUtils.isEmpty(item.className))
+ ? className : item.className;
+
+ indexFromResource(context, database, localeStr,
+ item.xmlResId, itemClassName, itemIconResId, itemRank,
+ item.intentAction, item.intentTargetPackage,
+ item.intentTargetClass, nonIndexableKeys);
+ }
+ }
+ }
+
+ private void updateOneRowWithFilteredData(SQLiteDatabase database, String locale,
+ String title, String summaryOn, String summaryOff, String entries,
+ String className,
+ String screenTitle, int iconResId, int rank, String keywords,
+ String intentAction, String intentTargetPackage, String intentTargetClass,
+ boolean enabled, String key, int userId) {
+
+ final String updatedTitle = normalizeHyphen(title);
+ final String updatedSummaryOn = normalizeHyphen(summaryOn);
+ final String updatedSummaryOff = normalizeHyphen(summaryOff);
+
+ final String normalizedTitle = normalizeString(updatedTitle);
+ final String normalizedSummaryOn = normalizeString(updatedSummaryOn);
+ final String normalizedSummaryOff = normalizeString(updatedSummaryOff);
+
+ updateOneRow(database, locale,
+ updatedTitle, normalizedTitle, updatedSummaryOn, normalizedSummaryOn,
+ updatedSummaryOff, normalizedSummaryOff, entries,
+ className, screenTitle, iconResId,
+ rank, keywords, intentAction, intentTargetPackage, intentTargetClass, enabled,
+ key, userId);
+ }
+
+ private static String normalizeHyphen(String input) {
+ return (input != null) ? input.replaceAll(NON_BREAKING_HYPHEN, HYPHEN) : EMPTY;
+ }
+
+ private static String normalizeString(String input) {
+ final String nohyphen = (input != null) ? input.replaceAll(HYPHEN, EMPTY) : EMPTY;
+ final String normalized = Normalizer.normalize(nohyphen, Normalizer.Form.NFD);
+
+ return REMOVE_DIACRITICALS_PATTERN.matcher(normalized).replaceAll("").toLowerCase();
+ }
+
+ private void updateOneRow(SQLiteDatabase database, String locale,
+ String updatedTitle, String normalizedTitle,
+ String updatedSummaryOn, String normalizedSummaryOn,
+ String updatedSummaryOff, String normalizedSummaryOff, String entries,
+ String className, String screenTitle, int iconResId, int rank, String keywords,
+ String intentAction, String intentTargetPackage, String intentTargetClass,
+ boolean enabled, String key, int userId) {
+
+ if (TextUtils.isEmpty(updatedTitle)) {
+ return;
+ }
+
+ // The DocID should contains more than the title string itself (you may have two settings
+ // with the same title). So we need to use a combination of the title and the screenTitle.
+ StringBuilder sb = new StringBuilder(updatedTitle);
+ sb.append(screenTitle);
+ int docId = sb.toString().hashCode();
+
+ ContentValues values = new ContentValues();
+ values.put(IndexColumns.DOCID, docId);
+ values.put(IndexColumns.LOCALE, locale);
+ values.put(IndexColumns.DATA_RANK, rank);
+ values.put(IndexColumns.DATA_TITLE, updatedTitle);
+ values.put(IndexColumns.DATA_TITLE_NORMALIZED, normalizedTitle);
+ values.put(IndexColumns.DATA_SUMMARY_ON, updatedSummaryOn);
+ values.put(IndexColumns.DATA_SUMMARY_ON_NORMALIZED, normalizedSummaryOn);
+ values.put(IndexColumns.DATA_SUMMARY_OFF, updatedSummaryOff);
+ values.put(IndexColumns.DATA_SUMMARY_OFF_NORMALIZED, normalizedSummaryOff);
+ values.put(IndexColumns.DATA_ENTRIES, entries);
+ values.put(IndexColumns.DATA_KEYWORDS, keywords);
+ values.put(IndexColumns.CLASS_NAME, className);
+ values.put(IndexColumns.SCREEN_TITLE, screenTitle);
+ values.put(IndexColumns.INTENT_ACTION, intentAction);
+ values.put(IndexColumns.INTENT_TARGET_PACKAGE, intentTargetPackage);
+ values.put(IndexColumns.INTENT_TARGET_CLASS, intentTargetClass);
+ values.put(IndexColumns.ICON, iconResId);
+ values.put(IndexColumns.ENABLED, enabled);
+ values.put(IndexColumns.DATA_KEY_REF, key);
+ values.put(IndexColumns.USER_ID, userId);
+
+ database.replaceOrThrow(Tables.TABLE_PREFS_INDEX, null, values);
+ }
+
+ private String getDataKey(Context context, AttributeSet attrs) {
+ return getData(context, attrs,
+ com.android.internal.R.styleable.Preference,
+ com.android.internal.R.styleable.Preference_key);
+ }
+
+ private String getDataTitle(Context context, AttributeSet attrs) {
+ return getData(context, attrs,
+ com.android.internal.R.styleable.Preference,
+ com.android.internal.R.styleable.Preference_title);
+ }
+
+ private String getDataSummary(Context context, AttributeSet attrs) {
+ return getData(context, attrs,
+ com.android.internal.R.styleable.Preference,
+ com.android.internal.R.styleable.Preference_summary);
+ }
+
+ private String getDataSummaryOn(Context context, AttributeSet attrs) {
+ return getData(context, attrs,
+ com.android.internal.R.styleable.CheckBoxPreference,
+ com.android.internal.R.styleable.CheckBoxPreference_summaryOn);
+ }
+
+ private String getDataSummaryOff(Context context, AttributeSet attrs) {
+ return getData(context, attrs,
+ com.android.internal.R.styleable.CheckBoxPreference,
+ com.android.internal.R.styleable.CheckBoxPreference_summaryOff);
+ }
+
+ private String getDataEntries(Context context, AttributeSet attrs) {
+ return getDataEntries(context, attrs,
+ com.android.internal.R.styleable.ListPreference,
+ com.android.internal.R.styleable.ListPreference_entries);
+ }
+
+ private String getDataKeywords(Context context, AttributeSet attrs) {
+ return getData(context, attrs, R.styleable.Preference, R.styleable.Preference_keywords);
+ }
+
+ private String getData(Context context, AttributeSet set, int[] attrs, int resId) {
+ final TypedArray sa = context.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 = context.getText(tv.resourceId);
+ } else {
+ data = tv.string;
+ }
+ }
+ return (data != null) ? data.toString() : null;
+ }
+
+ private String getDataEntries(Context context, AttributeSet set, int[] attrs, int resId) {
+ final TypedArray sa = context.obtainStyledAttributes(set, attrs);
+ final TypedValue tv = sa.peekValue(resId);
+
+ String[] data = null;
+ if (tv != null && tv.type == TypedValue.TYPE_REFERENCE) {
+ if (tv.resourceId != 0) {
+ data = context.getResources().getStringArray(tv.resourceId);
+ }
+ }
+ final int count = (data == null ) ? 0 : data.length;
+ if (count == 0) {
+ return null;
+ }
+ final StringBuilder result = new StringBuilder();
+ for (int n = 0; n < count; n++) {
+ result.append(data[n]);
+ result.append(ENTRIES_SEPARATOR);
+ }
+ return result.toString();
+ }
+
+ private int getResId(Context context, AttributeSet set, int[] attrs, int resId) {
+ final TypedArray sa = context.obtainStyledAttributes(set, attrs);
+ final TypedValue tv = sa.peekValue(resId);
+
+ if (tv != null && tv.type == TypedValue.TYPE_STRING) {
+ return tv.resourceId;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * A private class for updating the Index database
+ */
+ private class UpdateIndexTask extends AsyncTask<UpdateData, Integer, Void> {
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ mIsAvailable.set(false);
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
+ mIsAvailable.set(true);
+ }
+
+ @Override
+ protected Void doInBackground(UpdateData... params) {
+ final List<SearchIndexableData> dataToUpdate = params[0].dataToUpdate;
+ final List<SearchIndexableData> dataToDelete = params[0].dataToDelete;
+ final Map<String, List<String>> nonIndexableKeys = params[0].nonIndexableKeys;
+
+ final boolean forceUpdate = params[0].forceUpdate;
+
+ final SQLiteDatabase database = getWritableDatabase();
+ final String localeStr = Locale.getDefault().toString();
+
+ try {
+ database.beginTransaction();
+ if (dataToDelete.size() > 0) {
+ processDataToDelete(database, localeStr, dataToDelete);
+ }
+ if (dataToUpdate.size() > 0) {
+ processDataToUpdate(database, localeStr, dataToUpdate, nonIndexableKeys,
+ forceUpdate);
+ }
+ database.setTransactionSuccessful();
+ } finally {
+ database.endTransaction();
+ }
+
+ return null;
+ }
+
+ private boolean processDataToUpdate(SQLiteDatabase database, String localeStr,
+ List<SearchIndexableData> dataToUpdate, Map<String, List<String>> nonIndexableKeys,
+ boolean forceUpdate) {
+
+ if (!forceUpdate && isLocaleAlreadyIndexed(database, localeStr)) {
+ Log.d(LOG_TAG, "Locale '" + localeStr + "' is already indexed");
+ return true;
+ }
+
+ boolean result = false;
+ final long current = System.currentTimeMillis();
+
+ final int count = dataToUpdate.size();
+ for (int n = 0; n < count; n++) {
+ final SearchIndexableData data = dataToUpdate.get(n);
+ try {
+ indexOneSearchIndexableData(database, localeStr, data, nonIndexableKeys);
+ } catch (Exception e) {
+ Log.e(LOG_TAG,
+ "Cannot index: " + data.className + " for locale: " + localeStr, e);
+ }
+ }
+
+ final long now = System.currentTimeMillis();
+ Log.d(LOG_TAG, "Indexing locale '" + localeStr + "' took " +
+ (now - current) + " millis");
+ return result;
+ }
+
+ private boolean processDataToDelete(SQLiteDatabase database, String localeStr,
+ List<SearchIndexableData> dataToDelete) {
+
+ boolean result = false;
+ final long current = System.currentTimeMillis();
+
+ final int count = dataToDelete.size();
+ for (int n = 0; n < count; n++) {
+ final SearchIndexableData data = dataToDelete.get(n);
+ if (data == null) {
+ continue;
+ }
+ if (!TextUtils.isEmpty(data.className)) {
+ delete(database, IndexColumns.CLASS_NAME, data.className);
+ } else {
+ if (data instanceof SearchIndexableRaw) {
+ final SearchIndexableRaw raw = (SearchIndexableRaw) data;
+ if (!TextUtils.isEmpty(raw.title)) {
+ delete(database, IndexColumns.DATA_TITLE, raw.title);
+ }
+ }
+ }
+ }
+
+ final long now = System.currentTimeMillis();
+ Log.d(LOG_TAG, "Deleting data for locale '" + localeStr + "' took " +
+ (now - current) + " millis");
+ return result;
+ }
+
+ private int delete(SQLiteDatabase database, String columName, String value) {
+ final String whereClause = columName + "=?";
+ final String[] whereArgs = new String[] { value };
+
+ return database.delete(Tables.TABLE_PREFS_INDEX, whereClause, whereArgs);
+ }
+
+ private boolean isLocaleAlreadyIndexed(SQLiteDatabase database, String locale) {
+ Cursor cursor = null;
+ boolean result = false;
+ final StringBuilder sb = new StringBuilder(IndexColumns.LOCALE);
+ sb.append(" = ");
+ DatabaseUtils.appendEscapedSQLString(sb, locale);
+ 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;
+ }
+ }
+
+ /**
+ * A basic AsyncTask for saving a Search query into the database
+ */
+ private class SaveSearchQueryTask extends AsyncTask<String, Void, Long> {
+
+ @Override
+ protected Long doInBackground(String... params) {
+ final long now = new Date().getTime();
+
+ final ContentValues values = new ContentValues();
+ values.put(IndexDatabaseHelper.SavedQueriesColums.QUERY, params[0]);
+ values.put(IndexDatabaseHelper.SavedQueriesColums.TIME_STAMP, now);
+
+ final SQLiteDatabase database = getWritableDatabase();
+
+ long lastInsertedRowId = -1;
+ try {
+ // First, delete all saved queries that are the same
+ database.delete(Tables.TABLE_SAVED_QUERIES,
+ IndexDatabaseHelper.SavedQueriesColums.QUERY + " = ?",
+ new String[] { params[0] });
+
+ // Second, insert the saved query
+ lastInsertedRowId =
+ database.insertOrThrow(Tables.TABLE_SAVED_QUERIES, null, values);
+
+ // Last, remove "old" saved queries
+ final long delta = lastInsertedRowId - MAX_SAVED_SEARCH_QUERY;
+ if (delta > 0) {
+ int count = database.delete(Tables.TABLE_SAVED_QUERIES, "rowId <= ?",
+ new String[] { Long.toString(delta) });
+ Log.d(LOG_TAG, "Deleted '" + count + "' saved Search query(ies)");
+ }
+ } catch (Exception e) {
+ Log.d(LOG_TAG, "Cannot update saved Search queries", e);
+ }
+
+ return lastInsertedRowId;
+ }
+ }
+}
diff --git a/src/com/android/settings/search/IndexDatabaseHelper.java b/src/com/android/settings/search/IndexDatabaseHelper.java
new file mode 100644
index 0000000..152cbf3
--- /dev/null
+++ b/src/com/android/settings/search/IndexDatabaseHelper.java
@@ -0,0 +1,223 @@
+/*
+ * 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.search;
+
+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 = 115;
+
+ public interface Tables {
+ public static final String TABLE_PREFS_INDEX = "prefs_index";
+ public static final String TABLE_META_INDEX = "meta_index";
+ public static final String TABLE_SAVED_QUERIES = "saved_queries";
+ }
+
+ public interface IndexColumns {
+ public static final String DOCID = "docid";
+ 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_TITLE_NORMALIZED = "data_title_normalized";
+ public static final String DATA_SUMMARY_ON = "data_summary_on";
+ public static final String DATA_SUMMARY_ON_NORMALIZED = "data_summary_on_normalized";
+ public static final String DATA_SUMMARY_OFF = "data_summary_off";
+ public static final String DATA_SUMMARY_OFF_NORMALIZED = "data_summary_off_normalized";
+ public static final String DATA_ENTRIES = "data_entries";
+ public static final String DATA_KEYWORDS = "data_keywords";
+ public static final String CLASS_NAME = "class_name";
+ public static final String SCREEN_TITLE = "screen_title";
+ public static final String INTENT_ACTION = "intent_action";
+ public static final String INTENT_TARGET_PACKAGE = "intent_target_package";
+ public static final String INTENT_TARGET_CLASS = "intent_target_class";
+ public static final String ICON = "icon";
+ public static final String ENABLED = "enabled";
+ public static final String DATA_KEY_REF = "data_key_reference";
+ public static final String USER_ID = "user_id";
+ }
+
+ public interface MetaColumns {
+ public static final String BUILD = "build";
+ }
+
+ public interface SavedQueriesColums {
+ public static final String QUERY = "query";
+ public static final String TIME_STAMP = "timestamp";
+ }
+
+ 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_TITLE_NORMALIZED +
+ ", " +
+ IndexColumns.DATA_SUMMARY_ON +
+ ", " +
+ IndexColumns.DATA_SUMMARY_ON_NORMALIZED +
+ ", " +
+ IndexColumns.DATA_SUMMARY_OFF +
+ ", " +
+ IndexColumns.DATA_SUMMARY_OFF_NORMALIZED +
+ ", " +
+ IndexColumns.DATA_ENTRIES +
+ ", " +
+ IndexColumns.DATA_KEYWORDS +
+ ", " +
+ IndexColumns.SCREEN_TITLE +
+ ", " +
+ IndexColumns.CLASS_NAME +
+ ", " +
+ IndexColumns.ICON +
+ ", " +
+ IndexColumns.INTENT_ACTION +
+ ", " +
+ IndexColumns.INTENT_TARGET_PACKAGE +
+ ", " +
+ IndexColumns.INTENT_TARGET_CLASS +
+ ", " +
+ IndexColumns.ENABLED +
+ ", " +
+ IndexColumns.DATA_KEY_REF +
+ ", " +
+ IndexColumns.USER_ID +
+ ");";
+
+ private static final String CREATE_META_TABLE =
+ "CREATE TABLE " + Tables.TABLE_META_INDEX +
+ "(" +
+ MetaColumns.BUILD + " VARCHAR(32) NOT NULL" +
+ ")";
+
+ private static final String CREATE_SAVED_QUERIES_TABLE =
+ "CREATE TABLE " + Tables.TABLE_SAVED_QUERIES +
+ "(" +
+ SavedQueriesColums.QUERY + " VARCHAR(64) NOT NULL" +
+ ", " +
+ SavedQueriesColums.TIME_STAMP + " INTEGER" +
+ ")";
+
+ 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(CREATE_SAVED_QUERIES_TABLE);
+ db.execSQL(INSERT_BUILD_VERSION);
+ Log.i(TAG, "Bootstrapped database");
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ super.onOpen(db);
+
+ Log.i(TAG, "Using schema version: " + db.getVersion());
+
+ if (!Build.VERSION.INCREMENTAL.equals(getBuildVersion(db))) {
+ Log.w(TAG, "Index needs to be rebuilt as build-version is not the same");
+ // We need to drop the tables and recreate them
+ reconstruct(db);
+ } else {
+ Log.i(TAG, "Index is fine");
+ }
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion < DATABASE_VERSION) {
+ Log.w(TAG, "Detected schema version '" + oldVersion + "'. " +
+ "Index needs to be rebuilt for schema version '" + newVersion + "'.");
+ // We need to drop the tables and recreate them
+ reconstruct(db);
+ }
+ }
+
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.w(TAG, "Detected schema version '" + oldVersion + "'. " +
+ "Index needs to be rebuilt for schema version '" + newVersion + "'.");
+ // We need to drop the tables and recreate them
+ reconstruct(db);
+ }
+
+ private void reconstruct(SQLiteDatabase db) {
+ dropTables(db);
+ bootstrapDB(db);
+ }
+
+ 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);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SAVED_QUERIES);
+ }
+}
diff --git a/src/com/android/settings/search/Indexable.java b/src/com/android/settings/search/Indexable.java
new file mode 100644
index 0000000..19f88ae
--- /dev/null
+++ b/src/com/android/settings/search/Indexable.java
@@ -0,0 +1,69 @@
+/*
+ * 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.search;
+
+import android.content.Context;
+import android.provider.SearchIndexableResource;
+
+import java.util.List;
+
+/**
+ * Interface for classes whose instances can provide data for indexing.
+ *
+ * Classes implementing the Indexable interface must have a static field called
+ * <code>SEARCH_INDEX_DATA_PROVIDER</code>, which is an object implementing the
+ * {@link Indexable.SearchIndexProvider} interface.
+ *
+ * See {@link android.provider.SearchIndexableResource} and {@link SearchIndexableRaw}.
+ *
+ */
+public interface Indexable {
+
+ public interface SearchIndexProvider {
+ /**
+ * Return a list of references for indexing.
+ *
+ * See {@link android.provider.SearchIndexableResource}
+ *
+ *
+ * @param context the context.
+ * @param enabled hint telling if the data needs to be considered into the search results
+ * or not.
+ * @return a list of {@link android.provider.SearchIndexableResource} references.
+ * Can be null.
+ */
+ List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled);
+
+ /**
+ * Return a list of raw data for indexing. See {@link SearchIndexableRaw}
+ *
+ * @param context the context.
+ * @param enabled hint telling if the data needs to be considered into the search results
+ * or not.
+ * @return a list of {@link SearchIndexableRaw} references. Can be null.
+ */
+ List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled);
+
+ /**
+ * Return a list of data keys that cannot be indexed. See {@link SearchIndexableRaw}
+ *
+ * @param context the context.
+ * @return a list of {@link SearchIndexableRaw} references. Can be null.
+ */
+ List<String> getNonIndexableKeys(Context context);
+ }
+}
diff --git a/src/com/android/settings/search/Ranking.java b/src/com/android/settings/search/Ranking.java
new file mode 100644
index 0000000..2c76002
--- /dev/null
+++ b/src/com/android/settings/search/Ranking.java
@@ -0,0 +1,180 @@
+/*
+ * 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.search;
+
+import com.android.settings.ChooseLockGeneric;
+import com.android.settings.DataUsageSummary;
+import com.android.settings.DateTimeSettings;
+import com.android.settings.DevelopmentSettings;
+import com.android.settings.DeviceInfoSettings;
+import com.android.settings.DisplaySettings;
+import com.android.settings.HomeSettings;
+import com.android.settings.ScreenPinningSettings;
+import com.android.settings.PrivacySettings;
+import com.android.settings.SecuritySettings;
+import com.android.settings.WallpaperTypeSettings;
+import com.android.settings.WirelessSettings;
+import com.android.settings.accessibility.AccessibilitySettings;
+import com.android.settings.bluetooth.BluetoothSettings;
+import com.android.settings.deviceinfo.Memory;
+import com.android.settings.fuelgauge.BatterySaverSettings;
+import com.android.settings.fuelgauge.PowerUsageSummary;
+import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
+import com.android.settings.location.LocationSettings;
+import com.android.settings.net.DataUsageMeteredSettings;
+import com.android.settings.notification.NotificationSettings;
+import com.android.settings.notification.OtherSoundSettings;
+import com.android.settings.notification.ZenModeSettings;
+import com.android.settings.print.PrintSettingsFragment;
+import com.android.settings.sim.SimSettings;
+import com.android.settings.users.UserSettings;
+import com.android.settings.voice.VoiceInputSettings;
+import com.android.settings.wifi.AdvancedWifiSettings;
+import com.android.settings.wifi.SavedAccessPointsWifiSettings;
+import com.android.settings.wifi.WifiSettings;
+
+import java.util.HashMap;
+
+/**
+ * Utility class for dealing with Search Ranking.
+ */
+public final class Ranking {
+
+ public static final int RANK_WIFI = 1;
+ public static final int RANK_BT = 2;
+ public static final int RANK_SIM = 3;
+ public static final int RANK_DATA_USAGE = 4;
+ public static final int RANK_WIRELESS = 5;
+ public static final int RANK_HOME = 6;
+ public static final int RANK_DISPLAY = 7;
+ public static final int RANK_WALLPAPER = 8;
+ public static final int RANK_NOTIFICATIONS = 9;
+ public static final int RANK_MEMORY = 10;
+ public static final int RANK_POWER_USAGE = 11;
+ public static final int RANK_USERS = 12;
+ public static final int RANK_LOCATION = 13;
+ public static final int RANK_SECURITY = 14;
+ public static final int RANK_IME = 15;
+ public static final int RANK_PRIVACY = 16;
+ public static final int RANK_DATE_TIME = 17;
+ public static final int RANK_ACCESSIBILITY = 18;
+ public static final int RANK_PRINTING = 19;
+ public static final int RANK_DEVELOPEMENT = 20;
+ public static final int RANK_DEVICE_INFO = 21;
+
+ public static final int RANK_UNDEFINED = -1;
+ public static final int RANK_OTHERS = 1024;
+ public static final int BASE_RANK_DEFAULT = 2048;
+
+ public static int sCurrentBaseRank = BASE_RANK_DEFAULT;
+
+ private static HashMap<String, Integer> sRankMap = new HashMap<String, Integer>();
+ private static HashMap<String, Integer> sBaseRankMap = new HashMap<String, Integer>();
+
+ static {
+ // Wi-Fi
+ sRankMap.put(WifiSettings.class.getName(), RANK_WIFI);
+ sRankMap.put(AdvancedWifiSettings.class.getName(), RANK_WIFI);
+ sRankMap.put(SavedAccessPointsWifiSettings.class.getName(), RANK_WIFI);
+
+ // BT
+ sRankMap.put(BluetoothSettings.class.getName(), RANK_BT);
+
+ // SIM Cards
+ sRankMap.put(SimSettings.class.getName(), RANK_SIM);
+
+ // DataUsage
+ sRankMap.put(DataUsageSummary.class.getName(), RANK_DATA_USAGE);
+ sRankMap.put(DataUsageMeteredSettings.class.getName(), RANK_DATA_USAGE);
+
+ // Other wireless settinfs
+ sRankMap.put(WirelessSettings.class.getName(), RANK_WIRELESS);
+
+ // Home
+ sRankMap.put(HomeSettings.class.getName(), RANK_HOME);
+
+ // Display
+ sRankMap.put(DisplaySettings.class.getName(), RANK_DISPLAY);
+
+ // Wallpapers
+ sRankMap.put(WallpaperTypeSettings.class.getName(), RANK_WALLPAPER);
+
+ // Notifications
+ sRankMap.put(NotificationSettings.class.getName(), RANK_NOTIFICATIONS);
+ sRankMap.put(OtherSoundSettings.class.getName(), RANK_NOTIFICATIONS);
+ sRankMap.put(ZenModeSettings.class.getName(), RANK_NOTIFICATIONS);
+
+ // Memory
+ sRankMap.put(Memory.class.getName(), RANK_MEMORY);
+
+ // Battery
+ sRankMap.put(PowerUsageSummary.class.getName(), RANK_POWER_USAGE);
+ sRankMap.put(BatterySaverSettings.class.getName(), RANK_POWER_USAGE);
+
+ // Users
+ sRankMap.put(UserSettings.class.getName(), RANK_USERS);
+
+ // Location
+ sRankMap.put(LocationSettings.class.getName(), RANK_LOCATION);
+
+ // Security
+ sRankMap.put(SecuritySettings.class.getName(), RANK_SECURITY);
+ sRankMap.put(ChooseLockGeneric.ChooseLockGenericFragment.class.getName(), RANK_SECURITY);
+ sRankMap.put(ScreenPinningSettings.class.getName(), RANK_SECURITY);
+
+ // IMEs
+ sRankMap.put(InputMethodAndLanguageSettings.class.getName(), RANK_IME);
+ sRankMap.put(VoiceInputSettings.class.getName(), RANK_IME);
+
+ // Privacy
+ sRankMap.put(PrivacySettings.class.getName(), RANK_PRIVACY);
+
+ // Date / Time
+ sRankMap.put(DateTimeSettings.class.getName(), RANK_DATE_TIME);
+
+ // Accessibility
+ sRankMap.put(AccessibilitySettings.class.getName(), RANK_ACCESSIBILITY);
+
+ // Print
+ sRankMap.put(PrintSettingsFragment.class.getName(), RANK_PRINTING);
+
+ // Development
+ sRankMap.put(DevelopmentSettings.class.getName(), RANK_DEVELOPEMENT);
+
+ // Device infos
+ sRankMap.put(DeviceInfoSettings.class.getName(), RANK_DEVICE_INFO);
+
+ sBaseRankMap.put("com.android.settings", 0);
+ }
+
+ public static int getRankForClassName(String className) {
+ Integer rank = sRankMap.get(className);
+ return (rank != null) ? (int) rank: RANK_OTHERS;
+ }
+
+ public static int getBaseRankForAuthority(String authority) {
+ synchronized (sBaseRankMap) {
+ Integer base = sBaseRankMap.get(authority);
+ if (base != null) {
+ return base;
+ }
+ sCurrentBaseRank++;
+ sBaseRankMap.put(authority, sCurrentBaseRank);
+ return sCurrentBaseRank;
+ }
+ }
+}
diff --git a/src/com/android/settings/search/SearchIndexableRaw.java b/src/com/android/settings/search/SearchIndexableRaw.java
new file mode 100644
index 0000000..b8a1699
--- /dev/null
+++ b/src/com/android/settings/search/SearchIndexableRaw.java
@@ -0,0 +1,64 @@
+/*
+ * 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.search;
+
+import android.content.Context;
+import android.provider.SearchIndexableData;
+
+/**
+ * Indexable raw data for Search.
+ *
+ * This is the raw data used by the Indexer and should match its data model.
+ *
+ * See {@link Indexable} and {@link android.provider.SearchIndexableResource}.
+ */
+public class SearchIndexableRaw extends SearchIndexableData {
+
+ /**
+ * Title's raw data.
+ */
+ public String title;
+
+ /**
+ * Summary's raw data when the data is "ON".
+ */
+ public String summaryOn;
+
+ /**
+ * Summary's raw data when the data is "OFF".
+ */
+ public String summaryOff;
+
+ /**
+ * Entries associated with the raw data (when the data can have several values).
+ */
+ public String entries;
+
+ /**
+ * Keywords' raw data.
+ */
+ public String keywords;
+
+ /**
+ * Fragment's or Activity's title associated with the raw data.
+ */
+ public String screenTitle;
+
+ public SearchIndexableRaw(Context context) {
+ super(context);
+ }
+}
diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java
new file mode 100644
index 0000000..7b3fa77
--- /dev/null
+++ b/src/com/android/settings/search/SearchIndexableResources.java
@@ -0,0 +1,281 @@
+/*
+ * 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.search;
+
+import android.provider.SearchIndexableResource;
+
+import com.android.settings.DataUsageSummary;
+import com.android.settings.DateTimeSettings;
+import com.android.settings.DevelopmentSettings;
+import com.android.settings.DeviceInfoSettings;
+import com.android.settings.DisplaySettings;
+import com.android.settings.HomeSettings;
+import com.android.settings.ScreenPinningSettings;
+import com.android.settings.PrivacySettings;
+import com.android.settings.R;
+import com.android.settings.SecuritySettings;
+import com.android.settings.WallpaperTypeSettings;
+import com.android.settings.WirelessSettings;
+import com.android.settings.accessibility.AccessibilitySettings;
+import com.android.settings.bluetooth.BluetoothSettings;
+import com.android.settings.deviceinfo.Memory;
+import com.android.settings.fuelgauge.BatterySaverSettings;
+import com.android.settings.fuelgauge.PowerUsageSummary;
+import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
+import com.android.settings.location.LocationSettings;
+import com.android.settings.net.DataUsageMeteredSettings;
+import com.android.settings.notification.NotificationSettings;
+import com.android.settings.notification.OtherSoundSettings;
+import com.android.settings.notification.ZenModeSettings;
+import com.android.settings.print.PrintSettingsFragment;
+import com.android.settings.sim.SimSettings;
+import com.android.settings.users.UserSettings;
+import com.android.settings.voice.VoiceInputSettings;
+import com.android.settings.wifi.AdvancedWifiSettings;
+import com.android.settings.wifi.SavedAccessPointsWifiSettings;
+import com.android.settings.wifi.WifiSettings;
+
+import java.util.Collection;
+import java.util.HashMap;
+
+public final class SearchIndexableResources {
+
+ public static int NO_DATA_RES_ID = 0;
+
+ private static HashMap<String, SearchIndexableResource> sResMap =
+ new HashMap<String, SearchIndexableResource>();
+
+ static {
+ sResMap.put(WifiSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(WifiSettings.class.getName()),
+ NO_DATA_RES_ID,
+ WifiSettings.class.getName(),
+ R.drawable.ic_settings_wireless));
+
+ sResMap.put(AdvancedWifiSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(AdvancedWifiSettings.class.getName()),
+ R.xml.wifi_advanced_settings,
+ AdvancedWifiSettings.class.getName(),
+ R.drawable.ic_settings_wireless));
+
+ sResMap.put(SavedAccessPointsWifiSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(SavedAccessPointsWifiSettings.class.getName()),
+ R.xml.wifi_display_saved_access_points,
+ SavedAccessPointsWifiSettings.class.getName(),
+ R.drawable.ic_settings_wireless));
+
+ sResMap.put(BluetoothSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(BluetoothSettings.class.getName()),
+ NO_DATA_RES_ID,
+ BluetoothSettings.class.getName(),
+ R.drawable.ic_settings_bluetooth2));
+
+ sResMap.put(SimSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(SimSettings.class.getName()),
+ NO_DATA_RES_ID,
+ SimSettings.class.getName(),
+ R.drawable.ic_sim_sd));
+
+ sResMap.put(DataUsageSummary.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(DataUsageSummary.class.getName()),
+ NO_DATA_RES_ID,
+ DataUsageSummary.class.getName(),
+ R.drawable.ic_settings_data_usage));
+
+ sResMap.put(DataUsageMeteredSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(DataUsageMeteredSettings.class.getName()),
+ NO_DATA_RES_ID,
+ DataUsageMeteredSettings.class.getName(),
+ R.drawable.ic_settings_data_usage));
+
+ sResMap.put(WirelessSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(WirelessSettings.class.getName()),
+ NO_DATA_RES_ID,
+ WirelessSettings.class.getName(),
+ R.drawable.ic_settings_more));
+
+ sResMap.put(HomeSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(HomeSettings.class.getName()),
+ NO_DATA_RES_ID,
+ HomeSettings.class.getName(),
+ R.drawable.ic_settings_home));
+
+ sResMap.put(DisplaySettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(DisplaySettings.class.getName()),
+ NO_DATA_RES_ID,
+ DisplaySettings.class.getName(),
+ R.drawable.ic_settings_display));
+
+ sResMap.put(WallpaperTypeSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(WallpaperTypeSettings.class.getName()),
+ NO_DATA_RES_ID,
+ WallpaperTypeSettings.class.getName(),
+ R.drawable.ic_settings_display));
+
+ sResMap.put(NotificationSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(NotificationSettings.class.getName()),
+ NO_DATA_RES_ID,
+ NotificationSettings.class.getName(),
+ R.drawable.ic_settings_notifications));
+
+ sResMap.put(OtherSoundSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(OtherSoundSettings.class.getName()),
+ NO_DATA_RES_ID,
+ OtherSoundSettings.class.getName(),
+ R.drawable.ic_settings_notifications));
+
+ sResMap.put(ZenModeSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(ZenModeSettings.class.getName()),
+ NO_DATA_RES_ID,
+ ZenModeSettings.class.getName(),
+ R.drawable.ic_settings_notifications));
+
+ sResMap.put(Memory.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(Memory.class.getName()),
+ NO_DATA_RES_ID,
+ Memory.class.getName(),
+ R.drawable.ic_settings_storage));
+
+ sResMap.put(PowerUsageSummary.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(PowerUsageSummary.class.getName()),
+ R.xml.power_usage_summary,
+ PowerUsageSummary.class.getName(),
+ R.drawable.ic_settings_battery));
+
+ sResMap.put(BatterySaverSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(BatterySaverSettings.class.getName()),
+ R.xml.battery_saver_settings,
+ BatterySaverSettings.class.getName(),
+ R.drawable.ic_settings_battery));
+
+ sResMap.put(UserSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(UserSettings.class.getName()),
+ R.xml.user_settings,
+ UserSettings.class.getName(),
+ R.drawable.ic_settings_multiuser));
+
+ sResMap.put(LocationSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(LocationSettings.class.getName()),
+ R.xml.location_settings,
+ LocationSettings.class.getName(),
+ R.drawable.ic_settings_location));
+
+ sResMap.put(SecuritySettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(SecuritySettings.class.getName()),
+ NO_DATA_RES_ID,
+ SecuritySettings.class.getName(),
+ R.drawable.ic_settings_security));
+
+ sResMap.put(ScreenPinningSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(ScreenPinningSettings.class.getName()),
+ NO_DATA_RES_ID,
+ ScreenPinningSettings.class.getName(),
+ R.drawable.ic_settings_security));
+
+ sResMap.put(InputMethodAndLanguageSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(InputMethodAndLanguageSettings.class.getName()),
+ NO_DATA_RES_ID,
+ InputMethodAndLanguageSettings.class.getName(),
+ R.drawable.ic_settings_language));
+
+ sResMap.put(VoiceInputSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(VoiceInputSettings.class.getName()),
+ NO_DATA_RES_ID,
+ VoiceInputSettings.class.getName(),
+ R.drawable.ic_settings_language));
+
+ sResMap.put(PrivacySettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(PrivacySettings.class.getName()),
+ NO_DATA_RES_ID,
+ PrivacySettings.class.getName(),
+ R.drawable.ic_settings_backup));
+
+ sResMap.put(DateTimeSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(DateTimeSettings.class.getName()),
+ R.xml.date_time_prefs,
+ DateTimeSettings.class.getName(),
+ R.drawable.ic_settings_date_time));
+
+ sResMap.put(AccessibilitySettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(AccessibilitySettings.class.getName()),
+ NO_DATA_RES_ID,
+ AccessibilitySettings.class.getName(),
+ R.drawable.ic_settings_accessibility));
+
+ sResMap.put(PrintSettingsFragment.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(PrintSettingsFragment.class.getName()),
+ NO_DATA_RES_ID,
+ PrintSettingsFragment.class.getName(),
+ R.drawable.ic_settings_print));
+
+ sResMap.put(DevelopmentSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(DevelopmentSettings.class.getName()),
+ NO_DATA_RES_ID,
+ DevelopmentSettings.class.getName(),
+ R.drawable.ic_settings_development));
+
+ sResMap.put(DeviceInfoSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(DeviceInfoSettings.class.getName()),
+ NO_DATA_RES_ID,
+ DeviceInfoSettings.class.getName(),
+ R.drawable.ic_settings_about));
+ }
+
+ private SearchIndexableResources() {
+ }
+
+ public static int size() {
+ return sResMap.size();
+ }
+
+ public static SearchIndexableResource getResourceByName(String className) {
+ return sResMap.get(className);
+ }
+
+ public static Collection<SearchIndexableResource> values() {
+ return sResMap.values();
+ }
+}
diff --git a/src/com/android/settings/search/SettingsSearchIndexablesProvider.java b/src/com/android/settings/search/SettingsSearchIndexablesProvider.java
new file mode 100644
index 0000000..c0afcaf
--- /dev/null
+++ b/src/com/android/settings/search/SettingsSearchIndexablesProvider.java
@@ -0,0 +1,75 @@
+/*
+ * 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.search;
+
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.provider.SearchIndexableResource;
+import android.provider.SearchIndexablesProvider;
+
+import java.util.Collection;
+
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RANK;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RESID;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_CLASS_NAME;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_ICON_RESID;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_ACTION;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS;
+
+import static android.provider.SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS;
+import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS;
+import static android.provider.SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS;
+
+public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider {
+ private static final String TAG = "SettingsSearchIndexablesProvider";
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor queryXmlResources(String[] projection) {
+ MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);
+ Collection<SearchIndexableResource> values = SearchIndexableResources.values();
+ for (SearchIndexableResource val : values) {
+ Object[] ref = new Object[7];
+ ref[COLUMN_INDEX_XML_RES_RANK] = val.rank;
+ ref[COLUMN_INDEX_XML_RES_RESID] = val.xmlResId;
+ ref[COLUMN_INDEX_XML_RES_CLASS_NAME] = val.className;
+ ref[COLUMN_INDEX_XML_RES_ICON_RESID] = val.iconResId;
+ ref[COLUMN_INDEX_XML_RES_INTENT_ACTION] = null; // intent action
+ ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE] = null; // intent target package
+ ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = null; // intent target class
+ cursor.addRow(ref);
+ }
+ return cursor;
+ }
+
+ @Override
+ public Cursor queryRawData(String[] projection) {
+ MatrixCursor result = new MatrixCursor(INDEXABLES_RAW_COLUMNS);
+ return result;
+ }
+
+ @Override
+ public Cursor queryNonIndexableKeys(String[] projection) {
+ MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS);
+ return cursor;
+ }
+}