Merge "Move search querying into a single API"

This commit is contained in:
TreeHugger Robot
2017-10-31 20:42:33 +00:00
committed by Android (Google) Code Review
26 changed files with 1590 additions and 1878 deletions

View File

@@ -30,24 +30,36 @@ import android.os.UserHandle;
import android.support.annotation.VisibleForTesting;
import android.support.v4.content.ContextCompat;
import android.util.IconDrawableFactory;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilitySettings;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.utils.AsyncLoader;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class AccessibilityServiceResultLoader extends AsyncLoader<Set<? extends SearchResult>> {
public class AccessibilityServiceResultLoader extends
FutureTask<List<? extends SearchResult>> {
private static final String TAG = "A11yResultFutureTask";
public AccessibilityServiceResultLoader(Context context, String query,
SiteMapManager manager) {
super(new AccessibilityServiceResultCallable(context, query, manager));
}
static class AccessibilityServiceResultCallable implements
Callable<List<? extends SearchResult>> {
private static final int NAME_NO_MATCH = -1;
private final Context mContext;
private List<String> mBreadcrumb;
private SiteMapManager mSiteMapManager;
@VisibleForTesting
@@ -56,12 +68,10 @@ public class AccessibilityServiceResultLoader extends AsyncLoader<Set<? extends
private final PackageManager mPackageManager;
private final int mUserId;
public AccessibilityServiceResultLoader(Context context, String query,
public AccessibilityServiceResultCallable(Context context, String query,
SiteMapManager mapManager) {
super(context);
mContext = context;
mUserId = UserHandle.myUserId();
mContext = context;
mSiteMapManager = mapManager;
mPackageManager = context.getPackageManager();
mAccessibilityManager =
@@ -70,13 +80,13 @@ public class AccessibilityServiceResultLoader extends AsyncLoader<Set<? extends
}
@Override
public Set<? extends SearchResult> loadInBackground() {
final Set<SearchResult> results = new HashSet<>();
final Context context = getContext();
public List<? extends SearchResult> call() throws Exception {
long startTime = System.currentTimeMillis();
final List<SearchResult> results = new ArrayList<>();
final List<AccessibilityServiceInfo> services = mAccessibilityManager
.getInstalledAccessibilityServiceList();
final IconDrawableFactory iconFactory = IconDrawableFactory.newInstance(mContext);
final String screenTitle = context.getString(R.string.accessibility_settings);
final String screenTitle = mContext.getString(R.string.accessibility_settings);
for (AccessibilityServiceInfo service : services) {
if (service == null) {
continue;
@@ -93,7 +103,7 @@ public class AccessibilityServiceResultLoader extends AsyncLoader<Set<? extends
}
final Drawable icon;
if (resolveInfo.getIconResource() == 0) {
icon = ContextCompat.getDrawable(context, R.mipmap.ic_accessibility_generic);
icon = ContextCompat.getDrawable(mContext, R.mipmap.ic_accessibility_generic);
} else {
icon = iconFactory.getBadgedIcon(
resolveInfo.serviceInfo,
@@ -102,7 +112,7 @@ public class AccessibilityServiceResultLoader extends AsyncLoader<Set<? extends
}
final String componentName = new ComponentName(serviceInfo.packageName,
serviceInfo.name).flattenToString();
final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(context,
final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(mContext,
AccessibilitySettings.class.getName(), componentName, screenTitle);
results.add(new SearchResult.Builder()
@@ -114,21 +124,18 @@ public class AccessibilityServiceResultLoader extends AsyncLoader<Set<? extends
.setStableId(Objects.hash(screenTitle, componentName))
.build());
}
Collections.sort(results);
Log.i(TAG, "A11y search loading took:" + (System.currentTimeMillis() - startTime));
return results;
}
private List<String> getBreadCrumb() {
if (mBreadcrumb == null || mBreadcrumb.isEmpty()) {
final Context context = getContext();
mBreadcrumb = mSiteMapManager.buildBreadCrumb(
context, AccessibilitySettings.class.getName(),
context.getString(R.string.accessibility_settings));
mContext, AccessibilitySettings.class.getName(),
mContext.getString(R.string.accessibility_settings));
}
return mBreadcrumb;
}
@Override
protected void onDiscardResult(Set<? extends SearchResult> result) {
}
}

View File

@@ -36,16 +36,6 @@ import java.util.Map;
import java.util.Set;
import static com.android.settings.search.DatabaseResultLoader.BASE_RANKS;
import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_CLASS_NAME;
import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_ICON;
import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_ID;
import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE;
import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_KEY;
import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD;
import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD_TYPE;
import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_SCREEN_TITLE;
import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_SUMMARY_ON;
import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_TITLE;
import static com.android.settings.search.SearchResult.TOP_RANK;
/**
@@ -62,6 +52,25 @@ public class CursorToSearchResultConverter {
private static final String TAG = "CursorConverter";
/**
* These indices are used to match the columns of the this loader's SELECT statement.
* These are not necessarily the same order nor similar coverage as the schema defined in
* IndexDatabaseHelper
*/
public static final int COLUMN_INDEX_ID = 0;
public static final int COLUMN_INDEX_TITLE = 1;
public static final int COLUMN_INDEX_SUMMARY_ON = 2;
public static final int COLUMN_INDEX_SUMMARY_OFF = 3;
public static final int COLUMN_INDEX_CLASS_NAME = 4;
public static final int COLUMN_INDEX_SCREEN_TITLE = 5;
public static final int COLUMN_INDEX_ICON = 6;
public static final int COLUMN_INDEX_INTENT_ACTION = 7;
public static final int COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE = 8;
public static final int COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS = 9;
public static final int COLUMN_INDEX_KEY = 10;
public static final int COLUMN_INDEX_PAYLOAD_TYPE = 11;
public static final int COLUMN_INDEX_PAYLOAD = 12;
private final Context mContext;
private final int LONG_TITLE_LENGTH = 20;

View File

@@ -17,11 +17,13 @@
package com.android.settings.search;
import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_ID;
import static com.android.settings.search.DatabaseResultLoader
import static com.android.settings.search.CursorToSearchResultConverter.COLUMN_INDEX_ID;
import static com.android.settings.search.CursorToSearchResultConverter
.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE;
import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_KEY;
import static com.android.settings.search.CursorToSearchResultConverter.COLUMN_INDEX_KEY;
import static com.android.settings.search.DatabaseResultLoader.SELECT_COLUMNS;
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DOCID;
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.CLASS_NAME;
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_ENTRIES;
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS;
@@ -31,7 +33,6 @@ import static com.android.settings.search.IndexDatabaseHelper.IndexColumns
.DATA_SUMMARY_ON_NORMALIZED;
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_TITLE;
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED;
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DOCID;
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.ENABLED;
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.ICON;
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.INTENT_ACTION;

View File

@@ -24,35 +24,31 @@ import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.util.Pair;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.utils.AsyncLoader;
import com.android.settings.overlay.FeatureFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* AsyncTask to retrieve Settings, First party app and any intent based results.
* AsyncTask to retrieve Settings, first party app and any intent based results.
*/
public class DatabaseResultLoader extends AsyncLoader<Set<? extends SearchResult>> {
private static final String LOG = "DatabaseResultLoader";
public class DatabaseResultLoader extends FutureTask<List<? extends SearchResult>> {
/* These indices are used to match the columns of the this loader's SELECT statement.
These are not necessarily the same order nor similar coverage as the schema defined in
IndexDatabaseHelper */
public static final int COLUMN_INDEX_ID = 0;
public static final int COLUMN_INDEX_TITLE = 1;
public static final int COLUMN_INDEX_SUMMARY_ON = 2;
public static final int COLUMN_INDEX_SUMMARY_OFF = 3;
public static final int COLUMN_INDEX_CLASS_NAME = 4;
public static final int COLUMN_INDEX_SCREEN_TITLE = 5;
public static final int COLUMN_INDEX_ICON = 6;
public static final int COLUMN_INDEX_INTENT_ACTION = 7;
public static final int COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE = 8;
public static final int COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS = 9;
public static final int COLUMN_INDEX_KEY = 10;
public static final int COLUMN_INDEX_PAYLOAD_TYPE = 11;
public static final int COLUMN_INDEX_PAYLOAD = 12;
private static final String TAG = "DatabaseResultLoader";
public static final String[] SELECT_COLUMNS = {
IndexColumns.DOCID,
@@ -82,62 +78,92 @@ public class DatabaseResultLoader extends AsyncLoader<Set<? extends SearchResult
IndexColumns.DATA_SUMMARY_OFF_NORMALIZED,
};
public static final String[] MATCH_COLUMNS_TERTIARY = {
IndexColumns.DATA_KEYWORDS,
IndexColumns.DATA_ENTRIES
};
/**
* Base ranks defines the best possible rank based on what the query matches.
* If the query matches the prefix of the first word in the title, the best rank it can be is 1
* If the query matches the prefix of the other words in the title, the best rank it can be is 3
* If the query matches the prefix of the first word in the title, the best rank it can be
* is 1
* If the query matches the prefix of the other words in the title, the best rank it can be
* is 3
* If the query only matches the summary, the best rank it can be is 7
* If the query only matches keywords or entries, the best rank it can be is 9
*/
public static final int[] BASE_RANKS = {1, 3, 7, 9};
public DatabaseResultLoader(Context context, String query, SiteMapManager manager) {
super(new StaticSearchResultCallable(context, query, manager));
}
static class StaticSearchResultCallable implements
Callable<List<? extends SearchResult>> {
public final String[] MATCH_COLUMNS_TERTIARY = {
IndexColumns.DATA_KEYWORDS,
IndexColumns.DATA_ENTRIES
};
@VisibleForTesting
final String mQueryText;
private final Context mContext;
private final CursorToSearchResultConverter mConverter;
private final SiteMapManager mSiteMapManager;
private final SearchFeatureProvider mFeatureProvider;
public DatabaseResultLoader(Context context, String queryText, SiteMapManager mapManager) {
super(context);
mSiteMapManager = mapManager;
public StaticSearchResultCallable(Context context, String queryText,
SiteMapManager mapManager) {
mContext = context;
mSiteMapManager = mapManager;
mQueryText = queryText;
mConverter = new CursorToSearchResultConverter(context);
mFeatureProvider = FeatureFactory.getFactory(context).getSearchFeatureProvider();
}
@Override
protected void onDiscardResult(Set<? extends SearchResult> result) {
// TODO Search
}
@Override
public Set<? extends SearchResult> loadInBackground() {
public List<? extends SearchResult> call() {
if (mQueryText == null || mQueryText.isEmpty()) {
return null;
return new ArrayList<>();
}
final Set<SearchResult> results = new HashSet<>();
// TODO (b/68656233) Consolidate timing metrics
long startTime = System.currentTimeMillis();
// Start a Future to get search result scores.
FutureTask<List<Pair<String, Float>>> rankerTask = mFeatureProvider.getRankerTask(
mContext, mQueryText);
results.addAll(firstWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[0]));
results.addAll(secondaryWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[1]));
results.addAll(anyWordQuery(MATCH_COLUMNS_SECONDARY, BASE_RANKS[2]));
results.addAll(anyWordQuery(MATCH_COLUMNS_TERTIARY, BASE_RANKS[3]));
return results;
if (rankerTask != null) {
ExecutorService executorService = mFeatureProvider.getExecutorService();
executorService.execute(rankerTask);
}
@Override
protected boolean onCancelLoad() {
// TODO
return super.onCancelLoad();
final Set<SearchResult> resultSet = new HashSet<>();
resultSet.addAll(firstWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[0]));
resultSet.addAll(secondaryWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[1]));
resultSet.addAll(anyWordQuery(MATCH_COLUMNS_SECONDARY, BASE_RANKS[2]));
resultSet.addAll(anyWordQuery(MATCH_COLUMNS_TERTIARY, BASE_RANKS[3]));
// Try to retrieve the scores in time. Otherwise use static ranking.
if (rankerTask != null) {
try {
final long timeoutMs = mFeatureProvider.smartSearchRankingTimeoutMs(mContext);
List<Pair<String, Float>> searchRankScores = rankerTask.get(timeoutMs,
TimeUnit.MILLISECONDS);
return getDynamicRankedResults(resultSet, searchRankScores);
} catch (TimeoutException | InterruptedException | ExecutionException e) {
Log.d(TAG, "Error waiting for result scores: " + e);
}
}
List<SearchResult> resultList = new ArrayList<>(resultSet);
Collections.sort(resultList);
Log.i(TAG, "Static search loading took:" + (System.currentTimeMillis() - startTime));
return resultList;
}
// TODO (b/33577327) Retrieve all search results with a single query.
/**
* Creates and executes the query which matches prefixes of the first word of the given columns.
* Creates and executes the query which matches prefixes of the first word of the given
* columns.
*
* @param matchColumns The columns to match on
* @param baseRank The highest rank achievable by these results
@@ -168,7 +194,8 @@ public class DatabaseResultLoader extends AsyncLoader<Set<? extends SearchResult
}
/**
* Creates and executes the query which matches prefixes of the any word of the given columns.
* Creates and executes the query which matches prefixes of the any word of the given
* columns.
*
* @param matchColumns The columns to match on
* @param baseRank The highest rank achievable by these results
@@ -192,7 +219,8 @@ public class DatabaseResultLoader extends AsyncLoader<Set<? extends SearchResult
private Set<SearchResult> query(String whereClause, String[] selection, int baseRank) {
final SQLiteDatabase database =
IndexDatabaseHelper.getInstance(mContext).getReadableDatabase();
try (Cursor resultCursor = database.query(TABLE_PREFS_INDEX, SELECT_COLUMNS, whereClause,
try (Cursor resultCursor = database.query(TABLE_PREFS_INDEX, SELECT_COLUMNS,
whereClause,
selection, null, null, null)) {
return mConverter.convertCursor(mSiteMapManager, resultCursor, baseRank);
}
@@ -257,7 +285,8 @@ public class DatabaseResultLoader extends AsyncLoader<Set<? extends SearchResult
/**
* Fills out the selection array to match the query as the prefix of a word.
*
* @param size is twice the number of columns to be matched. The first match is for the prefix
* @param size is twice the number of columns to be matched. The first match is for the
* prefix
* of the first word in the column. The second match is for any subsequent word
* prefix match.
*/
@@ -272,4 +301,44 @@ public class DatabaseResultLoader extends AsyncLoader<Set<? extends SearchResult
}
return selection;
}
private List<SearchResult> getDynamicRankedResults(Set<SearchResult> unsortedSet,
List<Pair<String, Float>> searchRankScores) {
TreeSet<SearchResult> dbResultsSortedByScores = new TreeSet<>(
(o1, o2) -> {
float score1 = getRankingScoreByStableId(searchRankScores, o1.stableId);
float score2 = getRankingScoreByStableId(searchRankScores, o2.stableId);
if (score1 > score2) {
return -1;
} else if (score1 == score2) {
return 0;
} else {
return 1;
}
});
dbResultsSortedByScores.addAll(unsortedSet);
return new ArrayList<>(dbResultsSortedByScores);
}
/**
* 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(List<Pair<String, Float>> searchRankScores, int stableId) {
for (Pair<String, Float> rankingScore : searchRankScores) {
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;
}
}
}

View File

@@ -26,6 +26,7 @@ import android.content.pm.ServiceInfo;
import android.hardware.input.InputManager;
import android.hardware.input.KeyboardLayout;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.view.InputDevice;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
@@ -35,20 +36,24 @@ import com.android.settings.R;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
import com.android.settings.inputmethod.PhysicalKeyboardFragment;
import com.android.settings.utils.AsyncLoader;
import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* Search result for input devices (physical/virtual keyboard, game controllers, etc)
*/
public class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchResult>> {
private static final int NAME_NO_MATCH = -1;
public class InputDeviceResultLoader extends FutureTask<List<? extends SearchResult>> {
private static final String TAG = "InputResultFutureTask";
@VisibleForTesting
static final String PHYSICAL_KEYBOARD_FRAGMENT = PhysicalKeyboardFragment.class.getName();
@@ -56,6 +61,15 @@ public class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchRes
static final String VIRTUAL_KEYBOARD_FRAGMENT =
AvailableVirtualKeyboardFragment.class.getName();
public InputDeviceResultLoader(Context context, String query, SiteMapManager manager) {
super(new InputDeviceResultCallable(context, query, manager));
}
static class InputDeviceResultCallable implements
Callable<List<? extends SearchResult>> {
private static final int NAME_NO_MATCH = -1;
private final Context mContext;
private final SiteMapManager mSiteMapManager;
private final InputManager mInputManager;
private final InputMethodManager mImm;
@@ -66,8 +80,8 @@ public class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchRes
private List<String> mPhysicalKeyboardBreadcrumb;
private List<String> mVirtualKeyboardBreadcrumb;
public InputDeviceResultLoader(Context context, String query, SiteMapManager mapManager) {
super(context);
public InputDeviceResultCallable(Context context, String query, SiteMapManager mapManager) {
mContext = context;
mQuery = query;
mSiteMapManager = mapManager;
mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
@@ -76,25 +90,24 @@ public class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchRes
}
@Override
protected void onDiscardResult(Set<? extends SearchResult> result) {
}
@Override
public Set<? extends SearchResult> loadInBackground() {
final Set<SearchResult> results = new HashSet<>();
public List<? extends SearchResult> call() {
long startTime = System.currentTimeMillis();
final List<SearchResult> results = new ArrayList<>();
results.addAll(buildPhysicalKeyboardSearchResults());
results.addAll(buildVirtualKeyboardSearchResults());
Collections.sort(results);
Log.i(TAG, "Input search loading took:" + (System.currentTimeMillis() - startTime));
return results;
}
private Set<SearchResult> buildPhysicalKeyboardSearchResults() {
final Set<SearchResult> results = new HashSet<>();
final Context context = getContext();
final String screenTitle = context.getString(R.string.physical_keyboard_title);
final String screenTitle = mContext.getString(R.string.physical_keyboard_title);
for (final InputDevice device : getPhysicalFullKeyboards()) {
final String deviceName = device.getName();
final int wordDiff = InstalledAppResultLoader.getWordDifference(deviceName, mQuery);
final int wordDiff = InstalledAppResultLoader.getWordDifference(deviceName,
mQuery);
if (wordDiff == NAME_NO_MATCH) {
continue;
}
@@ -104,15 +117,14 @@ public class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchRes
? mInputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null;
final String summary = (keyboardLayout != null)
? keyboardLayout.toString()
: context.getString(R.string.keyboard_layout_default_label);
final String key = deviceName;
: mContext.getString(R.string.keyboard_layout_default_label);
final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(context,
PHYSICAL_KEYBOARD_FRAGMENT, key, screenTitle);
final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(mContext,
PHYSICAL_KEYBOARD_FRAGMENT, deviceName, screenTitle);
results.add(new SearchResult.Builder()
.setTitle(deviceName)
.setPayload(new ResultPayload(intent))
.setStableId(Objects.hash(PHYSICAL_KEYBOARD_FRAGMENT, key))
.setStableId(Objects.hash(PHYSICAL_KEYBOARD_FRAGMENT, deviceName))
.setSummary(summary)
.setRank(wordDiff)
.addBreadcrumbs(getPhysicalKeyboardBreadCrumb())
@@ -123,13 +135,12 @@ public class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchRes
private Set<SearchResult> buildVirtualKeyboardSearchResults() {
final Set<SearchResult> results = new HashSet<>();
final Context context = getContext();
final String screenTitle = context.getString(R.string.add_virtual_keyboard);
final String screenTitle = mContext.getString(R.string.add_virtual_keyboard);
final List<InputMethodInfo> inputMethods = mImm.getInputMethodList();
for (InputMethodInfo info : inputMethods) {
final String title = info.loadLabel(mPackageManager).toString();
final String summary = InputMethodAndSubtypeUtil
.getSubtypeLocaleNameListAsSentence(getAllSubtypesOf(info), context, info);
.getSubtypeLocaleNameListAsSentence(getAllSubtypesOf(info), mContext, info);
int wordDiff = InstalledAppResultLoader.getWordDifference(title, mQuery);
if (wordDiff == NAME_NO_MATCH) {
wordDiff = InstalledAppResultLoader.getWordDifference(summary, mQuery);
@@ -140,7 +151,7 @@ public class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchRes
final ServiceInfo serviceInfo = info.getServiceInfo();
final String key = new ComponentName(serviceInfo.packageName, serviceInfo.name)
.flattenToString();
final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(context,
final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(mContext,
VIRTUAL_KEYBOARD_FRAGMENT, key, screenTitle);
results.add(new SearchResult.Builder()
.setTitle(title)
@@ -156,10 +167,9 @@ public class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchRes
private List<String> getPhysicalKeyboardBreadCrumb() {
if (mPhysicalKeyboardBreadcrumb == null || mPhysicalKeyboardBreadcrumb.isEmpty()) {
final Context context = getContext();
mPhysicalKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
context, PHYSICAL_KEYBOARD_FRAGMENT,
context.getString(R.string.physical_keyboard_title));
mContext, PHYSICAL_KEYBOARD_FRAGMENT,
mContext.getString(R.string.physical_keyboard_title));
}
return mPhysicalKeyboardBreadcrumb;
}
@@ -167,7 +177,7 @@ public class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchRes
private List<String> getVirtualKeyboardBreadCrumb() {
if (mVirtualKeyboardBreadcrumb == null || mVirtualKeyboardBreadcrumb.isEmpty()) {
final Context context = getContext();
final Context context = mContext;
mVirtualKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
context, VIRTUAL_KEYBOARD_FRAGMENT,
context.getString(R.string.add_virtual_keyboard));
@@ -198,3 +208,4 @@ public class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchRes
return allSubtypes;
}
}
}

View File

@@ -29,124 +29,39 @@ import android.os.UserManager;
import android.provider.Settings;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.applications.manageapplications.ManageApplications;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.utils.AsyncLoader;
import com.android.settingslib.wrapper.PackageManagerWrapper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* Search loader for installed apps.
*/
public class InstalledAppResultLoader extends AsyncLoader<Set<? extends SearchResult>> {
public class InstalledAppResultLoader extends FutureTask<List<? extends SearchResult>> {
private static final String TAG = "InstalledAppFutureTask";
private static final int NAME_NO_MATCH = -1;
private static final Intent LAUNCHER_PROBE = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER);
private List<String> mBreadcrumb;
private SiteMapManager mSiteMapManager;
@VisibleForTesting
final String mQuery;
private final UserManager mUserManager;
private final PackageManagerWrapper mPackageManager;
private final List<ResolveInfo> mHomeActivities = new ArrayList<>();
public InstalledAppResultLoader(Context context, PackageManagerWrapper pmWrapper,
String query, SiteMapManager mapManager) {
super(context);
mSiteMapManager = mapManager;
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mPackageManager = pmWrapper;
mQuery = query;
}
@Override
public Set<? extends SearchResult> loadInBackground() {
final Set<AppSearchResult> results = new HashSet<>();
final PackageManager pm = mPackageManager.getPackageManager();
mHomeActivities.clear();
mPackageManager.getHomeActivities(mHomeActivities);
for (UserInfo user : getUsersToCount()) {
final List<ApplicationInfo> apps =
mPackageManager.getInstalledApplicationsAsUser(
PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
| (user.isAdmin() ? PackageManager.MATCH_ANY_USER : 0),
user.id);
for (ApplicationInfo info : apps) {
if (!shouldIncludeAsCandidate(info, user)) {
continue;
}
final CharSequence label = info.loadLabel(pm);
final int wordDiff = getWordDifference(label.toString(), mQuery);
if (wordDiff == NAME_NO_MATCH) {
continue;
}
final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", info.packageName, null))
.putExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY,
MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS);
final AppSearchResult.Builder builder = new AppSearchResult.Builder();
builder.setAppInfo(info)
.setStableId(Objects.hash(info.packageName, user.id))
.setTitle(info.loadLabel(pm))
.setRank(getRank(wordDiff))
.addBreadcrumbs(getBreadCrumb())
.setPayload(new ResultPayload(intent));
results.add(builder.build());
}
}
return results;
}
/**
* Returns true if the candidate should be included in candidate list
* <p/>
* This method matches logic in {@code ApplicationState#FILTER_DOWNLOADED_AND_LAUNCHER}.
*/
private boolean shouldIncludeAsCandidate(ApplicationInfo info, UserInfo user) {
// Not system app
if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
|| (info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
return true;
}
// Shows up in launcher
final Intent launchIntent = new Intent(LAUNCHER_PROBE)
.setPackage(info.packageName);
final List<ResolveInfo> intents = mPackageManager.queryIntentActivitiesAsUser(
launchIntent,
PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
user.id);
if (intents != null && intents.size() != 0) {
return true;
}
// Is launcher app itself
return isPackageInList(mHomeActivities, info.packageName);
}
@Override
protected void onDiscardResult(Set<? extends SearchResult> result) {
}
private List<UserInfo> getUsersToCount() {
return mUserManager.getProfiles(UserHandle.myUserId());
public InstalledAppResultLoader(Context context, PackageManagerWrapper wrapper,
String query, SiteMapManager manager) {
super(new InstalledAppResultCallable(context, wrapper, query, manager));
}
/**
@@ -213,6 +128,104 @@ public class InstalledAppResultLoader extends AsyncLoader<Set<? extends SearchRe
return NAME_NO_MATCH;
}
static class InstalledAppResultCallable implements
Callable<List<? extends SearchResult>> {
private final Context mContext;
private List<String> mBreadcrumb;
private SiteMapManager mSiteMapManager;
@VisibleForTesting
final String mQuery;
private final UserManager mUserManager;
private final PackageManagerWrapper mPackageManager;
private final List<ResolveInfo> mHomeActivities = new ArrayList<>();
public InstalledAppResultCallable(Context context, PackageManagerWrapper pmWrapper,
String query, SiteMapManager mapManager) {
mContext = context;
mSiteMapManager = mapManager;
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mPackageManager = pmWrapper;
mQuery = query;
}
@Override
public List<? extends SearchResult> call() throws Exception {
long startTime = System.currentTimeMillis();
final List<AppSearchResult> results = new ArrayList<>();
final PackageManager pm = mPackageManager.getPackageManager();
mHomeActivities.clear();
mPackageManager.getHomeActivities(mHomeActivities);
for (UserInfo user : getUsersToCount()) {
final List<ApplicationInfo> apps =
mPackageManager.getInstalledApplicationsAsUser(
PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
| (user.isAdmin() ? PackageManager.MATCH_ANY_USER : 0),
user.id);
for (ApplicationInfo info : apps) {
if (!shouldIncludeAsCandidate(info, user)) {
continue;
}
final CharSequence label = info.loadLabel(pm);
final int wordDiff = getWordDifference(label.toString(), mQuery);
if (wordDiff == NAME_NO_MATCH) {
continue;
}
final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", info.packageName, null))
.putExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY,
MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS);
final AppSearchResult.Builder builder = new AppSearchResult.Builder();
builder.setAppInfo(info)
.setStableId(Objects.hash(info.packageName, user.id))
.setTitle(info.loadLabel(pm))
.setRank(getRank(wordDiff))
.addBreadcrumbs(getBreadCrumb())
.setPayload(new ResultPayload(intent));
results.add(builder.build());
}
}
Collections.sort(results);
Log.i(TAG, "App search loading took:" + (System.currentTimeMillis() - startTime));
return results;
}
/**
* Returns true if the candidate should be included in candidate list
* <p/>
* This method matches logic in {@code ApplicationState#FILTER_DOWNLOADED_AND_LAUNCHER}.
*/
private boolean shouldIncludeAsCandidate(ApplicationInfo info, UserInfo user) {
// Not system app
if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
|| (info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
return true;
}
// Shows up in launcher
final Intent launchIntent = new Intent(LAUNCHER_PROBE)
.setPackage(info.packageName);
final List<ResolveInfo> intents = mPackageManager.queryIntentActivitiesAsUser(
launchIntent,
PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
user.id);
if (intents != null && intents.size() != 0) {
return true;
}
// Is launcher app itself
return isPackageInList(mHomeActivities, info.packageName);
}
private List<UserInfo> getUsersToCount() {
return mUserManager.getProfiles(UserHandle.myUserId());
}
private boolean isPackageInList(List<ResolveInfo> resolveInfos, String pkg) {
for (ResolveInfo info : resolveInfos) {
if (TextUtils.equals(info.activityInfo.packageName, pkg)) {
@@ -224,10 +237,9 @@ public class InstalledAppResultLoader extends AsyncLoader<Set<? extends SearchRe
private List<String> getBreadCrumb() {
if (mBreadcrumb == null || mBreadcrumb.isEmpty()) {
final Context context = getContext();
mBreadcrumb = mSiteMapManager.buildBreadCrumb(
context, ManageApplications.class.getName(),
context.getString(R.string.applications_settings));
mContext, ManageApplications.class.getName(),
mContext.getString(R.string.applications_settings));
}
return mBreadcrumb;
}
@@ -245,3 +257,4 @@ public class InstalledAppResultLoader extends AsyncLoader<Set<? extends SearchRe
return 3;
}
}
}

View File

@@ -19,10 +19,14 @@ package com.android.settings.search;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.util.Pair;
import android.view.View;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.search.ranking.SearchResultsRankerCallback;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.FutureTask;
/**
* FeatureProvider for Settings Search
@@ -43,26 +47,31 @@ public interface SearchFeatureProvider {
void verifyLaunchSearchResultPageCaller(Context context, @NonNull ComponentName caller)
throws SecurityException, IllegalArgumentException;
/**
* Returns a new loader to get settings search results.
*/
SearchResultLoader getSearchResultLoader(Context context, String query);
/**
* Returns a new loader to search in index database.
*/
DatabaseResultLoader getDatabaseSearchLoader(Context context, String query);
DatabaseResultLoader getStaticSearchResultTask(Context context, String query);
/**
* Returns a new loader to search installed apps.
*/
InstalledAppResultLoader getInstalledAppSearchLoader(Context context, String query);
InstalledAppResultLoader getInstalledAppSearchTask(Context context, String query);
/**
* Returns a new loader to search accessibility services.
*/
AccessibilityServiceResultLoader getAccessibilityServiceResultLoader(Context context,
AccessibilityServiceResultLoader getAccessibilityServiceResultTask(Context context,
String query);
/**
* Returns a new loader to search input devices.
*/
InputDeviceResultLoader getInputDeviceResultLoader(Context context, String query);
InputDeviceResultLoader getInputDeviceResultTask(Context context, String query);
/**
* Returns a new loader to get all recently saved queries search terms.
@@ -95,6 +104,11 @@ public interface SearchFeatureProvider {
*/
boolean isIndexingComplete(Context context);
/**
* @return a {@link ExecutorService} to be shared between search tasks.
*/
ExecutorService getExecutorService();
/**
* Initializes the feedback button in case it was dismissed.
*/
@@ -114,23 +128,6 @@ public interface SearchFeatureProvider {
default void hideFeedbackButton() {
}
/**
* Query search results based on the input query.
*
* @param context application context
* @param query input user query
* @param searchResultsRankerCallback {@link SearchResultsRankerCallback}
*/
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.
*
@@ -161,4 +158,10 @@ public interface SearchFeatureProvider {
default void searchRankingWarmup(Context context) {
}
/**
* Return a FutureTask to get a list of scores for search results.
*/
default FutureTask<List<Pair<String, Float>>> getRankerTask(Context context, String query) {
return null;
}
}

View File

@@ -22,12 +22,15 @@ import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.indexing.IndexData;
import com.android.settingslib.wrapper.PackageManagerWrapper;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* FeatureProvider for the refactored search code.
@@ -40,6 +43,7 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider {
private DatabaseIndexingManager mDatabaseIndexingManager;
private SiteMapManager mSiteMapManager;
private ExecutorService mExecutorService;
@Override
public boolean isEnabled(Context context) {
@@ -59,26 +63,31 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider {
}
@Override
public DatabaseResultLoader getDatabaseSearchLoader(Context context, String query) {
public SearchResultLoader getSearchResultLoader(Context context, String query) {
return new SearchResultLoader(context, cleanQuery(query));
}
@Override
public DatabaseResultLoader getStaticSearchResultTask(Context context, String query) {
return new DatabaseResultLoader(context, cleanQuery(query), getSiteMapManager());
}
@Override
public InstalledAppResultLoader getInstalledAppSearchLoader(Context context, String query) {
public InstalledAppResultLoader getInstalledAppSearchTask(Context context, String query) {
return new InstalledAppResultLoader(
context, new PackageManagerWrapper(context.getPackageManager()),
cleanQuery(query), getSiteMapManager());
}
@Override
public AccessibilityServiceResultLoader getAccessibilityServiceResultLoader(Context context,
public AccessibilityServiceResultLoader getAccessibilityServiceResultTask(Context context,
String query) {
return new AccessibilityServiceResultLoader(context, cleanQuery(query),
getSiteMapManager());
}
@Override
public InputDeviceResultLoader getInputDeviceResultLoader(Context context, String query) {
public InputDeviceResultLoader getInputDeviceResultTask(Context context, String query) {
return new InputDeviceResultLoader(context, cleanQuery(query), getSiteMapManager());
}
@@ -124,12 +133,21 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider {
.histogram(context, METRICS_ACTION_SETTINGS_INDEX, indexingTime);
}
@Override
public ExecutorService getExecutorService() {
if (mExecutorService == null) {
mExecutorService = Executors.newCachedThreadPool();
}
return mExecutorService;
}
/**
* A generic method to make the query suitable for searching the database.
*
* @return the cleaned query string
*/
private String cleanQuery(String query) {
@VisibleForTesting
String cleanQuery(String query) {
if (TextUtils.isEmpty(query)) {
return null;
}

View File

@@ -54,8 +54,6 @@ import com.android.settings.widget.ActionBarShadowController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
/**
* This fragment manages the lifecycle of indexing and searching.
@@ -68,7 +66,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* the query if the user has entered text.
*/
public class SearchFragment extends InstrumentedFragment implements SearchView.OnQueryTextListener,
LoaderManager.LoaderCallbacks<Set<? extends SearchResult>>, IndexingCallback {
LoaderManager.LoaderCallbacks<List<? extends SearchResult>>, IndexingCallback {
private static final String TAG = "SearchFragment";
// State values
@@ -78,23 +76,14 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
static final class SearchLoaderId {
// Search Query IDs
public static final int DATABASE = 1;
public static final int INSTALLED_APPS = 2;
public static final int ACCESSIBILITY_SERVICES = 3;
public static final int INPUT_DEVICES = 4;
public static final int SEARCH_RESULT = 1;
// Saved Query IDs
public static final int SAVE_QUERY_TASK = 5;
public static final int REMOVE_QUERY_TASK = 6;
public static final int SAVED_QUERIES = 7;
public static final int SAVE_QUERY_TASK = 2;
public static final int REMOVE_QUERY_TASK = 3;
public static final int SAVED_QUERIES = 4;
}
private static final int NUM_QUERY_LOADERS = 4;
@VisibleForTesting
AtomicInteger mUnfinishedLoadersCount = new AtomicInteger(NUM_QUERY_LOADERS);
@VisibleForTesting
String mQuery;
@@ -147,7 +136,7 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
setHasOptionsMenu(true);
final LoaderManager loaderManager = getLoaderManager();
mSearchAdapter = new SearchResultsAdapter(this, mSearchFeatureProvider);
mSearchAdapter = new SearchResultsAdapter(this /* fragment */);
mSavedQueryController = new SavedQueryController(
getContext(), loaderManager, mSearchAdapter);
mSearchFeatureProvider.initFeedbackButton();
@@ -277,15 +266,11 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
if (isEmptyQuery) {
final LoaderManager loaderManager = getLoaderManager();
loaderManager.destroyLoader(SearchLoaderId.DATABASE);
loaderManager.destroyLoader(SearchLoaderId.INSTALLED_APPS);
loaderManager.destroyLoader(SearchLoaderId.ACCESSIBILITY_SERVICES);
loaderManager.destroyLoader(SearchLoaderId.INPUT_DEVICES);
loaderManager.destroyLoader(SearchLoaderId.SEARCH_RESULT);
mShowingSavedQuery = true;
mSavedQueryController.loadSavedQueries();
mSearchFeatureProvider.hideFeedbackButton();
} else {
mSearchAdapter.initializeSearch(mQuery);
restartLoaders();
}
@@ -301,35 +286,25 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
}
@Override
public Loader<Set<? extends SearchResult>> onCreateLoader(int id, Bundle args) {
public Loader<List<? extends SearchResult>> onCreateLoader(int id, Bundle args) {
final Activity activity = getActivity();
switch(id) {
case SearchLoaderId.DATABASE:
return mSearchFeatureProvider.getDatabaseSearchLoader(activity, mQuery);
case SearchLoaderId.INSTALLED_APPS:
return mSearchFeatureProvider.getInstalledAppSearchLoader(activity, mQuery);
case SearchLoaderId.ACCESSIBILITY_SERVICES:
return mSearchFeatureProvider.getAccessibilityServiceResultLoader(activity, mQuery);
case SearchLoaderId.INPUT_DEVICES:
return mSearchFeatureProvider.getInputDeviceResultLoader(activity, mQuery);
case SearchLoaderId.SEARCH_RESULT:
return mSearchFeatureProvider.getSearchResultLoader(activity, mQuery);
default:
return null;
}
}
@Override
public void onLoadFinished(Loader<Set<? extends SearchResult>> loader,
Set<? extends SearchResult> data) {
mSearchAdapter.addSearchResults(data, loader.getClass().getName());
if (mUnfinishedLoadersCount.decrementAndGet() != 0) {
return;
}
mSearchAdapter.notifyResultsLoaded();
public void onLoadFinished(Loader<List<? extends SearchResult>> loader,
List<? extends SearchResult> data) {
mSearchAdapter.postSearchResults(data);
}
@Override
public void onLoaderReset(Loader<Set<? extends SearchResult>> loader) {
public void onLoaderReset(Loader<List<? extends SearchResult>> loader) {
}
/**
@@ -344,13 +319,8 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
mSavedQueryController.loadSavedQueries();
} else {
final LoaderManager loaderManager = getLoaderManager();
loaderManager.initLoader(SearchLoaderId.DATABASE, null /* args */, this /* callback */);
loaderManager.initLoader(
SearchLoaderId.INSTALLED_APPS, null /* args */, this /* callback */);
loaderManager.initLoader(
SearchLoaderId.ACCESSIBILITY_SERVICES, null /* args */, this /* callback */);
loaderManager.initLoader(
SearchLoaderId.INPUT_DEVICES, null /* args */, this /* callback */);
loaderManager.initLoader(SearchLoaderId.SEARCH_RESULT, null /* args */,
this /* callback */);
}
requery();
@@ -388,15 +358,8 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
private void restartLoaders() {
mShowingSavedQuery = false;
final LoaderManager loaderManager = getLoaderManager();
mUnfinishedLoadersCount.set(NUM_QUERY_LOADERS);
loaderManager.restartLoader(
SearchLoaderId.DATABASE, null /* args */, this /* callback */);
loaderManager.restartLoader(
SearchLoaderId.INSTALLED_APPS, null /* args */, this /* callback */);
loaderManager.restartLoader(
SearchLoaderId.ACCESSIBILITY_SERVICES, null /* args */, this /* callback */);
loaderManager.restartLoader(
SearchLoaderId.INPUT_DEVICES, null /* args */, this /* callback */);
SearchLoaderId.SEARCH_RESULT, null /* args */, this /* callback */);
}
public String getQuery() {
@@ -453,9 +416,7 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
taggedData.add(Pair.create(
MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_RANK,
resultViewHolder.getAdapterPosition()));
taggedData.add(Pair.create(
MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_ASYNC_RANKING_STATE,
mSearchAdapter.getAsyncRankingState()));
// TODO (b/67744820) Move metrics to SettingsIntelligence (including ranking state).
taggedData.add(Pair.create(
MetricsEvent.FIELD_SETTINGS_SEARCH_QUERY_LENGTH,
TextUtils.isEmpty(mQuery) ? 0 : mQuery.length()));

View File

@@ -0,0 +1,177 @@
package com.android.settings.search;
import android.annotation.NonNull;
import android.content.Context;
import android.util.Log;
import android.util.SparseArray;
import com.android.settings.overlay.FeatureFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Collects the sorted list of all setting search results.
*
* TODO (b/64939692) Convert the timing logs to metrics
*/
public class SearchResultAggregator {
private static final String TAG = "SearchResultAggregator";
/**
* Timeout for first task. Allows for longer delay.
*/
private static final long LONG_CHECK_TASK_TIMEOUT_MS = 500;
/**
* Timeout for subsequent tasks to allow for fast returning tasks.
*/
private static final long SHORT_CHECK_TASK_TIMEOUT_MS = 150;
private static SearchResultAggregator sResultAggregator;
// TODO (b/33577327) Merge the other loaders into a single dynamic loader
static final class ResultLoaderId {
static final int STATIC_RESULTS = 1;
static final int INSTALLED_RESULTS = 2;
static final int INPUT_RESULTS = 3;
static final int ACCESSIBILITY_RESULTS = 4;
}
private SearchResultAggregator() {
}
public static SearchResultAggregator getInstance() {
if (sResultAggregator == null) {
sResultAggregator = new SearchResultAggregator();
}
return sResultAggregator;
}
@NonNull
public synchronized List<? extends SearchResult> fetchResults(Context context, String query) {
SearchFeatureProvider mFeatureProvider = FeatureFactory.getFactory(
context).getSearchFeatureProvider();
ExecutorService executorService = mFeatureProvider.getExecutorService();
final DatabaseResultLoader staticResultsTask =
mFeatureProvider.getStaticSearchResultTask(context, query);
final InstalledAppResultLoader installedAppTask =
mFeatureProvider.getInstalledAppSearchTask(context, query);
final InputDeviceResultLoader inputDevicesTask =
mFeatureProvider.getInputDeviceResultTask(context, query);
final AccessibilityServiceResultLoader accessibilityServicesTask =
mFeatureProvider.getAccessibilityServiceResultTask(context,
query);
executorService.execute(staticResultsTask);
executorService.execute(installedAppTask);
executorService.execute(inputDevicesTask);
executorService.execute(accessibilityServicesTask);
SparseArray<List<? extends SearchResult>> resultsArray = new SparseArray<>();
List<? extends SearchResult> EMPTY_LIST = new ArrayList<>();
long allTasksStart = System.currentTimeMillis();
try {
resultsArray.put(ResultLoaderId.INPUT_RESULTS,
inputDevicesTask.get(SHORT_CHECK_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
} catch (TimeoutException | InterruptedException | ExecutionException e) {
Log.d(TAG, "Could not retrieve input devices results in time: " + e);
resultsArray.put(ResultLoaderId.INPUT_RESULTS, EMPTY_LIST);
}
try {
resultsArray.put(ResultLoaderId.ACCESSIBILITY_RESULTS,
accessibilityServicesTask.get(SHORT_CHECK_TASK_TIMEOUT_MS,
TimeUnit.MILLISECONDS));
} catch (TimeoutException | InterruptedException | ExecutionException e) {
Log.d(TAG, "Could not retrieve accessibility results in time: " + e);
resultsArray.put(ResultLoaderId.ACCESSIBILITY_RESULTS, EMPTY_LIST);
}
try {
resultsArray.put(ResultLoaderId.STATIC_RESULTS,
staticResultsTask.get(LONG_CHECK_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
} catch (TimeoutException | InterruptedException | ExecutionException e) {
Log.d(TAG, "Could not retrieve static results: " + e);
resultsArray.put(ResultLoaderId.STATIC_RESULTS, EMPTY_LIST);
}
try {
resultsArray.put(ResultLoaderId.INSTALLED_RESULTS,
installedAppTask.get(SHORT_CHECK_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
} catch (TimeoutException | InterruptedException | ExecutionException e) {
Log.d(TAG, "Could not retrieve installed app results in time: " + e);
resultsArray.put(ResultLoaderId.INSTALLED_RESULTS, EMPTY_LIST);
}
long mergeStartTime = System.currentTimeMillis();
Log.i(TAG, "Total result loader time: " + (mergeStartTime - allTasksStart));
List<? extends SearchResult> mergedResults = mergeSearchResults(resultsArray);
Log.i(TAG, "Total merge time: " + (System.currentTimeMillis() - mergeStartTime));
Log.i(TAG, "Total aggregator time: " + (System.currentTimeMillis() - allTasksStart));
return mergedResults;
}
// TODO (b/68255021) scale the dynamic search results ranks and do a k-way merge
private List<? extends SearchResult> mergeSearchResults(
SparseArray<List<? extends SearchResult>> resultsArray) {
List<? extends SearchResult> staticResults = resultsArray.get(
ResultLoaderId.STATIC_RESULTS);
List<? extends SearchResult> installedAppResults = resultsArray.get(
ResultLoaderId.INSTALLED_RESULTS);
List<? extends SearchResult> accessibilityResults = resultsArray.get(
ResultLoaderId.ACCESSIBILITY_RESULTS);
List<? extends SearchResult> inputDeviceResults = resultsArray.get(
ResultLoaderId.INPUT_RESULTS);
List<SearchResult> searchResults;
int staticSize = staticResults.size();
int appSize = installedAppResults.size();
int a11ySize = accessibilityResults.size();
int inputDeviceSize = inputDeviceResults.size();
int appIndex = 0;
int a11yIndex = 0;
int inputDeviceIndex = 0;
int rank = SearchResult.TOP_RANK;
// TODO: We need a helper method to do k-way merge.
searchResults = new ArrayList<>(staticSize + appSize + a11ySize + inputDeviceSize);
searchResults.addAll(resultsArray.get(ResultLoaderId.STATIC_RESULTS));
while (rank <= SearchResult.BOTTOM_RANK) {
while ((appIndex < appSize) && (installedAppResults.get(appIndex).rank == rank)) {
searchResults.add(installedAppResults.get(appIndex++));
}
while ((a11yIndex < a11ySize) && (accessibilityResults.get(a11yIndex).rank == rank)) {
searchResults.add(accessibilityResults.get(a11yIndex++));
}
while (inputDeviceIndex < inputDeviceSize
&& inputDeviceResults.get(inputDeviceIndex).rank == rank) {
searchResults.add(inputDeviceResults.get(inputDeviceIndex++));
}
rank++;
}
while (appIndex < appSize) {
searchResults.add(installedAppResults.get(appIndex++));
}
while (a11yIndex < a11ySize) {
searchResults.add(accessibilityResults.get(a11yIndex++));
}
while (inputDeviceIndex < inputDeviceSize) {
searchResults.add(inputDeviceResults.get(inputDeviceIndex++));
}
return searchResults;
}
}

View File

@@ -26,10 +26,11 @@ import java.util.List;
*/
public class SearchResultDiffCallback extends DiffUtil.Callback {
private List<SearchResult> mOldList;
private List<SearchResult> mNewList;
private List<? extends SearchResult> mOldList;
private List<? extends SearchResult> mNewList;
public SearchResultDiffCallback(List<SearchResult> oldList, List<SearchResult> newList) {
public SearchResultDiffCallback(List<? extends SearchResult> oldList,
List<? extends SearchResult> newList) {
mOldList = oldList;
mNewList = newList;
}

View File

@@ -0,0 +1,30 @@
package com.android.settings.search;
import com.android.settings.utils.AsyncLoader;
import android.content.Context;
import java.util.List;
/**
* Loads a sorted list of Search results for a given query.
*/
public class SearchResultLoader extends AsyncLoader<List<? extends SearchResult>> {
private final String mQuery;
public SearchResultLoader(Context context, String query) {
super(context);
mQuery = query;
}
@Override
public List<? extends SearchResult> loadInBackground() {
SearchResultAggregator aggregator = SearchResultAggregator.getInstance();
return aggregator.fetchResults(getContext(), mQuery);
}
@Override
protected void onDiscardResult(List<? extends SearchResult> result) {
}
}

View File

@@ -18,87 +18,25 @@
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;
import java.util.TreeSet;
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 String ACCESSIBILITY_LOADER_KEY = AccessibilityServiceResultLoader.class.getName();
@VisibleForTesting
static final String INPUT_DEVICE_LOADER_KEY = InputDeviceResultLoader.class.getName();
@VisibleForTesting
static final int MSG_RANKING_TIMED_OUT = 1;
public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder> {
private final SearchFragment mFragment;
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 {}
@VisibleForTesting
static final int DISABLED = 0;
@VisibleForTesting
static final int PENDING_RESULTS = 1;
@VisibleForTesting
static final int SUCCEEDED = 2;
@VisibleForTesting
static final int FAILED = 3;
@VisibleForTesting
static final int TIMED_OUT = 4;
private @AsyncRankingState int mAsyncRankingState;
public SearchResultsAdapter(SearchFragment fragment,
SearchFeatureProvider searchFeatureProvider) {
public SearchResultsAdapter(SearchFragment fragment) {
mFragment = fragment;
mContext = fragment.getContext().getApplicationContext();
mSearchResults = new ArrayList<>();
mResultsMap = new ArrayMap<>();
mSearchRankingScores = new ArrayList<>();
mStaticallyRankedSearchResults = new ArrayList<>();
mSearchFeatureProvider = searchFeatureProvider;
setHasStableIds(true);
}
@@ -149,298 +87,30 @@ 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.
* @param loaderClassName class name of the loader.
*/
@MainThread
public void addSearchResults(Set<? extends SearchResult> results, String loaderClassName) {
if (results == null) {
return;
}
mResultsMap.put(loaderClassName, results);
}
/**
* Displays recent searched queries.
*
* @return The number of saved queries to display
*/
public int displaySavedQuery(List<? extends SearchResult> data) {
public void displaySavedQuery(List<? extends SearchResult> data) {
clearResults();
mSearchResults.addAll(data);
notifyDataSetChanged();
return mSearchResults.size();
}
/**
* Notifies the adapter that all the unsorted results are loaded and now the ladapter can
* proceed with ranking the results.
*/
@MainThread
public void notifyResultsLoaded() {
mSearchResultsLoaded = true;
// static ranking is skipped only if asyc ranking is already succeeded.
if (mAsyncRankingState != SUCCEEDED) {
doStaticRanking();
}
if (canUpdateSearchResults()) {
updateSearchResults();
}
}
public void clearResults() {
mSearchResults.clear();
mStaticallyRankedSearchResults.clear();
mResultsMap.clear();
notifyDataSetChanged();
}
@VisibleForTesting
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();
final long timeoutMs = mSearchFeatureProvider.smartSearchRankingTimeoutMs(mContext);
handler.sendMessageDelayed(
handler.obtainMessage(MSG_RANKING_TIMED_OUT), timeoutMs);
mSearchFeatureProvider.querySearchResults(mContext, query, this);
} else {
mAsyncRankingState = DISABLED;
}
}
@AsyncRankingState int getAsyncRankingState() {
return mAsyncRankingState;
}
/**
* 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);
List<? extends SearchResult> accessibilityResults =
getSortedLoadedResults(ACCESSIBILITY_LOADER_KEY);
List<? extends SearchResult> inputDeviceResults =
getSortedLoadedResults(INPUT_DEVICE_LOADER_KEY);
int dbSize = databaseResults.size();
int appSize = installedAppResults.size();
int a11ySize = accessibilityResults.size();
int inputDeviceSize = inputDeviceResults.size();
int dbIndex = 0;
int appIndex = 0;
int a11yIndex = 0;
int inputDeviceIndex = 0;
int rank = SearchResult.TOP_RANK;
// TODO: We need a helper method to do k-way merge.
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++));
}
while ((a11yIndex < a11ySize) && (accessibilityResults.get(a11yIndex).rank == rank)) {
mStaticallyRankedSearchResults.add(accessibilityResults.get(a11yIndex++));
}
while (inputDeviceIndex < inputDeviceSize
&& inputDeviceResults.get(inputDeviceIndex).rank == rank) {
mStaticallyRankedSearchResults.add(inputDeviceResults.get(inputDeviceIndex++));
}
rank++;
}
while (dbIndex < dbSize) {
mStaticallyRankedSearchResults.add(databaseResults.get(dbIndex++));
}
while (appIndex < appSize) {
mStaticallyRankedSearchResults.add(installedAppResults.get(appIndex++));
}
while(a11yIndex < a11ySize) {
mStaticallyRankedSearchResults.add(accessibilityResults.get(a11yIndex++));
}
while (inputDeviceIndex < inputDeviceSize) {
mStaticallyRankedSearchResults.add(inputDeviceResults.get(inputDeviceIndex++));
}
}
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);
List<? extends SearchResult> accessibilityResults =
getSortedLoadedResults(ACCESSIBILITY_LOADER_KEY);
List<? extends SearchResult> inputDeviceResults =
getSortedLoadedResults(INPUT_DEVICE_LOADER_KEY);
int dbSize = databaseResults.size();
int appSize = installedAppResults.size();
int a11ySize = accessibilityResults.size();
int inputDeviceSize = inputDeviceResults.size();
final List<SearchResult> asyncRankingResults = new ArrayList<>(
dbSize + appSize + a11ySize + inputDeviceSize);
TreeSet<SearchResult> dbResultsSortedByScores = new TreeSet<>(
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;
}
}
});
dbResultsSortedByScores.addAll(databaseResults);
asyncRankingResults.addAll(dbResultsSortedByScores);
// Other results are not ranked by async ranking and appended at the end of the list.
asyncRankingResults.addAll(installedAppResults);
asyncRankingResults.addAll(accessibilityResults);
asyncRankingResults.addAll(inputDeviceResults);
return asyncRankingResults;
}
@VisibleForTesting
Set<? extends SearchResult> getUnsortedLoadedResults(String loaderKey) {
return mResultsMap.containsKey(loaderKey) ? mResultsMap.get(loaderKey) : new HashSet<>();
}
@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;
}
@VisibleForTesting
public void postSearchResults(List<SearchResult> newSearchResults, boolean detectMoves) {
public void postSearchResults(List<? extends SearchResult> newSearchResults) {
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
new SearchResultDiffCallback(mSearchResults, newSearchResults), detectMoves);
new SearchResultDiffCallback(mSearchResults, newSearchResults));
mSearchResults.clear();
mSearchResults.addAll(newSearchResults);
diffResult.dispatchUpdatesTo(this);
mFragment.onSearchResultsDisplayed(mSearchResults.size());
mSearchResultsUpdated = true;
}
}

View File

@@ -17,6 +17,7 @@
package com.android.settings.search;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -46,7 +47,7 @@ import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class AccessibilityServiceResultLoaderTest {
public class AccessibilityServiceResultFutureTaskTest {
private static final String QUERY = "test_query";
@@ -59,7 +60,7 @@ public class AccessibilityServiceResultLoaderTest {
@Mock
private SiteMapManager mSiteMapManager;
private AccessibilityServiceResultLoader mLoader;
private AccessibilityServiceResultLoader.AccessibilityServiceResultCallable mCallable;
@Before
public void setUp() {
@@ -68,19 +69,20 @@ public class AccessibilityServiceResultLoaderTest {
.thenReturn(mAccessibilityManager);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
mLoader = new AccessibilityServiceResultLoader(mContext, QUERY, mSiteMapManager);
mCallable = new AccessibilityServiceResultLoader.AccessibilityServiceResultCallable(
mContext, QUERY, mSiteMapManager);
}
@Test
public void query_noService_shouldNotReturnAnything() {
assertThat(mLoader.loadInBackground()).isEmpty();
public void query_noService_shouldNotReturnAnything() throws Exception {
assertThat(mCallable.call()).isEmpty();
}
@Test
public void query_hasServiceMatchingTitle_shouldReturnResult() {
public void query_hasServiceMatchingTitle_shouldReturnResult() throws Exception {
addFakeAccessibilityService();
List<? extends SearchResult> results = new ArrayList<>(mLoader.loadInBackground());
List<? extends SearchResult> results = mCallable.call();
assertThat(results).hasSize(1);
SearchResult result = results.get(0);
@@ -88,13 +90,14 @@ public class AccessibilityServiceResultLoaderTest {
}
@Test
public void query_serviceDoesNotMatchTitle_shouldReturnResult() {
public void query_serviceDoesNotMatchTitle_shouldReturnResult() throws Exception {
addFakeAccessibilityService();
mLoader = new AccessibilityServiceResultLoader(mContext,
mCallable = new AccessibilityServiceResultLoader.AccessibilityServiceResultCallable(
mContext,
QUERY + "no_match", mSiteMapManager);
assertThat(mLoader.loadInBackground()).isEmpty();
assertThat(mCallable.call()).isEmpty();
}
private void addFakeAccessibilityService() {

View File

@@ -17,9 +17,11 @@
package com.android.settings.search;
import static android.content.Context.INPUT_METHOD_SERVICE;
import static com.android.settings.search.InputDeviceResultLoader.PHYSICAL_KEYBOARD_FRAGMENT;
import static com.android.settings.search.InputDeviceResultLoader.VIRTUAL_KEYBOARD_FRAGMENT;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -58,7 +60,7 @@ import java.util.List;
shadows = {
ShadowInputDevice.class
})
public class InputDeviceResultLoaderTest {
public class InputDeviceResultFutureTaskTest {
private static final String QUERY = "test_query";
private static final List<String> PHYSICAL_KEYBOARD_BREADCRUMB;
@@ -84,7 +86,7 @@ public class InputDeviceResultLoaderTest {
@Mock
private PackageManager mPackageManager;
private InputDeviceResultLoader mLoader;
private InputDeviceResultLoader.InputDeviceResultCallable mCallable;
@Before
public void setUp() {
@@ -99,7 +101,8 @@ public class InputDeviceResultLoaderTest {
when(mContext.getString(anyInt()))
.thenAnswer(invocation -> RuntimeEnvironment.application.getString(
(Integer) invocation.getArguments()[0]));
mLoader = new InputDeviceResultLoader(mContext, QUERY, mSiteMapManager);
mCallable = new InputDeviceResultLoader.InputDeviceResultCallable(mContext, QUERY,
mSiteMapManager);
}
@After
@@ -108,18 +111,19 @@ public class InputDeviceResultLoaderTest {
}
@Test
public void query_noKeyboard_shouldNotReturnAnything() {
assertThat(mLoader.loadInBackground()).isEmpty();
public void query_noKeyboard_shouldNotReturnAnything() throws Exception {
assertThat(mCallable.call()).isEmpty();
}
@Test
public void query_hasPhysicalKeyboard_match() {
public void query_hasPhysicalKeyboard_match() throws Exception {
addPhysicalKeyboard(QUERY);
when(mSiteMapManager.buildBreadCrumb(mContext, PHYSICAL_KEYBOARD_FRAGMENT,
RuntimeEnvironment.application.getString(R.string.physical_keyboard_title)))
.thenReturn(PHYSICAL_KEYBOARD_BREADCRUMB);
final List<SearchResult> results = new ArrayList<>(mLoader.loadInBackground());
final List<? extends SearchResult> results = mCallable.call();
assertThat(results).hasSize(1);
assertThat(results.get(0).title).isEqualTo(QUERY);
@@ -128,13 +132,13 @@ public class InputDeviceResultLoaderTest {
}
@Test
public void query_hasVirtualKeyboard_match() {
public void query_hasVirtualKeyboard_match() throws Exception {
addVirtualKeyboard(QUERY);
when(mSiteMapManager.buildBreadCrumb(mContext, VIRTUAL_KEYBOARD_FRAGMENT,
RuntimeEnvironment.application.getString(R.string.add_virtual_keyboard)))
.thenReturn(VIRTUAL_KEYBOARD_BREADCRUMB);
final List<SearchResult> results = new ArrayList<>(mLoader.loadInBackground());
final List<? extends SearchResult> results = mCallable.call();
assertThat(results).hasSize(1);
assertThat(results.get(0).title).isEqualTo(QUERY);
assertThat(results.get(0).breadcrumbs)
@@ -142,11 +146,11 @@ public class InputDeviceResultLoaderTest {
}
@Test
public void query_hasPhysicalVirtualKeyboard_doNotMatch() {
public void query_hasPhysicalVirtualKeyboard_doNotMatch() throws Exception {
addPhysicalKeyboard("abc");
addVirtualKeyboard("def");
assertThat(mLoader.loadInBackground()).isEmpty();
assertThat(mCallable.call()).isEmpty();
verifyZeroInteractions(mSiteMapManager);
}

View File

@@ -19,7 +19,9 @@ package com.android.settings.search;
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyList;
@@ -53,8 +55,6 @@ import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
@@ -77,7 +77,7 @@ public class InstalledAppResultLoaderTest {
@Mock
private SiteMapManager mSiteMapManager;
private InstalledAppResultLoader mLoader;
private InstalledAppResultLoader.InstalledAppResultCallable mCallable;
@Before
public void setUp() {
@@ -109,49 +109,50 @@ public class InstalledAppResultLoaderTest {
}
@Test
public void query_noMatchingQuery_shouldReturnEmptyResult() {
public void query_noMatchingQuery_shouldReturnEmptyResult() throws Exception {
final String query = "abc";
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
assertThat(mLoader.loadInBackground()).isEmpty();
assertThat(mCallable.call()).isEmpty();
}
@Test
public void query_matchingQuery_shouldReturnNonSystemApps() {
public void query_matchingQuery_shouldReturnNonSystemApps() throws Exception {
final String query = "app";
mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = spy(new InstalledAppResultLoader.InstalledAppResultCallable(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(3);
assertThat(mCallable.call()).hasSize(3);
verify(mSiteMapManager)
.buildBreadCrumb(eq(mContext), anyString(), anyString());
}
@Test
public void query_matchingQuery_shouldReturnSystemAppUpdates() {
public void query_matchingQuery_shouldReturnSystemAppUpdates() throws Exception {
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_UPDATED_SYSTEM_APP,
0 /* targetSdkVersion */)));
final String query = "app";
mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = spy(new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager));
when(mLoader.getContext()).thenReturn(mContext);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
assertThat(mCallable.call()).hasSize(1);
verify(mSiteMapManager)
.buildBreadCrumb(eq(mContext), anyString(), anyString());
}
@Test
public void query_matchingQuery_shouldReturnSystemAppIfLaunchable() {
public void query_matchingQuery_shouldReturnSystemAppIfLaunchable() throws Exception {
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
@@ -164,14 +165,15 @@ public class InstalledAppResultLoaderTest {
final String query = "app";
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
assertThat(mCallable.call()).hasSize(1);
}
@Test
public void query_matchingQuery_shouldReturnSystemAppIfHomeApp() {
public void query_matchingQuery_shouldReturnSystemAppIfHomeApp() throws Exception {
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
@@ -180,28 +182,26 @@ public class InstalledAppResultLoaderTest {
any(Intent.class), anyInt(), anyInt()))
.thenReturn(null);
when(mPackageManagerWrapper.getHomeActivities(anyList())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
when(mPackageManagerWrapper.getHomeActivities(anyList())).thenAnswer(invocation -> {
final List<ResolveInfo> list = (List<ResolveInfo>) invocation.getArguments()[0];
final ResolveInfo info = new ResolveInfo();
info.activityInfo = new ActivityInfo();
info.activityInfo.packageName = "app1";
list.add(info);
return null;
}
});
final String query = "app";
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
assertThat(mCallable.call()).hasSize(1);
}
@Test
public void query_matchingQuery_shouldNotReturnSystemAppIfNotLaunchable() {
public void query_matchingQuery_shouldNotReturnSystemAppIfNotLaunchable() throws Exception {
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
@@ -212,21 +212,23 @@ public class InstalledAppResultLoaderTest {
final String query = "app";
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
assertThat(mLoader.loadInBackground()).isEmpty();
assertThat(mCallable.call()).isEmpty();
verify(mSiteMapManager, never())
.buildBreadCrumb(eq(mContext), anyString(), anyString());
}
@Test
public void query_matchingQuery_multipleResults() {
public void query_matchingQuery_multipleResults() throws Exception {
final String query = "app";
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
final Set<? extends SearchResult> results = mLoader.loadInBackground();
final List<? extends SearchResult> results = mCallable.call();
Set<CharSequence> expectedTitles = new HashSet<>(Arrays.asList("app4", "app", "appBuffer"));
Set<CharSequence> actualTitles = new HashSet<>();
@@ -237,161 +239,172 @@ public class InstalledAppResultLoaderTest {
}
@Test
public void query_normalWord_MatchPrefix() {
public void query_normalWord_MatchPrefix() throws Exception {
final String query = "ba";
final String packageName = "Bananas";
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
assertThat(mCallable.call()).hasSize(1);
}
@Test
public void query_CapitalCase_DoestMatchSecondWord() {
public void query_CapitalCase_DoestMatchSecondWord() throws Exception {
final String query = "Apples";
final String packageName = "BananasApples";
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(0);
assertThat(mCallable.call()).isEmpty();
}
@Test
public void query_TwoWords_MatchesFirstWord() {
public void query_TwoWords_MatchesFirstWord() throws Exception {
final String query = "Banana";
final String packageName = "Bananas Apples";
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
assertThat(mCallable.call()).hasSize(1);
}
@Test
public void query_TwoWords_MatchesSecondWord() {
public void query_TwoWords_MatchesSecondWord() throws Exception {
final String query = "Apple";
final String packageName = "Bananas Apples";
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
assertThat(mCallable.call()).hasSize(1);
}
@Test
public void query_ThreeWords_MatchesThirdWord() {
public void query_ThreeWords_MatchesThirdWord() throws Exception {
final String query = "Pear";
final String packageName = "Bananas Apples Pears";
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
assertThat(mCallable.call()).hasSize(1);
}
@Test
public void query_DoubleSpacedWords_MatchesSecondWord() {
public void query_DoubleSpacedWords_MatchesSecondWord() throws Exception {
final String query = "Apple";
final String packageName = "Bananas Apples";
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
assertThat(mCallable.call()).hasSize(1);
}
@Test
public void query_SpecialChar_MatchesSecondWord() {
public void query_SpecialChar_MatchesSecondWord() throws Exception {
final String query = "Apple";
final String packageName = "Bananas & Apples";
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
assertThat(mCallable.call()).hasSize(1);
}
@Test
public void query_TabSeparated_MatchesSecondWord() {
public void query_TabSeparated_MatchesSecondWord() throws Exception {
final String query = "Apple";
final String packageName = "Bananas\tApples";
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
assertThat(mCallable.call()).hasSize(1);
}
@Test
public void query_LeadingNumber_MatchesWord() {
public void query_LeadingNumber_MatchesWord() throws Exception {
final String query = "4";
final String packageName = "4Bananas";
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
assertThat(mCallable.call()).hasSize(1);
}
@Test
public void query_FirstWordPrefixOfQuery_NoMatch() {
public void query_FirstWordPrefixOfQuery_NoMatch() throws Exception {
final String query = "Bananass";
final String packageName = "Bananas Apples";
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(0);
assertThat(mCallable.call()).isEmpty();
}
@Test
public void query_QueryLongerThanAppName_NoMatch() {
public void query_QueryLongerThanAppName_NoMatch() throws Exception {
final String query = "BananasApples";
final String packageName = "Bananas";
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
assertThat(mLoader.loadInBackground().size()).isEqualTo(0);
assertThat(mCallable.call()).isEmpty();
}
@Test
public void query_appExistsInBothProfiles() {
public void query_appExistsInBothProfiles() throws Exception {
final String query = "carrot";
final String packageName = "carrot";
final int user1 = 0;
@@ -414,10 +427,11 @@ public class InstalledAppResultLoaderTest {
packageName, 0 /* flags */,
0 /* targetSdkVersion */)));
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
mPackageManagerWrapper, query,
mSiteMapManager);
Set<AppSearchResult> searchResults = (Set<AppSearchResult>) mLoader.loadInBackground();
List<AppSearchResult> searchResults = (List<AppSearchResult>) mCallable.call();
assertThat(searchResults).hasSize(2);
Set<Integer> uidResults = searchResults.stream().map(result -> result.info.uid).collect(

View File

@@ -1,39 +0,0 @@
/*
* 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;
import android.content.Context;
import java.util.HashSet;
import java.util.Set;
public class MockAccessibilityLoader extends AccessibilityServiceResultLoader {
public MockAccessibilityLoader(Context context) {
super(context, "test_query", null);
}
@Override
public Set<? extends SearchResult> loadInBackground() {
return new HashSet<>();
}
@Override
protected void onDiscardResult(Set<? extends SearchResult> result) {
}
}

View File

@@ -1,46 +0,0 @@
/*
* 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;
import android.content.Context;
import com.android.settings.search.InstalledAppResultLoader;
import com.android.settings.search.SearchResult;
import java.util.HashSet;
import java.util.Set;
/**
* 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 Set<? extends SearchResult> loadInBackground() {
return new HashSet<>();
}
@Override
protected void onDiscardResult(Set<? extends SearchResult> result) {
}
}

View File

@@ -1,46 +0,0 @@
/*
* 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;
import android.content.Context;
import com.android.settings.search.DatabaseResultLoader;
import com.android.settings.search.SearchResult;
import java.util.HashSet;
import java.util.Set;
/**
* 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 Set<? extends SearchResult> loadInBackground() {
return new HashSet<>();
}
@Override
protected void onDiscardResult(Set<? extends SearchResult> result) {
}
}

View File

@@ -1,38 +0,0 @@
/*
* 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;
import android.content.Context;
import java.util.HashSet;
import java.util.Set;
public class MockInputDeviceResultLoader extends InputDeviceResultLoader {
public MockInputDeviceResultLoader(Context context) {
super(context, "test_query", null);
}
@Override
public Set<? extends SearchResult> loadInBackground() {
return new HashSet<>();
}
@Override
protected void onDiscardResult(Set<? extends SearchResult> result) {
}
}

View File

@@ -0,0 +1,29 @@
package com.android.settings.search;
import android.content.Context;
import com.android.settings.search.SearchResult;
import com.android.settings.search.SearchResultLoader;
import java.util.ArrayList;
import java.util.List;
/**
* Mock loader to subvert the requirements of returning data while also driving the Loader
* lifecycle.
*/
public class MockSearchResultLoader extends SearchResultLoader {
public MockSearchResultLoader(Context context) {
super(context, "test");
}
@Override
public List<? extends SearchResult> loadInBackground() {
return new ArrayList<>();
}
@Override
protected void onDiscardResult(List<? extends SearchResult> result) {
}
}

View File

@@ -21,7 +21,6 @@ import static com.google.common.truth.Truth.assertThat;
import android.app.Activity;
import android.content.ComponentName;
import com.android.settings.TestConfig;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
@@ -33,6 +32,10 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class SearchFeatureProviderImplTest {
@@ -43,7 +46,7 @@ public class SearchFeatureProviderImplTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
mActivity = Robolectric.buildActivity(Activity.class).create().visible().get();
mProvider = new SearchFeatureProviderImpl();
mProvider = spy(new SearchFeatureProviderImpl());
}
@Test
@@ -57,18 +60,19 @@ public class SearchFeatureProviderImplTest {
@Test
public void getDatabaseSearchLoader_shouldCleanupQuery() {
final String query = " space ";
final DatabaseResultLoader loader = mProvider.getDatabaseSearchLoader(mActivity, query);
assertThat(loader.mQueryText).isEqualTo(query.trim());
mProvider.getStaticSearchResultTask(mActivity, query);
verify(mProvider).cleanQuery(eq(query));
}
@Test
public void getInstalledAppSearchLoader_shouldCleanupQuery() {
final String query = " space ";
final InstalledAppResultLoader loader =
mProvider.getInstalledAppSearchLoader(mActivity, query);
assertThat(loader.mQuery).isEqualTo(query.trim());
mProvider.getInstalledAppSearchTask(mActivity, query);
verify(mProvider).cleanQuery(eq(query));
}
@Test(expected = IllegalArgumentException.class)
@@ -87,4 +91,12 @@ public class SearchFeatureProviderImplTest {
final ComponentName cn = new ComponentName(mActivity.getPackageName(), "class");
mProvider.verifyLaunchSearchResultPageCaller(mActivity, cn);
}
@Test
public void cleanQuery_trimsWhitespace() {
final String query = " space ";
final String cleanQuery = "space";
assertThat(mProvider.cleanQuery(query)).isEqualTo(cleanQuery);
}
}

View File

@@ -18,6 +18,7 @@
package com.android.settings.search;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
@@ -54,9 +55,7 @@ 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;
@@ -65,7 +64,7 @@ import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;
import java.util.Set;
import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH,
@@ -79,22 +78,13 @@ public class SearchFragmentTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
@Mock
private DatabaseResultLoader mDatabaseResultLoader;
@Mock
private InstalledAppResultLoader mInstalledAppResultLoader;
@Mock
private AccessibilityServiceResultLoader mAccessibilityServiceResultLoader;
@Mock
private InputDeviceResultLoader mInputDeviceResultLoader;
private SearchResultLoader mSearchResultLoader;
@Mock
private SavedQueryLoader mSavedQueryLoader;
@Mock
private SavedQueryController mSavedQueryController;
@Mock
private SearchResultsAdapter mSearchResultsAdapter;
@Captor
private ArgumentCaptor<String> mQueryCaptor = ArgumentCaptor.forClass(String.class);
private FakeFeatureFactory mFeatureFactory;
@@ -113,17 +103,8 @@ public class SearchFragmentTest {
@Test
public void screenRotate_shouldPersistQuery() {
when(mFeatureFactory.searchFeatureProvider
.getDatabaseSearchLoader(any(Context.class), anyString()))
.thenReturn(mDatabaseResultLoader);
when(mFeatureFactory.searchFeatureProvider
.getInstalledAppSearchLoader(any(Context.class), anyString()))
.thenReturn(mInstalledAppResultLoader);
when(mFeatureFactory.searchFeatureProvider
.getAccessibilityServiceResultLoader(any(Context.class), anyString()))
.thenReturn(mAccessibilityServiceResultLoader);
when(mFeatureFactory.searchFeatureProvider
.getInputDeviceResultLoader(any(Context.class), anyString()))
.thenReturn(mInputDeviceResultLoader);
.getSearchResultLoader(any(Context.class), anyString()))
.thenReturn(new MockSearchResultLoader(RuntimeEnvironment.application));
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader);
@@ -168,25 +149,16 @@ public class SearchFragmentTest {
activityController.setup(bundle);
verify(mFeatureFactory.searchFeatureProvider, never())
.getDatabaseSearchLoader(any(Context.class), anyString());
.getStaticSearchResultTask(any(Context.class), anyString());
verify(mFeatureFactory.searchFeatureProvider, never())
.getInstalledAppSearchLoader(any(Context.class), anyString());
.getInstalledAppSearchTask(any(Context.class), anyString());
}
@Test
public void queryTextChange_shouldTriggerLoaderAndInitializeSearch() {
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
.getAccessibilityServiceResultLoader(any(Context.class), anyString()))
.thenReturn(mAccessibilityServiceResultLoader);
when(mFeatureFactory.searchFeatureProvider
.getInputDeviceResultLoader(any(Context.class), anyString()))
.thenReturn(mInputDeviceResultLoader);
.getSearchResultLoader(any(Context.class), anyString()))
.thenReturn(mSearchResultLoader);
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader);
@@ -199,7 +171,6 @@ public class SearchFragmentTest {
when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
.thenReturn(true);
ReflectionHelpers.setField(fragment, "mSearchAdapter", mSearchResultsAdapter);
fragment.onQueryTextChange(testQuery);
activityController.get().onBackPressed();
@@ -209,11 +180,7 @@ public class SearchFragmentTest {
any(Context.class),
eq(MetricsProto.MetricsEvent.ACTION_LEAVE_SEARCH_RESULT_WITHOUT_QUERY));
verify(mFeatureFactory.searchFeatureProvider)
.getDatabaseSearchLoader(any(Context.class), anyString());
verify(mFeatureFactory.searchFeatureProvider)
.getInstalledAppSearchLoader(any(Context.class), anyString());
verify(mSearchResultsAdapter).initializeSearch(mQueryCaptor.capture());
assertThat(mQueryCaptor.getValue()).isEqualTo(testQuery);
.getSearchResultLoader(any(Context.class), anyString());
}
@Test
@@ -238,18 +205,6 @@ public class SearchFragmentTest {
@Test
public void queryTextChangeToEmpty_shouldLoadSavedQueryAndNotInitializeSearch() {
when(mFeatureFactory.searchFeatureProvider
.getDatabaseSearchLoader(any(Context.class), anyString()))
.thenReturn(mDatabaseResultLoader);
when(mFeatureFactory.searchFeatureProvider
.getInstalledAppSearchLoader(any(Context.class), anyString()))
.thenReturn(mInstalledAppResultLoader);
when(mFeatureFactory.searchFeatureProvider
.getAccessibilityServiceResultLoader(any(Context.class), anyString()))
.thenReturn(mAccessibilityServiceResultLoader);
when(mFeatureFactory.searchFeatureProvider
.getInputDeviceResultLoader(any(Context.class), anyString()))
.thenReturn(mInputDeviceResultLoader);
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader);
ActivityController<SearchActivity> activityController =
@@ -266,27 +221,14 @@ public class SearchFragmentTest {
fragment.onQueryTextChange("");
verify(mFeatureFactory.searchFeatureProvider, never())
.getDatabaseSearchLoader(any(Context.class), anyString());
.getStaticSearchResultTask(any(Context.class), anyString());
verify(mFeatureFactory.searchFeatureProvider, never())
.getInstalledAppSearchLoader(any(Context.class), anyString());
.getInstalledAppSearchTask(any(Context.class), anyString());
verify(mSavedQueryController).loadSavedQueries();
verify(mSearchResultsAdapter, never()).initializeSearch(anyString());
}
@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
.getAccessibilityServiceResultLoader(any(Context.class), anyString()))
.thenReturn(mAccessibilityServiceResultLoader);
when(mFeatureFactory.searchFeatureProvider
.getInputDeviceResultLoader(any(Context.class), anyString()))
.thenReturn(mInputDeviceResultLoader);
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader);
@@ -303,41 +245,11 @@ public class SearchFragmentTest {
any(IndexingCallback.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));
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader);
ActivityController<SearchActivity> activityController =
Robolectric.buildActivity(SearchActivity.class);
activityController.setup();
SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
.findFragmentById(R.id.main_content));
when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
.thenReturn(true);
fragment.onQueryTextChange("non-empty");
Robolectric.flushForegroundThreadScheduler();
verify(fragment, times(2)).onLoadFinished(any(Loader.class), any(Set.class));
}
@Test
public void whenNoQuery_HideFeedbackIsCalled() {
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));
.getSearchResultLoader(any(Context.class), anyString()))
.thenReturn(new MockSearchResultLoader(RuntimeEnvironment.application));
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader);
@@ -359,17 +271,8 @@ public class SearchFragmentTest {
@Test
public void onLoadFinished_ShowsFeedback() {
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));
when(mFeatureFactory.searchFeatureProvider
.getAccessibilityServiceResultLoader(any(Context.class), anyString()))
.thenReturn(new MockAccessibilityLoader(RuntimeEnvironment.application));
when(mFeatureFactory.searchFeatureProvider
.getInputDeviceResultLoader(any(Context.class), anyString()))
.thenReturn(new MockInputDeviceResultLoader(RuntimeEnvironment.application));
.getSearchResultLoader(any(Context.class), anyString()))
.thenReturn(new MockSearchResultLoader(RuntimeEnvironment.application));
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
.thenReturn(mSavedQueryLoader);
ActivityController<SearchActivity> activityController =
@@ -413,9 +316,7 @@ public class SearchFragmentTest {
fragment.onIndexingFinished();
verify(loaderManager).initLoader(eq(SearchFragment.SearchLoaderId.DATABASE),
eq(null), any(LoaderManager.LoaderCallbacks.class));
verify(loaderManager).initLoader(eq(SearchFragment.SearchLoaderId.INSTALLED_APPS),
verify(loaderManager).initLoader(eq(SearchFragment.SearchLoaderId.SEARCH_RESULT),
eq(null), any(LoaderManager.LoaderCallbacks.class));
}
@@ -480,10 +381,7 @@ public class SearchFragmentTest {
eq("test_setting"),
argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_COUNT)),
argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_RANK)),
argThat(pairMatches(MetricsProto.MetricsEvent
.FIELD_SETTINGS_SEARCH_RESULT_ASYNC_RANKING_STATE)),
argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_QUERY_LENGTH)));
verify(mFeatureFactory.searchFeatureProvider).searchResultClicked(nullable(Context.class),
nullable(String.class), eq(searchResult));
}

View File

@@ -0,0 +1,271 @@
package com.android.settings.search;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
import com.android.settings.TestConfig;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class SearchResultAggregatorTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
private FakeFeatureFactory mFeatureFactory;
private SearchResultAggregator mAggregator;
@Mock
private DatabaseResultLoader mStaticTask;
@Mock
private InstalledAppResultLoader mAppTask;
@Mock
private InputDeviceResultLoader mInputTask;
@Mock
private AccessibilityServiceResultLoader mMAccessibilityTask;
@Mock
private ExecutorService mService;
private String[] DB_TITLES = {"static_one", "static_two"};
private String[] INPUT_TITLES = {"input_one", "input_two"};
private String[] ACCESS_TITLES = {"access_one", "access_two"};
private String[] APP_TITLES = {"app_one", "app_two"};
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mAggregator = spy(SearchResultAggregator.getInstance());
FakeFeatureFactory.setupForTest(mContext);
mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
// Return mock loaders from feature provider
when(mFeatureFactory.searchFeatureProvider.getStaticSearchResultTask(any(Context.class),
anyString())).thenReturn(mStaticTask);
when(mFeatureFactory.searchFeatureProvider.getInstalledAppSearchTask(any(Context.class),
anyString())).thenReturn(mAppTask);
when(mFeatureFactory.searchFeatureProvider.getInputDeviceResultTask(any(Context.class),
anyString())).thenReturn(mInputTask);
when(mFeatureFactory.searchFeatureProvider.getAccessibilityServiceResultTask(
any(Context.class),
anyString())).thenReturn(mMAccessibilityTask);
when(mFeatureFactory.searchFeatureProvider.getExecutorService()).thenReturn(mService);
// Return fake data from the loaders
List<? extends SearchResult> dbResults = getDummyDbResults();
doReturn(dbResults).when(mStaticTask).get(anyLong(), any(TimeUnit.class));
List<? extends SearchResult> appResults = getDummyAppResults();
doReturn(appResults).when(mAppTask).get(anyLong(), any(TimeUnit.class));
List<? extends SearchResult> inputResults = getDummyInputDeviceResults();
doReturn(inputResults).when(mInputTask).get(anyLong(), any(TimeUnit.class));
List<? extends SearchResult> accessResults = getDummyAccessibilityResults();
doReturn(accessResults).when(mMAccessibilityTask).get(anyLong(), any(TimeUnit.class));
}
@Test
public void testStaticResults_mergedProperly() {
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(mContext))
.thenReturn(false);
List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
assertThat(results).hasSize(8);
assertThat(results.get(0).title).isEqualTo(DB_TITLES[0]);
assertThat(results.get(1).title).isEqualTo(DB_TITLES[1]);
assertThat(results.get(2).title).isEqualTo(APP_TITLES[0]);
assertThat(results.get(3).title).isEqualTo(ACCESS_TITLES[0]);
assertThat(results.get(4).title).isEqualTo(INPUT_TITLES[0]);
assertThat(results.get(5).title).isEqualTo(APP_TITLES[1]);
assertThat(results.get(6).title).isEqualTo(ACCESS_TITLES[1]);
assertThat(results.get(7).title).isEqualTo(INPUT_TITLES[1]);
}
@Test
public void testStaticRanking_staticThrowsException_dbResultsAreMissing() throws Exception {
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(mContext))
.thenReturn(false);
when(mStaticTask.get(anyLong(), any(TimeUnit.class))).thenThrow(new InterruptedException());
List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
assertThat(results).hasSize(6);
assertThat(results.get(0).title).isEqualTo(APP_TITLES[0]);
assertThat(results.get(1).title).isEqualTo(ACCESS_TITLES[0]);
assertThat(results.get(2).title).isEqualTo(INPUT_TITLES[0]);
assertThat(results.get(3).title).isEqualTo(APP_TITLES[1]);
assertThat(results.get(4).title).isEqualTo(ACCESS_TITLES[1]);
assertThat(results.get(5).title).isEqualTo(INPUT_TITLES[1]);
}
@Test
public void testStaticRanking_appsThrowException_appResultsAreMissing() throws Exception {
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(mContext))
.thenReturn(false);
when(mAppTask.get(anyLong(), any(TimeUnit.class))).thenThrow(new InterruptedException());
List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
assertThat(results).hasSize(6);
assertThat(results.get(0).title).isEqualTo(DB_TITLES[0]);
assertThat(results.get(1).title).isEqualTo(DB_TITLES[1]);
assertThat(results.get(2).title).isEqualTo(ACCESS_TITLES[0]);
assertThat(results.get(3).title).isEqualTo(INPUT_TITLES[0]);
assertThat(results.get(4).title).isEqualTo(ACCESS_TITLES[1]);
assertThat(results.get(5).title).isEqualTo(INPUT_TITLES[1]);
}
@Test
public void testStaticRanking_inputThrowException_inputResultsAreMissing() throws Exception {
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(mContext))
.thenReturn(false);
when(mInputTask.get(anyLong(), any(TimeUnit.class))).thenThrow(new InterruptedException());
List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
assertThat(results).hasSize(6);
assertThat(results.get(0).title).isEqualTo(DB_TITLES[0]);
assertThat(results.get(1).title).isEqualTo(DB_TITLES[1]);
assertThat(results.get(2).title).isEqualTo(APP_TITLES[0]);
assertThat(results.get(3).title).isEqualTo(ACCESS_TITLES[0]);
assertThat(results.get(4).title).isEqualTo(APP_TITLES[1]);
assertThat(results.get(5).title).isEqualTo(ACCESS_TITLES[1]);
}
@Test
public void testStaticRanking_accessThrowException_accessResultsAreMissing() throws Exception {
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(mContext))
.thenReturn(false);
when(mMAccessibilityTask.get(anyLong(), any(TimeUnit.class))).thenThrow(
new InterruptedException());
List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
assertThat(results).hasSize(6);
assertThat(results.get(0).title).isEqualTo(DB_TITLES[0]);
assertThat(results.get(1).title).isEqualTo(DB_TITLES[1]);
assertThat(results.get(2).title).isEqualTo(APP_TITLES[0]);
assertThat(results.get(3).title).isEqualTo(INPUT_TITLES[0]);
assertThat(results.get(4).title).isEqualTo(APP_TITLES[1]);
assertThat(results.get(5).title).isEqualTo(INPUT_TITLES[1]);
}
@Test
public void testDynamicRanking_sortsWithDynamicRanking() {
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(
true);
List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
assertThat(results).hasSize(8);
assertThat(results.get(0).title).isEqualTo(DB_TITLES[0]);
assertThat(results.get(1).title).isEqualTo(DB_TITLES[1]);
assertThat(results.get(2).title).isEqualTo(APP_TITLES[0]);
assertThat(results.get(3).title).isEqualTo(ACCESS_TITLES[0]);
assertThat(results.get(4).title).isEqualTo(INPUT_TITLES[0]);
assertThat(results.get(5).title).isEqualTo(APP_TITLES[1]);
assertThat(results.get(6).title).isEqualTo(ACCESS_TITLES[1]);
assertThat(results.get(7).title).isEqualTo(INPUT_TITLES[1]);
}
private List<? extends SearchResult> getDummyDbResults() {
List<SearchResult> results = new ArrayList<>();
ResultPayload payload = new ResultPayload(new Intent());
SearchResult.Builder builder = new SearchResult.Builder();
builder.setPayload(payload)
.setTitle(DB_TITLES[0])
.setRank(1)
.setStableId(Objects.hash(DB_TITLES[0], "db"));
results.add(builder.build());
builder.setTitle(DB_TITLES[1])
.setRank(2)
.setStableId(Objects.hash(DB_TITLES[1], "db"));
results.add(builder.build());
return results;
}
private List<? extends SearchResult> getDummyAppResults() {
List<AppSearchResult> results = new ArrayList<>();
ResultPayload payload = new ResultPayload(new Intent());
AppSearchResult.Builder builder = new AppSearchResult.Builder();
builder.setPayload(payload)
.setTitle(APP_TITLES[0])
.setRank(1)
.setStableId(Objects.hash(APP_TITLES[0], "app"));
results.add(builder.build());
builder.setTitle(APP_TITLES[1])
.setRank(2)
.setStableId(Objects.hash(APP_TITLES[1], "app"));
results.add(builder.build());
return results;
}
public List<? extends SearchResult> getDummyInputDeviceResults() {
List<SearchResult> results = new ArrayList<>();
ResultPayload payload = new ResultPayload(new Intent());
AppSearchResult.Builder builder = new AppSearchResult.Builder();
builder.setPayload(payload)
.setTitle(INPUT_TITLES[0])
.setRank(1)
.setStableId(Objects.hash(INPUT_TITLES[0], "app"));
results.add(builder.build());
builder.setTitle(INPUT_TITLES[1])
.setRank(2)
.setStableId(Objects.hash(INPUT_TITLES[1], "app"));
results.add(builder.build());
return results;
}
public List<? extends SearchResult> getDummyAccessibilityResults() {
List<SearchResult> results = new ArrayList<>();
ResultPayload payload = new ResultPayload(new Intent());
AppSearchResult.Builder builder = new AppSearchResult.Builder();
builder.setPayload(payload)
.setTitle(ACCESS_TITLES[0])
.setRank(1)
.setStableId(Objects.hash(ACCESS_TITLES[0], "app"));
results.add(builder.build());
builder.setTitle(ACCESS_TITLES[1])
.setRank(2)
.setStableId(Objects.hash(ACCESS_TITLES[1], "app"));
results.add(builder.build());
return results;
}
}

View File

@@ -18,44 +18,32 @@
package com.android.settings.search;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
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;
import com.android.settings.R;
import com.android.settings.TestConfig;
import com.android.settings.search.SearchResult.Builder;
import com.android.settings.search.ranking.SearchResultsRankerCallback;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
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 java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -67,25 +55,18 @@ 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;
private String[] TITLES = {"alpha", "bravo", "charlie", "appAlpha", "appBravo", "appCharlie"};
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = Robolectric.buildActivity(Activity.class).get();
mLoaderClassName = DatabaseResultLoader.class.getName();
when(mFragment.getContext()).thenReturn(mMockContext);
when(mMockContext.getApplicationContext()).thenReturn(mContext);
when(mSearchFeatureProvider.smartSearchRankingTimeoutMs(any(Context.class)))
.thenReturn(300L);
mAdapter = new SearchResultsAdapter(mFragment, mSearchFeatureProvider);
mAdapter = new SearchResultsAdapter(mFragment);
}
@Test
@@ -94,17 +75,6 @@ public class SearchResultsAdapterTest {
assertThat(updatedResults).isEmpty();
}
@Test
public void testSingleSourceMerge_exactCopyReturned() {
Set<SearchResult> intentResults = getIntentSampleResults();
mAdapter.initializeSearch("");
mAdapter.addSearchResults(intentResults, mLoaderClassName);
mAdapter.notifyResultsLoaded();
List<SearchResult> updatedResults = mAdapter.getSearchResults();
assertThat(updatedResults).containsAllIn(intentResults);
}
@Test
public void testCreateViewHolder_returnsIntentResult() {
ViewGroup group = new FrameLayout(mContext);
@@ -123,387 +93,13 @@ public class SearchResultsAdapterTest {
}
@Test
public void testEndToEndSearch_properResultsMerged_correctOrder() {
mAdapter.initializeSearch("");
mAdapter.addSearchResults(new HashSet<>(getDummyAppResults()),
InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(new HashSet<>(getDummyDbResults()),
DatabaseResultLoader.class.getName());
mAdapter.notifyResultsLoaded();
public void testPostSearchResults_addsDataAndDisplays() {
List<SearchResult> results = getDummyDbResults();
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);
}
mAdapter.postSearchResults(results);
@Test
public void testEndToEndSearch_addResults_resultsAddedInOrder() {
List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
mAdapter.initializeSearch("");
// Add two individual items
mAdapter.addSearchResults(new HashSet<>(appResults.subList(0, 1)),
InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(new HashSet<>(dbResults.subList(0, 1)),
DatabaseResultLoader.class.getName());
mAdapter.notifyResultsLoaded();
// Add super-set of items
mAdapter.initializeSearch("");
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, times(2)).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
assertThat(mSearchResultsCountCaptor.getAllValues().toArray())
.isEqualTo(new Integer[] {2, 6});
}
@Test
public void testEndToEndSearch_removeResults_resultsAdded() {
List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
// Add list of items
mAdapter.initializeSearch("");
mAdapter.addSearchResults(new HashSet<>(appResults),
InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(new HashSet<>(dbResults),
DatabaseResultLoader.class.getName());
mAdapter.notifyResultsLoaded();
// Add subset of items
mAdapter.initializeSearch("");
mAdapter.addSearchResults(new HashSet<>(appResults.subList(0, 1)),
InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(new HashSet<>(dbResults.subList(0, 1)),
DatabaseResultLoader.class.getName());
mAdapter.notifyResultsLoaded();
List<SearchResult> results = mAdapter.getSearchResults();
assertThat(results.get(0).title).isEqualTo(TITLES[0]);
assertThat(results.get(1).title).isEqualTo(TITLES[3]);
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);
assertThat(mAdapter.getAsyncRankingState()).isEqualTo(SearchResultsAdapter.SUCCEEDED);
}
@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);
assertThat(mAdapter.getAsyncRankingState()).isEqualTo(SearchResultsAdapter.SUCCEEDED);
}
@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);
assertThat(mAdapter.getAsyncRankingState()).isEqualTo(SearchResultsAdapter.FAILED);
}
@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);
assertThat(mAdapter.getAsyncRankingState()).isEqualTo(SearchResultsAdapter.FAILED);
}
@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);
assertThat(mAdapter.getAsyncRankingState()).isEqualTo(SearchResultsAdapter.TIMED_OUT);
}
@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);
assertThat(mAdapter.getAsyncRankingState()).isEqualTo(SearchResultsAdapter.TIMED_OUT);
}
@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));
assertThat(mAdapter.getAsyncRankingState()).isEqualTo(SearchResultsAdapter.DISABLED);
}
@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));
assertThat(mAdapter.getAsyncRankingState())
.isEqualTo(SearchResultsAdapter.PENDING_RESULTS);
}
@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
}
}
assertThat(mAdapter.getSearchResults()).containsExactlyElementsIn(results);
verify(mFragment).onSearchResultsDisplayed(anyInt());
}
private List<SearchResult> getDummyDbResults() {
@@ -511,78 +107,21 @@ public class SearchResultsAdapterTest {
ResultPayload payload = new ResultPayload(new Intent());
SearchResult.Builder builder = new SearchResult.Builder();
builder.setPayload(payload)
.setTitle(TITLES[0])
.setTitle("one")
.setRank(1)
.setStableId(Objects.hash(TITLES[0], "db"));
.setStableId(Objects.hash("one", "db"));
results.add(builder.build());
builder.setTitle(TITLES[1])
builder.setTitle("two")
.setRank(3)
.setStableId(Objects.hash(TITLES[1], "db"));
.setStableId(Objects.hash("two", "db"));
results.add(builder.build());
builder.setTitle(TITLES[2])
builder.setTitle("three")
.setRank(6)
.setStableId(Objects.hash(TITLES[2], "db"));
.setStableId(Objects.hash("three", "db"));
results.add(builder.build());
return results;
}
private List<SearchResult> getDummyAppResults() {
List<SearchResult> results = new ArrayList<>();
ResultPayload payload = new ResultPayload(new Intent());
AppSearchResult.Builder builder = new AppSearchResult.Builder();
builder.setPayload(payload)
.setTitle(TITLES[3])
.setRank(1)
.setStableId(Objects.hash(TITLES[3], "app"));
results.add(builder.build());
builder.setTitle(TITLES[4])
.setRank(2)
.setStableId(Objects.hash(TITLES[4], "app"));
results.add(builder.build());
builder.setTitle(TITLES[5])
.setRank(4)
.setStableId(Objects.hash(TITLES[5], "app"));
results.add(builder.build());
return results;
}
private Set<SearchResult> getIntentSampleResults() {
Set<SearchResult> sampleResults = new HashSet<>();
ArrayList<String> breadcrumbs = new ArrayList<>();
final Drawable icon = mContext.getDrawable(R.drawable.ic_search_24dp);
final ResultPayload payload = new ResultPayload(null);
final SearchResult.Builder builder = new Builder();
builder.setTitle("title")
.setSummary("summary")
.setRank(1)
.addBreadcrumbs(breadcrumbs)
.setIcon(icon)
.setPayload(payload)
.setStableId(Objects.hash("title", "summary", 1));
sampleResults.add(builder.build());
builder.setRank(2)
.setStableId(Objects.hash("title", "summary", 2));
sampleResults.add(builder.build());
builder.setRank(3)
.setStableId(Objects.hash("title", "summary", 3));
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<>(Long.toString(results.get(2).stableId), 0.9f)); // charlie
scores.add(new Pair<>(Long.toString(results.get(0).stableId), 0.8f)); // alpha
scores.add(new Pair<>(Long.toString(results.get(1).stableId), 0.2f)); // bravo
return scores;
}
}

View File

@@ -21,9 +21,11 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.util.Pair;
import com.android.settings.TestConfig;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.search.DatabaseResultLoader.StaticSearchResultCallable;
import com.android.settings.search.indexing.IndexData;
import com.android.settings.testutils.DatabaseTestUtils;
import com.android.settings.testutils.FakeFeatureFactory;
@@ -39,37 +41,54 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class DatabaseResultLoaderTest {
public class StaticSearchResultFutureTaskTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mMockContext;
@Mock
private SiteMapManager mSiteMapManager;
@Mock
private ExecutorService mService;
private Context mContext;
SQLiteDatabase mDb;
FakeFeatureFactory mFeatureFactory;
private final String[] STATIC_TITLES = {"static one", "static two", "static three"};
private final int[] STABLE_IDS =
{"id_one".hashCode(), "id_two".hashCode(), "id_three".hashCode()};
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
FakeFeatureFactory.setupForTest(mMockContext);
FakeFeatureFactory factory =
(FakeFeatureFactory) FakeFeatureFactory.getFactory(mMockContext);
when(factory.searchFeatureProvider.getSiteMapManager())
mFeatureFactory = FakeFeatureFactory.setupForTest(mMockContext);
when(mFeatureFactory.searchFeatureProvider.getExecutorService()).thenReturn(mService);
when(mFeatureFactory.searchFeatureProvider.getSiteMapManager())
.thenReturn(mSiteMapManager);
mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
setUpDb();
@@ -81,159 +100,252 @@ public class DatabaseResultLoaderTest {
}
@Test
public void testMatchTitle() {
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "title", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(2);
public void testMatchTitle() throws Exception {
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "title",
mSiteMapManager);
assertThat(loader.call()).hasSize(2);
verify(mSiteMapManager, times(2)).buildBreadCrumb(eq(mContext), anyString(), anyString());
}
@Test
public void testMatchSummary() {
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "summary",
public void testMatchSummary() throws Exception {
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "summary",
mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(2);
assertThat(loader.call()).hasSize(2);
}
@Test
public void testMatchKeywords() {
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "keywords",
public void testMatchKeywords() throws Exception {
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "keywords",
mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(2);
assertThat(loader.call()).hasSize(2);
}
@Test
public void testMatchEntries() {
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "entries",
public void testMatchEntries() throws Exception {
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "entries",
mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(2);
assertThat(loader.call()).hasSize(2);
}
@Test
public void testSpecialCaseWord_matchesNonPrefix() {
public void testSpecialCaseWord_matchesNonPrefix() throws Exception {
insertSpecialCase("Data usage");
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "usage", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "usage",
mSiteMapManager);
assertThat(loader.call()).hasSize(1);
}
@Test
public void testSpecialCaseDash_matchesWordNoDash() {
public void testSpecialCaseDash_matchesWordNoDash() throws Exception {
insertSpecialCase("wi-fi calling");
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "wifi", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
}
@Test
public void testSpecialCaseDash_matchesWordWithDash() {
insertSpecialCase("priorités seulment");
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "priorités",
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "wifi",
mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
assertThat(loader.call()).hasSize(1);
}
@Test
public void testSpecialCaseDash_matchesWordWithoutDash() {
public void testSpecialCaseDash_matchesWordWithDash() throws Exception {
insertSpecialCase("priorités seulment");
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "priorites",
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "priorités",
mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
assertThat(loader.call()).hasSize(1);
}
@Test
public void testSpecialCaseDash_matchesEntireQueryWithoutDash() {
public void testSpecialCaseDash_matchesWordWithoutDash() throws Exception {
insertSpecialCase("priorités seulment");
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "priorites",
mSiteMapManager);
assertThat(loader.call()).hasSize(1);
}
@Test
public void testSpecialCaseDash_matchesEntireQueryWithoutDash() throws Exception {
insertSpecialCase("wi-fi calling");
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "wifi calling",
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "wifi calling",
mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
assertThat(loader.call()).hasSize(1);
}
@Test
public void testSpecialCasePrefix_matchesPrefixOfEntry() {
public void testSpecialCasePrefix_matchesPrefixOfEntry() throws Exception {
insertSpecialCase("Photos");
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "pho", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "pho",
mSiteMapManager);
assertThat(loader.call()).hasSize(1);
}
@Test
public void testSpecialCasePrefix_DoesNotMatchNonPrefixSubstring() {
public void testSpecialCasePrefix_DoesNotMatchNonPrefixSubstring() throws Exception {
insertSpecialCase("Photos");
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "hot", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(0);
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "hot",
mSiteMapManager);
assertThat(loader.call()).hasSize(0);
}
@Test
public void testSpecialCaseMultiWordPrefix_matchesPrefixOfEntry() {
public void testSpecialCaseMultiWordPrefix_matchesPrefixOfEntry() throws Exception {
insertSpecialCase("Apps Notifications");
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "Apps", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "Apps",
mSiteMapManager);
assertThat(loader.call()).hasSize(1);
}
@Test
public void testSpecialCaseMultiWordPrefix_matchesSecondWordPrefixOfEntry() {
public void testSpecialCaseMultiWordPrefix_matchesSecondWordPrefixOfEntry() throws Exception {
insertSpecialCase("Apps Notifications");
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "Not", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "Not",
mSiteMapManager);
assertThat(loader.call()).hasSize(1);
}
@Test
public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfFirstEntry() {
public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfFirstEntry()
throws Exception {
insertSpecialCase("Apps Notifications");
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "pp", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(0);
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "pp",
mSiteMapManager);
assertThat(loader.call()).hasSize(0);
}
@Test
public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfSecondEntry() {
public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfSecondEntry()
throws Exception {
insertSpecialCase("Apps Notifications");
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "tion", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(0);
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "tion",
mSiteMapManager);
assertThat(loader.call()).hasSize(0);
}
@Test
public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfEntry() {
public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfEntry() throws
Exception {
insertSpecialCase("Apps & Notifications");
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "App", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "App",
mSiteMapManager);
assertThat(loader.call()).hasSize(1);
}
@Test
public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfSecondEntry() {
public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfSecondEntry()
throws Exception {
insertSpecialCase("Apps & Notifications");
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "No", mSiteMapManager);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "No",
mSiteMapManager);
assertThat(loader.call()).hasSize(1);
}
@Test
public void testResultMatchedByMultipleQueries_duplicatesRemoved() {
public void testResultMatchedByMultipleQueries_duplicatesRemoved() throws Exception {
String key = "durr";
insertSameValueAllFieldsCase(key);
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, key, null);
assertThat(loader.loadInBackground().size()).isEqualTo(1);
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, key, null);
assertThat(loader.call()).hasSize(1);
}
@Test
public void testSpecialCaseTwoWords_multipleResults() {
public void testSpecialCaseTwoWords_multipleResults() throws Exception {
final String caseOne = "Apple pear";
final String caseTwo = "Banana apple";
insertSpecialCase(caseOne);
insertSpecialCase(caseTwo);
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "App", null);
Set<? extends SearchResult> results = loader.loadInBackground();
Set<CharSequence> expectedTitles = new HashSet<>(Arrays.asList(caseOne, caseTwo));
Set<CharSequence> actualTitles = new HashSet<>();
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "App", null);
List<? extends SearchResult> results = loader.call();
Set<String> actualTitles = new HashSet<>();
for (SearchResult result : results) {
actualTitles.add(result.title);
actualTitles.add(result.title.toString());
}
assertThat(actualTitles).isEqualTo(expectedTitles);
assertThat(actualTitles).containsAllOf(caseOne, caseTwo);
}
@Test
public void testGetRankingScoreByStableId_sortedDynamically() throws Exception {
FutureTask<List<Pair<String, Float>>> task = mock(FutureTask.class);
when(task.get(anyLong(), any(TimeUnit.class))).thenReturn(getDummyRankingScores());
when(mFeatureFactory.searchFeatureProvider.getRankerTask(any(Context.class),
anyString())).thenReturn(task);
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(
true);
insertSpecialCase(STATIC_TITLES[0], STABLE_IDS[0]);
insertSpecialCase(STATIC_TITLES[1], STABLE_IDS[1]);
insertSpecialCase(STATIC_TITLES[2], STABLE_IDS[2]);
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "Static",
null);
List<? extends SearchResult> results = loader.call();
assertThat(results.get(0).title).isEqualTo(STATIC_TITLES[2]);
assertThat(results.get(1).title).isEqualTo(STATIC_TITLES[0]);
assertThat(results.get(2).title).isEqualTo(STATIC_TITLES[1]);
}
@Test
public void testGetRankingScoreByStableId_scoresTimeout_sortedStatically() throws Exception {
Callable<List<Pair<String, Float>>> callable = mock(Callable.class);
when(callable.call()).thenThrow(new TimeoutException());
FutureTask<List<Pair<String, Float>>> task = new FutureTask<>(callable);
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(
true);
when(mFeatureFactory.searchFeatureProvider.getRankerTask(any(Context.class),
anyString())).thenReturn(task);
insertSpecialCase("title", STABLE_IDS[0]);
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "title", null);
List<? extends SearchResult> results = loader.call();
assertThat(results.get(0).title).isEqualTo("title");
assertThat(results.get(1).title).isEqualTo("alpha_title");
assertThat(results.get(2).title).isEqualTo("bravo_title");
}
private void insertSpecialCase(String specialCase) {
insertSpecialCase(specialCase, specialCase.hashCode());
}
private void insertSpecialCase(String specialCase, int docId) {
String normalized = IndexData.normalizeHyphen(specialCase);
normalized = IndexData.normalizeString(normalized);
final ResultPayload payload = new ResultPayload(new Intent());
ContentValues values = new ContentValues();
values.put(IndexDatabaseHelper.IndexColumns.DOCID, normalized.hashCode());
values.put(IndexDatabaseHelper.IndexColumns.DOCID, docId);
values.put(IndexDatabaseHelper.IndexColumns.LOCALE, "en-us");
values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1);
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, specialCase);
@@ -373,4 +485,33 @@ public class DatabaseResultLoaderTest {
mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
}
private List<? extends SearchResult> getDummyDbResults() {
List<SearchResult> results = new ArrayList<>();
ResultPayload payload = new ResultPayload(new Intent());
SearchResult.Builder builder = new SearchResult.Builder();
builder.setPayload(payload)
.setTitle(STATIC_TITLES[0])
.setStableId(STABLE_IDS[0]);
results.add(builder.build());
builder.setTitle(STATIC_TITLES[1])
.setStableId(STABLE_IDS[1]);
results.add(builder.build());
builder.setTitle(STATIC_TITLES[2])
.setStableId(STABLE_IDS[2]);
results.add(builder.build());
return results;
}
private List<Pair<String, Float>> getDummyRankingScores() {
List<? extends SearchResult> results = getDummyDbResults();
List<Pair<String, Float>> scores = new ArrayList<>();
scores.add(new Pair<>(Long.toString(results.get(2).stableId), 0.9f)); // static_three
scores.add(new Pair<>(Long.toString(results.get(0).stableId), 0.8f)); // static_one
scores.add(new Pair<>(Long.toString(results.get(1).stableId), 0.2f)); // static_two
return scores;
}
}