Merge "Sync search result loaders"

This commit is contained in:
TreeHugger Robot
2017-02-12 01:19:15 +00:00
committed by Android (Google) Code Review
17 changed files with 473 additions and 103 deletions

View File

@@ -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);
}
}
}

View File

@@ -36,7 +36,7 @@ import static com.android.settings.search.IndexDatabaseHelper.Tables.TABLE_PREFS
/**
* 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 @@ public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
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 @@ public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
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);
}

View File

@@ -42,7 +42,7 @@ import java.util.List;
/**
* 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 class InstalledAppResultLoader extends AsyncLoader<List<SearchResult>> {
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 @@ public class InstalledAppResultLoader extends AsyncLoader<List<SearchResult>> {
.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 @@ public class InstalledAppResultLoader extends AsyncLoader<List<SearchResult>> {
}
@Override
protected void onDiscardResult(List<SearchResult> result) {
protected void onDiscardResult(List<? extends SearchResult> result) {
}
@@ -200,4 +199,16 @@ public class InstalledAppResultLoader extends AsyncLoader<List<SearchResult>> {
}
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;
}
}

View File

@@ -31,7 +31,7 @@ import java.util.List;
/**
* 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 @@ public class SavedQueryLoader extends AsyncLoader<List<SearchResult>> {
}
@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 */,

View File

@@ -65,13 +65,14 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider {
@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

View File

@@ -41,9 +41,11 @@ import com.android.settings.core.instrumentation.MetricsFeatureProvider;
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 @@ public class SearchFragment extends InstrumentedFragment implements
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 @@ public class SearchFragment extends InstrumentedFragment implements
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 @@ 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);
@@ -181,7 +194,7 @@ public class SearchFragment extends InstrumentedFragment implements
}
@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 @@ public class SearchFragment extends InstrumentedFragment implements
}
@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 @@ public class SearchFragment extends InstrumentedFragment implements
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 */);
}

View File

@@ -26,6 +26,12 @@ import java.util.Objects;
*/
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 class SearchResult implements Comparable<SearchResult> {
*/
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 @@ public class SearchResult implements Comparable<SearchResult> {
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 @@ public class SearchResult implements Comparable<SearchResult> {
return new SearchResult(this);
}
}
}
}

View File

@@ -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.ArrayList;
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 @@ public class SearchResultsAdapter extends Adapter<SearchViewHolder> {
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() {

View File

@@ -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 @@ public abstract class SearchViewHolder extends RecyclerView.ViewHolder {
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);

View File

@@ -79,124 +79,124 @@ public class DatabaseResultLoaderTest {
@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);
}

View File

@@ -19,6 +19,7 @@ package com.android.settings.search;
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 android.widget.FrameLayout;
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.Robolectric;
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 @@ public class SearchAdapterTest {
@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 @@ public class SearchAdapterTest {
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<>();

View File

@@ -94,6 +94,8 @@ public class InstalledAppResultLoaderTest {
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 class InstalledAppResultLoaderTest {
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 class InstalledAppResultLoaderTest {
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 @@ public class InstalledAppResultLoaderTest {
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 @@ public class InstalledAppResultLoaderTest {
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 @@ public class InstalledAppResultLoaderTest {
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 class InstalledAppResultLoaderTest {
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 @@ public class InstalledAppResultLoaderTest {
.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 @@ public class InstalledAppResultLoaderTest {
.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 @@ public class InstalledAppResultLoaderTest {
.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 @@ public class InstalledAppResultLoaderTest {
.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 @@ public class InstalledAppResultLoaderTest {
.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 @@ public class InstalledAppResultLoaderTest {
.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 @@ public class InstalledAppResultLoaderTest {
.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 @@ public class InstalledAppResultLoaderTest {
.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 @@ public class InstalledAppResultLoaderTest {
.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 @@ public class InstalledAppResultLoaderTest {
.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 @@ public class InstalledAppResultLoaderTest {
.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);
}

View File

@@ -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) {
}
}

View File

@@ -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) {
}
}

View File

@@ -60,7 +60,7 @@ public class SavedQueryLoaderTest {
@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);

View File

@@ -59,7 +59,7 @@ public class SavedQueryRecorderTest {
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);

View File

@@ -20,12 +20,16 @@ import android.content.Context;
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.Answers;
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 @@ public class SearchFragmentTest {
private DatabaseResultLoader mDatabaseResultLoader;
@Mock
private InstalledAppResultLoader mInstalledAppResultLoader;
@Mock
private SavedQueryLoader mSavedQueryLoader;
@@ -65,8 +74,13 @@ public class SearchFragmentTest {
@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 @@ public class SearchFragmentTest {
.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 @@ public class SearchFragmentTest {
@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 @@ public class SearchFragmentTest {
@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 @@ public class SearchFragmentTest {
@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 @@ public class SearchFragmentTest {
@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 @@ public class SearchFragmentTest {
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));
}
}