From a8b089db8a6d495a18dc3ad91d3bd84b042fb004 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Thu, 19 Jan 2017 16:17:16 -0800 Subject: [PATCH] Showing recents in search A list of recent queries will display when search first launches, or when text query is empty. Fix: 32452066 Test: make RunSettingsRoboTests Change-Id: Ic697f8c0795f2315a01406537c7ce1bbf9a813be --- res/layout/search_saved_query_item.xml | 45 +++++++++ src/com/android/settings/search/Index.java | 10 +- .../settings/search/IndexDatabaseHelper.java | 6 +- .../settings/search2/ResultPayload.java | 10 +- .../settings/search2/SavedQueryLoader.java | 77 +++++++++++++++ .../settings/search2/SavedQueryPayload.java | 64 ++++++++++++ .../settings/search2/SavedQueryRecorder.java | 97 +++++++++++++++++++ .../search2/SavedQueryViewHolder.java | 38 ++++++++ .../search2/SearchFeatureProvider.java | 5 + .../search2/SearchFeatureProviderImpl.java | 5 + .../settings/search2/SearchFragment.java | 55 ++++++++++- .../search2/SearchResultsAdapter.java | 5 +- .../grandfather_not_implementing_indexable | 2 +- .../search/DatabaseIndexingManagerTest.java | 13 +-- .../search/DatabaseResultLoaderTest.java | 15 +-- .../search2/SavedQueryLoaderTest.java | 79 +++++++++++++++ .../search2/SavedQueryPayloadTest.java | 40 ++++++++ .../search2/SavedQueryRecorderTest.java | 67 +++++++++++++ .../settings/search2/SearchFragmentTest.java | 27 ++++++ .../settings/testutils/DatabaseTestUtils.java | 36 +++++++ 20 files changed, 658 insertions(+), 38 deletions(-) create mode 100644 res/layout/search_saved_query_item.xml create mode 100644 src/com/android/settings/search2/SavedQueryLoader.java create mode 100644 src/com/android/settings/search2/SavedQueryPayload.java create mode 100644 src/com/android/settings/search2/SavedQueryRecorder.java create mode 100644 src/com/android/settings/search2/SavedQueryViewHolder.java create mode 100644 tests/robotests/src/com/android/settings/search2/SavedQueryLoaderTest.java create mode 100644 tests/robotests/src/com/android/settings/search2/SavedQueryPayloadTest.java create mode 100644 tests/robotests/src/com/android/settings/search2/SavedQueryRecorderTest.java create mode 100644 tests/robotests/src/com/android/settings/testutils/DatabaseTestUtils.java diff --git a/res/layout/search_saved_query_item.xml b/res/layout/search_saved_query_item.xml new file mode 100644 index 00000000000..71c8482e737 --- /dev/null +++ b/res/layout/search_saved_query_item.xml @@ -0,0 +1,45 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/search/Index.java b/src/com/android/settings/search/Index.java index cd6e5629c4c..b79a7f4b9cb 100644 --- a/src/com/android/settings/search/Index.java +++ b/src/com/android/settings/search/Index.java @@ -266,7 +266,7 @@ public class Index { StringBuilder sb = new StringBuilder(); sb.append("SELECT "); - sb.append(IndexDatabaseHelper.SavedQueriesColums.QUERY); + sb.append(IndexDatabaseHelper.SavedQueriesColumns.QUERY); sb.append(" FROM "); sb.append(Tables.TABLE_SAVED_QUERIES); @@ -274,7 +274,7 @@ public class Index { sb.append(" ORDER BY rowId DESC"); } else { sb.append(" WHERE "); - sb.append(IndexDatabaseHelper.SavedQueriesColums.QUERY); + sb.append(IndexDatabaseHelper.SavedQueriesColumns.QUERY); sb.append(" LIKE "); sb.append("'"); sb.append(query); @@ -1299,8 +1299,8 @@ public class Index { 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); + values.put(IndexDatabaseHelper.SavedQueriesColumns.QUERY, params[0]); + values.put(IndexDatabaseHelper.SavedQueriesColumns.TIME_STAMP, now); final SQLiteDatabase database = getWritableDatabase(); if (database == null) { @@ -1312,7 +1312,7 @@ public class Index { try { // First, delete all saved queries that are the same database.delete(Tables.TABLE_SAVED_QUERIES, - IndexDatabaseHelper.SavedQueriesColums.QUERY + " = ?", + IndexDatabaseHelper.SavedQueriesColumns.QUERY + " = ?", new String[] { params[0] }); // Second, insert the saved query diff --git a/src/com/android/settings/search/IndexDatabaseHelper.java b/src/com/android/settings/search/IndexDatabaseHelper.java index ba53e94c320..8de6c54e3c2 100644 --- a/src/com/android/settings/search/IndexDatabaseHelper.java +++ b/src/com/android/settings/search/IndexDatabaseHelper.java @@ -67,7 +67,7 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper { String BUILD = "build"; } - public interface SavedQueriesColums { + public interface SavedQueriesColumns { String QUERY = "query"; String TIME_STAMP = "timestamp"; } @@ -127,9 +127,9 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper { private static final String CREATE_SAVED_QUERIES_TABLE = "CREATE TABLE " + Tables.TABLE_SAVED_QUERIES + "(" + - SavedQueriesColums.QUERY + " VARCHAR(64) NOT NULL" + + SavedQueriesColumns.QUERY + " VARCHAR(64) NOT NULL" + ", " + - SavedQueriesColums.TIME_STAMP + " INTEGER" + + SavedQueriesColumns.TIME_STAMP + " INTEGER" + ")"; private static final String INSERT_BUILD_VERSION = diff --git a/src/com/android/settings/search2/ResultPayload.java b/src/com/android/settings/search2/ResultPayload.java index 3842def2acd..42942341fdb 100644 --- a/src/com/android/settings/search2/ResultPayload.java +++ b/src/com/android/settings/search2/ResultPayload.java @@ -29,7 +29,7 @@ import java.lang.annotation.RetentionPolicy; public abstract class ResultPayload implements Parcelable { @IntDef({PayloadType.INLINE_SLIDER, PayloadType.INLINE_SWITCH, - PayloadType.INTENT}) + PayloadType.INTENT, PayloadType.SAVED_QUERY}) @Retention(RetentionPolicy.SOURCE) public @interface PayloadType { /** @@ -46,6 +46,11 @@ public abstract class ResultPayload implements Parcelable { * Result is a inline widget, using a toggle widget as UI. */ int INLINE_SWITCH = 2; + + /** + * Result is a recently saved query. + */ + int SAVED_QUERY = 3; } @IntDef({SettingsSource.UNKNOWN, SettingsSource.SYSTEM, SettingsSource.SECURE, @@ -59,5 +64,6 @@ public abstract class ResultPayload implements Parcelable { } - @ResultPayload.PayloadType public abstract int getType(); + @ResultPayload.PayloadType + public abstract int getType(); } diff --git a/src/com/android/settings/search2/SavedQueryLoader.java b/src/com/android/settings/search2/SavedQueryLoader.java new file mode 100644 index 00000000000..b034b443a62 --- /dev/null +++ b/src/com/android/settings/search2/SavedQueryLoader.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017 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.search2; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.support.annotation.VisibleForTesting; + +import com.android.settings.search.IndexDatabaseHelper; +import com.android.settings.search.IndexDatabaseHelper.SavedQueriesColumns; +import com.android.settings.utils.AsyncLoader; + +import java.util.ArrayList; +import java.util.List; + +/** + * Loader for recently searched queries. + */ +public class SavedQueryLoader extends AsyncLoader> { + + // Max number of proposed suggestions + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + static final int MAX_PROPOSED_SUGGESTIONS = 5; + + private final SQLiteDatabase mDatabase; + + public SavedQueryLoader(Context context) { + super(context); + mDatabase = IndexDatabaseHelper.getInstance(context).getReadableDatabase(); + } + + @Override + protected void onDiscardResult(List result) { + + } + + @Override + public List loadInBackground() { + Cursor cursor = mDatabase.query(IndexDatabaseHelper.Tables.TABLE_SAVED_QUERIES /* table */, + new String[]{SavedQueriesColumns.QUERY} /* columns */, + null /* selection */, + null /* selectionArgs */, + null /* groupBy */, + null /* having */, + "rowId DESC" /* orderBy */, + String.valueOf(MAX_PROPOSED_SUGGESTIONS) /* limit */); + return convertCursorToResult(cursor); + } + + private List convertCursorToResult(Cursor cursor) { + final List results = new ArrayList<>(); + while (cursor.moveToNext()) { + final SavedQueryPayload payload = new SavedQueryPayload( + cursor.getString(cursor.getColumnIndex(SavedQueriesColumns.QUERY))); + results.add(new SearchResult.Builder() + .addTitle(payload.query) + .addPayload(payload) + .build()); + } + return results; + } +} diff --git a/src/com/android/settings/search2/SavedQueryPayload.java b/src/com/android/settings/search2/SavedQueryPayload.java new file mode 100644 index 00000000000..6316894a2ad --- /dev/null +++ b/src/com/android/settings/search2/SavedQueryPayload.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 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.search2; + +import android.os.Parcel; +import android.support.annotation.VisibleForTesting; + +/** + * {@link ResultPayload} for saved query. + */ +public class SavedQueryPayload extends ResultPayload { + + public final String query; + + public SavedQueryPayload(String query) { + this.query = query; + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + SavedQueryPayload(Parcel in) { + query = in.readString(); + } + + @Override + public int getType() { + return PayloadType.SAVED_QUERY; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(query); + } + + public static final Creator CREATOR = new Creator() { + @Override + public SavedQueryPayload createFromParcel(Parcel in) { + return new SavedQueryPayload(in); + } + + @Override + public SavedQueryPayload[] newArray(int size) { + return new SavedQueryPayload[size]; + } + }; +} diff --git a/src/com/android/settings/search2/SavedQueryRecorder.java b/src/com/android/settings/search2/SavedQueryRecorder.java new file mode 100644 index 00000000000..e2325e83104 --- /dev/null +++ b/src/com/android/settings/search2/SavedQueryRecorder.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017 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.search2; + +import android.content.ContentValues; +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.util.Log; + +import com.android.settings.search.IndexDatabaseHelper; +import com.android.settings.utils.AsyncLoader; + +import static com.android.settings.search.IndexDatabaseHelper.Tables.TABLE_SAVED_QUERIES; + +/** + * A background task to update saved queries. + */ +public class SavedQueryRecorder extends AsyncLoader { + + private static final String LOG_TAG = "SavedQueryRecorder"; + + // Max number of saved search queries (who will be used for proposing suggestions) + private static long MAX_SAVED_SEARCH_QUERY = 64; + + private final String mQuery; + + public SavedQueryRecorder(Context context, String query) { + super(context); + mQuery = query; + } + + @Override + protected void onDiscardResult(Void result) { + + } + + @Override + public Void loadInBackground() { + final long now = System.currentTimeMillis(); + + final ContentValues values = new ContentValues(); + values.put(IndexDatabaseHelper.SavedQueriesColumns.QUERY, mQuery); + values.put(IndexDatabaseHelper.SavedQueriesColumns.TIME_STAMP, now); + + final SQLiteDatabase database = getWritableDatabase(); + if (database == null) { + return null; + } + + long lastInsertedRowId; + try { + // First, delete all saved queries that are the same + database.delete(TABLE_SAVED_QUERIES, + IndexDatabaseHelper.SavedQueriesColumns.QUERY + " = ?", + new String[]{mQuery}); + + // Second, insert the saved query + lastInsertedRowId = database.insertOrThrow(TABLE_SAVED_QUERIES, null, values); + + // Last, remove "old" saved queries + final long delta = lastInsertedRowId - MAX_SAVED_SEARCH_QUERY; + if (delta > 0) { + int count = database.delete(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 null; + } + + private SQLiteDatabase getWritableDatabase() { + try { + return IndexDatabaseHelper.getInstance(getContext()).getWritableDatabase(); + } catch (SQLiteException e) { + Log.e(LOG_TAG, "Cannot open writable database", e); + return null; + } + } +} diff --git a/src/com/android/settings/search2/SavedQueryViewHolder.java b/src/com/android/settings/search2/SavedQueryViewHolder.java new file mode 100644 index 00000000000..a32ed0523f8 --- /dev/null +++ b/src/com/android/settings/search2/SavedQueryViewHolder.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 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.search2; + +import android.view.View; +import android.widget.TextView; + +public class SavedQueryViewHolder extends SearchViewHolder { + + public final TextView titleView; + + public SavedQueryViewHolder(View view) { + super(view); + titleView = (TextView) view.findViewById(android.R.id.title); + } + + @Override + public void onBind(SearchFragment fragment, SearchResult result) { + titleView.setText(result.title); + itemView.setOnClickListener(v -> { + fragment.onSavedQueryClicked(result.title); + }); + } +} diff --git a/src/com/android/settings/search2/SearchFeatureProvider.java b/src/com/android/settings/search2/SearchFeatureProvider.java index 8a616a7d399..91a14444c45 100644 --- a/src/com/android/settings/search2/SearchFeatureProvider.java +++ b/src/com/android/settings/search2/SearchFeatureProvider.java @@ -47,6 +47,11 @@ public interface SearchFeatureProvider { */ InstalledAppResultLoader getInstalledAppSearchLoader(Context context, String query); + /** + * Returns a new loader to get all recently saved queries search terms. + */ + SavedQueryLoader getSavedQueryLoader(Context context); + /** * Returns the manager for indexing Settings data. */ diff --git a/src/com/android/settings/search2/SearchFeatureProviderImpl.java b/src/com/android/settings/search2/SearchFeatureProviderImpl.java index a76d9055d74..5d624124140 100644 --- a/src/com/android/settings/search2/SearchFeatureProviderImpl.java +++ b/src/com/android/settings/search2/SearchFeatureProviderImpl.java @@ -72,6 +72,11 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider { context, new PackageManagerWrapperImpl(context.getPackageManager()), query); } + @Override + public SavedQueryLoader getSavedQueryLoader(Context context) { + return new SavedQueryLoader(context); + } + @Override public DatabaseIndexingManager getIndexingManager(Context context) { if (mDatabaseIndexingManager == null) { diff --git a/src/com/android/settings/search2/SearchFragment.java b/src/com/android/settings/search2/SearchFragment.java index e26f5edbf2d..b688a455522 100644 --- a/src/com/android/settings/search2/SearchFragment.java +++ b/src/com/android/settings/search2/SearchFragment.java @@ -52,8 +52,9 @@ public class SearchFragment extends InstrumentedFragment implements private static final String STATE_RESULT_CLICK_COUNT = "state_result_click_count"; // Loader IDs - private static final int LOADER_ID_DATABASE = 0; - private static final int LOADER_ID_INSTALLED_APPS = 1; + private static final int LOADER_ID_RECENTS = 0; + private static final int LOADER_ID_DATABASE = 1; + private static final int LOADER_ID_INSTALLED_APPS = 2; // Logging @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @@ -61,6 +62,10 @@ public class SearchFragment extends InstrumentedFragment implements @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) String mQuery; + + private final SaveQueryRecorderCallback mSaveQueryRecorderCallback = + new SaveQueryRecorderCallback(); + private boolean mNeverEnteredQuery = true; private int mResultClickCount; private MetricsFeatureProvider mMetricsFeatureProvider; @@ -68,6 +73,7 @@ public class SearchFragment extends InstrumentedFragment implements private SearchResultsAdapter mSearchAdapter; private RecyclerView mResultsRecyclerView; + private SearchView mSearchView; @Override public int getMetricsCategory() { @@ -86,18 +92,21 @@ public class SearchFragment extends InstrumentedFragment implements super.onCreate(savedInstanceState); setHasOptionsMenu(true); mSearchAdapter = new SearchResultsAdapter(this); + final LoaderManager loaderManager = getLoaderManager(); if (savedInstanceState != null) { mQuery = savedInstanceState.getString(STATE_QUERY); mNeverEnteredQuery = savedInstanceState.getBoolean(STATE_NEVER_ENTERED_QUERY); mResultClickCount = savedInstanceState.getInt(STATE_RESULT_CLICK_COUNT); - final LoaderManager loaderManager = getLoaderManager(); loaderManager.initLoader(LOADER_ID_DATABASE, null, this); loaderManager.initLoader(LOADER_ID_INSTALLED_APPS, null, this); + } else { + loaderManager.initLoader(LOADER_ID_RECENTS, null, this); } final Activity activity = getActivity(); final ActionBar actionBar = activity.getActionBar(); - actionBar.setCustomView(makeSearchView(actionBar, mQuery)); + mSearchView = makeSearchView(actionBar, mQuery); + actionBar.setCustomView(mSearchView); actionBar.setDisplayShowCustomEnabled(true); actionBar.setDisplayShowTitleEnabled(false); @@ -151,7 +160,10 @@ public class SearchFragment extends InstrumentedFragment implements mSearchAdapter.clearResults(); if (TextUtils.isEmpty(mQuery)) { - getLoaderManager().destroyLoader(LOADER_ID_DATABASE); + final LoaderManager loaderManager = getLoaderManager(); + loaderManager.destroyLoader(LOADER_ID_DATABASE); + loaderManager.destroyLoader(LOADER_ID_INSTALLED_APPS); + loaderManager.restartLoader(LOADER_ID_RECENTS, null /* args */, this /* callback */); } else { restartLoaders(); } @@ -161,6 +173,10 @@ public class SearchFragment extends InstrumentedFragment implements @Override public boolean onQueryTextSubmit(String query) { + // Save submitted query. + getLoaderManager().restartLoader(SaveQueryRecorderCallback.LOADER_ID_SAVE_QUERY_TASK, null, + mSaveQueryRecorderCallback); + return true; } @@ -173,6 +189,8 @@ public class SearchFragment extends InstrumentedFragment implements return mSearchFeatureProvider.getDatabaseSearchLoader(activity, mQuery); case LOADER_ID_INSTALLED_APPS: return mSearchFeatureProvider.getInstalledAppSearchLoader(activity, mQuery); + case LOADER_ID_RECENTS: + return mSearchFeatureProvider.getSavedQueryLoader(activity); default: return null; } @@ -191,6 +209,12 @@ public class SearchFragment extends InstrumentedFragment implements mResultClickCount++; } + public void onSavedQueryClicked(CharSequence query) { + final String queryString = query.toString(); + mSearchView.setQuery(queryString, false /* submit */); + onQueryTextChange(queryString); + } + private void restartLoaders() { final LoaderManager loaderManager = getLoaderManager(); loaderManager.restartLoader(LOADER_ID_DATABASE, null /* args */, this /* callback */); @@ -207,4 +231,25 @@ public class SearchFragment extends InstrumentedFragment implements searchView.setLayoutParams(lp); return searchView; } + + private class SaveQueryRecorderCallback implements LoaderManager.LoaderCallbacks { + // TODO: make a generic background task manager to handle one-off tasks like this one. + + private static final int LOADER_ID_SAVE_QUERY_TASK = 0; + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new SavedQueryRecorder(getActivity(), mQuery); + } + + @Override + public void onLoadFinished(Loader loader, Void data) { + + } + + @Override + public void onLoaderReset(Loader loader) { + + } + } } diff --git a/src/com/android/settings/search2/SearchResultsAdapter.java b/src/com/android/settings/search2/SearchResultsAdapter.java index c318b410a99..999a485b3da 100644 --- a/src/com/android/settings/search2/SearchResultsAdapter.java +++ b/src/com/android/settings/search2/SearchResultsAdapter.java @@ -49,13 +49,16 @@ public class SearchResultsAdapter extends Adapter { final Context context = parent.getContext(); final LayoutInflater inflater = LayoutInflater.from(context); final View view; - switch(viewType) { + switch (viewType) { case PayloadType.INTENT: view = inflater.inflate(R.layout.search_intent_item, parent, false); return new IntentSearchViewHolder(view); case PayloadType.INLINE_SWITCH: view = inflater.inflate(R.layout.search_inline_switch_item, parent, false); return new InlineSwitchViewHolder(view, context); + case PayloadType.SAVED_QUERY: + view = inflater.inflate(R.layout.search_saved_query_item, parent, false); + return new SavedQueryViewHolder(view); default: return null; } diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable index cd0822b04c8..a17859655be 100644 --- a/tests/robotests/assets/grandfather_not_implementing_indexable +++ b/tests/robotests/assets/grandfather_not_implementing_indexable @@ -90,4 +90,4 @@ com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment com.android.settings.applications.ConvertToFbe com.android.settings.localepicker.LocaleListEditor com.android.settings.qstile.DevelopmentTileConfigActivity$DevelopmentTileConfigFragment -com.android.settings.applications.ExternalSourcesDetails \ No newline at end of file +com.android.settings.applications.ExternalSourcesDetails diff --git a/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java b/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java index 8b363b04897..3d469dd0b68 100644 --- a/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java +++ b/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java @@ -21,10 +21,12 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.provider.SearchIndexableResource; + import com.android.settings.R; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.search2.DatabaseIndexingManager; +import com.android.settings.testutils.DatabaseTestUtils; import org.junit.After; import org.junit.Before; @@ -33,7 +35,6 @@ import org.junit.runner.RunWith; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowApplication; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -85,15 +86,7 @@ public class DatabaseIndexingManagerTest { @After public void cleanUp() { - Field instance; - Class clazz = IndexDatabaseHelper.class; - try { - instance = clazz.getDeclaredField("sSingleton"); - instance.setAccessible(true); - instance.set(null, null); - } catch (Exception e) { - throw new RuntimeException(); - } + DatabaseTestUtils.clearDb(); } @Test diff --git a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java index c592aef8dbf..2b29a1679ce 100644 --- a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java +++ b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java @@ -20,10 +20,13 @@ package com.android.settings.search; import android.content.ContentValues; import android.content.Context; import android.database.sqlite.SQLiteDatabase; + import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.search2.DatabaseIndexingUtils; import com.android.settings.search2.DatabaseResultLoader; +import com.android.settings.testutils.DatabaseTestUtils; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -31,8 +34,6 @@ import org.junit.runner.RunWith; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import java.lang.reflect.Field; - import static com.google.common.truth.Truth.assertThat; @RunWith(SettingsRobolectricTestRunner.class) @@ -53,15 +54,7 @@ public class DatabaseResultLoaderTest { @After public void cleanUp() { - Field instance; - Class clazz = IndexDatabaseHelper.class; - try { - instance = clazz.getDeclaredField("sSingleton"); - instance.setAccessible(true); - instance.set(null, null); - } catch (Exception e) { - throw new RuntimeException(); - } + DatabaseTestUtils.clearDb(); } @Test diff --git a/tests/robotests/src/com/android/settings/search2/SavedQueryLoaderTest.java b/tests/robotests/src/com/android/settings/search2/SavedQueryLoaderTest.java new file mode 100644 index 00000000000..d975f0c8f97 --- /dev/null +++ b/tests/robotests/src/com/android/settings/search2/SavedQueryLoaderTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017 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.search2; + + +import android.content.ContentValues; +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; + +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.search.IndexDatabaseHelper; +import com.android.settings.testutils.DatabaseTestUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.List; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class SavedQueryLoaderTest { + + private Context mContext; + private SQLiteDatabase mDb; + private SavedQueryLoader mLoader; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase(); + mLoader = new SavedQueryLoader(mContext); + setUpDb(); + } + + @After + public void cleanUp() { + DatabaseTestUtils.clearDb(); + } + + @Test + public void loadInBackground_shouldReturnSavedQueries() { + final List results = mLoader.loadInBackground(); + assertThat(results.size()).isEqualTo(SavedQueryLoader.MAX_PROPOSED_SUGGESTIONS); + for (SearchResult result : results) { + assertThat(result.viewType).isEqualTo(ResultPayload.PayloadType.SAVED_QUERY); + } + } + + private void setUpDb() { + final long now = System.currentTimeMillis(); + for (int i = 0; i < SavedQueryLoader.MAX_PROPOSED_SUGGESTIONS + 2; i++) { + ContentValues values = new ContentValues(); + values.put(IndexDatabaseHelper.SavedQueriesColumns.QUERY, String.valueOf(i)); + values.put(IndexDatabaseHelper.SavedQueriesColumns.TIME_STAMP, now); + mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_SAVED_QUERIES, null, values); + } + } +} diff --git a/tests/robotests/src/com/android/settings/search2/SavedQueryPayloadTest.java b/tests/robotests/src/com/android/settings/search2/SavedQueryPayloadTest.java new file mode 100644 index 00000000000..daa6d5ecc08 --- /dev/null +++ b/tests/robotests/src/com/android/settings/search2/SavedQueryPayloadTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 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.search2; + + +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class SavedQueryPayloadTest { + + private SavedQueryPayload mPayload; + + @Test + public void getType_shouldBeSavedQueryType() { + mPayload = new SavedQueryPayload("Test"); + assertThat(mPayload.getType()).isEqualTo(ResultPayload.PayloadType.SAVED_QUERY); + } +} diff --git a/tests/robotests/src/com/android/settings/search2/SavedQueryRecorderTest.java b/tests/robotests/src/com/android/settings/search2/SavedQueryRecorderTest.java new file mode 100644 index 00000000000..c56ecce537a --- /dev/null +++ b/tests/robotests/src/com/android/settings/search2/SavedQueryRecorderTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 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.search2; + + +import android.content.Context; + +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.testutils.DatabaseTestUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.List; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class SavedQueryRecorderTest { + + private Context mContext; + private SavedQueryRecorder mRecorder; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + } + + @After + public void cleanUp() { + DatabaseTestUtils.clearDb(); + } + + @Test + public void canSaveQueryToDb() { + final String query = "test"; + mRecorder = new SavedQueryRecorder(mContext, query); + + mRecorder.loadInBackground(); + + final SavedQueryLoader loader = new SavedQueryLoader(mContext); + List results = loader.loadInBackground(); + + assertThat(results.size()).isEqualTo(1); + assertThat(results.get(0).title).isEqualTo(query); + } +} diff --git a/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java index d97360d0414..7a0bb5471f5 100644 --- a/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java +++ b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java @@ -39,6 +39,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -52,6 +53,9 @@ public class SearchFragmentTest { private DatabaseResultLoader mDatabaseResultLoader; @Mock private InstalledAppResultLoader mInstalledAppResultLoader; + @Mock + private SavedQueryLoader mSavedQueryLoader; + private FakeFeatureFactory mFeatureFactory; @Before @@ -65,6 +69,8 @@ public class SearchFragmentTest { when(mFeatureFactory.searchFeatureProvider .getInstalledAppSearchLoader(any(Context.class), anyString())) .thenReturn(mInstalledAppResultLoader); + when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class))) + .thenReturn(mSavedQueryLoader); } @Test @@ -114,6 +120,27 @@ public class SearchFragmentTest { .getInstalledAppSearchLoader(any(Context.class), anyString()); } + @Test + public void queryTextChangeToEmpty_shouldTriggerSavedQueryLoader() { + ActivityController activityController = + Robolectric.buildActivity(SearchActivity.class); + activityController.setup(); + SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager() + .findFragmentById(R.id.main_content); + + fragment.onQueryTextChange(""); + activityController.get().onBackPressed(); + activityController.pause().stop().destroy(); + + verify(mFeatureFactory.searchFeatureProvider, never()) + .getDatabaseSearchLoader(any(Context.class), anyString()); + verify(mFeatureFactory.searchFeatureProvider, never()) + .getInstalledAppSearchLoader(any(Context.class), anyString()); + // Saved query loaded 2 times: fragment start, and query change to empty. + verify(mFeatureFactory.searchFeatureProvider, times(2)) + .getSavedQueryLoader(any(Context.class)); + } + @Test public void updateIndex_TriggerOnCreate() { ActivityController activityController = diff --git a/tests/robotests/src/com/android/settings/testutils/DatabaseTestUtils.java b/tests/robotests/src/com/android/settings/testutils/DatabaseTestUtils.java new file mode 100644 index 00000000000..8fbe1c9ff39 --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/DatabaseTestUtils.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 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.testutils; + +import com.android.settings.search.IndexDatabaseHelper; + +import java.lang.reflect.Field; + +public class DatabaseTestUtils { + + public static void clearDb() { + Field instance; + Class clazz = IndexDatabaseHelper.class; + try { + instance = clazz.getDeclaredField("sSingleton"); + instance.setAccessible(true); + instance.set(null, null); + } catch (Exception e) { + throw new RuntimeException(); + } + } +}