Sync search result loaders
The loaders should be syncronized so the results can be
properly ranked without sudden insertions.
This means InstalledAppsLoader needs to finish faster,
which is accomplished by delaying icon loading to bind time
rather than as the apps are queried.
Bug: 34772522
Test: make RunSettingsRoboTests
Change-Id: I7f5244c574d37c6cfd8bbd0d3d40488f38211be3
diff --git a/src/com/android/settings/search2/AppSearchResult.java b/src/com/android/settings/search2/AppSearchResult.java
new file mode 100644
index 0000000..111f775
--- /dev/null
+++ b/src/com/android/settings/search2/AppSearchResult.java
@@ -0,0 +1,45 @@
+/*
+ * 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.pm.ApplicationInfo;
+
+public class AppSearchResult extends SearchResult {
+ /**
+ * Installed app's ApplicationInfo for delayed loading of icons
+ */
+ public final ApplicationInfo info;
+
+ public AppSearchResult(Builder builder) {
+ super(builder);
+ info = builder.mInfo;
+ }
+
+ public static class Builder extends SearchResult.Builder {
+ protected ApplicationInfo mInfo;
+
+ public SearchResult.Builder setAppInfo(ApplicationInfo info) {
+ mInfo = info;
+ return this;
+ }
+
+ public AppSearchResult build() {
+ return new AppSearchResult(this);
+ }
+ }
+}
diff --git a/src/com/android/settings/search2/DatabaseResultLoader.java b/src/com/android/settings/search2/DatabaseResultLoader.java
index 1f032a0..854b8dd 100644
--- a/src/com/android/settings/search2/DatabaseResultLoader.java
+++ b/src/com/android/settings/search2/DatabaseResultLoader.java
@@ -36,7 +36,7 @@
/**
* AsyncTask to retrieve Settings, First party app and any intent based results.
*/
-public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
+public class DatabaseResultLoader extends AsyncLoader<List<? extends SearchResult>> {
private static final String LOG = "DatabaseResultLoader";
/* These indices are used to match the columns of the this loader's SELECT statement.
@@ -98,26 +98,25 @@
private static final int[] BASE_RANKS = {1, 4, 7};
private final String mQueryText;
- private final SQLiteDatabase mDatabase;
+ private final Context mContext;
private final CursorToSearchResultConverter mConverter;
private final SiteMapManager mSiteMapManager;
- public DatabaseResultLoader(Context context, String queryText) {
+ public DatabaseResultLoader(Context context, String queryText, SiteMapManager mapManager) {
super(context);
- mSiteMapManager = FeatureFactory.getFactory(context)
- .getSearchFeatureProvider().getSiteMapManager();
- mDatabase = IndexDatabaseHelper.getInstance(context).getReadableDatabase();
+ mSiteMapManager = mapManager;
+ mContext = context;
mQueryText = cleanQuery(queryText);
mConverter = new CursorToSearchResultConverter(context, mQueryText);
}
@Override
- protected void onDiscardResult(List<SearchResult> result) {
+ protected void onDiscardResult(List<? extends SearchResult> result) {
// TODO Search
}
@Override
- public List<SearchResult> loadInBackground() {
+ public List<? extends SearchResult> loadInBackground() {
if (mQueryText == null || mQueryText.isEmpty()) {
return null;
}
@@ -144,7 +143,9 @@
final String whereClause = buildWhereClause(matchColumns);
final String[] selection = buildQuerySelection(matchColumns.length * 2);
- final Cursor resultCursor = mDatabase.query(TABLE_PREFS_INDEX, SELECT_COLUMNS, whereClause,
+ final SQLiteDatabase database = IndexDatabaseHelper.getInstance(mContext)
+ .getReadableDatabase();
+ final Cursor resultCursor = database.query(TABLE_PREFS_INDEX, SELECT_COLUMNS, whereClause,
selection, null, null, null);
return mConverter.convertCursor(mSiteMapManager, resultCursor, baseRank);
}
diff --git a/src/com/android/settings/search2/InstalledAppResultLoader.java b/src/com/android/settings/search2/InstalledAppResultLoader.java
index e60598d..c6a1b82 100644
--- a/src/com/android/settings/search2/InstalledAppResultLoader.java
+++ b/src/com/android/settings/search2/InstalledAppResultLoader.java
@@ -42,7 +42,7 @@
/**
* Search loader for installed apps.
*/
-public class InstalledAppResultLoader extends AsyncLoader<List<SearchResult>> {
+public class InstalledAppResultLoader extends AsyncLoader<List<? extends SearchResult>> {
private static final int NAME_NO_MATCH = -1;
private static final Intent LAUNCHER_PROBE = new Intent(Intent.ACTION_MAIN)
@@ -56,18 +56,17 @@
public InstalledAppResultLoader(Context context, PackageManagerWrapper pmWrapper,
- String query) {
+ String query, SiteMapManager mapManager) {
super(context);
- mSiteMapManager = FeatureFactory.getFactory(context)
- .getSearchFeatureProvider().getSiteMapManager();
+ mSiteMapManager = mapManager;
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mPackageManager = pmWrapper;
mQuery = query;
}
@Override
- public List<SearchResult> loadInBackground() {
- final List<SearchResult> results = new ArrayList<>();
+ public List<? extends SearchResult> loadInBackground() {
+ final List<AppSearchResult> results = new ArrayList<>();
final PackageManager pm = mPackageManager.getPackageManager();
for (UserInfo user : getUsersToCount()) {
@@ -90,10 +89,10 @@
.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", info.packageName, null));
- final SearchResult.Builder builder = new SearchResult.Builder();
- builder.addIcon(info.loadIcon(pm))
+ final AppSearchResult.Builder builder = new AppSearchResult.Builder();
+ builder.setAppInfo(info)
.addTitle(info.loadLabel(pm))
- .addRank(wordDiff)
+ .addRank(getRank(wordDiff))
.addBreadcrumbs(getBreadCrumb())
.addPayload(new IntentPayload(intent));
results.add(builder.build());
@@ -120,7 +119,7 @@
}
@Override
- protected void onDiscardResult(List<SearchResult> result) {
+ protected void onDiscardResult(List<? extends SearchResult> result) {
}
@@ -200,4 +199,16 @@
}
return mBreadcrumb;
}
+
+ /**
+ * A temporary ranking scheme for installed apps.
+ * @param wordDiff difference between query length and app name length.
+ * @return the ranking.
+ */
+ private int getRank(int wordDiff) {
+ if (wordDiff < 6) {
+ return 3;
+ }
+ return 4;
+ }
}
diff --git a/src/com/android/settings/search2/SavedQueryLoader.java b/src/com/android/settings/search2/SavedQueryLoader.java
index b034b44..245842c 100644
--- a/src/com/android/settings/search2/SavedQueryLoader.java
+++ b/src/com/android/settings/search2/SavedQueryLoader.java
@@ -31,7 +31,7 @@
/**
* Loader for recently searched queries.
*/
-public class SavedQueryLoader extends AsyncLoader<List<SearchResult>> {
+public class SavedQueryLoader extends AsyncLoader<List<? extends SearchResult>> {
// Max number of proposed suggestions
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@@ -45,12 +45,12 @@
}
@Override
- protected void onDiscardResult(List<SearchResult> result) {
+ protected void onDiscardResult(List<? extends SearchResult> result) {
}
@Override
- public List<SearchResult> loadInBackground() {
+ public List<? extends SearchResult> loadInBackground() {
Cursor cursor = mDatabase.query(IndexDatabaseHelper.Tables.TABLE_SAVED_QUERIES /* table */,
new String[]{SavedQueriesColumns.QUERY} /* columns */,
null /* selection */,
diff --git a/src/com/android/settings/search2/SearchFeatureProviderImpl.java b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
index b575b15..b161686 100644
--- a/src/com/android/settings/search2/SearchFeatureProviderImpl.java
+++ b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
@@ -65,13 +65,14 @@
@Override
public DatabaseResultLoader getDatabaseSearchLoader(Context context, String query) {
- return new DatabaseResultLoader(context, query);
+ return new DatabaseResultLoader(context, query, getSiteMapManager());
}
@Override
public InstalledAppResultLoader getInstalledAppSearchLoader(Context context, String query) {
return new InstalledAppResultLoader(
- context, new PackageManagerWrapperImpl(context.getPackageManager()), query);
+ context, new PackageManagerWrapperImpl(context.getPackageManager()), query,
+ getSiteMapManager());
}
@Override
diff --git a/src/com/android/settings/search2/SearchFragment.java b/src/com/android/settings/search2/SearchFragment.java
index b688a45..a1233b7 100644
--- a/src/com/android/settings/search2/SearchFragment.java
+++ b/src/com/android/settings/search2/SearchFragment.java
@@ -41,9 +41,11 @@
import com.android.settings.overlay.FeatureFactory;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
public class SearchFragment extends InstrumentedFragment implements
- SearchView.OnQueryTextListener, LoaderManager.LoaderCallbacks<List<SearchResult>> {
+ SearchView.OnQueryTextListener, LoaderManager.LoaderCallbacks<List<? extends SearchResult>>
+{
private static final String TAG = "SearchFragment";
// State values
@@ -56,6 +58,11 @@
private static final int LOADER_ID_DATABASE = 1;
private static final int LOADER_ID_INSTALLED_APPS = 2;
+ private static final int NUM_QUERY_LOADERS = 2;
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ AtomicInteger mUnfinishedLoadersCount = new AtomicInteger(NUM_QUERY_LOADERS);;
+
// Logging
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
static final String RESULT_CLICK_COUNT = "settings_search_result_click_count";
@@ -69,9 +76,13 @@
private boolean mNeverEnteredQuery = true;
private int mResultClickCount;
private MetricsFeatureProvider mMetricsFeatureProvider;
- private SearchFeatureProvider mSearchFeatureProvider;
- private SearchResultsAdapter mSearchAdapter;
+ @VisibleForTesting (otherwise = VisibleForTesting.PRIVATE)
+ SearchFeatureProvider mSearchFeatureProvider;
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ SearchResultsAdapter mSearchAdapter;
+
private RecyclerView mResultsRecyclerView;
private SearchView mSearchView;
@@ -92,7 +103,9 @@
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);
@@ -181,7 +194,7 @@
}
@Override
- public Loader<List<SearchResult>> onCreateLoader(int id, Bundle args) {
+ public Loader<List<? extends SearchResult>> onCreateLoader(int id, Bundle args) {
final Activity activity = getActivity();
switch (id) {
@@ -197,12 +210,17 @@
}
@Override
- public void onLoadFinished(Loader<List<SearchResult>> loader, List<SearchResult> data) {
- mSearchAdapter.mergeResults(data, loader.getClass().getName());
+ public void onLoadFinished(Loader<List<? extends SearchResult>> loader,
+ List<? extends SearchResult> data) {
+ mSearchAdapter.addResultsToMap(data, loader.getClass().getName());
+
+ if (mUnfinishedLoadersCount.decrementAndGet() == 0) {
+ mSearchAdapter.mergeResults();
+ }
}
@Override
- public void onLoaderReset(Loader<List<SearchResult>> loader) {
+ public void onLoaderReset(Loader<List<? extends SearchResult>> loader) {
}
public void onSearchResultClicked() {
@@ -217,6 +235,7 @@
private void restartLoaders() {
final LoaderManager loaderManager = getLoaderManager();
+ mUnfinishedLoadersCount.set(NUM_QUERY_LOADERS);
loaderManager.restartLoader(LOADER_ID_DATABASE, null /* args */, this /* callback */);
loaderManager.restartLoader(LOADER_ID_INSTALLED_APPS, null /* args */, this /* callback */);
}
diff --git a/src/com/android/settings/search2/SearchResult.java b/src/com/android/settings/search2/SearchResult.java
index e817a9d..37559aa 100644
--- a/src/com/android/settings/search2/SearchResult.java
+++ b/src/com/android/settings/search2/SearchResult.java
@@ -27,6 +27,12 @@
public class SearchResult implements Comparable<SearchResult> {
/**
+ * Defines the max rank for a search result to be considered as ranked. Results with ranks
+ * higher than this have no guarantee for sorting order.
+ */
+ public static final int MAX_RANK = 9;
+
+ /**
* The title of the result and main text displayed.
* Intent Results: Displays as the primary
*/
@@ -74,7 +80,7 @@
*/
public final long stableId;
- private SearchResult(Builder builder) {
+ protected SearchResult(Builder builder) {
title = builder.mTitle;
summary = builder.mSummary;
breadcrumbs = builder.mBreadcrumbs;
@@ -116,19 +122,19 @@
return this;
}
- public Builder addRank(int rank) {
+ public Builder addRank(int rank) {
if (rank >= 0 && rank <= 9) {
mRank = rank;
}
return this;
}
- public Builder addIcon(Drawable icon) {
+ public Builder addIcon(Drawable icon) {
mIcon = icon;
return this;
}
- public Builder addPayload(ResultPayload payload) {
+ public Builder addPayload(ResultPayload payload) {
mResultPayload = payload;
return this;
}
@@ -143,4 +149,4 @@
return new SearchResult(this);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/com/android/settings/search2/SearchResultsAdapter.java b/src/com/android/settings/search2/SearchResultsAdapter.java
index 999a485..afe432a 100644
--- a/src/com/android/settings/search2/SearchResultsAdapter.java
+++ b/src/com/android/settings/search2/SearchResultsAdapter.java
@@ -17,6 +17,7 @@
package com.android.settings.search2;
import android.content.Context;
+import android.support.annotation.MainThread;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.RecyclerView.Adapter;
import android.util.ArrayMap;
@@ -31,10 +32,13 @@
import java.util.List;
import java.util.Map;
+import static com.android.settings.search2.SearchResult.MAX_RANK;
+
public class SearchResultsAdapter extends Adapter<SearchViewHolder> {
+
private final List<SearchResult> mSearchResults;
- private final Map<String, List<SearchResult>> mResultsMap;
private final SearchFragment mFragment;
+ private Map<String, List<? extends SearchResult>> mResultsMap;
public SearchResultsAdapter(SearchFragment fragment) {
mFragment = fragment;
@@ -84,15 +88,56 @@
return mSearchResults.size();
}
- public void mergeResults(List<SearchResult> freshResults, String loaderClassName) {
+ /**
+ * Store the results from each of the loaders to be merged when all loaders are finished.
+ * @param freshResults are the results from the loader.
+ * @param loaderClassName class name of the loader.
+ */
+ @MainThread
+ public void addResultsToMap(List<? extends SearchResult> freshResults,
+ String loaderClassName) {
if (freshResults == null) {
return;
}
mResultsMap.put(loaderClassName, freshResults);
- final int oldSize = mSearchResults.size();
- mSearchResults.addAll(freshResults);
- final int newSize = mSearchResults.size();
- notifyItemRangeInserted(oldSize, newSize - oldSize);
+ }
+
+ /**
+ * Merge the results from each of the loaders into one list for the adapter.
+ * Prioritizes results from the local database over installed apps.
+ */
+ public void mergeResults() {
+ final List<? extends SearchResult> databaseResults = mResultsMap
+ .get(DatabaseResultLoader.class.getName());
+ final List<? extends SearchResult> installedAppResults = mResultsMap
+ .get(InstalledAppResultLoader.class.getName());
+ final int dbSize = (databaseResults != null) ? databaseResults.size() : 0;
+ final int appSize = (installedAppResults != null) ? installedAppResults.size() : 0;
+ final List<SearchResult> results = new ArrayList<>(dbSize + appSize);
+
+ int dbIndex = 0;
+ int appIndex = 0;
+ int rank = 1;
+
+ while (rank <= MAX_RANK) {
+ while ((dbIndex < dbSize) && (databaseResults.get(dbIndex).rank == rank)) {
+ results.add(databaseResults.get(dbIndex++));
+ }
+ while ((appIndex < appSize) && (installedAppResults.get(appIndex).rank == rank)) {
+ results.add(installedAppResults.get(appIndex++));
+ }
+ rank ++;
+ }
+
+ while (dbIndex < dbSize) {
+ results.add(databaseResults.get(dbIndex++));
+ }
+ while (appIndex < appSize) {
+ results.add(installedAppResults.get(appIndex++));
+ }
+
+ mSearchResults.addAll(results);
+ notifyDataSetChanged();
}
public void clearResults() {
diff --git a/src/com/android/settings/search2/SearchViewHolder.java b/src/com/android/settings/search2/SearchViewHolder.java
index 0b10b06..e369391 100644
--- a/src/com/android/settings/search2/SearchViewHolder.java
+++ b/src/com/android/settings/search2/SearchViewHolder.java
@@ -16,6 +16,9 @@
package com.android.settings.search2;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.View;
@@ -52,8 +55,14 @@
summaryView.setText(result.summary);
summaryView.setVisibility(View.VISIBLE);
}
- iconView.setImageDrawable(result.icon);
- if (result.icon == null) {
+
+ if (result instanceof AppSearchResult) {
+ AppSearchResult appResult = (AppSearchResult) result;
+ PackageManager pm = fragment.getActivity().getPackageManager();
+ iconView.setImageDrawable(appResult.info.loadIcon(pm));
+ } else if (result.icon != null) {
+ iconView.setImageDrawable(result.icon);
+ } else {
iconView.setBackgroundResource(R.drawable.empty_icon);
}
bindBreadcrumbView(result);
diff --git a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
index 6368fcb..26b231e 100644
--- a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
@@ -79,124 +79,124 @@
@Test
public void testMatchTitle() {
- loader = new DatabaseResultLoader(mContext, "title");
+ loader = new DatabaseResultLoader(mContext, "title", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(2);
verify(mSiteMapManager, times(2)).buildBreadCrumb(eq(mContext), anyString(), anyString());
}
@Test
public void testMatchSummary() {
- loader = new DatabaseResultLoader(mContext, "summary");
+ loader = new DatabaseResultLoader(mContext, "summary", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(2);
}
@Test
public void testMatchKeywords() {
- loader = new DatabaseResultLoader(mContext, "keywords");
+ loader = new DatabaseResultLoader(mContext, "keywords", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(2);
}
@Test
public void testMatchEntries() {
- loader = new DatabaseResultLoader(mContext, "entries");
+ loader = new DatabaseResultLoader(mContext, "entries", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(2);
}
@Test
public void testSpecialCaseWord_MatchesNonPrefix() {
insertSpecialCase("Data usage");
- loader = new DatabaseResultLoader(mContext, "usage");
+ loader = new DatabaseResultLoader(mContext, "usage", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseSpace_Matches() {
insertSpecialCase("space");
- loader = new DatabaseResultLoader(mContext, " space ");
+ loader = new DatabaseResultLoader(mContext, " space ", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseDash_MatchesWordNoDash() {
insertSpecialCase("wi-fi calling");
- loader = new DatabaseResultLoader(mContext, "wifi");
+ loader = new DatabaseResultLoader(mContext, "wifi", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseDash_MatchesWordWithDash() {
insertSpecialCase("priorités seulment");
- loader = new DatabaseResultLoader(mContext, "priorités");
+ loader = new DatabaseResultLoader(mContext, "priorités", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseDash_MatchesWordWithoutDash() {
insertSpecialCase("priorités seulment");
- loader = new DatabaseResultLoader(mContext, "priorites");
+ loader = new DatabaseResultLoader(mContext, "priorites", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseDash_MatchesEntireQueryWithoutDash() {
insertSpecialCase("wi-fi calling");
- loader = new DatabaseResultLoader(mContext, "wifi calling");
+ loader = new DatabaseResultLoader(mContext, "wifi calling", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCasePrefix_MatchesPrefixOfEntry() {
insertSpecialCase("Photos");
- loader = new DatabaseResultLoader(mContext, "pho");
+ loader = new DatabaseResultLoader(mContext, "pho", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCasePrefix_DoesNotMatchNonPrefixSubstring() {
insertSpecialCase("Photos");
- loader = new DatabaseResultLoader(mContext, "hot");
+ loader = new DatabaseResultLoader(mContext, "hot", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(0);
}
@Test
public void testSpecialCaseMultiWordPrefix_MatchesPrefixOfEntry() {
insertSpecialCase("Apps Notifications");
- loader = new DatabaseResultLoader(mContext, "Apps");
+ loader = new DatabaseResultLoader(mContext, "Apps", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseMultiWordPrefix_MatchesSecondWordPrefixOfEntry() {
insertSpecialCase("Apps Notifications");
- loader = new DatabaseResultLoader(mContext, "Not");
+ loader = new DatabaseResultLoader(mContext, "Not", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfFirstEntry() {
insertSpecialCase("Apps Notifications");
- loader = new DatabaseResultLoader(mContext, "pp");
+ loader = new DatabaseResultLoader(mContext, "pp", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(0);
}
@Test
public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfSecondEntry() {
insertSpecialCase("Apps Notifications");
- loader = new DatabaseResultLoader(mContext, "tion");
+ loader = new DatabaseResultLoader(mContext, "tion", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(0);
}
@Test
public void testSpecialCaseMultiWordPrefixWithSpecial_MatchesPrefixOfEntry() {
insertSpecialCase("Apps & Notifications");
- loader = new DatabaseResultLoader(mContext, "App");
+ loader = new DatabaseResultLoader(mContext, "App", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseMultiWordPrefixWithSpecial_MatchesPrefixOfSecondEntry() {
insertSpecialCase("Apps & Notifications");
- loader = new DatabaseResultLoader(mContext, "No");
+ loader = new DatabaseResultLoader(mContext, "No", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
diff --git a/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java b/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java
index 3627898..fdcea6f 100644
--- a/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java
@@ -19,6 +19,7 @@
import android.app.Activity;
import android.content.Context;
+import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.view.ViewGroup;
@@ -26,11 +27,14 @@
import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
+import com.android.settings.search2.AppSearchResult;
import com.android.settings.search2.DatabaseResultLoader;
import com.android.settings.search2.InlineSwitchViewHolder;
+import com.android.settings.search2.InstalledAppResultLoader;
import com.android.settings.search2.IntentPayload;
import com.android.settings.search2.IntentSearchViewHolder;
import com.android.settings.search2.ResultPayload;
+import com.android.settings.search2.SearchActivity;
import com.android.settings.search2.SearchFragment;
import com.android.settings.search2.SearchResult;
import com.android.settings.search2.SearchResult.Builder;
@@ -46,11 +50,13 @@
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
import org.robolectric.shadows.ShadowViewGroup;
+import org.robolectric.util.ActivityController;
import java.util.ArrayList;
import java.util.List;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -79,17 +85,8 @@
@Test
public void testSingleSourceMerge_ExactCopyReturned() {
ArrayList<SearchResult> intentResults = getIntentSampleResults();
- mAdapter.mergeResults(intentResults, mLoaderClassName);
-
- List<SearchResult> updatedResults = mAdapter.getSearchResults();
- assertThat(updatedResults).containsAllIn(intentResults);
- }
-
- @Test
- public void testDuplicateSourceMerge_ExactCopyReturned() {
- ArrayList<SearchResult> intentResults = getIntentSampleResults();
- mAdapter.mergeResults(intentResults, mLoaderClassName);
- mAdapter.mergeResults(intentResults, mLoaderClassName);
+ mAdapter.addResultsToMap(intentResults, mLoaderClassName);
+ mAdapter.mergeResults();
List<SearchResult> updatedResults = mAdapter.getSearchResults();
assertThat(updatedResults).containsAllIn(intentResults);
@@ -111,6 +108,65 @@
assertThat(view).isInstanceOf(InlineSwitchViewHolder.class);
}
+ @Test
+ public void testEndToEndSearch_ProperResultsMerged() {
+ mAdapter.addResultsToMap(getDummyAppResults(),
+ InstalledAppResultLoader.class.getName());
+ mAdapter.addResultsToMap(getDummyDbResults(),
+ DatabaseResultLoader.class.getName());
+ mAdapter.mergeResults();
+
+ List<SearchResult> results = mAdapter.getSearchResults();
+ assertThat(results.get(0).title).isEqualTo("alpha");
+ assertThat(results.get(1).title).isEqualTo("appAlpha");
+ assertThat(results.get(2).title).isEqualTo("appBravo");
+ assertThat(results.get(3).title).isEqualTo("bravo");
+ assertThat(results.get(4).title).isEqualTo("appCharlie");
+ assertThat(results.get(5).title).isEqualTo("Charlie");
+ }
+
+ private List<SearchResult> getDummyDbResults() {
+ List<SearchResult> results = new ArrayList<>();
+ IntentPayload payload = new IntentPayload(new Intent());
+ SearchResult.Builder builder = new SearchResult.Builder();
+ builder.addPayload(payload);
+
+ builder.addTitle("alpha")
+ .addRank(1);
+ results.add(builder.build());
+
+ builder.addTitle("bravo")
+ .addRank(3);
+ results.add(builder.build());
+
+ builder.addTitle("Charlie")
+ .addRank(6);
+ results.add(builder.build());
+
+ return results;
+ }
+
+ private List<AppSearchResult> getDummyAppResults() {
+ List<AppSearchResult> results = new ArrayList<>();
+ IntentPayload payload = new IntentPayload(new Intent());
+ AppSearchResult.Builder builder = new AppSearchResult.Builder();
+ builder.addPayload(payload);
+
+ builder.addTitle("appAlpha")
+ .addRank(1);
+ results.add(builder.build());
+
+ builder.addTitle("appBravo")
+ .addRank(2);
+ results.add(builder.build());
+
+ builder.addTitle("appCharlie")
+ .addRank(4);
+ results.add(builder.build());
+
+ return results;
+ }
+
private ArrayList<SearchResult> getIntentSampleResults() {
ArrayList<SearchResult> sampleResults = new ArrayList<>();
ArrayList<String> breadcrumbs = new ArrayList<>();
diff --git a/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java b/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
index c4c6788..e808946 100644
--- a/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
@@ -94,6 +94,8 @@
ApplicationTestUtils.buildInfo(0 /* uid */, "app4", 0 /* flags */,
0 /* targetSdkVersion */),
ApplicationTestUtils.buildInfo(0 /* uid */, "app", 0 /* flags */,
+ 0 /* targetSdkVersion */),
+ ApplicationTestUtils.buildInfo(0 /* uid */, "appBuffer", 0 /* flags */,
0 /* targetSdkVersion */)));
}
@@ -101,7 +103,8 @@
public void query_noMatchingQuery_shouldReturnEmptyResult() {
final String query = "abc";
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager);
assertThat(mLoader.loadInBackground()).isEmpty();
}
@@ -110,12 +113,13 @@
public void query_matchingQuery_shouldReturnNonSystemApps() {
final String query = "app";
- mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query));
+ mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager));
when(mLoader.getContext()).thenReturn(mContext);
when(mSiteMapManager.buildBreadCrumb(eq(mContext), anyString(), anyString()))
.thenReturn(Arrays.asList(new String[]{"123"}));
- assertThat(mLoader.loadInBackground().size()).isEqualTo(2);
+ assertThat(mLoader.loadInBackground().size()).isEqualTo(3);
verify(mSiteMapManager)
.buildBreadCrumb(eq(mContext), anyString(), anyString());
}
@@ -128,7 +132,8 @@
0 /* targetSdkVersion */)));
final String query = "app";
- mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query));
+ mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager));
when(mLoader.getContext()).thenReturn(mContext);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
@@ -150,7 +155,8 @@
final String query = "app";
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
}
@@ -167,7 +173,8 @@
final String query = "app";
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager);
assertThat(mLoader.loadInBackground()).isEmpty();
verify(mSiteMapManager, never())
@@ -178,15 +185,15 @@
public void query_matchingQuery_shouldRankBasedOnSimilarity() {
final String query = "app";
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
- final List<SearchResult> results = mLoader.loadInBackground();
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager);
+ final List<? extends SearchResult> results = mLoader.loadInBackground();
// List is sorted by rank
- assertThat(results.get(0).rank).isLessThan(results.get(1).rank);
- // perfect match first
- assertThat(results.get(0).title).isEqualTo(query);
- // Then partial match
- assertThat(results.get(1).title).isNotEqualTo(query);
+ assertThat(results.get(0).rank).isAtMost(results.get(1).rank);
+ assertThat(results.get(0).title).isEqualTo("app4");
+ assertThat(results.get(1).title).isEqualTo("app");
+ assertThat(results.get(2).title).isEqualTo("appBuffer");
}
@Test
@@ -197,7 +204,8 @@
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
}
@@ -210,7 +218,8 @@
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(0);
}
@@ -223,7 +232,8 @@
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
}
@@ -236,7 +246,8 @@
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
}
@@ -249,7 +260,8 @@
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
}
@@ -262,7 +274,8 @@
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
}
@@ -275,7 +288,8 @@
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
}
@@ -288,7 +302,8 @@
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
}
@@ -301,7 +316,8 @@
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
}
@@ -314,7 +330,8 @@
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(0);
}
@@ -327,7 +344,8 @@
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+ mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(0);
}
diff --git a/tests/robotests/src/com/android/settings/search2/MockAppLoader.java b/tests/robotests/src/com/android/settings/search2/MockAppLoader.java
new file mode 100644
index 0000000..17cda43
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search2/MockAppLoader.java
@@ -0,0 +1,44 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Mock loader to subvert the requirements of returning data while also driving the Loader
+ * lifecycle.
+ */
+class MockAppLoader extends InstalledAppResultLoader {
+
+ public MockAppLoader(Context context) {
+ super(context, null, "", null);
+ }
+
+ @Override
+ public List<? extends SearchResult> loadInBackground() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ protected void onDiscardResult(List<? extends SearchResult> result) {
+
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/search2/MockDBLoader.java b/tests/robotests/src/com/android/settings/search2/MockDBLoader.java
new file mode 100644
index 0000000..18d8771
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search2/MockDBLoader.java
@@ -0,0 +1,44 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Mock loader to subvert the requirements of returning data while also driving the Loader
+ * lifecycle.
+ */
+class MockDBLoader extends DatabaseResultLoader {
+
+ public MockDBLoader(Context context) {
+ super(context, "test", null);
+ }
+
+ @Override
+ public List<? extends SearchResult> loadInBackground() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ protected void onDiscardResult(List<? extends SearchResult> result) {
+
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/search2/SavedQueryLoaderTest.java b/tests/robotests/src/com/android/settings/search2/SavedQueryLoaderTest.java
index d975f0c..5209ed9 100644
--- a/tests/robotests/src/com/android/settings/search2/SavedQueryLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search2/SavedQueryLoaderTest.java
@@ -60,7 +60,7 @@
@Test
public void loadInBackground_shouldReturnSavedQueries() {
- final List<SearchResult> results = mLoader.loadInBackground();
+ final List<? extends SearchResult> results = mLoader.loadInBackground();
assertThat(results.size()).isEqualTo(SavedQueryLoader.MAX_PROPOSED_SUGGESTIONS);
for (SearchResult result : results) {
assertThat(result.viewType).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
index c56ecce..5374aae 100644
--- a/tests/robotests/src/com/android/settings/search2/SavedQueryRecorderTest.java
+++ b/tests/robotests/src/com/android/settings/search2/SavedQueryRecorderTest.java
@@ -59,7 +59,7 @@
mRecorder.loadInBackground();
final SavedQueryLoader loader = new SavedQueryLoader(mContext);
- List<SearchResult> results = loader.loadInBackground();
+ List<? extends SearchResult> 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 2d4ac54..2a10078 100644
--- a/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java
@@ -20,12 +20,16 @@
import android.content.Loader;
import android.os.Bundle;
+import android.os.UserManager;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
+import com.android.settings.dashboard.SiteMapManager;
+import com.android.settings.testutils.DatabaseTestUtils;
import com.android.settings.testutils.FakeFeatureFactory;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,16 +37,20 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;
+
+import java.util.ArrayList;
import java.util.List;
-import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -57,6 +65,7 @@
private DatabaseResultLoader mDatabaseResultLoader;
@Mock
private InstalledAppResultLoader mInstalledAppResultLoader;
+
@Mock
private SavedQueryLoader mSavedQueryLoader;
@@ -65,8 +74,13 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+
FakeFeatureFactory.setupForTest(mContext);
mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+ }
+
+ @Test
+ public void screenRotate_shouldPersistQuery() {
when(mFeatureFactory.searchFeatureProvider
.getDatabaseSearchLoader(any(Context.class), anyString()))
.thenReturn(mDatabaseResultLoader);
@@ -75,10 +89,7 @@
.thenReturn(mInstalledAppResultLoader);
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader);
- }
- @Test
- public void screenRotate_shouldPersistQuery() {
final Bundle bundle = new Bundle();
final String testQuery = "test";
ActivityController<SearchActivity> activityController =
@@ -102,6 +113,15 @@
@Test
public void screenRotateEmptyString_ShouldNotCrash() {
+ when(mFeatureFactory.searchFeatureProvider
+ .getDatabaseSearchLoader(any(Context.class), anyString()))
+ .thenReturn(mDatabaseResultLoader);
+ when(mFeatureFactory.searchFeatureProvider
+ .getInstalledAppSearchLoader(any(Context.class), anyString()))
+ .thenReturn(mInstalledAppResultLoader);
+ when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
+ .thenReturn(mSavedQueryLoader);
+
final Bundle bundle = new Bundle();
ActivityController<SearchActivity> activityController =
Robolectric.buildActivity(SearchActivity.class);
@@ -124,6 +144,15 @@
@Test
public void queryTextChange_shouldTriggerLoader() {
+ when(mFeatureFactory.searchFeatureProvider
+ .getDatabaseSearchLoader(any(Context.class), anyString()))
+ .thenReturn(mDatabaseResultLoader);
+ when(mFeatureFactory.searchFeatureProvider
+ .getInstalledAppSearchLoader(any(Context.class), anyString()))
+ .thenReturn(mInstalledAppResultLoader);
+ when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
+ .thenReturn(mSavedQueryLoader);
+
final String testQuery = "test";
ActivityController<SearchActivity> activityController =
Robolectric.buildActivity(SearchActivity.class);
@@ -148,6 +177,15 @@
@Test
public void queryTextChangeToEmpty_shouldTriggerSavedQueryLoader() {
+ when(mFeatureFactory.searchFeatureProvider
+ .getDatabaseSearchLoader(any(Context.class), anyString()))
+ .thenReturn(mDatabaseResultLoader);
+ when(mFeatureFactory.searchFeatureProvider
+ .getInstalledAppSearchLoader(any(Context.class), anyString()))
+ .thenReturn(mInstalledAppResultLoader);
+ when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
+ .thenReturn(mSavedQueryLoader);
+
ActivityController<SearchActivity> activityController =
Robolectric.buildActivity(SearchActivity.class);
activityController.setup();
@@ -169,6 +207,15 @@
@Test
public void updateIndex_TriggerOnCreate() {
+ when(mFeatureFactory.searchFeatureProvider
+ .getDatabaseSearchLoader(any(Context.class), anyString()))
+ .thenReturn(mDatabaseResultLoader);
+ when(mFeatureFactory.searchFeatureProvider
+ .getInstalledAppSearchLoader(any(Context.class), anyString()))
+ .thenReturn(mInstalledAppResultLoader);
+ when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
+ .thenReturn(mSavedQueryLoader);
+
ActivityController<SearchActivity> activityController =
Robolectric.buildActivity(SearchActivity.class);
activityController.setup();
@@ -178,4 +225,28 @@
fragment.onAttach(null);
verify(mFeatureFactory.searchFeatureProvider).updateIndex(any(Context.class));
}
+
+ @Test
+ public void syncLoaders_MergeWhenAllLoadersDone() {
+
+ when(mFeatureFactory.searchFeatureProvider
+ .getDatabaseSearchLoader(any(Context.class), anyString()))
+ .thenReturn(new MockDBLoader(RuntimeEnvironment.application));
+ when(mFeatureFactory.searchFeatureProvider
+ .getInstalledAppSearchLoader(any(Context.class), anyString()))
+ .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
+
+ ActivityController<SearchActivity> activityController =
+ Robolectric.buildActivity(SearchActivity.class);
+ activityController.setup();
+ SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
+ .findFragmentById(R.id.main_content));
+
+ fragment.onQueryTextChange("non-empty");
+
+ Robolectric.flushForegroundThreadScheduler();
+
+ verify(fragment, times(2)).onLoadFinished(any(Loader.class), any(List.class));
+ }
+
}