Merge "Updating the search ranking API and some improvements:"

This commit is contained in:
TreeHugger Robot
2017-06-02 20:49:26 +00:00
committed by Android (Google) Code Review
9 changed files with 747 additions and 144 deletions

View File

@@ -46,7 +46,7 @@ public class InlineSwitchViewHolder extends SearchViewHolder {
}
@Override
public void onBind(SearchFragment fragment, SearchResult result) {
public void onBind(SearchFragment fragment, final SearchResult result) {
super.onBind(fragment, result);
if (mContext == null) {
return;
@@ -57,7 +57,7 @@ public class InlineSwitchViewHolder extends SearchViewHolder {
final Pair<Integer, Object> value = Pair.create(
MetricsEvent.FIELD_SETTINGS_SEARCH_INLINE_RESULT_VALUE, isChecked
? 1L : 0L);
fragment.onSearchResultClicked(this, payload.mSettingKey, value);
fragment.onSearchResultClicked(this, result, value);
int newValue = isChecked ? InlineSwitchPayload.TRUE : InlineSwitchPayload.FALSE;
payload.setValue(mContext, newValue);
});

View File

@@ -16,13 +16,9 @@
*/
package com.android.settings.search;
import android.content.ComponentName;
import android.content.Intent;
import android.text.TextUtils;
import android.view.View;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.SettingsActivity;
/**
* ViewHolder for intent based search results.
@@ -44,14 +40,8 @@ public class IntentSearchViewHolder extends SearchViewHolder {
super.onBind(fragment, result);
itemView.setOnClickListener(v -> {
final Intent intent = result.payload.getIntent();
final ComponentName cn = intent.getComponent();
String resultName = intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT);
if (TextUtils.isEmpty(resultName) && cn != null) {
resultName = cn.flattenToString();
}
fragment.onSearchResultClicked(this, resultName);
fragment.startActivity(intent);
fragment.onSearchResultClicked(this, result);
fragment.startActivity(result.payload.getIntent());
});
}
}

View File

@@ -22,6 +22,7 @@ import android.view.Menu;
import android.view.View;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.search.ranking.SearchResultsRankerCallback;
import java.util.List;
@@ -98,21 +99,30 @@ public interface SearchFeatureProvider {
}
/**
* Ranks search results based on the input query.
* Query search results based on the input query.
*
* @param context application context
* @param query input user query
* @param searchResults list of search results to be ranked
* @param searchResultsRankerCallback {@link SearchResultsRankerCallback}
*/
default void rankSearchResults(String query, List<SearchResult> searchResults) {
default void querySearchResults(Context context, String query,
SearchResultsRankerCallback searchResultsRankerCallback) {
}
/**
* Cancel pending search query
*/
default void cancelPendingSearchQuery(Context context) {
}
/**
* Notify that a search result is clicked.
*
* @param context application context
* @param query input user query
* @param searchResult clicked result
*/
default void searchResultClicked(String query, SearchResult searchResult) {
default void searchResultClicked(Context context, String query, SearchResult searchResult) {
}
/**

View File

@@ -19,7 +19,9 @@ package com.android.settings.search;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.os.Bundle;
import android.support.annotation.VisibleForTesting;
@@ -37,6 +39,7 @@ import android.widget.SearchView;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.core.instrumentation.MetricsFeatureProvider;
@@ -234,6 +237,7 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
mSavedQueryController.loadSavedQueries();
mSearchFeatureProvider.hideFeedbackButton();
} else {
mSearchAdapter.initializeSearch(mQuery);
restartLoaders();
}
@@ -270,15 +274,7 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
return;
}
final int resultCount = mSearchAdapter.displaySearchResults(mQuery);
if (resultCount == 0) {
mNoResultsView.setVisibility(View.VISIBLE);
} else {
mNoResultsView.setVisibility(View.GONE);
mResultsRecyclerView.scrollToPosition(0);
}
mSearchFeatureProvider.showFeedbackButton(this, getView());
mSearchAdapter.notifyResultsLoaded();
}
@Override
@@ -304,30 +300,24 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
requery();
}
public void onSearchResultClicked(SearchViewHolder result, String settingName,
public void onSearchResultClicked(SearchViewHolder resultViewHolder, SearchResult result,
Pair<Integer, Object>... logTaggedData) {
final List<Pair<Integer, Object>> taggedData = new ArrayList<>();
if (logTaggedData != null) {
taggedData.addAll(Arrays.asList(logTaggedData));
}
taggedData.add(Pair.create(
MetricsEvent.FIELD_SETTINGS_SERACH_RESULT_COUNT,
mSearchAdapter.getItemCount()));
taggedData.add(Pair.create(
MetricsEvent.FIELD_SETTINGS_SERACH_RESULT_RANK,
result.getAdapterPosition()));
taggedData.add(Pair.create(
MetricsEvent.FIELD_SETTINGS_SERACH_QUERY_LENGTH,
TextUtils.isEmpty(mQuery) ? 0 : mQuery.length()));
logSearchResultClicked(resultViewHolder, result, logTaggedData);
mMetricsFeatureProvider.action(getContext(),
MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_RESULT,
settingName,
taggedData.toArray(new Pair[0]));
mSavedQueryController.saveQuery(mQuery);
mResultClickCount++;
}
public void onSearchResultsDisplayed(int resultCount) {
if (resultCount == 0) {
mNoResultsView.setVisibility(View.VISIBLE);
} else {
mNoResultsView.setVisibility(View.GONE);
mResultsRecyclerView.scrollToPosition(0);
}
mSearchFeatureProvider.showFeedbackButton(this, getView());
}
public void onSavedQueryClicked(CharSequence query) {
final String queryString = query.toString();
mMetricsFeatureProvider.action(getContext(),
@@ -378,4 +368,38 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
mResultsRecyclerView.requestFocus();
}
}
}
private void logSearchResultClicked(SearchViewHolder resultViewHolder, SearchResult result,
Pair<Integer, Object>... logTaggedData) {
final Intent intent = result.payload.getIntent();
if (intent == null) {
Log.w(TAG, "Skipped logging click on search result because of null intent, which can " +
"happen on saved query results.");
return;
}
final ComponentName cn = intent.getComponent();
String resultName = intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT);
if (TextUtils.isEmpty(resultName) && cn != null) {
resultName = cn.flattenToString();
}
final List<Pair<Integer, Object>> taggedData = new ArrayList<>();
if (logTaggedData != null) {
taggedData.addAll(Arrays.asList(logTaggedData));
}
taggedData.add(Pair.create(
MetricsEvent.FIELD_SETTINGS_SERACH_RESULT_COUNT,
mSearchAdapter.getItemCount()));
taggedData.add(Pair.create(
MetricsEvent.FIELD_SETTINGS_SERACH_RESULT_RANK,
resultViewHolder.getAdapterPosition()));
taggedData.add(Pair.create(
MetricsEvent.FIELD_SETTINGS_SERACH_QUERY_LENGTH,
TextUtils.isEmpty(mQuery) ? 0 : mQuery.length()));
mMetricsFeatureProvider.action(getContext(),
MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_RESULT,
resultName,
taggedData.toArray(new Pair[0]));
mSearchFeatureProvider.searchResultClicked(getContext(), mQuery, result);
}
}

View File

@@ -18,36 +18,79 @@
package com.android.settings.search;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.IntDef;
import android.support.annotation.MainThread;
import android.support.annotation.VisibleForTesting;
import android.support.v7.util.DiffUtil;
import android.support.v7.widget.RecyclerView;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.android.settings.R;
import com.android.settings.search.ranking.SearchResultsRankerCallback;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder> {
public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
implements SearchResultsRankerCallback {
private static final String TAG = "SearchResultsAdapter";
@VisibleForTesting
static final String DB_RESULTS_LOADER_KEY = DatabaseResultLoader.class.getName();
@VisibleForTesting
static final String APP_RESULTS_LOADER_KEY = InstalledAppResultLoader.class.getName();
@VisibleForTesting
static final int MSG_RANKING_TIMED_OUT = 1;
// TODO(b/38197948): Tune this timeout based on latency of static and async rankings. Also, we
// should add a gservices flag to control this.
private static final long RANKING_TIMEOUT_MS = 300;
private final SearchFragment mFragment;
private List<SearchResult> mSearchResults;
private final Context mContext;
private final List<SearchResult> mSearchResults;
private final List<SearchResult> mStaticallyRankedSearchResults;
private Map<String, Set<? extends SearchResult>> mResultsMap;
private final SearchFeatureProvider mSearchFeatureProvider;
private List<Pair<String, Float>> mSearchRankingScores;
private Handler mHandler;
private boolean mSearchResultsLoaded;
private boolean mSearchResultsUpdated;
@IntDef({DISABLED, PENDING_RESULTS, SUCCEEDED, FAILED, TIMED_OUT})
@Retention(RetentionPolicy.SOURCE)
private @interface AsyncRankingState {}
private static final int DISABLED = 0;
private static final int PENDING_RESULTS = 1;
private static final int SUCCEEDED = 2;
private static final int FAILED = 3;
private static final int TIMED_OUT = 4;
private @AsyncRankingState int mAsyncRankingState;
public SearchResultsAdapter(SearchFragment fragment,
SearchFeatureProvider searchFeatureProvider) {
mFragment = fragment;
mContext = fragment.getContext().getApplicationContext();
mSearchResults = new ArrayList<>();
mResultsMap = new ArrayMap<>();
mSearchRankingScores = new ArrayList<>();
mStaticallyRankedSearchResults = new ArrayList<>();
mSearchFeatureProvider = searchFeatureProvider;
setHasStableIds(true);
@@ -93,7 +136,37 @@ public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
return mSearchResults.size();
}
/**
@MainThread
@Override
public void onRankingScoresAvailable(List<Pair<String, Float>> searchRankingScores) {
// Received the scores, stop the timeout timer.
getHandler().removeMessages(MSG_RANKING_TIMED_OUT);
if (mAsyncRankingState == PENDING_RESULTS) {
mAsyncRankingState = SUCCEEDED;
mSearchRankingScores.clear();
mSearchRankingScores.addAll(searchRankingScores);
if (canUpdateSearchResults()) {
updateSearchResults();
}
} else {
Log.w(TAG, "Ranking scores became available in invalid state: " + mAsyncRankingState);
}
}
@MainThread
@Override
public void onRankingFailed() {
if (mAsyncRankingState == PENDING_RESULTS) {
mAsyncRankingState = FAILED;
if (canUpdateSearchResults()) {
updateSearchResults();
}
} else {
Log.w(TAG, "Ranking scores failed in invalid states: " + mAsyncRankingState);
}
}
/**
* Store the results from each of the loaders to be merged when all loaders are finished.
*
* @param results the results from the loader.
@@ -120,71 +193,24 @@ public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
}
/**
* Merge the results from each of the loaders into one list for the adapter.
* Prioritizes results from the local database over installed apps.
*
* @param query user query corresponding to these results
* @return Number of matched results
* Notifies the adapter that all the unsorted results are loaded and now the ladapter can
* proceed with ranking the results.
*/
public int displaySearchResults(String query) {
List<? extends SearchResult> databaseResults = null;
List<? extends SearchResult> installedAppResults = null;
final String dbLoaderKey = DatabaseResultLoader.class.getName();
final String appLoaderKey = InstalledAppResultLoader.class.getName();
int dbSize = 0;
int appSize = 0;
if (mResultsMap.containsKey(dbLoaderKey)) {
databaseResults = new ArrayList<>(mResultsMap.get(dbLoaderKey));
dbSize = databaseResults.size();
Collections.sort(databaseResults);
@MainThread
public void notifyResultsLoaded() {
mSearchResultsLoaded = true;
// static ranking is skipped only if asyc ranking is already succeeded.
if (mAsyncRankingState != SUCCEEDED) {
doStaticRanking();
}
if (mResultsMap.containsKey(appLoaderKey)) {
installedAppResults = new ArrayList<>(mResultsMap.get(appLoaderKey));
appSize = installedAppResults.size();
Collections.sort(installedAppResults);
if (canUpdateSearchResults()) {
updateSearchResults();
}
final List<SearchResult> newResults = new ArrayList<>(dbSize + appSize);
int dbIndex = 0;
int appIndex = 0;
int rank = SearchResult.TOP_RANK;
while (rank <= SearchResult.BOTTOM_RANK) {
while ((dbIndex < dbSize) && (databaseResults.get(dbIndex).rank == rank)) {
newResults.add(databaseResults.get(dbIndex++));
}
while ((appIndex < appSize) && (installedAppResults.get(appIndex).rank == rank)) {
newResults.add(installedAppResults.get(appIndex++));
}
rank++;
}
while (dbIndex < dbSize) {
newResults.add(databaseResults.get(dbIndex++));
}
while (appIndex < appSize) {
newResults.add(installedAppResults.get(appIndex++));
}
final boolean isSmartSearchRankingEnabled = mSearchFeatureProvider
.isSmartSearchRankingEnabled(mFragment.getContext().getApplicationContext());
if (isSmartSearchRankingEnabled) {
// TODO: run this in parallel to loading the results if takes too long
mSearchFeatureProvider.rankSearchResults(query, newResults);
}
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
new SearchResultDiffCallback(mSearchResults, newResults),
isSmartSearchRankingEnabled);
mSearchResults = newResults;
diffResult.dispatchUpdatesTo(this);
return mSearchResults.size();
}
public void clearResults() {
mSearchResults.clear();
mStaticallyRankedSearchResults.clear();
mResultsMap.clear();
notifyDataSetChanged();
}
@@ -193,4 +219,178 @@ public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
public List<SearchResult> getSearchResults() {
return mSearchResults;
}
@MainThread
public void initializeSearch(String query) {
clearResults();
mSearchResultsLoaded = false;
mSearchResultsUpdated = false;
if (mSearchFeatureProvider.isSmartSearchRankingEnabled(mContext)) {
mAsyncRankingState = PENDING_RESULTS;
mSearchFeatureProvider.cancelPendingSearchQuery(mContext);
final Handler handler = getHandler();
handler.sendMessageDelayed(
handler.obtainMessage(MSG_RANKING_TIMED_OUT), RANKING_TIMEOUT_MS);
mSearchFeatureProvider.querySearchResults(mContext, query, this);
} else {
mAsyncRankingState = DISABLED;
}
}
/**
* Merge the results from each of the loaders into one list for the adapter.
* Prioritizes results from the local database over installed apps.
*/
private void doStaticRanking() {
List<? extends SearchResult> databaseResults =
getSortedLoadedResults(DB_RESULTS_LOADER_KEY);
List<? extends SearchResult> installedAppResults =
getSortedLoadedResults(APP_RESULTS_LOADER_KEY);
int dbSize = databaseResults.size();
int appSize = installedAppResults.size();
int dbIndex = 0;
int appIndex = 0;
int rank = SearchResult.TOP_RANK;
mStaticallyRankedSearchResults.clear();
while (rank <= SearchResult.BOTTOM_RANK) {
while ((dbIndex < dbSize) && (databaseResults.get(dbIndex).rank == rank)) {
mStaticallyRankedSearchResults.add(databaseResults.get(dbIndex++));
}
while ((appIndex < appSize) && (installedAppResults.get(appIndex).rank == rank)) {
mStaticallyRankedSearchResults.add(installedAppResults.get(appIndex++));
}
rank++;
}
while (dbIndex < dbSize) {
mStaticallyRankedSearchResults.add(databaseResults.get(dbIndex++));
}
while (appIndex < appSize) {
mStaticallyRankedSearchResults.add(installedAppResults.get(appIndex++));
}
}
private void updateSearchResults() {
switch (mAsyncRankingState) {
case PENDING_RESULTS:
break;
case DISABLED:
case FAILED:
case TIMED_OUT:
// When DISABLED or FAILED or TIMED_OUT, we use static ranking results.
postSearchResults(mStaticallyRankedSearchResults, false);
break;
case SUCCEEDED:
postSearchResults(doAsyncRanking(), true);
break;
}
}
private boolean canUpdateSearchResults() {
// Results are not updated yet and db results are loaded and we are not waiting on async
// ranking scores.
return !mSearchResultsUpdated
&& mSearchResultsLoaded
&& mAsyncRankingState != PENDING_RESULTS;
}
@VisibleForTesting
List<SearchResult> doAsyncRanking() {
Set<? extends SearchResult> databaseResults =
getUnsortedLoadedResults(DB_RESULTS_LOADER_KEY);
List<? extends SearchResult> installedAppResults =
getSortedLoadedResults(APP_RESULTS_LOADER_KEY);
int dbSize = databaseResults.size();
int appSize = installedAppResults.size();
final List<SearchResult> asyncRankingResults = new ArrayList<>(dbSize + appSize);
List<SearchResult> databaseResultsSortedByScores = new ArrayList<>(databaseResults);
Collections.sort(databaseResultsSortedByScores, new Comparator<SearchResult>() {
@Override
public int compare(SearchResult o1, SearchResult o2) {
float score1 = getRankingScoreByStableId(o1.stableId);
float score2 = getRankingScoreByStableId(o2.stableId);
if (score1 > score2) {
return -1;
} else if (score1 == score2) {
return 0;
} else {
return 1;
}
}
});
asyncRankingResults.addAll(databaseResultsSortedByScores);
// App results are not ranked by async ranking and appended at the end of the list.
asyncRankingResults.addAll(installedAppResults);
return asyncRankingResults;
}
@VisibleForTesting
Set<? extends SearchResult> getUnsortedLoadedResults(String loaderKey) {
return mResultsMap.containsKey(loaderKey) ?
mResultsMap.get(loaderKey) : new HashSet<SearchResult>();
}
@VisibleForTesting
List<? extends SearchResult> getSortedLoadedResults(String loaderKey) {
List<? extends SearchResult> sortedLoadedResults =
new ArrayList<>(getUnsortedLoadedResults(loaderKey));
Collections.sort(sortedLoadedResults);
return sortedLoadedResults;
}
/**
* Looks up ranking score for stableId
* @param stableId String of stableId
* @return the ranking score corresponding to the given stableId. If there is no score
* available for this stableId, -Float.MAX_VALUE is returned.
*/
@VisibleForTesting
Float getRankingScoreByStableId(int stableId) {
for (Pair<String, Float> rankingScore : mSearchRankingScores) {
if (Integer.toString(stableId).compareTo(rankingScore.first) == 0) {
return rankingScore.second;
}
}
// If stableId not found in the list, we assign the minimum score so it will appear at
// the end of the list.
Log.w(TAG, "stableId " + stableId + " was not in the ranking scores.");
return -Float.MAX_VALUE;
}
@VisibleForTesting
Handler getHandler() {
if (mHandler == null) {
mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_RANKING_TIMED_OUT) {
mSearchFeatureProvider.cancelPendingSearchQuery(mContext);
if (mAsyncRankingState == PENDING_RESULTS) {
mAsyncRankingState = TIMED_OUT;
if (canUpdateSearchResults()) {
updateSearchResults();
}
} else {
Log.w(TAG, "Ranking scores timed out in invalid state: " +
mAsyncRankingState);
}
}
}
};
}
return mHandler;
}
private void postSearchResults(List<SearchResult> newSearchResults, boolean detectMoves) {
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
new SearchResultDiffCallback(mSearchResults, newSearchResults), detectMoves);
mSearchResults.clear();
mSearchResults.addAll(newSearchResults);
diffResult.dispatchUpdatesTo(this);
mFragment.onSearchResultsDisplayed(mSearchResults.size());
mSearchResultsUpdated = true;
}
}

View File

@@ -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.search.ranking;
import android.util.Pair;
import java.util.List;
public interface SearchResultsRankerCallback {
/**
* Called when ranker provides the ranking scores.
* @param searchRankingScores Ordered List of Pairs of String and Float corresponding to
* stableIds and ranking scores. The list must be descendingly
* ordered based on scores.
*/
public void onRankingScoresAvailable(List<Pair<String, Float>> searchRankingScores);
/**
* Called when for any reason ranker fails, which notifies the client to proceed
* without ranking results.
*/
public void onRankingFailed();
}

View File

@@ -97,7 +97,7 @@ public class IntentSearchViewHolderTest {
assertThat(mHolder.summaryView.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mHolder.breadcrumbView.getVisibility()).isEqualTo(View.GONE);
verify(mFragment).onSearchResultClicked(eq(mHolder), anyString());
verify(mFragment).onSearchResultClicked(eq(mHolder), any(SearchResult.class));
verify(mFragment).startActivity(any(Intent.class));
}

View File

@@ -18,6 +18,7 @@
package com.android.settings.search;
import android.app.LoaderManager;
import android.content.Intent;
import android.content.Context;
import android.content.Loader;
import android.os.Bundle;
@@ -26,6 +27,7 @@ import android.view.View;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.testutils.DatabaseTestUtils;
@@ -36,7 +38,9 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
@@ -75,6 +79,11 @@ public class SearchFragmentTest {
private SavedQueryLoader mSavedQueryLoader;
@Mock
private SavedQueryController mSavedQueryController;
@Mock
private SearchResultsAdapter mSearchResultsAdapter;
@Captor
private ArgumentCaptor<String> mQueryCaptor = ArgumentCaptor.forClass(String.class);
private FakeFeatureFactory mFeatureFactory;
@Before
@@ -148,7 +157,7 @@ public class SearchFragmentTest {
}
@Test
public void queryTextChange_shouldTriggerLoader() {
public void queryTextChange_shouldTriggerLoaderAndInitializeSearch() {
when(mFeatureFactory.searchFeatureProvider
.getDatabaseSearchLoader(any(Context.class), anyString()))
.thenReturn(mDatabaseResultLoader);
@@ -167,6 +176,7 @@ public class SearchFragmentTest {
when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
.thenReturn(true);
ReflectionHelpers.setField(fragment, "mSearchAdapter", mSearchResultsAdapter);
fragment.onQueryTextChange(testQuery);
activityController.get().onBackPressed();
@@ -181,10 +191,12 @@ public class SearchFragmentTest {
.getDatabaseSearchLoader(any(Context.class), anyString());
verify(mFeatureFactory.searchFeatureProvider)
.getInstalledAppSearchLoader(any(Context.class), anyString());
verify(mSearchResultsAdapter).initializeSearch(mQueryCaptor.capture());
assertThat(mQueryCaptor.getValue()).isEqualTo(testQuery);
}
@Test
public void queryTextChangeToEmpty_shouldLoadSavedQuery() {
public void queryTextChangeToEmpty_shouldLoadSavedQueryAndNotInitializeSearch() {
when(mFeatureFactory.searchFeatureProvider
.getDatabaseSearchLoader(any(Context.class), anyString()))
.thenReturn(mDatabaseResultLoader);
@@ -201,6 +213,7 @@ public class SearchFragmentTest {
when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
.thenReturn(true);
ReflectionHelpers.setField(fragment, "mSavedQueryController", mSavedQueryController);
ReflectionHelpers.setField(fragment, "mSearchAdapter", mSearchResultsAdapter);
fragment.mQuery = "123";
fragment.onQueryTextChange("");
@@ -210,6 +223,7 @@ public class SearchFragmentTest {
verify(mFeatureFactory.searchFeatureProvider, never())
.getInstalledAppSearchLoader(any(Context.class), anyString());
verify(mSavedQueryController).loadSavedQueries();
verify(mSearchResultsAdapter, never()).initializeSearch(anyString());
}
@Test
@@ -383,12 +397,21 @@ public class SearchFragmentTest {
SearchFragment fragment = new SearchFragment();
ReflectionHelpers.setField(fragment, "mMetricsFeatureProvider",
mFeatureFactory.metricsFeatureProvider);
ReflectionHelpers.setField(fragment, "mSearchFeatureProvider",
mFeatureFactory.searchFeatureProvider);
ReflectionHelpers.setField(fragment, "mSearchAdapter", mock(SearchResultsAdapter.class));
fragment.mSavedQueryController = mock(SavedQueryController.class);
// Should log result name, result count, clicked rank, etc.
final SearchViewHolder result = mock(SearchViewHolder.class);
fragment.onSearchResultClicked(result, "test_setting");
final SearchViewHolder resultViewHolder = mock(SearchViewHolder.class);
ResultPayload payLoad = new ResultPayload(
(new Intent()).putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, "test_setting"));
SearchResult searchResult = new SearchResult.Builder()
.setStableId(payLoad.hashCode())
.setPayload(payLoad)
.setTitle("setting_title")
.build();
fragment.onSearchResultClicked(resultViewHolder, searchResult);
verify(mFeatureFactory.metricsFeatureProvider).action(
nullable(Context.class),
@@ -397,6 +420,9 @@ public class SearchFragmentTest {
argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SERACH_RESULT_COUNT)),
argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SERACH_RESULT_RANK)),
argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SERACH_QUERY_LENGTH)));
verify(mFeatureFactory.searchFeatureProvider).searchResultClicked(nullable(Context.class),
nullable(String.class), eq(searchResult));
}
private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag) {

View File

@@ -21,6 +21,7 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.util.Pair;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -28,24 +29,31 @@ import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.search.SearchResult.Builder;
import com.android.settings.search.ranking.SearchResultsRankerCallback;
import static org.junit.Assert.assertNull;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyList;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -63,6 +71,9 @@ public class SearchResultsAdapterTest {
private SearchFeatureProvider mSearchFeatureProvider;
@Mock
private Context mMockContext;
@Captor
private ArgumentCaptor<Integer> mSearchResultsCountCaptor =
ArgumentCaptor.forClass(Integer.class);
private SearchResultsAdapter mAdapter;
private Context mContext;
private String mLoaderClassName;
@@ -73,10 +84,10 @@ public class SearchResultsAdapterTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = Robolectric.buildActivity(Activity.class).get();
mAdapter = new SearchResultsAdapter(mFragment, mSearchFeatureProvider);
mLoaderClassName = DatabaseResultLoader.class.getName();
when(mFragment.getContext()).thenReturn(mMockContext);
when(mMockContext.getApplicationContext()).thenReturn(mContext);
mAdapter = new SearchResultsAdapter(mFragment, mSearchFeatureProvider);
}
@Test
@@ -88,8 +99,9 @@ public class SearchResultsAdapterTest {
@Test
public void testSingleSourceMerge_exactCopyReturned() {
Set<SearchResult> intentResults = getIntentSampleResults();
mAdapter.initializeSearch("");
mAdapter.addSearchResults(intentResults, mLoaderClassName);
mAdapter.displaySearchResults("");
mAdapter.notifyResultsLoaded();
List<SearchResult> updatedResults = mAdapter.getSearchResults();
assertThat(updatedResults).containsAllIn(intentResults);
@@ -113,11 +125,12 @@ public class SearchResultsAdapterTest {
@Test
public void testEndToEndSearch_properResultsMerged_correctOrder() {
mAdapter.initializeSearch("");
mAdapter.addSearchResults(new HashSet<SearchResult>(getDummyAppResults()),
InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(new HashSet<SearchResult>(getDummyDbResults()),
DatabaseResultLoader.class.getName());
int count = mAdapter.displaySearchResults("");
mAdapter.notifyResultsLoaded();
List<SearchResult> results = mAdapter.getSearchResults();
assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
@@ -126,25 +139,28 @@ public class SearchResultsAdapterTest {
assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
assertThat(count).isEqualTo(6);
verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
}
@Test
public void testEndToEndSearch_addResults_resultsAddedInOrder() {
List<AppSearchResult> appResults = getDummyAppResults();
List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
mAdapter.initializeSearch("");
// Add two individual items
mAdapter.addSearchResults(new HashSet<SearchResult>(appResults.subList(0, 1)),
InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(new HashSet<SearchResult>(dbResults.subList(0, 1)),
DatabaseResultLoader.class.getName());
mAdapter.displaySearchResults("");
mAdapter.notifyResultsLoaded();
// Add super-set of items
mAdapter.initializeSearch("");
mAdapter.addSearchResults(
new HashSet<SearchResult>(appResults), InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(
new HashSet<SearchResult>(dbResults), DatabaseResultLoader.class.getName());
int count = mAdapter.displaySearchResults("");
mAdapter.notifyResultsLoaded();
List<SearchResult> results = mAdapter.getSearchResults();
assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
@@ -153,46 +169,333 @@ public class SearchResultsAdapterTest {
assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
assertThat(count).isEqualTo(6);
}
@Test
public void testDisplayResults_ShouldNotRunSmartRankingIfDisabled() {
when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any()))
.thenReturn(false);
mAdapter.displaySearchResults("");
verify(mSearchFeatureProvider, never()).rankSearchResults(anyString(), anyList());
}
@Test
public void testDisplayResults_ShouldRunSmartRankingIfEnabled() {
when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any()))
.thenReturn(true);
mAdapter.displaySearchResults("");
verify(mSearchFeatureProvider, times(1)).rankSearchResults(anyString(), anyList());
verify(mFragment, times(2)).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
assertThat(mSearchResultsCountCaptor.getAllValues().toArray())
.isEqualTo(new Integer[] {2, 6});
}
@Test
public void testEndToEndSearch_removeResults_resultsAdded() {
List<AppSearchResult> appResults = getDummyAppResults();
List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
// Add list of items
mAdapter.initializeSearch("");
mAdapter.addSearchResults(new HashSet<SearchResult>(appResults),
InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(new HashSet<SearchResult>(dbResults),
DatabaseResultLoader.class.getName());
mAdapter.displaySearchResults("");
mAdapter.notifyResultsLoaded();
// Add subset of items
mAdapter.initializeSearch("");
mAdapter.addSearchResults(new HashSet<SearchResult>(appResults.subList(0, 1)),
InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(new HashSet<>(dbResults.subList(0, 1)),
DatabaseResultLoader.class.getName());
int count = mAdapter.displaySearchResults("");
mAdapter.notifyResultsLoaded();
List<SearchResult> results = mAdapter.getSearchResults();
assertThat(results.get(0).title).isEqualTo(TITLES[0]);
assertThat(results.get(1).title).isEqualTo(TITLES[3]);
assertThat(count).isEqualTo(2);
verify(mFragment, times(2)).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
assertThat(mSearchResultsCountCaptor.getAllValues().toArray())
.isEqualTo(new Integer[] {6, 2});
}
@Test
public void testEndToEndSearch_smartSearchRankingEnabledAndSucceededAfterResultsLoaded() {
when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
mAdapter.initializeSearch("");
mAdapter.addSearchResults(
new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(
new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
mAdapter.notifyResultsLoaded();
mAdapter.onRankingScoresAvailable(getDummyRankingScores());
List<SearchResult> results = mAdapter.getSearchResults();
assertThat(results.get(0).title).isEqualTo(TITLES[2]); // charlie
assertThat(results.get(1).title).isEqualTo(TITLES[0]); // alpha
assertThat(results.get(2).title).isEqualTo(TITLES[1]); // bravo
assertThat(results.get(3).title).isEqualTo(TITLES[3]); // appAlpha
assertThat(results.get(4).title).isEqualTo(TITLES[4]); // appBravo
assertThat(results.get(5).title).isEqualTo(TITLES[5]); // appCharlie
verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
}
@Test
public void testEndToEndSearch_smartSearchRankingEnabledAndSucceededBeforeResultsLoaded() {
when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
mAdapter.initializeSearch("");
mAdapter.onRankingScoresAvailable(getDummyRankingScores());
mAdapter.addSearchResults(
new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(
new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
mAdapter.notifyResultsLoaded();
List<SearchResult> results = mAdapter.getSearchResults();
assertThat(results.get(0).title).isEqualTo(TITLES[2]); // charlie
assertThat(results.get(1).title).isEqualTo(TITLES[0]); // alpha
assertThat(results.get(2).title).isEqualTo(TITLES[1]); // bravo
assertThat(results.get(3).title).isEqualTo(TITLES[3]); // appAlpha
assertThat(results.get(4).title).isEqualTo(TITLES[4]); // appBravo
assertThat(results.get(5).title).isEqualTo(TITLES[5]); // appCharlie
verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
}
@Test
public void testEndToEndSearch_smartSearchRankingEnabledAndFailedAfterResultsLoaded() {
when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
mAdapter.initializeSearch("");
mAdapter.addSearchResults(
new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(
new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
mAdapter.notifyResultsLoaded();
mAdapter.onRankingFailed();
List<SearchResult> results = mAdapter.getSearchResults();
assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
}
@Test
public void testEndToEndSearch_smartSearchRankingEnabledAndFailedBeforeResultsLoaded() {
when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
mAdapter.initializeSearch("");
mAdapter.onRankingFailed();
mAdapter.addSearchResults(
new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(
new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
mAdapter.notifyResultsLoaded();
List<SearchResult> results = mAdapter.getSearchResults();
assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
}
@Test
public void testEndToEndSearch_smartSearchRankingEnabledAndTimedoutAfterResultsLoaded() {
when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
mAdapter.initializeSearch("");
mAdapter.addSearchResults(
new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(
new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
mAdapter.notifyResultsLoaded();
waitUntilRankingTimesOut();
List<SearchResult> results = mAdapter.getSearchResults();
assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
}
@Test
public void testEndToEndSearch_smartSearchRankingEnabledAndTimedoutBeforeResultsLoaded() {
when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
mAdapter.initializeSearch("");
waitUntilRankingTimesOut();
mAdapter.addSearchResults(
new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(
new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
mAdapter.notifyResultsLoaded();
List<SearchResult> results = mAdapter.getSearchResults();
assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
}
@Test
public void testDoSmartRanking_shouldRankAppResultsAfterDbResults() {
when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
mAdapter.initializeSearch("");
mAdapter.addSearchResults(
new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(
new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
mAdapter.notifyResultsLoaded();
mAdapter.onRankingScoresAvailable(getDummyRankingScores());
List<SearchResult> results = mAdapter.doAsyncRanking();
assertThat(results.get(0).title).isEqualTo(TITLES[2]); // charlie
assertThat(results.get(1).title).isEqualTo(TITLES[0]); // alpha
assertThat(results.get(2).title).isEqualTo(TITLES[1]); // bravo
assertThat(results.get(3).title).isEqualTo(TITLES[3]); // appAlpha
assertThat(results.get(4).title).isEqualTo(TITLES[4]); // appBravo
assertThat(results.get(5).title).isEqualTo(TITLES[5]); // appCharlie
}
@Test
public void testDoSmartRanking_shouldRankResultsWithMissingScoresAfterScoredResults() {
when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
mAdapter.initializeSearch("");
mAdapter.addSearchResults(
new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(
new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
mAdapter.notifyResultsLoaded();
List<Pair<String, Float>> rankingScores = getDummyRankingScores();
rankingScores.remove(1); // no ranking score for alpha
mAdapter.onRankingScoresAvailable(rankingScores);
List<SearchResult> results = mAdapter.doAsyncRanking();
assertThat(results.get(0).title).isEqualTo(TITLES[2]); // charlie
assertThat(results.get(1).title).isEqualTo(TITLES[1]); // bravo
assertThat(results.get(2).title).isEqualTo(TITLES[0]); // alpha
assertThat(results.get(3).title).isEqualTo(TITLES[3]); // appAlpha
assertThat(results.get(4).title).isEqualTo(TITLES[4]); // appBravo
assertThat(results.get(5).title).isEqualTo(TITLES[5]); // appCharlie
}
@Test
public void testGetUnsortedLoadedResults () {
List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
mAdapter.initializeSearch("");
mAdapter.addSearchResults(
new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(
new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
Set<CharSequence> expectedDbTitles = new HashSet<>(
Arrays.asList("alpha", "bravo", "charlie"));
Set<CharSequence> expectedAppTitles = new HashSet<>(
Arrays.asList("appAlpha", "appBravo", "appCharlie"));
Set<CharSequence> actualDbTitles = new HashSet<>();
Set<CharSequence> actualAppTitles = new HashSet<>();
for (SearchResult result : mAdapter.getUnsortedLoadedResults(SearchResultsAdapter
.DB_RESULTS_LOADER_KEY)) {
actualDbTitles.add(result.title);
}
for (SearchResult result : mAdapter.getUnsortedLoadedResults(SearchResultsAdapter
.APP_RESULTS_LOADER_KEY)) {
actualAppTitles.add(result.title);
}
assertThat(actualDbTitles).isEqualTo(expectedDbTitles);
assertThat(actualAppTitles).isEqualTo(expectedAppTitles);
}
@Test
public void testGetSortedLoadedResults() {
List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
mAdapter.initializeSearch("");
mAdapter.addSearchResults(
new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(
new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
List<? extends SearchResult> actualDbResults =
mAdapter.getSortedLoadedResults(SearchResultsAdapter.DB_RESULTS_LOADER_KEY);
List<? extends SearchResult> actualAppResults =
mAdapter.getSortedLoadedResults(SearchResultsAdapter.APP_RESULTS_LOADER_KEY);
assertThat(actualDbResults.get(0).title).isEqualTo(TITLES[0]); // charlie
assertThat(actualDbResults.get(1).title).isEqualTo(TITLES[1]); // bravo
assertThat(actualDbResults.get(2).title).isEqualTo(TITLES[2]); // alpha
assertThat(actualAppResults.get(0).title).isEqualTo(TITLES[3]); // appAlpha
assertThat(actualAppResults.get(1).title).isEqualTo(TITLES[4]); // appBravo
assertThat(actualAppResults.get(2).title).isEqualTo(TITLES[5]); // appCharlie
}
@Test
public void testInitializeSearch_shouldNotRunSmartRankingIfDisabled() {
when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(false);
mAdapter.initializeSearch("");
mAdapter.notifyResultsLoaded();
verify(mSearchFeatureProvider, never()).querySearchResults(
any(Context.class), anyString(), any(SearchResultsRankerCallback.class));
}
@Test
public void testInitialSearch_shouldRunSmartRankingIfEnabled() {
when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
mAdapter.initializeSearch("");
mAdapter.notifyResultsLoaded();
verify(mSearchFeatureProvider, times(1)).querySearchResults(
any(Context.class), anyString(), any(SearchResultsRankerCallback.class));
}
@Test
public void testGetRankingScoreByStableId() {
when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
mAdapter.initializeSearch("");
mAdapter.onRankingScoresAvailable(getDummyRankingScores());
assertThat(mAdapter.getRankingScoreByStableId(dbResults.get(0).stableId))
.isWithin(1e-10f).of(0.8f);
assertThat(mAdapter.getRankingScoreByStableId(dbResults.get(1).stableId))
.isWithin(1e-10f).of(0.2f);
assertThat(mAdapter.getRankingScoreByStableId(dbResults.get(2).stableId))
.isWithin(1e-10f).of(0.9f);
assertThat(mAdapter.getRankingScoreByStableId(appResults.get(0).stableId))
.isEqualTo(-Float.MAX_VALUE);
assertThat(mAdapter.getRankingScoreByStableId(appResults.get(1).stableId))
.isEqualTo(-Float.MAX_VALUE);
assertThat(mAdapter.getRankingScoreByStableId(appResults.get(2).stableId))
.isEqualTo(-Float.MAX_VALUE);
}
private void waitUntilRankingTimesOut() {
while (mAdapter.getHandler().hasMessages(mAdapter.MSG_RANKING_TIMED_OUT)) {
try {
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Thread.sleep(100);
} catch (InterruptedException e) {
// Do nothing
}
}
}
private List<SearchResult> getDummyDbResults() {
@@ -218,8 +521,8 @@ public class SearchResultsAdapterTest {
return results;
}
private List<AppSearchResult> getDummyAppResults() {
List<AppSearchResult> results = new ArrayList<>();
private List<SearchResult> getDummyAppResults() {
List<SearchResult> results = new ArrayList<>();
ResultPayload payload = new ResultPayload(new Intent());
AppSearchResult.Builder builder = new AppSearchResult.Builder();
builder.setPayload(payload)
@@ -265,4 +568,16 @@ public class SearchResultsAdapterTest {
sampleResults.add(builder.build());
return sampleResults;
}
}
private List<Pair<String, Float>> getDummyRankingScores() {
List<SearchResult> results = getDummyDbResults();
List<Pair<String, Float>> scores = new ArrayList<>();
scores.add(
new Pair<String, Float>(Long.toString(results.get(2).stableId), 0.9f)); // charlie
scores.add(
new Pair<String, Float>(Long.toString(results.get(0).stableId), 0.8f)); // alpha
scores.add(
new Pair<String, Float>(Long.toString(results.get(1).stableId), 0.2f)); // bravo
return scores;
}
}