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_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"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
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>
|
||||
|
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>
|
||||
|
||||
<attr name="apnPreferenceStyle" format="reference" />
|
||||
|
||||
<!-- For Search -->
|
||||
<declare-styleable name="Preference">
|
||||
<attr name="keywords" format="string" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
@@ -17,6 +17,7 @@
|
||||
<resources>
|
||||
<color name="background_drawer">@android:color/white</color>
|
||||
<color name="background_drawer_icon">#ffcccccc</color>
|
||||
<color name="background_search_result_icon">#ffcccccc</color>
|
||||
|
||||
<color name="black">#000</color>
|
||||
<color name="red">#F00</color>
|
||||
|
@@ -4998,7 +4998,14 @@
|
||||
<!--Dashboard strings-->
|
||||
<!-- Text to describe the dashboard entry into the Drawer [CHAR LIMIT=16] -->
|
||||
<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 -->
|
||||
<!-- Label for checkbox controlling the contents of notifications shown on
|
||||
|
@@ -15,6 +15,8 @@
|
||||
-->
|
||||
|
||||
<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>
|
||||
|
@@ -94,6 +94,8 @@ import com.android.settings.dashboard.DashboardSummary;
|
||||
import com.android.settings.deviceinfo.Memory;
|
||||
import com.android.settings.deviceinfo.UsbSettings;
|
||||
import com.android.settings.fuelgauge.PowerUsageSummary;
|
||||
import com.android.settings.indexer.Index;
|
||||
import com.android.settings.indexer.IndexableData;
|
||||
import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
|
||||
import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
|
||||
import com.android.settings.inputmethod.SpellCheckersSettings;
|
||||
@@ -159,6 +161,8 @@ public class SettingsActivity extends Activity
|
||||
*/
|
||||
public static final String EXTRA_NO_HEADERS = ":settings:no_headers";
|
||||
|
||||
public static final String BACK_STACK_PREFS = ":settings:prefs";
|
||||
|
||||
// extras that allow any preference activity to be launched as part of a wizard
|
||||
|
||||
// show Back and Next buttons? takes boolean parameter
|
||||
@@ -180,8 +184,6 @@ public class SettingsActivity extends Activity
|
||||
*/
|
||||
protected static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
|
||||
|
||||
private static final String BACK_STACK_PREFS = ":settings:prefs";
|
||||
|
||||
private static final String META_DATA_KEY_HEADER_ID =
|
||||
"com.android.settings.TOP_LEVEL_HEADER_ID";
|
||||
|
||||
@@ -340,6 +342,71 @@ public class SettingsActivity extends Activity
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Searchable data description.
|
||||
*
|
||||
* Known restriction: we are only searching (for now) the first level of Settings.
|
||||
*/
|
||||
private static IndexableData[] INDEXABLE_DATA = new IndexableData[] {
|
||||
new IndexableData(1, R.xml.wifi_settings,
|
||||
"com.android.settings.wifi.WifiSettings",
|
||||
R.drawable.ic_settings_wireless),
|
||||
new IndexableData(2, R.xml.bluetooth_settings,
|
||||
"com.android.settings.bluetooth.BluetoothSettings",
|
||||
R.drawable.ic_settings_bluetooth2),
|
||||
new IndexableData(3, R.xml.data_usage_metered_prefs,
|
||||
"com.android.settings.net.DataUsageMeteredSettings",
|
||||
R.drawable.ic_settings_data_usage),
|
||||
new IndexableData(4, R.xml.wireless_settings,
|
||||
"com.android.settings.WirelessSettings",
|
||||
R.drawable.empty_icon),
|
||||
new IndexableData(5, R.xml.home_selection,
|
||||
"com.android.settings.HomeSettings",
|
||||
R.drawable.ic_settings_home),
|
||||
new IndexableData(6, R.xml.sound_settings,
|
||||
"com.android.settings.SoundSettings",
|
||||
R.drawable.ic_settings_sound),
|
||||
new IndexableData(7, R.xml.display_settings,
|
||||
"com.android.settings.DisplaySettings",
|
||||
R.drawable.ic_settings_display),
|
||||
new IndexableData(8, R.xml.device_info_memory,
|
||||
"com.android.settings.deviceinfo.Memory",
|
||||
R.drawable.ic_settings_storage),
|
||||
new IndexableData(9, R.xml.power_usage_summary,
|
||||
"com.android.settings.fuelgauge.PowerUsageSummary",
|
||||
R.drawable.ic_settings_battery),
|
||||
new IndexableData(10, R.xml.user_settings,
|
||||
"com.android.settings.users.UserSettings",
|
||||
R.drawable.ic_settings_multiuser),
|
||||
new IndexableData(11, R.xml.location_settings,
|
||||
"com.android.settings.location.LocationSettings",
|
||||
R.drawable.ic_settings_location),
|
||||
new IndexableData(12, R.xml.security_settings,
|
||||
"com.android.settings.SecuritySettings",
|
||||
R.drawable.ic_settings_security),
|
||||
new IndexableData(13, R.xml.language_settings,
|
||||
"com.android.settings.inputmethod.InputMethodAndLanguageSettings",
|
||||
R.drawable.ic_settings_language),
|
||||
new IndexableData(14, R.xml.privacy_settings,
|
||||
"com.android.settings.PrivacySettings",
|
||||
R.drawable.ic_settings_backup),
|
||||
new IndexableData(15, R.xml.date_time_prefs,
|
||||
"com.android.settings.DateTimeSettings",
|
||||
R.drawable.ic_settings_date_time),
|
||||
new IndexableData(16, R.xml.accessibility_settings,
|
||||
"com.android.settings.accessibility.AccessibilitySettings",
|
||||
R.drawable.ic_settings_accessibility),
|
||||
new IndexableData(17, R.xml.print_settings,
|
||||
"com.android.settings.print.PrintSettingsFragment",
|
||||
com.android.internal.R.drawable.ic_print),
|
||||
new IndexableData(18, R.xml.development_prefs,
|
||||
"com.android.settings.DevelopmentSettings",
|
||||
R.drawable.ic_settings_development),
|
||||
new IndexableData(19, R.xml.device_info_settings,
|
||||
"com.android.settings.DeviceInfoSettings",
|
||||
R.drawable.ic_settings_about),
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
|
||||
// Override the fragment title for Wallpaper settings
|
||||
@@ -463,6 +530,7 @@ public class SettingsActivity extends Activity
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
mDrawerToggle.onConfigurationChanged(newConfig);
|
||||
Index.getInstance(this).update();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -479,6 +547,9 @@ public class SettingsActivity extends Activity
|
||||
getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
|
||||
}
|
||||
|
||||
Index.getInstance(this).addIndexableData(INDEXABLE_DATA);
|
||||
Index.getInstance(this).update();
|
||||
|
||||
mAuthenticatorHelper = new AuthenticatorHelper();
|
||||
mAuthenticatorHelper.updateAuthDescriptions(this);
|
||||
mAuthenticatorHelper.onAccountsUpdated(this, null);
|
||||
|
@@ -17,17 +17,97 @@
|
||||
package com.android.settings.dashboard;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.indexer.Index;
|
||||
|
||||
public class DashboardSummary extends Fragment {
|
||||
|
||||
private static final String SAVE_KEY_QUERY = ":settings:query";
|
||||
|
||||
private EditText mEditText;
|
||||
private ListView mListView;
|
||||
|
||||
private SearchResultsAdapter mAdapter;
|
||||
private Index mIndex;
|
||||
private UpdateSearchResultsTask mUpdateSearchResultsTask;
|
||||
|
||||
/**
|
||||
* A basic AsyncTask for updating the query results cursor
|
||||
*/
|
||||
private class UpdateSearchResultsTask extends AsyncTask<String, Void, Cursor> {
|
||||
@Override
|
||||
protected Cursor doInBackground(String... params) {
|
||||
return mIndex.search(params[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Cursor cursor) {
|
||||
if (!isCancelled()) {
|
||||
setCursor(cursor);
|
||||
} else if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mIndex = Index.getInstance(getActivity());
|
||||
mAdapter = new SearchResultsAdapter(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
clearResults();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
updateSearchResults();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (outState != null) {
|
||||
outState.putString(SAVE_KEY_QUERY, mEditText.getText().toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
final String query = savedInstanceState.getString(SAVE_KEY_QUERY);
|
||||
if (query != null && !TextUtils.isEmpty(query)) {
|
||||
mEditText.setText(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -36,6 +116,185 @@ public class DashboardSummary extends Fragment {
|
||||
|
||||
final View view = inflater.inflate(R.layout.dashboard, container, false);
|
||||
|
||||
mEditText = (EditText)view.findViewById(R.id.edittext_query);
|
||||
mEditText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
updateSearchResults();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
});
|
||||
mEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (!hasFocus) {
|
||||
closeSoftKeyboard();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mListView = (ListView) view.findViewById(R.id.list_results);
|
||||
mListView.setAdapter(mAdapter);
|
||||
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
closeSoftKeyboard();
|
||||
final Cursor cursor = mAdapter.mCursor;
|
||||
cursor.moveToPosition(position);
|
||||
final String fragmentName = cursor.getString(Index.COLUMN_INDEX_FRAGMENT_NAME);
|
||||
final String fragmentTitle = cursor.getString(Index.COLUMN_INDEX_FRAGMENT_TITLE);
|
||||
|
||||
((SettingsActivity) getActivity()).startPreferencePanel(fragmentName, null, 0,
|
||||
fragmentTitle, null, 0);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void closeSoftKeyboard() {
|
||||
InputMethodManager imm = InputMethodManager.peekInstance();
|
||||
if (imm != null && imm.isActive(mEditText)) {
|
||||
imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearResults() {
|
||||
if (mUpdateSearchResultsTask != null) {
|
||||
mUpdateSearchResultsTask.cancel(false);
|
||||
mUpdateSearchResultsTask = null;
|
||||
}
|
||||
setCursor(null);
|
||||
}
|
||||
|
||||
private void setCursor(Cursor cursor) {
|
||||
Cursor oldCursor = mAdapter.swapCursor(cursor);
|
||||
if (oldCursor != null) {
|
||||
oldCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSearchResults() {
|
||||
if (mUpdateSearchResultsTask != null) {
|
||||
mUpdateSearchResultsTask.cancel(false);
|
||||
mUpdateSearchResultsTask = null;
|
||||
}
|
||||
final String query = mEditText.getText().toString();
|
||||
if (TextUtils.isEmpty(query)) {
|
||||
setCursor(null);
|
||||
} else {
|
||||
mUpdateSearchResultsTask = new UpdateSearchResultsTask();
|
||||
mUpdateSearchResultsTask.execute(query);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SearchResult {
|
||||
public String title;
|
||||
public String summary;
|
||||
public int iconResId;
|
||||
|
||||
public SearchResult(String title, String summary, int iconResId) {
|
||||
this.title = title;
|
||||
this.summary = summary;
|
||||
this.iconResId = iconResId;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SearchResultsAdapter extends BaseAdapter {
|
||||
|
||||
private Cursor mCursor;
|
||||
private LayoutInflater mInflater;
|
||||
private boolean mDataValid;
|
||||
|
||||
public SearchResultsAdapter(Context context) {
|
||||
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mDataValid = false;
|
||||
}
|
||||
|
||||
public Cursor swapCursor(Cursor newCursor) {
|
||||
if (newCursor == mCursor) {
|
||||
return null;
|
||||
}
|
||||
Cursor oldCursor = mCursor;
|
||||
mCursor = newCursor;
|
||||
if (newCursor != null) {
|
||||
mDataValid = true;
|
||||
notifyDataSetChanged();
|
||||
} else {
|
||||
mDataValid = false;
|
||||
notifyDataSetInvalidated();
|
||||
}
|
||||
return oldCursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
if (!mDataValid || mCursor == null || mCursor.isClosed()) return 0;
|
||||
return mCursor.getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
if (mDataValid && mCursor.moveToPosition(position)) {
|
||||
final String title = mCursor.getString(Index.COLUMN_INDEX_TITLE);
|
||||
final String summary = mCursor.getString(Index.COLUMN_INDEX_SUMMARY);
|
||||
final String iconResStr = mCursor.getString(Index.COLUMN_INDEX_ICON);
|
||||
final int iconResId =
|
||||
TextUtils.isEmpty(iconResStr) ? 0 : Integer.parseInt(iconResStr);
|
||||
return new SearchResult(title, summary, iconResId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
if (!mDataValid && convertView == null) {
|
||||
throw new IllegalStateException(
|
||||
"this should only be called when the cursor is valid");
|
||||
}
|
||||
if (!mCursor.moveToPosition(position)) {
|
||||
throw new IllegalStateException("couldn't move cursor to position " + position);
|
||||
}
|
||||
|
||||
View view;
|
||||
TextView textTitle;
|
||||
TextView textSummary;
|
||||
ImageView imageView;
|
||||
|
||||
if (convertView == null) {
|
||||
view = mInflater.inflate(R.layout.search_result, parent, false);
|
||||
} else {
|
||||
view = convertView;
|
||||
}
|
||||
textTitle = (TextView) view.findViewById(R.id.title);
|
||||
textSummary = (TextView) view.findViewById(R.id.summary);
|
||||
imageView = (ImageView) view.findViewById(R.id.icon);
|
||||
|
||||
SearchResult result = (SearchResult) getItem(position);
|
||||
|
||||
textTitle.setText(result.title);
|
||||
textSummary.setText(result.summary);
|
||||
if (result.iconResId != R.drawable.empty_icon) {
|
||||
imageView.setImageResource(result.iconResId);
|
||||
imageView.setBackgroundResource(R.color.background_search_result_icon);
|
||||
} else {
|
||||
imageView.setImageDrawable(null);
|
||||
imageView.setBackgroundResource(R.drawable.empty_icon);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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