Add saved Search queries feature

- update SearchResultsSummary fragment to have two lists:
one for Search suggestions (saved queries) and one for
Search results
- a tap on a saved query will launch that Search query
- show the list of saved queries when tapping on the SearchView
- do some fancy hidding / unhidding of the saved queries list
and results list

Change-Id: If15055ab78b0ec5eef4e543173dc7b866bd08e27
This commit is contained in:
Fabrice Di Meglio
2014-04-22 17:23:23 -07:00
parent 891bbfdbb7
commit d297a58402
7 changed files with 468 additions and 108 deletions

View File

@@ -0,0 +1,78 @@
<?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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dashboard"
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">
<LinearLayout android:id="@+id/layout_suggestions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/search_recents_queries_label"/>
<ListView android:id="@+id/list_suggestions"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView android:src="?android:attr/dividerHorizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scaleType="fitXY"
android:paddingBottom="2dp"
android:paddingTop="2dp" />
</LinearLayout>
<LinearLayout android:id="@+id/layout_results"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:orientation="vertical"
android:layout_weight="1" >
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/search_results_label"/>
<ListView android:id="@+id/list_results"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
</LinearLayout>
</FrameLayout>

View File

@@ -1,33 +0,0 @@
<?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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dashboard"
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">
<ListView android:id="@+id/list_results"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</FrameLayout>

View File

@@ -0,0 +1,62 @@
<?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">
<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>
<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" />
</RelativeLayout>
</LinearLayout>

View File

@@ -5019,6 +5019,11 @@
<!-- Text used as a search hint into the search box --> <!-- Text used as a search hint into the search box -->
<string name="query_hint_text">Search settings</string> <string name="query_hint_text">Search settings</string>
<!-- Text used to identify the search query suggestions / recent searches -->
<string name="search_recents_queries_label">Recent searches</string>
<!-- Text used to identify the search results -->
<string name="search_results_label">Results</string>
<!--Search Keywords--> <!--Search Keywords-->
<string name="keywords_wifi">wifi wi-fi network connection</string> <string name="keywords_wifi">wifi wi-fi network connection</string>

View File

@@ -1280,6 +1280,7 @@ public class SettingsActivity extends Activity
mSearchResultsFragment = (SearchResultsSummary) switchToFragment( mSearchResultsFragment = (SearchResultsSummary) switchToFragment(
SearchResultsSummary.class.getName(), null, false, true, title, true); SearchResultsSummary.class.getName(), null, false, true, title, true);
} }
mSearchResultsFragment.setSearchView(mSearchView);
mSearchMenuItemExpanded = true; mSearchMenuItemExpanded = true;
} }

View File

@@ -28,7 +28,6 @@ import android.database.sqlite.SQLiteDatabase;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -38,6 +37,7 @@ import android.widget.AdapterView;
import android.widget.BaseAdapter; import android.widget.BaseAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ListView; import android.widget.ListView;
import android.widget.SearchView;
import android.widget.TextView; import android.widget.TextView;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
@@ -54,17 +54,23 @@ public class SearchResultsSummary extends Fragment {
private static final String LOG_TAG = "SearchResultsSummary"; private static final String LOG_TAG = "SearchResultsSummary";
private static final String EMPTY_QUERY = "";
private static char ELLIPSIS = '\u2026'; private static char ELLIPSIS = '\u2026';
private ListView mListView; private SearchView mSearchView;
private SearchResultsAdapter mAdapter; private ListView mResultsListView;
private SearchResultsAdapter mResultsAdapter;
private UpdateSearchResultsTask mUpdateSearchResultsTask; private UpdateSearchResultsTask mUpdateSearchResultsTask;
private String mQuery; private ListView mSuggestionsListView;
private SaveSearchQueryTask mSaveSearchQueryTask; private SuggestionsAdapter mSuggestionsAdapter;
private UpdateSuggestionsTask mUpdateSuggestionsTask;
private static long MAX_SAVED_SEARCH_QUERY = 5; private ViewGroup mLayoutSuggestions;
private ViewGroup mLayoutResults;
private String mQuery;
/** /**
* A basic AsyncTask for updating the query results cursor * A basic AsyncTask for updating the query results cursor
@@ -78,7 +84,7 @@ public class SearchResultsSummary extends Fragment {
@Override @Override
protected void onPostExecute(Cursor cursor) { protected void onPostExecute(Cursor cursor) {
if (!isCancelled()) { if (!isCancelled()) {
setCursor(cursor); setResultsCursor(cursor);
} else if (cursor != null) { } else if (cursor != null) {
cursor.close(); cursor.close();
} }
@@ -86,37 +92,21 @@ public class SearchResultsSummary extends Fragment {
} }
/** /**
* A basic AsynTask for saving the Search query into the database * A basic AsyncTask for updating the suggestions cursor
*/ */
private class SaveSearchQueryTask extends AsyncTask<String, Void, Long> { private class UpdateSuggestionsTask extends AsyncTask<String, Void, Cursor> {
@Override
protected Cursor doInBackground(String... params) {
return Index.getInstance(getActivity()).getSuggestions(params[0]);
}
@Override @Override
protected Long doInBackground(String... params) { protected void onPostExecute(Cursor cursor) {
final long now = new Date().getTime(); if (!isCancelled()) {
setSuggestionsCursor(cursor);
final ContentValues values = new ContentValues(); } else if (cursor != null) {
values.put(SavedQueriesColums.QUERY, params[0]); cursor.close();
values.put(SavedQueriesColums.TIME_STAMP, now);
SQLiteDatabase database = IndexDatabaseHelper.getInstance(
getActivity()).getWritableDatabase();
long lastInsertedRowId = -1;
try {
lastInsertedRowId =
database.insert(Tables.TABLE_SAVED_QUERIES, null, values);
final long delta = lastInsertedRowId - MAX_SAVED_SEARCH_QUERY;
if (delta > 0) {
int count = database.delete(Tables.TABLE_SAVED_QUERIES, "rowId <= ?",
new String[] { Long.toString(delta) });
Log.d(LOG_TAG, "Deleted '" + count + "' saved Search query(ies)");
} }
} catch (Exception e) {
Log.d(LOG_TAG, "Cannot update saved Search queries", e);
}
return lastInsertedRowId;
} }
} }
@@ -124,22 +114,30 @@ public class SearchResultsSummary extends Fragment {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mAdapter = new SearchResultsAdapter(getActivity()); mResultsAdapter = new SearchResultsAdapter(getActivity());
mSuggestionsAdapter = new SuggestionsAdapter(getActivity());
} }
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
clearSuggestions();
clearResults(); clearResults();
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
mListView = null; mResultsListView = null;
mAdapter = null; mResultsAdapter = null;
mUpdateSearchResultsTask = null; mUpdateSearchResultsTask = null;
mSuggestionsListView = null;
mSuggestionsAdapter = null;
mUpdateSuggestionsTask = null;
mSearchView = null;
super.onDestroy(); super.onDestroy();
} }
@@ -147,14 +145,17 @@ public class SearchResultsSummary extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.search_results, container, false); final View view = inflater.inflate(R.layout.search_panel, container, false);
mListView = (ListView) view.findViewById(R.id.list_results); mLayoutSuggestions = (ViewGroup) view.findViewById(R.id.layout_suggestions);
mListView.setAdapter(mAdapter); mLayoutResults = (ViewGroup) view.findViewById(R.id.layout_results);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
mResultsListView = (ListView) view.findViewById(R.id.list_results);
mResultsListView.setAdapter(mResultsAdapter);
mResultsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Cursor cursor = mAdapter.mCursor; final Cursor cursor = mResultsAdapter.mCursor;
cursor.moveToPosition(position); cursor.moveToPosition(position);
final String className = cursor.getString(Index.COLUMN_INDEX_CLASS_NAME); final String className = cursor.getString(Index.COLUMN_INDEX_CLASS_NAME);
@@ -192,33 +193,85 @@ public class SearchResultsSummary extends Fragment {
} }
}); });
mSuggestionsListView = (ListView) view.findViewById(R.id.list_suggestions);
mSuggestionsListView.setAdapter(mSuggestionsAdapter);
mSuggestionsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Cursor cursor = mSuggestionsAdapter.mCursor;
cursor.moveToPosition(position);
mQuery = cursor.getString(0);
mSearchView.setQuery(mQuery, false);
setSuggestionsVisibility(false);
}
});
return view; return view;
} }
private void saveQueryToDatabase() { @Override
if (mSaveSearchQueryTask != null) { public void onActivityCreated(Bundle savedInstanceState) {
mSaveSearchQueryTask.cancel(false); super.onActivityCreated(savedInstanceState);
mSaveSearchQueryTask = null;
showSomeSuggestions();
} }
if (!TextUtils.isEmpty(mQuery)) {
mSaveSearchQueryTask = new SaveSearchQueryTask(); public void setSearchView(SearchView searchView) {
mSaveSearchQueryTask.execute(mQuery); mSearchView = searchView;
}
private void setSuggestionsVisibility(boolean visible) {
if (mLayoutSuggestions != null) {
mLayoutSuggestions.setVisibility(visible ? View.VISIBLE : View.GONE);
} }
} }
private void setResultsVisibility(boolean visible) {
if (mLayoutResults != null) {
mLayoutResults.setVisibility(visible ? View.VISIBLE : View.GONE);
}
}
private void saveQueryToDatabase() {
Index.getInstance(getActivity()).addSavedQuery(mQuery);
}
public boolean onQueryTextSubmit(String query) { public boolean onQueryTextSubmit(String query) {
updateSearchResults(query); mQuery = getFilteredQueryString(query);
updateSearchResults();
return true; return true;
} }
public boolean onQueryTextChange(String query) { public boolean onQueryTextChange(String query) {
updateSearchResults(query); mQuery = getFilteredQueryString(query);
updateSuggestions();
updateSearchResults();
return true; return true;
} }
public boolean onClose() { public void showSomeSuggestions() {
clearResults(); setResultsVisibility(false);
return false; mQuery = EMPTY_QUERY;
updateSuggestions();
}
private void clearSuggestions() {
if (mUpdateSuggestionsTask != null) {
mUpdateSuggestionsTask.cancel(false);
mUpdateSuggestionsTask = null;
}
setSuggestionsCursor(null);
}
private void setSuggestionsCursor(Cursor cursor) {
if (mSuggestionsAdapter == null) {
return;
}
Cursor oldCursor = mSuggestionsAdapter.swapCursor(cursor);
if (oldCursor != null) {
oldCursor.close();
}
} }
private void clearResults() { private void clearResults() {
@@ -226,20 +279,23 @@ public class SearchResultsSummary extends Fragment {
mUpdateSearchResultsTask.cancel(false); mUpdateSearchResultsTask.cancel(false);
mUpdateSearchResultsTask = null; mUpdateSearchResultsTask = null;
} }
setCursor(null); setResultsCursor(null);
} }
private void setCursor(Cursor cursor) { private void setResultsCursor(Cursor cursor) {
if (mAdapter == null) { if (mResultsAdapter == null) {
return; return;
} }
Cursor oldCursor = mAdapter.swapCursor(cursor); Cursor oldCursor = mResultsAdapter.swapCursor(cursor);
if (oldCursor != null) { if (oldCursor != null) {
oldCursor.close(); oldCursor.close();
} }
} }
private String getFilteredQueryString(CharSequence query) { private String getFilteredQueryString(CharSequence query) {
if (query == null) {
return null;
}
final StringBuilder filtered = new StringBuilder(); final StringBuilder filtered = new StringBuilder();
for (int n = 0; n < query.length(); n++) { for (int n = 0; n < query.length(); n++) {
char c = query.charAt(n); char c = query.charAt(n);
@@ -251,20 +307,123 @@ public class SearchResultsSummary extends Fragment {
return filtered.toString(); return filtered.toString();
} }
private void updateSearchResults(CharSequence cs) { private void updateSuggestions() {
if (mUpdateSuggestionsTask != null) {
mUpdateSuggestionsTask.cancel(false);
mUpdateSuggestionsTask = null;
}
if (mQuery == null) {
setSuggestionsCursor(null);
} else {
setSuggestionsVisibility(true);
mUpdateSuggestionsTask = new UpdateSuggestionsTask();
mUpdateSuggestionsTask.execute(mQuery);
}
}
private void updateSearchResults() {
if (mUpdateSearchResultsTask != null) { if (mUpdateSearchResultsTask != null) {
mUpdateSearchResultsTask.cancel(false); mUpdateSearchResultsTask.cancel(false);
mUpdateSearchResultsTask = null; mUpdateSearchResultsTask = null;
} }
mQuery = getFilteredQueryString(cs);
if (TextUtils.isEmpty(mQuery)) { if (TextUtils.isEmpty(mQuery)) {
setCursor(null); setResultsVisibility(false);
setResultsCursor(null);
} else { } else {
setResultsVisibility(true);
mUpdateSearchResultsTask = new UpdateSearchResultsTask(); mUpdateSearchResultsTask = new UpdateSearchResultsTask();
mUpdateSearchResultsTask.execute(mQuery); mUpdateSearchResultsTask.execute(mQuery);
} }
} }
private static class SuggestionItem {
public String query;
public SuggestionItem(String query) {
this.query = query;
}
}
private static class SuggestionsAdapter extends BaseAdapter {
private static final int COLUMN_SUGGESTION_QUERY = 0;
private static final int COLUMN_SUGGESTION_TIMESTAMP = 1;
private Context mContext;
private Cursor mCursor;
private LayoutInflater mInflater;
private boolean mDataValid = false;
public SuggestionsAdapter(Context context) {
mContext = context;
mInflater = (LayoutInflater) mContext.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 query = mCursor.getString(COLUMN_SUGGESTION_QUERY);
return new SuggestionItem(query);
}
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;
if (convertView == null) {
view = mInflater.inflate(R.layout.search_suggestion_item, parent, false);
} else {
view = convertView;
}
TextView query = (TextView) view.findViewById(R.id.title);
SuggestionItem item = (SuggestionItem) getItem(position);
query.setText(item.query);
return view;
}
}
private static class SearchResult { private static class SearchResult {
public Context context; public Context context;
public String title; public String title;
@@ -288,10 +447,10 @@ public class SearchResultsSummary extends Fragment {
private static class SearchResultsAdapter extends BaseAdapter { private static class SearchResultsAdapter extends BaseAdapter {
private Context mContext;
private Cursor mCursor; private Cursor mCursor;
private LayoutInflater mInflater; private LayoutInflater mInflater;
private boolean mDataValid; private boolean mDataValid;
private Context mContext;
private HashMap<String, Context> mContextMap = new HashMap<String, Context>(); private HashMap<String, Context> mContextMap = new HashMap<String, Context>();
private static final String PERCENT_RECLACE = "%s"; private static final String PERCENT_RECLACE = "%s";

View File

@@ -47,6 +47,7 @@ import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -131,6 +132,11 @@ public class Index {
IndexColumns.DATA_KEYWORDS IndexColumns.DATA_KEYWORDS
}; };
// Max number of saved search queries (who will be used for proposing suggestions)
private static long MAX_SAVED_SEARCH_QUERY = 64;
// Max number of proposed suggestions
private static final int MAX_PROPOSED_SUGGESTIONS = 5;
private static final String EMPTY = ""; private static final String EMPTY = "";
private static final String NON_BREAKING_HYPHEN = "\u2011"; private static final String NON_BREAKING_HYPHEN = "\u2011";
private static final String HYPHEN = "-"; private static final String HYPHEN = "-";
@@ -144,6 +150,7 @@ public class Index {
private static final List<String> EMPTY_LIST = Collections.<String>emptyList(); private static final List<String> EMPTY_LIST = Collections.<String>emptyList();
private static Index sInstance; private static Index sInstance;
private final AtomicBoolean mIsAvailable = new AtomicBoolean(false); private final AtomicBoolean mIsAvailable = new AtomicBoolean(false);
private final UpdateData mDataToProcess = new UpdateData(); private final UpdateData mDataToProcess = new UpdateData();
@@ -198,11 +205,57 @@ public class Index {
} }
public Cursor search(String query) { public Cursor search(String query) {
final String sql = buildSQL(query); final String sql = buildSearchSQL(query);
Log.d(LOG_TAG, "Query: " + sql); Log.d(LOG_TAG, "Search query: " + sql);
return getReadableDatabase().rawQuery(sql, null); return getReadableDatabase().rawQuery(sql, null);
} }
public Cursor getSuggestions(String query) {
final String sql = buildSuggestionsSQL(query);
Log.d(LOG_TAG, "Suggestions query: " + sql);
return getReadableDatabase().rawQuery(sql, null);
}
private String buildSuggestionsSQL(String query) {
StringBuilder sb = new StringBuilder();
sb.append("SELECT ");
sb.append(IndexDatabaseHelper.SavedQueriesColums.QUERY);
sb.append(" FROM ");
sb.append(Tables.TABLE_SAVED_QUERIES);
if (TextUtils.isEmpty(query)) {
sb.append(" ORDER BY rowId DESC");
} else {
sb.append(" WHERE ");
sb.append(IndexDatabaseHelper.SavedQueriesColums.QUERY);
sb.append(" LIKE ");
sb.append("'");
sb.append(query);
sb.append("%");
sb.append("'");
}
sb.append(" LIMIT ");
sb.append(MAX_PROPOSED_SUGGESTIONS);
return sb.toString();
}
public long addSavedQuery(String query){
final SaveSearchQueryTask task = new SaveSearchQueryTask();
task.execute(query);
try {
return task.get();
} catch (InterruptedException e) {
Log.e(LOG_TAG, "Cannot insert saved query: " + query, e);
return -1 ;
} catch (ExecutionException e) {
Log.e(LOG_TAG, "Cannot insert saved query: " + query, e);
return -1;
}
}
public boolean update() { public boolean update() {
final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE); final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE);
List<ResolveInfo> list = List<ResolveInfo> list =
@@ -432,10 +485,10 @@ public class Index {
mDataToProcess.clear(); mDataToProcess.clear();
return result; return result;
} catch (InterruptedException e) { } catch (InterruptedException e) {
Log.e(LOG_TAG, "Cannot update index: " + e.getMessage()); Log.e(LOG_TAG, "Cannot update index", e);
return false; return false;
} catch (ExecutionException e) { } catch (ExecutionException e) {
Log.e(LOG_TAG, "Cannot update index: " + e.getMessage()); Log.e(LOG_TAG, "Cannot update index", e);
return false; return false;
} }
} }
@@ -545,15 +598,15 @@ public class Index {
} }
} }
private String buildSQL(String query) { private String buildSearchSQL(String query) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(buildSQLForColumn(query, MATCH_COLUMNS)); sb.append(buildSearchSQLForColumn(query, MATCH_COLUMNS));
sb.append(" ORDER BY "); sb.append(" ORDER BY ");
sb.append(IndexColumns.DATA_RANK); sb.append(IndexColumns.DATA_RANK);
return sb.toString(); return sb.toString();
} }
private String buildSQLForColumn(String query, String[] columnNames) { private String buildSearchSQLForColumn(String query, String[] columnNames) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("SELECT "); sb.append("SELECT ");
for (int n = 0; n < SELECT_COLUMNS.length; n++) { for (int n = 0; n < SELECT_COLUMNS.length; n++) {
@@ -565,15 +618,16 @@ public class Index {
sb.append(" FROM "); sb.append(" FROM ");
sb.append(Tables.TABLE_PREFS_INDEX); sb.append(Tables.TABLE_PREFS_INDEX);
sb.append(" WHERE "); sb.append(" WHERE ");
sb.append(buildWhereStringForColumns(query, columnNames)); sb.append(buildSearchWhereStringForColumns(query, columnNames));
return sb.toString(); return sb.toString();
} }
private String buildWhereStringForColumns(String query, String[] columnNames) { private String buildSearchWhereStringForColumns(String query, String[] columnNames) {
final StringBuilder sb = new StringBuilder(Tables.TABLE_PREFS_INDEX); final StringBuilder sb = new StringBuilder(Tables.TABLE_PREFS_INDEX);
sb.append(" MATCH "); sb.append(" MATCH ");
DatabaseUtils.appendEscapedSQLString(sb, buildMatchStringForColumns(query, columnNames)); DatabaseUtils.appendEscapedSQLString(sb,
buildSearchMatchStringForColumns(query, columnNames));
sb.append(" AND "); sb.append(" AND ");
sb.append(IndexColumns.LOCALE); sb.append(IndexColumns.LOCALE);
sb.append(" = "); sb.append(" = ");
@@ -584,7 +638,7 @@ public class Index {
return sb.toString(); return sb.toString();
} }
private String buildMatchStringForColumns(String query, String[] columnNames) { private String buildSearchMatchStringForColumns(String query, String[] columnNames) {
final String value = query + "*"; final String value = query + "*";
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
final int count = columnNames.length; final int count = columnNames.length;
@@ -1144,4 +1198,38 @@ public class Index {
return result; return result;
} }
} }
/**
* A basic AsynTask for saving a Search query into the database
*/
private class SaveSearchQueryTask extends AsyncTask<String, Void, Long> {
@Override
protected Long doInBackground(String... params) {
final long now = new Date().getTime();
final ContentValues values = new ContentValues();
values.put(IndexDatabaseHelper.SavedQueriesColums.QUERY, params[0]);
values.put(IndexDatabaseHelper.SavedQueriesColums.TIME_STAMP, now);
final SQLiteDatabase database = getWritableDatabase();
long lastInsertedRowId = -1;
try {
lastInsertedRowId =
database.replaceOrThrow(Tables.TABLE_SAVED_QUERIES, null, values);
final long delta = lastInsertedRowId - MAX_SAVED_SEARCH_QUERY;
if (delta > 0) {
int count = database.delete(Tables.TABLE_SAVED_QUERIES, "rowId <= ?",
new String[] { Long.toString(delta) });
Log.d(LOG_TAG, "Deleted '" + count + "' saved Search query(ies)");
}
} catch (Exception e) {
Log.d(LOG_TAG, "Cannot update saved Search queries", e);
}
return lastInsertedRowId;
}
}
} }