Add Search to the Settings App (initial release)
- add basic UI for search - build the search Index thru sqlite FTS4 (faster than FTS3) - create the search Index on the fly depending on the locale - re-index if there is a configuration change - re-index too if the Android build version has changed (usefull for an Android OTA or when a new Android version is pushed as we need to recompute the Index) - search thru "title" and "summary" Preference's data - group results in the same order of the Settings categories into the Drawer - rewrite "title" and/or "summary" if they are containing an hyphen "\u2011" - add Preference Keywords (only for the Settings App) in the Index and allow search on them (Wi-Fi network preference is used as an example) Known restrictions: - we cannot yet search for "dynamic settings" - ... nor we cannot search for settings coming from an external App (like the Phone App and its related settings that are surfacing into the Settings App). - will need a few other CLs to add more keywords (and have them translated) Change-Id: I017a4d6c433f28c257c08cacc1bed98c4c517039
This commit is contained in:
@@ -19,11 +19,39 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView android:id="@+id/dash_text"
|
<TextView android:id="@+id/dash_text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:text="@string/dashboard_wip"
|
android:text="@string/dashboard_wip"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_weight="0"
|
||||||
|
android:background="#ffcccccc"
|
||||||
|
android:textSize="16sp"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<EditText android:id="@+id/edittext_query"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/query_hint_text"
|
||||||
|
android:layout_weight="0"/>
|
||||||
|
|
||||||
|
<FrameLayout android:id="@+id/dashboard"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dip"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<ListView android:id="@+id/list_results"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
71
res/layout/search_result.xml
Normal file
71
res/layout/search_result.xml
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="@*android:dimen/preference_item_padding_side"
|
||||||
|
android:paddingEnd="?android:attr/scrollbarSize">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:paddingEnd="@*android:dimen/preference_item_padding_inner"
|
||||||
|
android:paddingTop="6dip"
|
||||||
|
android:paddingBottom="6dip">
|
||||||
|
|
||||||
|
<TextView android:id="@+id/title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:fadingEdge="horizontal" />
|
||||||
|
|
||||||
|
<TextView android:id="@+id/summary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/title"
|
||||||
|
android:layout_alignStart="@id/title"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:maxLines="10" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="@*android:dimen/preference_icon_minWidth"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:minWidth="48dp"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:layout_marginEnd="@*android:dimen/preference_item_padding_inner"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
@@ -83,4 +83,9 @@
|
|||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
<attr name="apnPreferenceStyle" format="reference" />
|
<attr name="apnPreferenceStyle" format="reference" />
|
||||||
|
|
||||||
|
<!-- For Search -->
|
||||||
|
<declare-styleable name="Preference">
|
||||||
|
<attr name="keywords" format="string" />
|
||||||
|
</declare-styleable>
|
||||||
</resources>
|
</resources>
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<color name="background_drawer">@android:color/white</color>
|
<color name="background_drawer">@android:color/white</color>
|
||||||
<color name="background_drawer_icon">#ffcccccc</color>
|
<color name="background_drawer_icon">#ffcccccc</color>
|
||||||
|
<color name="background_search_result_icon">#ffcccccc</color>
|
||||||
|
|
||||||
<color name="black">#000</color>
|
<color name="black">#000</color>
|
||||||
<color name="red">#F00</color>
|
<color name="red">#F00</color>
|
||||||
|
@@ -4998,7 +4998,14 @@
|
|||||||
<!--Dashboard strings-->
|
<!--Dashboard strings-->
|
||||||
<!-- Text to describe the dashboard entry into the Drawer [CHAR LIMIT=16] -->
|
<!-- Text to describe the dashboard entry into the Drawer [CHAR LIMIT=16] -->
|
||||||
<string name="dashboard_title">Overview</string>
|
<string name="dashboard_title">Overview</string>
|
||||||
<string name="dashboard_wip" translatable="false">Overview - work in progress\n\nUse the Drawer on the left to see the settings list</string>
|
<string name="dashboard_wip" translatable="false">Overview and Search are work in progress and Confidential\n\nDrag the Drawer on the left to see the settings list</string>
|
||||||
|
|
||||||
|
<!-- Search strings -->
|
||||||
|
<!-- Text used as a search hint into the search box -->
|
||||||
|
<string name="query_hint_text">What are you looking for?</string>
|
||||||
|
|
||||||
|
<!--Search Keywords-->
|
||||||
|
<string name="keywords_wifi">wifi wi-fi network connection</string>
|
||||||
|
|
||||||
<!-- Notifications on lockscreen -->
|
<!-- Notifications on lockscreen -->
|
||||||
<!-- Label for checkbox controlling the contents of notifications shown on
|
<!-- Label for checkbox controlling the contents of notifications shown on
|
||||||
|
@@ -15,6 +15,8 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:title="@string/wifi_settings">
|
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
|
||||||
|
android:title="@string/wifi_settings"
|
||||||
|
settings:keywords="@string/keywords_wifi">
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
@@ -94,6 +94,8 @@ import com.android.settings.dashboard.DashboardSummary;
|
|||||||
import com.android.settings.deviceinfo.Memory;
|
import com.android.settings.deviceinfo.Memory;
|
||||||
import com.android.settings.deviceinfo.UsbSettings;
|
import com.android.settings.deviceinfo.UsbSettings;
|
||||||
import com.android.settings.fuelgauge.PowerUsageSummary;
|
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.InputMethodAndLanguageSettings;
|
||||||
import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
|
import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
|
||||||
import com.android.settings.inputmethod.SpellCheckersSettings;
|
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 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
|
// extras that allow any preference activity to be launched as part of a wizard
|
||||||
|
|
||||||
// show Back and Next buttons? takes boolean parameter
|
// 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";
|
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 =
|
private static final String META_DATA_KEY_HEADER_ID =
|
||||||
"com.android.settings.TOP_LEVEL_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
|
@Override
|
||||||
public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
|
public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
|
||||||
// Override the fragment title for Wallpaper settings
|
// Override the fragment title for Wallpaper settings
|
||||||
@@ -463,6 +530,7 @@ public class SettingsActivity extends Activity
|
|||||||
public void onConfigurationChanged(Configuration newConfig) {
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
super.onConfigurationChanged(newConfig);
|
super.onConfigurationChanged(newConfig);
|
||||||
mDrawerToggle.onConfigurationChanged(newConfig);
|
mDrawerToggle.onConfigurationChanged(newConfig);
|
||||||
|
Index.getInstance(this).update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -479,6 +547,9 @@ public class SettingsActivity extends Activity
|
|||||||
getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
|
getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Index.getInstance(this).addIndexableData(INDEXABLE_DATA);
|
||||||
|
Index.getInstance(this).update();
|
||||||
|
|
||||||
mAuthenticatorHelper = new AuthenticatorHelper();
|
mAuthenticatorHelper = new AuthenticatorHelper();
|
||||||
mAuthenticatorHelper.updateAuthDescriptions(this);
|
mAuthenticatorHelper.updateAuthDescriptions(this);
|
||||||
mAuthenticatorHelper.onAccountsUpdated(this, null);
|
mAuthenticatorHelper.onAccountsUpdated(this, null);
|
||||||
|
@@ -17,17 +17,97 @@
|
|||||||
package com.android.settings.dashboard;
|
package com.android.settings.dashboard;
|
||||||
|
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.text.TextWatcher;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
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.R;
|
||||||
|
import com.android.settings.SettingsActivity;
|
||||||
|
import com.android.settings.indexer.Index;
|
||||||
|
|
||||||
public class DashboardSummary extends Fragment {
|
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
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(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
|
@Override
|
||||||
@@ -36,6 +116,185 @@ public class DashboardSummary extends Fragment {
|
|||||||
|
|
||||||
final View view = inflater.inflate(R.layout.dashboard, container, false);
|
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;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
379
src/com/android/settings/indexer/Index.java
Normal file
379
src/com/android/settings/indexer/Index.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
156
src/com/android/settings/indexer/IndexDatabaseHelper.java
Normal file
156
src/com/android/settings/indexer/IndexDatabaseHelper.java
Normal file
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
src/com/android/settings/indexer/IndexableData.java
Normal file
32
src/com/android/settings/indexer/IndexableData.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user