Merge "Move search querying into a single API"
This commit is contained in:
committed by
Android (Google) Code Review
commit
e717a5fdd1
@@ -30,105 +30,112 @@ import android.os.UserHandle;
|
|||||||
import android.support.annotation.VisibleForTesting;
|
import android.support.annotation.VisibleForTesting;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.util.IconDrawableFactory;
|
import android.util.IconDrawableFactory;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.accessibility.AccessibilityManager;
|
import android.view.accessibility.AccessibilityManager;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.accessibility.AccessibilitySettings;
|
import com.android.settings.accessibility.AccessibilitySettings;
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
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.List;
|
||||||
import java.util.Objects;
|
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 int NAME_NO_MATCH = -1;
|
|
||||||
|
|
||||||
private final Context mContext;
|
|
||||||
|
|
||||||
private List<String> mBreadcrumb;
|
|
||||||
private SiteMapManager mSiteMapManager;
|
|
||||||
@VisibleForTesting
|
|
||||||
final String mQuery;
|
|
||||||
private final AccessibilityManager mAccessibilityManager;
|
|
||||||
private final PackageManager mPackageManager;
|
|
||||||
private final int mUserId;
|
|
||||||
|
|
||||||
|
private static final String TAG = "A11yResultFutureTask";
|
||||||
|
|
||||||
public AccessibilityServiceResultLoader(Context context, String query,
|
public AccessibilityServiceResultLoader(Context context, String query,
|
||||||
SiteMapManager mapManager) {
|
SiteMapManager manager) {
|
||||||
super(context);
|
super(new AccessibilityServiceResultCallable(context, query, manager));
|
||||||
mContext = context;
|
|
||||||
mUserId = UserHandle.myUserId();
|
|
||||||
mSiteMapManager = mapManager;
|
|
||||||
mPackageManager = context.getPackageManager();
|
|
||||||
mAccessibilityManager =
|
|
||||||
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
|
||||||
mQuery = query;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
static class AccessibilityServiceResultCallable implements
|
||||||
public Set<? extends SearchResult> loadInBackground() {
|
Callable<List<? extends SearchResult>> {
|
||||||
final Set<SearchResult> results = new HashSet<>();
|
|
||||||
final Context context = getContext();
|
|
||||||
final List<AccessibilityServiceInfo> services = mAccessibilityManager
|
|
||||||
.getInstalledAccessibilityServiceList();
|
|
||||||
final IconDrawableFactory iconFactory = IconDrawableFactory.newInstance(mContext);
|
|
||||||
final String screenTitle = context.getString(R.string.accessibility_settings);
|
|
||||||
for (AccessibilityServiceInfo service : services) {
|
|
||||||
if (service == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final ResolveInfo resolveInfo = service.getResolveInfo();
|
|
||||||
if (service.getResolveInfo() == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
|
|
||||||
final CharSequence title = resolveInfo.loadLabel(mPackageManager);
|
|
||||||
final int wordDiff = getWordDifference(title.toString(), mQuery);
|
|
||||||
if (wordDiff == NAME_NO_MATCH) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final Drawable icon;
|
|
||||||
if (resolveInfo.getIconResource() == 0) {
|
|
||||||
icon = ContextCompat.getDrawable(context, R.mipmap.ic_accessibility_generic);
|
|
||||||
} else {
|
|
||||||
icon = iconFactory.getBadgedIcon(
|
|
||||||
resolveInfo.serviceInfo,
|
|
||||||
resolveInfo.serviceInfo.applicationInfo,
|
|
||||||
mUserId);
|
|
||||||
}
|
|
||||||
final String componentName = new ComponentName(serviceInfo.packageName,
|
|
||||||
serviceInfo.name).flattenToString();
|
|
||||||
final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(context,
|
|
||||||
AccessibilitySettings.class.getName(), componentName, screenTitle);
|
|
||||||
|
|
||||||
results.add(new SearchResult.Builder()
|
private static final int NAME_NO_MATCH = -1;
|
||||||
.setTitle(title)
|
|
||||||
.addBreadcrumbs(getBreadCrumb())
|
private final Context mContext;
|
||||||
.setPayload(new ResultPayload(intent))
|
private List<String> mBreadcrumb;
|
||||||
.setRank(wordDiff)
|
private SiteMapManager mSiteMapManager;
|
||||||
.setIcon(icon)
|
@VisibleForTesting
|
||||||
.setStableId(Objects.hash(screenTitle, componentName))
|
final String mQuery;
|
||||||
.build());
|
private final AccessibilityManager mAccessibilityManager;
|
||||||
|
private final PackageManager mPackageManager;
|
||||||
|
private final int mUserId;
|
||||||
|
|
||||||
|
public AccessibilityServiceResultCallable(Context context, String query,
|
||||||
|
SiteMapManager mapManager) {
|
||||||
|
mUserId = UserHandle.myUserId();
|
||||||
|
mContext = context;
|
||||||
|
mSiteMapManager = mapManager;
|
||||||
|
mPackageManager = context.getPackageManager();
|
||||||
|
mAccessibilityManager =
|
||||||
|
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||||
|
mQuery = query;
|
||||||
}
|
}
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> getBreadCrumb() {
|
@Override
|
||||||
if (mBreadcrumb == null || mBreadcrumb.isEmpty()) {
|
public List<? extends SearchResult> call() throws Exception {
|
||||||
final Context context = getContext();
|
long startTime = System.currentTimeMillis();
|
||||||
mBreadcrumb = mSiteMapManager.buildBreadCrumb(
|
final List<SearchResult> results = new ArrayList<>();
|
||||||
context, AccessibilitySettings.class.getName(),
|
final List<AccessibilityServiceInfo> services = mAccessibilityManager
|
||||||
context.getString(R.string.accessibility_settings));
|
.getInstalledAccessibilityServiceList();
|
||||||
|
final IconDrawableFactory iconFactory = IconDrawableFactory.newInstance(mContext);
|
||||||
|
final String screenTitle = mContext.getString(R.string.accessibility_settings);
|
||||||
|
for (AccessibilityServiceInfo service : services) {
|
||||||
|
if (service == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final ResolveInfo resolveInfo = service.getResolveInfo();
|
||||||
|
if (service.getResolveInfo() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
|
||||||
|
final CharSequence title = resolveInfo.loadLabel(mPackageManager);
|
||||||
|
final int wordDiff = getWordDifference(title.toString(), mQuery);
|
||||||
|
if (wordDiff == NAME_NO_MATCH) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final Drawable icon;
|
||||||
|
if (resolveInfo.getIconResource() == 0) {
|
||||||
|
icon = ContextCompat.getDrawable(mContext, R.mipmap.ic_accessibility_generic);
|
||||||
|
} else {
|
||||||
|
icon = iconFactory.getBadgedIcon(
|
||||||
|
resolveInfo.serviceInfo,
|
||||||
|
resolveInfo.serviceInfo.applicationInfo,
|
||||||
|
mUserId);
|
||||||
|
}
|
||||||
|
final String componentName = new ComponentName(serviceInfo.packageName,
|
||||||
|
serviceInfo.name).flattenToString();
|
||||||
|
final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(mContext,
|
||||||
|
AccessibilitySettings.class.getName(), componentName, screenTitle);
|
||||||
|
|
||||||
|
results.add(new SearchResult.Builder()
|
||||||
|
.setTitle(title)
|
||||||
|
.addBreadcrumbs(getBreadCrumb())
|
||||||
|
.setPayload(new ResultPayload(intent))
|
||||||
|
.setRank(wordDiff)
|
||||||
|
.setIcon(icon)
|
||||||
|
.setStableId(Objects.hash(screenTitle, componentName))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
Collections.sort(results);
|
||||||
|
Log.i(TAG, "A11y search loading took:" + (System.currentTimeMillis() - startTime));
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
return mBreadcrumb;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDiscardResult(Set<? extends SearchResult> result) {
|
|
||||||
|
|
||||||
|
private List<String> getBreadCrumb() {
|
||||||
|
if (mBreadcrumb == null || mBreadcrumb.isEmpty()) {
|
||||||
|
mBreadcrumb = mSiteMapManager.buildBreadCrumb(
|
||||||
|
mContext, AccessibilitySettings.class.getName(),
|
||||||
|
mContext.getString(R.string.accessibility_settings));
|
||||||
|
}
|
||||||
|
return mBreadcrumb;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -36,16 +36,6 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static com.android.settings.search.DatabaseResultLoader.BASE_RANKS;
|
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;
|
import static com.android.settings.search.SearchResult.TOP_RANK;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,6 +52,25 @@ public class CursorToSearchResultConverter {
|
|||||||
|
|
||||||
private static final String TAG = "CursorConverter";
|
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 Context mContext;
|
||||||
|
|
||||||
private final int LONG_TITLE_LENGTH = 20;
|
private final int LONG_TITLE_LENGTH = 20;
|
||||||
|
@@ -17,11 +17,13 @@
|
|||||||
|
|
||||||
package com.android.settings.search;
|
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;
|
.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.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.CLASS_NAME;
|
||||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_ENTRIES;
|
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_ENTRIES;
|
||||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS;
|
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;
|
.DATA_SUMMARY_ON_NORMALIZED;
|
||||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_TITLE;
|
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.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.ENABLED;
|
||||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.ICON;
|
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.ICON;
|
||||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.INTENT_ACTION;
|
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.INTENT_ACTION;
|
||||||
|
@@ -24,35 +24,31 @@ import android.content.Context;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.support.annotation.VisibleForTesting;
|
import android.support.annotation.VisibleForTesting;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
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.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
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>> {
|
public class DatabaseResultLoader extends FutureTask<List<? extends SearchResult>> {
|
||||||
private static final String LOG = "DatabaseResultLoader";
|
|
||||||
|
|
||||||
/* These indices are used to match the columns of the this loader's SELECT statement.
|
private static final String TAG = "DatabaseResultLoader";
|
||||||
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;
|
|
||||||
|
|
||||||
public static final String[] SELECT_COLUMNS = {
|
public static final String[] SELECT_COLUMNS = {
|
||||||
IndexColumns.DOCID,
|
IndexColumns.DOCID,
|
||||||
@@ -82,194 +78,267 @@ public class DatabaseResultLoader extends AsyncLoader<Set<? extends SearchResult
|
|||||||
IndexColumns.DATA_SUMMARY_OFF_NORMALIZED,
|
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.
|
* 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 first word in the title, the best rank it can be
|
||||||
* If the query matches the prefix of the other words in the title, the best rank it can be is 3
|
* 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 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
|
* 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 static final int[] BASE_RANKS = {1, 3, 7, 9};
|
||||||
|
|
||||||
@VisibleForTesting
|
public DatabaseResultLoader(Context context, String query, SiteMapManager manager) {
|
||||||
final String mQueryText;
|
super(new StaticSearchResultCallable(context, query, manager));
|
||||||
private final Context mContext;
|
|
||||||
private final CursorToSearchResultConverter mConverter;
|
|
||||||
private final SiteMapManager mSiteMapManager;
|
|
||||||
|
|
||||||
public DatabaseResultLoader(Context context, String queryText, SiteMapManager mapManager) {
|
|
||||||
super(context);
|
|
||||||
mSiteMapManager = mapManager;
|
|
||||||
mContext = context;
|
|
||||||
mQueryText = queryText;
|
|
||||||
mConverter = new CursorToSearchResultConverter(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
static class StaticSearchResultCallable implements
|
||||||
protected void onDiscardResult(Set<? extends SearchResult> result) {
|
Callable<List<? extends SearchResult>> {
|
||||||
// TODO Search
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public final String[] MATCH_COLUMNS_TERTIARY = {
|
||||||
public Set<? extends SearchResult> loadInBackground() {
|
IndexColumns.DATA_KEYWORDS,
|
||||||
if (mQueryText == null || mQueryText.isEmpty()) {
|
IndexColumns.DATA_ENTRIES
|
||||||
return null;
|
};
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
final String mQueryText;
|
||||||
|
private final Context mContext;
|
||||||
|
private final CursorToSearchResultConverter mConverter;
|
||||||
|
private final SiteMapManager mSiteMapManager;
|
||||||
|
private final SearchFeatureProvider mFeatureProvider;
|
||||||
|
|
||||||
|
public StaticSearchResultCallable(Context context, String queryText,
|
||||||
|
SiteMapManager mapManager) {
|
||||||
|
mContext = context;
|
||||||
|
mSiteMapManager = mapManager;
|
||||||
|
mQueryText = queryText;
|
||||||
|
mConverter = new CursorToSearchResultConverter(context);
|
||||||
|
mFeatureProvider = FeatureFactory.getFactory(context).getSearchFeatureProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
final Set<SearchResult> results = new HashSet<>();
|
@Override
|
||||||
|
public List<? extends SearchResult> call() {
|
||||||
|
if (mQueryText == null || mQueryText.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
results.addAll(firstWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[0]));
|
// TODO (b/68656233) Consolidate timing metrics
|
||||||
results.addAll(secondaryWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[1]));
|
long startTime = System.currentTimeMillis();
|
||||||
results.addAll(anyWordQuery(MATCH_COLUMNS_SECONDARY, BASE_RANKS[2]));
|
// Start a Future to get search result scores.
|
||||||
results.addAll(anyWordQuery(MATCH_COLUMNS_TERTIARY, BASE_RANKS[3]));
|
FutureTask<List<Pair<String, Float>>> rankerTask = mFeatureProvider.getRankerTask(
|
||||||
return results;
|
mContext, mQueryText);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
if (rankerTask != null) {
|
||||||
protected boolean onCancelLoad() {
|
ExecutorService executorService = mFeatureProvider.getExecutorService();
|
||||||
// TODO
|
executorService.execute(rankerTask);
|
||||||
return super.onCancelLoad();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
final Set<SearchResult> resultSet = new HashSet<>();
|
||||||
* 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
|
|
||||||
* @return A set of the matching results.
|
|
||||||
*/
|
|
||||||
private Set<SearchResult> firstWordQuery(String[] matchColumns, int baseRank) {
|
|
||||||
final String whereClause = buildSingleWordWhereClause(matchColumns);
|
|
||||||
final String query = mQueryText + "%";
|
|
||||||
final String[] selection = buildSingleWordSelection(query, matchColumns.length);
|
|
||||||
|
|
||||||
return query(whereClause, selection, baseRank);
|
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.
|
||||||
* Creates and executes the query which matches prefixes of the non-first words of the
|
if (rankerTask != null) {
|
||||||
* given columns.
|
try {
|
||||||
*
|
final long timeoutMs = mFeatureProvider.smartSearchRankingTimeoutMs(mContext);
|
||||||
* @param matchColumns The columns to match on
|
List<Pair<String, Float>> searchRankScores = rankerTask.get(timeoutMs,
|
||||||
* @param baseRank The highest rank achievable by these results
|
TimeUnit.MILLISECONDS);
|
||||||
* @return A set of the matching results.
|
return getDynamicRankedResults(resultSet, searchRankScores);
|
||||||
*/
|
} catch (TimeoutException | InterruptedException | ExecutionException e) {
|
||||||
private Set<SearchResult> secondaryWordQuery(String[] matchColumns, int baseRank) {
|
Log.d(TAG, "Error waiting for result scores: " + e);
|
||||||
final String whereClause = buildSingleWordWhereClause(matchColumns);
|
}
|
||||||
final String query = "% " + mQueryText + "%";
|
}
|
||||||
final String[] selection = buildSingleWordSelection(query, matchColumns.length);
|
|
||||||
|
|
||||||
return query(whereClause, selection, baseRank);
|
List<SearchResult> resultList = new ArrayList<>(resultSet);
|
||||||
}
|
Collections.sort(resultList);
|
||||||
|
Log.i(TAG, "Static search loading took:" + (System.currentTimeMillis() - startTime));
|
||||||
/**
|
return resultList;
|
||||||
* 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
|
|
||||||
* @return A set of the matching results.
|
|
||||||
*/
|
|
||||||
private Set<SearchResult> anyWordQuery(String[] matchColumns, int baseRank) {
|
|
||||||
final String whereClause = buildTwoWordWhereClause(matchColumns);
|
|
||||||
final String[] selection = buildAnyWordSelection(matchColumns.length * 2);
|
|
||||||
|
|
||||||
return query(whereClause, selection, baseRank);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic method used by all of the query methods above to execute a query.
|
|
||||||
*
|
|
||||||
* @param whereClause Where clause for the SQL query which uses bindings.
|
|
||||||
* @param selection List of the transformed query to match each bind in the whereClause
|
|
||||||
* @param baseRank The highest rank achievable by these results.
|
|
||||||
* @return A set of the matching results.
|
|
||||||
*/
|
|
||||||
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,
|
|
||||||
selection, null, null, null)) {
|
|
||||||
return mConverter.convertCursor(mSiteMapManager, resultCursor, baseRank);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// TODO (b/33577327) Retrieve all search results with a single query.
|
||||||
* Builds the SQLite WHERE clause that matches all matchColumns for a single query.
|
|
||||||
*
|
/**
|
||||||
* @param matchColumns List of columns that will be used for matching.
|
* Creates and executes the query which matches prefixes of the first word of the given
|
||||||
* @return The constructed WHERE clause.
|
* columns.
|
||||||
*/
|
*
|
||||||
private static String buildSingleWordWhereClause(String[] matchColumns) {
|
* @param matchColumns The columns to match on
|
||||||
StringBuilder sb = new StringBuilder(" (");
|
* @param baseRank The highest rank achievable by these results
|
||||||
final int count = matchColumns.length;
|
* @return A set of the matching results.
|
||||||
for (int n = 0; n < count; n++) {
|
*/
|
||||||
sb.append(matchColumns[n]);
|
private Set<SearchResult> firstWordQuery(String[] matchColumns, int baseRank) {
|
||||||
sb.append(" like ? ");
|
final String whereClause = buildSingleWordWhereClause(matchColumns);
|
||||||
if (n < count - 1) {
|
final String query = mQueryText + "%";
|
||||||
sb.append(" OR ");
|
final String[] selection = buildSingleWordSelection(query, matchColumns.length);
|
||||||
|
|
||||||
|
return query(whereClause, selection, baseRank);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and executes the query which matches prefixes of the non-first words of the
|
||||||
|
* given columns.
|
||||||
|
*
|
||||||
|
* @param matchColumns The columns to match on
|
||||||
|
* @param baseRank The highest rank achievable by these results
|
||||||
|
* @return A set of the matching results.
|
||||||
|
*/
|
||||||
|
private Set<SearchResult> secondaryWordQuery(String[] matchColumns, int baseRank) {
|
||||||
|
final String whereClause = buildSingleWordWhereClause(matchColumns);
|
||||||
|
final String query = "% " + mQueryText + "%";
|
||||||
|
final String[] selection = buildSingleWordSelection(query, matchColumns.length);
|
||||||
|
|
||||||
|
return query(whereClause, selection, baseRank);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @return A set of the matching results.
|
||||||
|
*/
|
||||||
|
private Set<SearchResult> anyWordQuery(String[] matchColumns, int baseRank) {
|
||||||
|
final String whereClause = buildTwoWordWhereClause(matchColumns);
|
||||||
|
final String[] selection = buildAnyWordSelection(matchColumns.length * 2);
|
||||||
|
|
||||||
|
return query(whereClause, selection, baseRank);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic method used by all of the query methods above to execute a query.
|
||||||
|
*
|
||||||
|
* @param whereClause Where clause for the SQL query which uses bindings.
|
||||||
|
* @param selection List of the transformed query to match each bind in the whereClause
|
||||||
|
* @param baseRank The highest rank achievable by these results.
|
||||||
|
* @return A set of the matching results.
|
||||||
|
*/
|
||||||
|
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,
|
||||||
|
selection, null, null, null)) {
|
||||||
|
return mConverter.convertCursor(mSiteMapManager, resultCursor, baseRank);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sb.append(") AND enabled = 1");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the SQLite WHERE clause that matches all matchColumns to two different queries.
|
* Builds the SQLite WHERE clause that matches all matchColumns for a single query.
|
||||||
*
|
*
|
||||||
* @param matchColumns List of columns that will be used for matching.
|
* @param matchColumns List of columns that will be used for matching.
|
||||||
* @return The constructed WHERE clause.
|
* @return The constructed WHERE clause.
|
||||||
*/
|
*/
|
||||||
private static String buildTwoWordWhereClause(String[] matchColumns) {
|
private static String buildSingleWordWhereClause(String[] matchColumns) {
|
||||||
StringBuilder sb = new StringBuilder(" (");
|
StringBuilder sb = new StringBuilder(" (");
|
||||||
final int count = matchColumns.length;
|
final int count = matchColumns.length;
|
||||||
for (int n = 0; n < count; n++) {
|
for (int n = 0; n < count; n++) {
|
||||||
sb.append(matchColumns[n]);
|
sb.append(matchColumns[n]);
|
||||||
sb.append(" like ? OR ");
|
sb.append(" like ? ");
|
||||||
sb.append(matchColumns[n]);
|
if (n < count - 1) {
|
||||||
sb.append(" like ?");
|
sb.append(" OR ");
|
||||||
if (n < count - 1) {
|
}
|
||||||
sb.append(" OR ");
|
|
||||||
}
|
}
|
||||||
|
sb.append(") AND enabled = 1");
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
sb.append(") AND enabled = 1");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fills out the selection array to match the query as the prefix of a single word.
|
* Builds the SQLite WHERE clause that matches all matchColumns to two different queries.
|
||||||
*
|
*
|
||||||
* @param size is the number of columns to be matched.
|
* @param matchColumns List of columns that will be used for matching.
|
||||||
*/
|
* @return The constructed WHERE clause.
|
||||||
private String[] buildSingleWordSelection(String query, int size) {
|
*/
|
||||||
String[] selection = new String[size];
|
private static String buildTwoWordWhereClause(String[] matchColumns) {
|
||||||
|
StringBuilder sb = new StringBuilder(" (");
|
||||||
for (int i = 0; i < size; i++) {
|
final int count = matchColumns.length;
|
||||||
selection[i] = query;
|
for (int n = 0; n < count; n++) {
|
||||||
|
sb.append(matchColumns[n]);
|
||||||
|
sb.append(" like ? OR ");
|
||||||
|
sb.append(matchColumns[n]);
|
||||||
|
sb.append(" like ?");
|
||||||
|
if (n < count - 1) {
|
||||||
|
sb.append(" OR ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append(") AND enabled = 1");
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
return selection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fills out the selection array to match the query as the prefix of a word.
|
* Fills out the selection array to match the query as the prefix of a single word.
|
||||||
*
|
*
|
||||||
* @param size is twice the number of columns to be matched. The first match is for the prefix
|
* @param size is the number of columns to be matched.
|
||||||
* of the first word in the column. The second match is for any subsequent word
|
*/
|
||||||
* prefix match.
|
private String[] buildSingleWordSelection(String query, int size) {
|
||||||
*/
|
String[] selection = new String[size];
|
||||||
private String[] buildAnyWordSelection(int size) {
|
|
||||||
String[] selection = new String[size];
|
|
||||||
final String query = mQueryText + "%";
|
|
||||||
final String subStringQuery = "% " + mQueryText + "%";
|
|
||||||
|
|
||||||
for (int i = 0; i < (size - 1); i += 2) {
|
for (int i = 0; i < size; i++) {
|
||||||
selection[i] = query;
|
selection[i] = query;
|
||||||
selection[i + 1] = subStringQuery;
|
}
|
||||||
|
return selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* of the first word in the column. The second match is for any subsequent word
|
||||||
|
* prefix match.
|
||||||
|
*/
|
||||||
|
private String[] buildAnyWordSelection(int size) {
|
||||||
|
String[] selection = new String[size];
|
||||||
|
final String query = mQueryText + "%";
|
||||||
|
final String subStringQuery = "% " + mQueryText + "%";
|
||||||
|
|
||||||
|
for (int i = 0; i < (size - 1); i += 2) {
|
||||||
|
selection[i] = query;
|
||||||
|
selection[i + 1] = subStringQuery;
|
||||||
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
return selection;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -26,6 +26,7 @@ import android.content.pm.ServiceInfo;
|
|||||||
import android.hardware.input.InputManager;
|
import android.hardware.input.InputManager;
|
||||||
import android.hardware.input.KeyboardLayout;
|
import android.hardware.input.KeyboardLayout;
|
||||||
import android.support.annotation.VisibleForTesting;
|
import android.support.annotation.VisibleForTesting;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.inputmethod.InputMethodInfo;
|
import android.view.inputmethod.InputMethodInfo;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
@@ -35,20 +36,24 @@ import com.android.settings.R;
|
|||||||
import com.android.settings.dashboard.SiteMapManager;
|
import com.android.settings.dashboard.SiteMapManager;
|
||||||
import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
|
import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
|
||||||
import com.android.settings.inputmethod.PhysicalKeyboardFragment;
|
import com.android.settings.inputmethod.PhysicalKeyboardFragment;
|
||||||
import com.android.settings.utils.AsyncLoader;
|
|
||||||
import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtil;
|
import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtil;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
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)
|
* 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
|
@VisibleForTesting
|
||||||
static final String PHYSICAL_KEYBOARD_FRAGMENT = PhysicalKeyboardFragment.class.getName();
|
static final String PHYSICAL_KEYBOARD_FRAGMENT = PhysicalKeyboardFragment.class.getName();
|
||||||
@@ -56,145 +61,151 @@ public class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchRes
|
|||||||
static final String VIRTUAL_KEYBOARD_FRAGMENT =
|
static final String VIRTUAL_KEYBOARD_FRAGMENT =
|
||||||
AvailableVirtualKeyboardFragment.class.getName();
|
AvailableVirtualKeyboardFragment.class.getName();
|
||||||
|
|
||||||
private final SiteMapManager mSiteMapManager;
|
public InputDeviceResultLoader(Context context, String query, SiteMapManager manager) {
|
||||||
private final InputManager mInputManager;
|
super(new InputDeviceResultCallable(context, query, manager));
|
||||||
private final InputMethodManager mImm;
|
|
||||||
private final PackageManager mPackageManager;
|
|
||||||
@VisibleForTesting
|
|
||||||
final String mQuery;
|
|
||||||
|
|
||||||
private List<String> mPhysicalKeyboardBreadcrumb;
|
|
||||||
private List<String> mVirtualKeyboardBreadcrumb;
|
|
||||||
|
|
||||||
public InputDeviceResultLoader(Context context, String query, SiteMapManager mapManager) {
|
|
||||||
super(context);
|
|
||||||
mQuery = query;
|
|
||||||
mSiteMapManager = mapManager;
|
|
||||||
mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
|
|
||||||
mImm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE);
|
|
||||||
mPackageManager = context.getPackageManager();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
static class InputDeviceResultCallable implements
|
||||||
protected void onDiscardResult(Set<? extends SearchResult> result) {
|
Callable<List<? extends SearchResult>> {
|
||||||
}
|
private static final int NAME_NO_MATCH = -1;
|
||||||
|
|
||||||
@Override
|
private final Context mContext;
|
||||||
public Set<? extends SearchResult> loadInBackground() {
|
private final SiteMapManager mSiteMapManager;
|
||||||
final Set<SearchResult> results = new HashSet<>();
|
private final InputManager mInputManager;
|
||||||
results.addAll(buildPhysicalKeyboardSearchResults());
|
private final InputMethodManager mImm;
|
||||||
results.addAll(buildVirtualKeyboardSearchResults());
|
private final PackageManager mPackageManager;
|
||||||
return results;
|
@VisibleForTesting
|
||||||
}
|
final String mQuery;
|
||||||
|
|
||||||
private Set<SearchResult> buildPhysicalKeyboardSearchResults() {
|
private List<String> mPhysicalKeyboardBreadcrumb;
|
||||||
final Set<SearchResult> results = new HashSet<>();
|
private List<String> mVirtualKeyboardBreadcrumb;
|
||||||
final Context context = getContext();
|
|
||||||
final String screenTitle = context.getString(R.string.physical_keyboard_title);
|
|
||||||
|
|
||||||
for (final InputDevice device : getPhysicalFullKeyboards()) {
|
public InputDeviceResultCallable(Context context, String query, SiteMapManager mapManager) {
|
||||||
final String deviceName = device.getName();
|
mContext = context;
|
||||||
final int wordDiff = InstalledAppResultLoader.getWordDifference(deviceName, mQuery);
|
mQuery = query;
|
||||||
if (wordDiff == NAME_NO_MATCH) {
|
mSiteMapManager = mapManager;
|
||||||
continue;
|
mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
|
||||||
|
mImm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE);
|
||||||
|
mPackageManager = context.getPackageManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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 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);
|
||||||
|
if (wordDiff == NAME_NO_MATCH) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final String keyboardLayoutDescriptor = mInputManager
|
||||||
|
.getCurrentKeyboardLayoutForInputDevice(device.getIdentifier());
|
||||||
|
final KeyboardLayout keyboardLayout = (keyboardLayoutDescriptor != null)
|
||||||
|
? mInputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null;
|
||||||
|
final String summary = (keyboardLayout != null)
|
||||||
|
? keyboardLayout.toString()
|
||||||
|
: mContext.getString(R.string.keyboard_layout_default_label);
|
||||||
|
|
||||||
|
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, deviceName))
|
||||||
|
.setSummary(summary)
|
||||||
|
.setRank(wordDiff)
|
||||||
|
.addBreadcrumbs(getPhysicalKeyboardBreadCrumb())
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
final String keyboardLayoutDescriptor = mInputManager
|
return results;
|
||||||
.getCurrentKeyboardLayoutForInputDevice(device.getIdentifier());
|
|
||||||
final KeyboardLayout keyboardLayout = (keyboardLayoutDescriptor != null)
|
|
||||||
? mInputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null;
|
|
||||||
final String summary = (keyboardLayout != null)
|
|
||||||
? keyboardLayout.toString()
|
|
||||||
: context.getString(R.string.keyboard_layout_default_label);
|
|
||||||
final String key = deviceName;
|
|
||||||
|
|
||||||
final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(context,
|
|
||||||
PHYSICAL_KEYBOARD_FRAGMENT, key, screenTitle);
|
|
||||||
results.add(new SearchResult.Builder()
|
|
||||||
.setTitle(deviceName)
|
|
||||||
.setPayload(new ResultPayload(intent))
|
|
||||||
.setStableId(Objects.hash(PHYSICAL_KEYBOARD_FRAGMENT, key))
|
|
||||||
.setSummary(summary)
|
|
||||||
.setRank(wordDiff)
|
|
||||||
.addBreadcrumbs(getPhysicalKeyboardBreadCrumb())
|
|
||||||
.build());
|
|
||||||
}
|
}
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<SearchResult> buildVirtualKeyboardSearchResults() {
|
private Set<SearchResult> buildVirtualKeyboardSearchResults() {
|
||||||
final Set<SearchResult> results = new HashSet<>();
|
final Set<SearchResult> results = new HashSet<>();
|
||||||
final Context context = getContext();
|
final String screenTitle = mContext.getString(R.string.add_virtual_keyboard);
|
||||||
final String screenTitle = context.getString(R.string.add_virtual_keyboard);
|
final List<InputMethodInfo> inputMethods = mImm.getInputMethodList();
|
||||||
final List<InputMethodInfo> inputMethods = mImm.getInputMethodList();
|
for (InputMethodInfo info : inputMethods) {
|
||||||
for (InputMethodInfo info : inputMethods) {
|
final String title = info.loadLabel(mPackageManager).toString();
|
||||||
final String title = info.loadLabel(mPackageManager).toString();
|
final String summary = InputMethodAndSubtypeUtil
|
||||||
final String summary = InputMethodAndSubtypeUtil
|
.getSubtypeLocaleNameListAsSentence(getAllSubtypesOf(info), mContext, info);
|
||||||
.getSubtypeLocaleNameListAsSentence(getAllSubtypesOf(info), context, info);
|
int wordDiff = InstalledAppResultLoader.getWordDifference(title, mQuery);
|
||||||
int wordDiff = InstalledAppResultLoader.getWordDifference(title, mQuery);
|
if (wordDiff == NAME_NO_MATCH) {
|
||||||
if (wordDiff == NAME_NO_MATCH) {
|
wordDiff = InstalledAppResultLoader.getWordDifference(summary, mQuery);
|
||||||
wordDiff = InstalledAppResultLoader.getWordDifference(summary, mQuery);
|
}
|
||||||
|
if (wordDiff == NAME_NO_MATCH) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final ServiceInfo serviceInfo = info.getServiceInfo();
|
||||||
|
final String key = new ComponentName(serviceInfo.packageName, serviceInfo.name)
|
||||||
|
.flattenToString();
|
||||||
|
final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(mContext,
|
||||||
|
VIRTUAL_KEYBOARD_FRAGMENT, key, screenTitle);
|
||||||
|
results.add(new SearchResult.Builder()
|
||||||
|
.setTitle(title)
|
||||||
|
.setSummary(summary)
|
||||||
|
.setRank(wordDiff)
|
||||||
|
.setStableId(Objects.hash(VIRTUAL_KEYBOARD_FRAGMENT, key))
|
||||||
|
.addBreadcrumbs(getVirtualKeyboardBreadCrumb())
|
||||||
|
.setPayload(new ResultPayload(intent))
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
if (wordDiff == NAME_NO_MATCH) {
|
return results;
|
||||||
continue;
|
}
|
||||||
|
|
||||||
|
private List<String> getPhysicalKeyboardBreadCrumb() {
|
||||||
|
if (mPhysicalKeyboardBreadcrumb == null || mPhysicalKeyboardBreadcrumb.isEmpty()) {
|
||||||
|
mPhysicalKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
|
||||||
|
mContext, PHYSICAL_KEYBOARD_FRAGMENT,
|
||||||
|
mContext.getString(R.string.physical_keyboard_title));
|
||||||
}
|
}
|
||||||
final ServiceInfo serviceInfo = info.getServiceInfo();
|
return mPhysicalKeyboardBreadcrumb;
|
||||||
final String key = new ComponentName(serviceInfo.packageName, serviceInfo.name)
|
|
||||||
.flattenToString();
|
|
||||||
final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(context,
|
|
||||||
VIRTUAL_KEYBOARD_FRAGMENT, key, screenTitle);
|
|
||||||
results.add(new SearchResult.Builder()
|
|
||||||
.setTitle(title)
|
|
||||||
.setSummary(summary)
|
|
||||||
.setRank(wordDiff)
|
|
||||||
.setStableId(Objects.hash(VIRTUAL_KEYBOARD_FRAGMENT, key))
|
|
||||||
.addBreadcrumbs(getVirtualKeyboardBreadCrumb())
|
|
||||||
.setPayload(new ResultPayload(intent))
|
|
||||||
.build());
|
|
||||||
}
|
}
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> getPhysicalKeyboardBreadCrumb() {
|
|
||||||
if (mPhysicalKeyboardBreadcrumb == null || mPhysicalKeyboardBreadcrumb.isEmpty()) {
|
private List<String> getVirtualKeyboardBreadCrumb() {
|
||||||
final Context context = getContext();
|
if (mVirtualKeyboardBreadcrumb == null || mVirtualKeyboardBreadcrumb.isEmpty()) {
|
||||||
mPhysicalKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
|
final Context context = mContext;
|
||||||
context, PHYSICAL_KEYBOARD_FRAGMENT,
|
mVirtualKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
|
||||||
context.getString(R.string.physical_keyboard_title));
|
context, VIRTUAL_KEYBOARD_FRAGMENT,
|
||||||
|
context.getString(R.string.add_virtual_keyboard));
|
||||||
|
}
|
||||||
|
return mVirtualKeyboardBreadcrumb;
|
||||||
}
|
}
|
||||||
return mPhysicalKeyboardBreadcrumb;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private List<InputDevice> getPhysicalFullKeyboards() {
|
||||||
private List<String> getVirtualKeyboardBreadCrumb() {
|
final List<InputDevice> keyboards = new ArrayList<>();
|
||||||
if (mVirtualKeyboardBreadcrumb == null || mVirtualKeyboardBreadcrumb.isEmpty()) {
|
final int[] deviceIds = InputDevice.getDeviceIds();
|
||||||
final Context context = getContext();
|
if (deviceIds != null) {
|
||||||
mVirtualKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
|
for (int deviceId : deviceIds) {
|
||||||
context, VIRTUAL_KEYBOARD_FRAGMENT,
|
final InputDevice device = InputDevice.getDevice(deviceId);
|
||||||
context.getString(R.string.add_virtual_keyboard));
|
if (device != null && !device.isVirtual() && device.isFullKeyboard()) {
|
||||||
}
|
keyboards.add(device);
|
||||||
return mVirtualKeyboardBreadcrumb;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private List<InputDevice> getPhysicalFullKeyboards() {
|
|
||||||
final List<InputDevice> keyboards = new ArrayList<>();
|
|
||||||
final int[] deviceIds = InputDevice.getDeviceIds();
|
|
||||||
if (deviceIds != null) {
|
|
||||||
for (int deviceId : deviceIds) {
|
|
||||||
final InputDevice device = InputDevice.getDevice(deviceId);
|
|
||||||
if (device != null && !device.isVirtual() && device.isFullKeyboard()) {
|
|
||||||
keyboards.add(device);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return keyboards;
|
||||||
}
|
}
|
||||||
return keyboards;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<InputMethodSubtype> getAllSubtypesOf(final InputMethodInfo imi) {
|
private static List<InputMethodSubtype> getAllSubtypesOf(final InputMethodInfo imi) {
|
||||||
final int subtypeCount = imi.getSubtypeCount();
|
final int subtypeCount = imi.getSubtypeCount();
|
||||||
final List<InputMethodSubtype> allSubtypes = new ArrayList<>(subtypeCount);
|
final List<InputMethodSubtype> allSubtypes = new ArrayList<>(subtypeCount);
|
||||||
for (int index = 0; index < subtypeCount; index++) {
|
for (int index = 0; index < subtypeCount; index++) {
|
||||||
allSubtypes.add(imi.getSubtypeAt(index));
|
allSubtypes.add(imi.getSubtypeAt(index));
|
||||||
|
}
|
||||||
|
return allSubtypes;
|
||||||
}
|
}
|
||||||
return allSubtypes;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -29,124 +29,39 @@ import android.os.UserManager;
|
|||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.support.annotation.VisibleForTesting;
|
import android.support.annotation.VisibleForTesting;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.internal.logging.nano.MetricsProto;
|
import com.android.internal.logging.nano.MetricsProto;
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.SettingsActivity;
|
import com.android.settings.SettingsActivity;
|
||||||
import com.android.settings.applications.manageapplications.ManageApplications;
|
import com.android.settings.applications.manageapplications.ManageApplications;
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
import com.android.settings.dashboard.SiteMapManager;
|
||||||
import com.android.settings.utils.AsyncLoader;
|
|
||||||
import com.android.settingslib.wrapper.PackageManagerWrapper;
|
import com.android.settingslib.wrapper.PackageManagerWrapper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.FutureTask;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search loader for installed apps.
|
* 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 int NAME_NO_MATCH = -1;
|
||||||
private static final Intent LAUNCHER_PROBE = new Intent(Intent.ACTION_MAIN)
|
private static final Intent LAUNCHER_PROBE = new Intent(Intent.ACTION_MAIN)
|
||||||
.addCategory(Intent.CATEGORY_LAUNCHER);
|
.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||||
|
|
||||||
private List<String> mBreadcrumb;
|
public InstalledAppResultLoader(Context context, PackageManagerWrapper wrapper,
|
||||||
private SiteMapManager mSiteMapManager;
|
String query, SiteMapManager manager) {
|
||||||
@VisibleForTesting
|
super(new InstalledAppResultCallable(context, wrapper, query, manager));
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -213,35 +128,133 @@ public class InstalledAppResultLoader extends AsyncLoader<Set<? extends SearchRe
|
|||||||
return NAME_NO_MATCH;
|
return NAME_NO_MATCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPackageInList(List<ResolveInfo> resolveInfos, String pkg) {
|
static class InstalledAppResultCallable implements
|
||||||
for (ResolveInfo info : resolveInfos) {
|
Callable<List<? extends SearchResult>> {
|
||||||
if (TextUtils.equals(info.activityInfo.packageName, pkg)) {
|
|
||||||
|
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;
|
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);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> getBreadCrumb() {
|
private List<UserInfo> getUsersToCount() {
|
||||||
if (mBreadcrumb == null || mBreadcrumb.isEmpty()) {
|
return mUserManager.getProfiles(UserHandle.myUserId());
|
||||||
final Context context = getContext();
|
|
||||||
mBreadcrumb = mSiteMapManager.buildBreadCrumb(
|
|
||||||
context, ManageApplications.class.getName(),
|
|
||||||
context.getString(R.string.applications_settings));
|
|
||||||
}
|
}
|
||||||
return mBreadcrumb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private boolean isPackageInList(List<ResolveInfo> resolveInfos, String pkg) {
|
||||||
* A temporary ranking scheme for installed apps.
|
for (ResolveInfo info : resolveInfos) {
|
||||||
*
|
if (TextUtils.equals(info.activityInfo.packageName, pkg)) {
|
||||||
* @param wordDiff difference between query length and app name length.
|
return true;
|
||||||
* @return the ranking.
|
}
|
||||||
*/
|
}
|
||||||
private int getRank(int wordDiff) {
|
return false;
|
||||||
if (wordDiff < 6) {
|
}
|
||||||
return 2;
|
|
||||||
|
private List<String> getBreadCrumb() {
|
||||||
|
if (mBreadcrumb == null || mBreadcrumb.isEmpty()) {
|
||||||
|
mBreadcrumb = mSiteMapManager.buildBreadCrumb(
|
||||||
|
mContext, ManageApplications.class.getName(),
|
||||||
|
mContext.getString(R.string.applications_settings));
|
||||||
|
}
|
||||||
|
return mBreadcrumb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A temporary ranking scheme for installed apps.
|
||||||
|
*
|
||||||
|
* @param wordDiff difference between query length and app name length.
|
||||||
|
* @return the ranking.
|
||||||
|
*/
|
||||||
|
private int getRank(int wordDiff) {
|
||||||
|
if (wordDiff < 6) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 3;
|
||||||
}
|
}
|
||||||
return 3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,10 +19,14 @@ package com.android.settings.search;
|
|||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.util.Pair;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
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
|
* FeatureProvider for Settings Search
|
||||||
@@ -43,26 +47,31 @@ public interface SearchFeatureProvider {
|
|||||||
void verifyLaunchSearchResultPageCaller(Context context, @NonNull ComponentName caller)
|
void verifyLaunchSearchResultPageCaller(Context context, @NonNull ComponentName caller)
|
||||||
throws SecurityException, IllegalArgumentException;
|
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.
|
* 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.
|
* 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.
|
* Returns a new loader to search accessibility services.
|
||||||
*/
|
*/
|
||||||
AccessibilityServiceResultLoader getAccessibilityServiceResultLoader(Context context,
|
AccessibilityServiceResultLoader getAccessibilityServiceResultTask(Context context,
|
||||||
String query);
|
String query);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new loader to search input devices.
|
* 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.
|
* Returns a new loader to get all recently saved queries search terms.
|
||||||
@@ -95,6 +104,11 @@ public interface SearchFeatureProvider {
|
|||||||
*/
|
*/
|
||||||
boolean isIndexingComplete(Context context);
|
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.
|
* Initializes the feedback button in case it was dismissed.
|
||||||
*/
|
*/
|
||||||
@@ -114,23 +128,6 @@ public interface SearchFeatureProvider {
|
|||||||
default void hideFeedbackButton() {
|
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.
|
* Notify that a search result is clicked.
|
||||||
*
|
*
|
||||||
@@ -161,4 +158,10 @@ public interface SearchFeatureProvider {
|
|||||||
default void searchRankingWarmup(Context context) {
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,12 +22,15 @@ import android.content.Context;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
import com.android.settings.dashboard.SiteMapManager;
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
import com.android.settings.overlay.FeatureFactory;
|
||||||
import com.android.settings.search.indexing.IndexData;
|
import com.android.settings.search.indexing.IndexData;
|
||||||
import com.android.settingslib.wrapper.PackageManagerWrapper;
|
import com.android.settingslib.wrapper.PackageManagerWrapper;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FeatureProvider for the refactored search code.
|
* FeatureProvider for the refactored search code.
|
||||||
@@ -40,6 +43,7 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider {
|
|||||||
|
|
||||||
private DatabaseIndexingManager mDatabaseIndexingManager;
|
private DatabaseIndexingManager mDatabaseIndexingManager;
|
||||||
private SiteMapManager mSiteMapManager;
|
private SiteMapManager mSiteMapManager;
|
||||||
|
private ExecutorService mExecutorService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabled(Context context) {
|
public boolean isEnabled(Context context) {
|
||||||
@@ -59,26 +63,31 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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());
|
return new DatabaseResultLoader(context, cleanQuery(query), getSiteMapManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InstalledAppResultLoader getInstalledAppSearchLoader(Context context, String query) {
|
public InstalledAppResultLoader getInstalledAppSearchTask(Context context, String query) {
|
||||||
return new InstalledAppResultLoader(
|
return new InstalledAppResultLoader(
|
||||||
context, new PackageManagerWrapper(context.getPackageManager()),
|
context, new PackageManagerWrapper(context.getPackageManager()),
|
||||||
cleanQuery(query), getSiteMapManager());
|
cleanQuery(query), getSiteMapManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AccessibilityServiceResultLoader getAccessibilityServiceResultLoader(Context context,
|
public AccessibilityServiceResultLoader getAccessibilityServiceResultTask(Context context,
|
||||||
String query) {
|
String query) {
|
||||||
return new AccessibilityServiceResultLoader(context, cleanQuery(query),
|
return new AccessibilityServiceResultLoader(context, cleanQuery(query),
|
||||||
getSiteMapManager());
|
getSiteMapManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputDeviceResultLoader getInputDeviceResultLoader(Context context, String query) {
|
public InputDeviceResultLoader getInputDeviceResultTask(Context context, String query) {
|
||||||
return new InputDeviceResultLoader(context, cleanQuery(query), getSiteMapManager());
|
return new InputDeviceResultLoader(context, cleanQuery(query), getSiteMapManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,12 +133,21 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider {
|
|||||||
.histogram(context, METRICS_ACTION_SETTINGS_INDEX, indexingTime);
|
.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.
|
* A generic method to make the query suitable for searching the database.
|
||||||
*
|
*
|
||||||
* @return the cleaned query string
|
* @return the cleaned query string
|
||||||
*/
|
*/
|
||||||
private String cleanQuery(String query) {
|
@VisibleForTesting
|
||||||
|
String cleanQuery(String query) {
|
||||||
if (TextUtils.isEmpty(query)) {
|
if (TextUtils.isEmpty(query)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@@ -54,8 +54,6 @@ import com.android.settings.widget.ActionBarShadowController;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This fragment manages the lifecycle of indexing and searching.
|
* 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.
|
* the query if the user has entered text.
|
||||||
*/
|
*/
|
||||||
public class SearchFragment extends InstrumentedFragment implements SearchView.OnQueryTextListener,
|
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";
|
private static final String TAG = "SearchFragment";
|
||||||
|
|
||||||
// State values
|
// State values
|
||||||
@@ -78,23 +76,14 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
|
|||||||
|
|
||||||
static final class SearchLoaderId {
|
static final class SearchLoaderId {
|
||||||
// Search Query IDs
|
// Search Query IDs
|
||||||
public static final int DATABASE = 1;
|
public static final int SEARCH_RESULT = 1;
|
||||||
public static final int INSTALLED_APPS = 2;
|
|
||||||
public static final int ACCESSIBILITY_SERVICES = 3;
|
|
||||||
public static final int INPUT_DEVICES = 4;
|
|
||||||
|
|
||||||
// Saved Query IDs
|
// Saved Query IDs
|
||||||
public static final int SAVE_QUERY_TASK = 5;
|
public static final int SAVE_QUERY_TASK = 2;
|
||||||
public static final int REMOVE_QUERY_TASK = 6;
|
public static final int REMOVE_QUERY_TASK = 3;
|
||||||
public static final int SAVED_QUERIES = 7;
|
public static final int SAVED_QUERIES = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static final int NUM_QUERY_LOADERS = 4;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
AtomicInteger mUnfinishedLoadersCount = new AtomicInteger(NUM_QUERY_LOADERS);
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
String mQuery;
|
String mQuery;
|
||||||
|
|
||||||
@@ -147,7 +136,7 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
|
|||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
final LoaderManager loaderManager = getLoaderManager();
|
final LoaderManager loaderManager = getLoaderManager();
|
||||||
mSearchAdapter = new SearchResultsAdapter(this, mSearchFeatureProvider);
|
mSearchAdapter = new SearchResultsAdapter(this /* fragment */);
|
||||||
mSavedQueryController = new SavedQueryController(
|
mSavedQueryController = new SavedQueryController(
|
||||||
getContext(), loaderManager, mSearchAdapter);
|
getContext(), loaderManager, mSearchAdapter);
|
||||||
mSearchFeatureProvider.initFeedbackButton();
|
mSearchFeatureProvider.initFeedbackButton();
|
||||||
@@ -277,15 +266,11 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
|
|||||||
|
|
||||||
if (isEmptyQuery) {
|
if (isEmptyQuery) {
|
||||||
final LoaderManager loaderManager = getLoaderManager();
|
final LoaderManager loaderManager = getLoaderManager();
|
||||||
loaderManager.destroyLoader(SearchLoaderId.DATABASE);
|
loaderManager.destroyLoader(SearchLoaderId.SEARCH_RESULT);
|
||||||
loaderManager.destroyLoader(SearchLoaderId.INSTALLED_APPS);
|
|
||||||
loaderManager.destroyLoader(SearchLoaderId.ACCESSIBILITY_SERVICES);
|
|
||||||
loaderManager.destroyLoader(SearchLoaderId.INPUT_DEVICES);
|
|
||||||
mShowingSavedQuery = true;
|
mShowingSavedQuery = true;
|
||||||
mSavedQueryController.loadSavedQueries();
|
mSavedQueryController.loadSavedQueries();
|
||||||
mSearchFeatureProvider.hideFeedbackButton();
|
mSearchFeatureProvider.hideFeedbackButton();
|
||||||
} else {
|
} else {
|
||||||
mSearchAdapter.initializeSearch(mQuery);
|
|
||||||
restartLoaders();
|
restartLoaders();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,35 +286,25 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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();
|
final Activity activity = getActivity();
|
||||||
|
|
||||||
switch (id) {
|
switch(id) {
|
||||||
case SearchLoaderId.DATABASE:
|
case SearchLoaderId.SEARCH_RESULT:
|
||||||
return mSearchFeatureProvider.getDatabaseSearchLoader(activity, mQuery);
|
return mSearchFeatureProvider.getSearchResultLoader(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);
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadFinished(Loader<Set<? extends SearchResult>> loader,
|
public void onLoadFinished(Loader<List<? extends SearchResult>> loader,
|
||||||
Set<? extends SearchResult> data) {
|
List<? extends SearchResult> data) {
|
||||||
mSearchAdapter.addSearchResults(data, loader.getClass().getName());
|
mSearchAdapter.postSearchResults(data);
|
||||||
if (mUnfinishedLoadersCount.decrementAndGet() != 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mSearchAdapter.notifyResultsLoaded();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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();
|
mSavedQueryController.loadSavedQueries();
|
||||||
} else {
|
} else {
|
||||||
final LoaderManager loaderManager = getLoaderManager();
|
final LoaderManager loaderManager = getLoaderManager();
|
||||||
loaderManager.initLoader(SearchLoaderId.DATABASE, null /* args */, this /* callback */);
|
loaderManager.initLoader(SearchLoaderId.SEARCH_RESULT, null /* args */,
|
||||||
loaderManager.initLoader(
|
this /* callback */);
|
||||||
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 */);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
requery();
|
requery();
|
||||||
@@ -388,15 +358,8 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
|
|||||||
private void restartLoaders() {
|
private void restartLoaders() {
|
||||||
mShowingSavedQuery = false;
|
mShowingSavedQuery = false;
|
||||||
final LoaderManager loaderManager = getLoaderManager();
|
final LoaderManager loaderManager = getLoaderManager();
|
||||||
mUnfinishedLoadersCount.set(NUM_QUERY_LOADERS);
|
|
||||||
loaderManager.restartLoader(
|
loaderManager.restartLoader(
|
||||||
SearchLoaderId.DATABASE, null /* args */, this /* callback */);
|
SearchLoaderId.SEARCH_RESULT, 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 */);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getQuery() {
|
public String getQuery() {
|
||||||
@@ -453,9 +416,7 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
|
|||||||
taggedData.add(Pair.create(
|
taggedData.add(Pair.create(
|
||||||
MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_RANK,
|
MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_RANK,
|
||||||
resultViewHolder.getAdapterPosition()));
|
resultViewHolder.getAdapterPosition()));
|
||||||
taggedData.add(Pair.create(
|
// TODO (b/67744820) Move metrics to SettingsIntelligence (including ranking state).
|
||||||
MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_ASYNC_RANKING_STATE,
|
|
||||||
mSearchAdapter.getAsyncRankingState()));
|
|
||||||
taggedData.add(Pair.create(
|
taggedData.add(Pair.create(
|
||||||
MetricsEvent.FIELD_SETTINGS_SEARCH_QUERY_LENGTH,
|
MetricsEvent.FIELD_SETTINGS_SEARCH_QUERY_LENGTH,
|
||||||
TextUtils.isEmpty(mQuery) ? 0 : mQuery.length()));
|
TextUtils.isEmpty(mQuery) ? 0 : mQuery.length()));
|
||||||
|
177
src/com/android/settings/search/SearchResultAggregator.java
Normal file
177
src/com/android/settings/search/SearchResultAggregator.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -26,10 +26,11 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class SearchResultDiffCallback extends DiffUtil.Callback {
|
public class SearchResultDiffCallback extends DiffUtil.Callback {
|
||||||
|
|
||||||
private List<SearchResult> mOldList;
|
private List<? extends SearchResult> mOldList;
|
||||||
private List<SearchResult> mNewList;
|
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;
|
mOldList = oldList;
|
||||||
mNewList = newList;
|
mNewList = newList;
|
||||||
}
|
}
|
||||||
|
30
src/com/android/settings/search/SearchResultLoader.java
Normal file
30
src/com/android/settings/search/SearchResultLoader.java
Normal 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) {
|
||||||
|
}
|
||||||
|
}
|
@@ -18,87 +18,25 @@
|
|||||||
package com.android.settings.search;
|
package com.android.settings.search;
|
||||||
|
|
||||||
import android.content.Context;
|
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.util.DiffUtil;
|
||||||
import android.support.v7.widget.RecyclerView;
|
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.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import com.android.settings.R;
|
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.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
|
public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder> {
|
||||||
implements SearchResultsRankerCallback {
|
|
||||||
private static final String TAG = "SearchResultsAdapter";
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
static final String DB_RESULTS_LOADER_KEY = DatabaseResultLoader.class.getName();
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
static final String APP_RESULTS_LOADER_KEY = InstalledAppResultLoader.class.getName();
|
|
||||||
@VisibleForTesting
|
|
||||||
static final 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;
|
|
||||||
|
|
||||||
private final SearchFragment mFragment;
|
private final SearchFragment mFragment;
|
||||||
private final Context mContext;
|
|
||||||
private final List<SearchResult> mSearchResults;
|
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})
|
public SearchResultsAdapter(SearchFragment fragment) {
|
||||||
@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) {
|
|
||||||
mFragment = fragment;
|
mFragment = fragment;
|
||||||
mContext = fragment.getContext().getApplicationContext();
|
|
||||||
mSearchResults = new ArrayList<>();
|
mSearchResults = new ArrayList<>();
|
||||||
mResultsMap = new ArrayMap<>();
|
|
||||||
mSearchRankingScores = new ArrayList<>();
|
|
||||||
mStaticallyRankedSearchResults = new ArrayList<>();
|
|
||||||
mSearchFeatureProvider = searchFeatureProvider;
|
|
||||||
|
|
||||||
setHasStableIds(true);
|
setHasStableIds(true);
|
||||||
}
|
}
|
||||||
@@ -149,298 +87,30 @@ public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
|
|||||||
return mSearchResults.size();
|
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.
|
* 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();
|
clearResults();
|
||||||
mSearchResults.addAll(data);
|
mSearchResults.addAll(data);
|
||||||
notifyDataSetChanged();
|
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() {
|
public void clearResults() {
|
||||||
mSearchResults.clear();
|
mSearchResults.clear();
|
||||||
mStaticallyRankedSearchResults.clear();
|
|
||||||
mResultsMap.clear();
|
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public List<SearchResult> getSearchResults() {
|
public List<SearchResult> getSearchResults() {
|
||||||
return mSearchResults;
|
return mSearchResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
public void postSearchResults(List<? extends SearchResult> newSearchResults) {
|
||||||
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) {
|
|
||||||
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
|
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
|
||||||
new SearchResultDiffCallback(mSearchResults, newSearchResults), detectMoves);
|
new SearchResultDiffCallback(mSearchResults, newSearchResults));
|
||||||
mSearchResults.clear();
|
mSearchResults.clear();
|
||||||
mSearchResults.addAll(newSearchResults);
|
mSearchResults.addAll(newSearchResults);
|
||||||
diffResult.dispatchUpdatesTo(this);
|
diffResult.dispatchUpdatesTo(this);
|
||||||
mFragment.onSearchResultsDisplayed(mSearchResults.size());
|
mFragment.onSearchResultsDisplayed(mSearchResults.size());
|
||||||
mSearchResultsUpdated = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
package com.android.settings.search;
|
package com.android.settings.search;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ import java.util.List;
|
|||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
@RunWith(SettingsRobolectricTestRunner.class)
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
||||||
public class AccessibilityServiceResultLoaderTest {
|
public class AccessibilityServiceResultFutureTaskTest {
|
||||||
|
|
||||||
private static final String QUERY = "test_query";
|
private static final String QUERY = "test_query";
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ public class AccessibilityServiceResultLoaderTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private SiteMapManager mSiteMapManager;
|
private SiteMapManager mSiteMapManager;
|
||||||
|
|
||||||
private AccessibilityServiceResultLoader mLoader;
|
private AccessibilityServiceResultLoader.AccessibilityServiceResultCallable mCallable;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
@@ -68,19 +69,20 @@ public class AccessibilityServiceResultLoaderTest {
|
|||||||
.thenReturn(mAccessibilityManager);
|
.thenReturn(mAccessibilityManager);
|
||||||
when(mContext.getPackageManager()).thenReturn(mPackageManager);
|
when(mContext.getPackageManager()).thenReturn(mPackageManager);
|
||||||
|
|
||||||
mLoader = new AccessibilityServiceResultLoader(mContext, QUERY, mSiteMapManager);
|
mCallable = new AccessibilityServiceResultLoader.AccessibilityServiceResultCallable(
|
||||||
|
mContext, QUERY, mSiteMapManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_noService_shouldNotReturnAnything() {
|
public void query_noService_shouldNotReturnAnything() throws Exception {
|
||||||
assertThat(mLoader.loadInBackground()).isEmpty();
|
assertThat(mCallable.call()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_hasServiceMatchingTitle_shouldReturnResult() {
|
public void query_hasServiceMatchingTitle_shouldReturnResult() throws Exception {
|
||||||
addFakeAccessibilityService();
|
addFakeAccessibilityService();
|
||||||
|
|
||||||
List<? extends SearchResult> results = new ArrayList<>(mLoader.loadInBackground());
|
List<? extends SearchResult> results = mCallable.call();
|
||||||
assertThat(results).hasSize(1);
|
assertThat(results).hasSize(1);
|
||||||
|
|
||||||
SearchResult result = results.get(0);
|
SearchResult result = results.get(0);
|
||||||
@@ -88,13 +90,14 @@ public class AccessibilityServiceResultLoaderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_serviceDoesNotMatchTitle_shouldReturnResult() {
|
public void query_serviceDoesNotMatchTitle_shouldReturnResult() throws Exception {
|
||||||
addFakeAccessibilityService();
|
addFakeAccessibilityService();
|
||||||
|
|
||||||
mLoader = new AccessibilityServiceResultLoader(mContext,
|
mCallable = new AccessibilityServiceResultLoader.AccessibilityServiceResultCallable(
|
||||||
|
mContext,
|
||||||
QUERY + "no_match", mSiteMapManager);
|
QUERY + "no_match", mSiteMapManager);
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground()).isEmpty();
|
assertThat(mCallable.call()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addFakeAccessibilityService() {
|
private void addFakeAccessibilityService() {
|
@@ -17,9 +17,11 @@
|
|||||||
package com.android.settings.search;
|
package com.android.settings.search;
|
||||||
|
|
||||||
import static android.content.Context.INPUT_METHOD_SERVICE;
|
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.PHYSICAL_KEYBOARD_FRAGMENT;
|
||||||
import static com.android.settings.search.InputDeviceResultLoader.VIRTUAL_KEYBOARD_FRAGMENT;
|
import static com.android.settings.search.InputDeviceResultLoader.VIRTUAL_KEYBOARD_FRAGMENT;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import static org.mockito.Matchers.anyInt;
|
import static org.mockito.Matchers.anyInt;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
@@ -58,7 +60,7 @@ import java.util.List;
|
|||||||
shadows = {
|
shadows = {
|
||||||
ShadowInputDevice.class
|
ShadowInputDevice.class
|
||||||
})
|
})
|
||||||
public class InputDeviceResultLoaderTest {
|
public class InputDeviceResultFutureTaskTest {
|
||||||
|
|
||||||
private static final String QUERY = "test_query";
|
private static final String QUERY = "test_query";
|
||||||
private static final List<String> PHYSICAL_KEYBOARD_BREADCRUMB;
|
private static final List<String> PHYSICAL_KEYBOARD_BREADCRUMB;
|
||||||
@@ -84,7 +86,7 @@ public class InputDeviceResultLoaderTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private PackageManager mPackageManager;
|
private PackageManager mPackageManager;
|
||||||
|
|
||||||
private InputDeviceResultLoader mLoader;
|
private InputDeviceResultLoader.InputDeviceResultCallable mCallable;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
@@ -99,7 +101,8 @@ public class InputDeviceResultLoaderTest {
|
|||||||
when(mContext.getString(anyInt()))
|
when(mContext.getString(anyInt()))
|
||||||
.thenAnswer(invocation -> RuntimeEnvironment.application.getString(
|
.thenAnswer(invocation -> RuntimeEnvironment.application.getString(
|
||||||
(Integer) invocation.getArguments()[0]));
|
(Integer) invocation.getArguments()[0]));
|
||||||
mLoader = new InputDeviceResultLoader(mContext, QUERY, mSiteMapManager);
|
mCallable = new InputDeviceResultLoader.InputDeviceResultCallable(mContext, QUERY,
|
||||||
|
mSiteMapManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -108,18 +111,19 @@ public class InputDeviceResultLoaderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_noKeyboard_shouldNotReturnAnything() {
|
public void query_noKeyboard_shouldNotReturnAnything() throws Exception {
|
||||||
assertThat(mLoader.loadInBackground()).isEmpty();
|
|
||||||
|
assertThat(mCallable.call()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_hasPhysicalKeyboard_match() {
|
public void query_hasPhysicalKeyboard_match() throws Exception {
|
||||||
addPhysicalKeyboard(QUERY);
|
addPhysicalKeyboard(QUERY);
|
||||||
when(mSiteMapManager.buildBreadCrumb(mContext, PHYSICAL_KEYBOARD_FRAGMENT,
|
when(mSiteMapManager.buildBreadCrumb(mContext, PHYSICAL_KEYBOARD_FRAGMENT,
|
||||||
RuntimeEnvironment.application.getString(R.string.physical_keyboard_title)))
|
RuntimeEnvironment.application.getString(R.string.physical_keyboard_title)))
|
||||||
.thenReturn(PHYSICAL_KEYBOARD_BREADCRUMB);
|
.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).hasSize(1);
|
||||||
assertThat(results.get(0).title).isEqualTo(QUERY);
|
assertThat(results.get(0).title).isEqualTo(QUERY);
|
||||||
@@ -128,13 +132,13 @@ public class InputDeviceResultLoaderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_hasVirtualKeyboard_match() {
|
public void query_hasVirtualKeyboard_match() throws Exception {
|
||||||
addVirtualKeyboard(QUERY);
|
addVirtualKeyboard(QUERY);
|
||||||
when(mSiteMapManager.buildBreadCrumb(mContext, VIRTUAL_KEYBOARD_FRAGMENT,
|
when(mSiteMapManager.buildBreadCrumb(mContext, VIRTUAL_KEYBOARD_FRAGMENT,
|
||||||
RuntimeEnvironment.application.getString(R.string.add_virtual_keyboard)))
|
RuntimeEnvironment.application.getString(R.string.add_virtual_keyboard)))
|
||||||
.thenReturn(VIRTUAL_KEYBOARD_BREADCRUMB);
|
.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).hasSize(1);
|
||||||
assertThat(results.get(0).title).isEqualTo(QUERY);
|
assertThat(results.get(0).title).isEqualTo(QUERY);
|
||||||
assertThat(results.get(0).breadcrumbs)
|
assertThat(results.get(0).breadcrumbs)
|
||||||
@@ -142,11 +146,11 @@ public class InputDeviceResultLoaderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_hasPhysicalVirtualKeyboard_doNotMatch() {
|
public void query_hasPhysicalVirtualKeyboard_doNotMatch() throws Exception {
|
||||||
addPhysicalKeyboard("abc");
|
addPhysicalKeyboard("abc");
|
||||||
addVirtualKeyboard("def");
|
addVirtualKeyboard("def");
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground()).isEmpty();
|
assertThat(mCallable.call()).isEmpty();
|
||||||
verifyZeroInteractions(mSiteMapManager);
|
verifyZeroInteractions(mSiteMapManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,4 +174,4 @@ public class InputDeviceResultLoaderTest {
|
|||||||
when(mImm.getInputMethodList()).thenReturn(imis);
|
when(mImm.getInputMethodList()).thenReturn(imis);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -19,7 +19,9 @@ package com.android.settings.search;
|
|||||||
|
|
||||||
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
|
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
|
||||||
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
import static org.mockito.Matchers.anyInt;
|
import static org.mockito.Matchers.anyInt;
|
||||||
import static org.mockito.Matchers.anyList;
|
import static org.mockito.Matchers.anyList;
|
||||||
@@ -53,8 +55,6 @@ import org.junit.runner.RunWith;
|
|||||||
import org.mockito.Answers;
|
import org.mockito.Answers;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
|
||||||
import org.mockito.stubbing.Answer;
|
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -77,7 +77,7 @@ public class InstalledAppResultLoaderTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private SiteMapManager mSiteMapManager;
|
private SiteMapManager mSiteMapManager;
|
||||||
|
|
||||||
private InstalledAppResultLoader mLoader;
|
private InstalledAppResultLoader.InstalledAppResultCallable mCallable;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
@@ -109,49 +109,50 @@ public class InstalledAppResultLoaderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_noMatchingQuery_shouldReturnEmptyResult() {
|
public void query_noMatchingQuery_shouldReturnEmptyResult() throws Exception {
|
||||||
final String query = "abc";
|
final String query = "abc";
|
||||||
|
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground()).isEmpty();
|
assertThat(mCallable.call()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_matchingQuery_shouldReturnNonSystemApps() {
|
public void query_matchingQuery_shouldReturnNonSystemApps() throws Exception {
|
||||||
final String query = "app";
|
final String query = "app";
|
||||||
|
|
||||||
mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = spy(new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager));
|
mSiteMapManager));
|
||||||
when(mLoader.getContext()).thenReturn(mContext);
|
|
||||||
when(mSiteMapManager.buildBreadCrumb(eq(mContext), anyString(), anyString()))
|
when(mSiteMapManager.buildBreadCrumb(eq(mContext), anyString(), anyString()))
|
||||||
.thenReturn(Arrays.asList(new String[]{"123"}));
|
.thenReturn(Arrays.asList(new String[]{"123"}));
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground().size()).isEqualTo(3);
|
assertThat(mCallable.call()).hasSize(3);
|
||||||
verify(mSiteMapManager)
|
verify(mSiteMapManager)
|
||||||
.buildBreadCrumb(eq(mContext), anyString(), anyString());
|
.buildBreadCrumb(eq(mContext), anyString(), anyString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_matchingQuery_shouldReturnSystemAppUpdates() {
|
public void query_matchingQuery_shouldReturnSystemAppUpdates() throws Exception {
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
||||||
.thenReturn(Arrays.asList(
|
.thenReturn(Arrays.asList(
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_UPDATED_SYSTEM_APP,
|
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_UPDATED_SYSTEM_APP,
|
||||||
0 /* targetSdkVersion */)));
|
0 /* targetSdkVersion */)));
|
||||||
final String query = "app";
|
final String query = "app";
|
||||||
|
|
||||||
mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = spy(new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager));
|
mSiteMapManager));
|
||||||
when(mLoader.getContext()).thenReturn(mContext);
|
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
|
assertThat(mCallable.call()).hasSize(1);
|
||||||
verify(mSiteMapManager)
|
verify(mSiteMapManager)
|
||||||
.buildBreadCrumb(eq(mContext), anyString(), anyString());
|
.buildBreadCrumb(eq(mContext), anyString(), anyString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_matchingQuery_shouldReturnSystemAppIfLaunchable() {
|
public void query_matchingQuery_shouldReturnSystemAppIfLaunchable() throws Exception {
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
||||||
.thenReturn(Arrays.asList(
|
.thenReturn(Arrays.asList(
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
|
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
|
||||||
@@ -164,14 +165,15 @@ public class InstalledAppResultLoaderTest {
|
|||||||
|
|
||||||
final String query = "app";
|
final String query = "app";
|
||||||
|
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
|
assertThat(mCallable.call()).hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_matchingQuery_shouldReturnSystemAppIfHomeApp() {
|
public void query_matchingQuery_shouldReturnSystemAppIfHomeApp() throws Exception {
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
||||||
.thenReturn(Arrays.asList(
|
.thenReturn(Arrays.asList(
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
|
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
|
||||||
@@ -180,28 +182,26 @@ public class InstalledAppResultLoaderTest {
|
|||||||
any(Intent.class), anyInt(), anyInt()))
|
any(Intent.class), anyInt(), anyInt()))
|
||||||
.thenReturn(null);
|
.thenReturn(null);
|
||||||
|
|
||||||
when(mPackageManagerWrapper.getHomeActivities(anyList())).thenAnswer(new Answer<Object>() {
|
when(mPackageManagerWrapper.getHomeActivities(anyList())).thenAnswer(invocation -> {
|
||||||
@Override
|
final List<ResolveInfo> list = (List<ResolveInfo>) invocation.getArguments()[0];
|
||||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
final ResolveInfo info = new ResolveInfo();
|
||||||
final List<ResolveInfo> list = (List<ResolveInfo>) invocation.getArguments()[0];
|
info.activityInfo = new ActivityInfo();
|
||||||
final ResolveInfo info = new ResolveInfo();
|
info.activityInfo.packageName = "app1";
|
||||||
info.activityInfo = new ActivityInfo();
|
list.add(info);
|
||||||
info.activityInfo.packageName = "app1";
|
return null;
|
||||||
list.add(info);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final String query = "app";
|
final String query = "app";
|
||||||
|
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
|
assertThat(mCallable.call()).hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_matchingQuery_shouldNotReturnSystemAppIfNotLaunchable() {
|
public void query_matchingQuery_shouldNotReturnSystemAppIfNotLaunchable() throws Exception {
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
||||||
.thenReturn(Arrays.asList(
|
.thenReturn(Arrays.asList(
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
|
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
|
||||||
@@ -212,21 +212,23 @@ public class InstalledAppResultLoaderTest {
|
|||||||
|
|
||||||
final String query = "app";
|
final String query = "app";
|
||||||
|
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground()).isEmpty();
|
assertThat(mCallable.call()).isEmpty();
|
||||||
verify(mSiteMapManager, never())
|
verify(mSiteMapManager, never())
|
||||||
.buildBreadCrumb(eq(mContext), anyString(), anyString());
|
.buildBreadCrumb(eq(mContext), anyString(), anyString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_matchingQuery_multipleResults() {
|
public void query_matchingQuery_multipleResults() throws Exception {
|
||||||
final String query = "app";
|
final String query = "app";
|
||||||
|
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
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> expectedTitles = new HashSet<>(Arrays.asList("app4", "app", "appBuffer"));
|
||||||
Set<CharSequence> actualTitles = new HashSet<>();
|
Set<CharSequence> actualTitles = new HashSet<>();
|
||||||
@@ -237,161 +239,172 @@ public class InstalledAppResultLoaderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_normalWord_MatchPrefix() {
|
public void query_normalWord_MatchPrefix() throws Exception {
|
||||||
final String query = "ba";
|
final String query = "ba";
|
||||||
final String packageName = "Bananas";
|
final String packageName = "Bananas";
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
||||||
.thenReturn(Arrays.asList(
|
.thenReturn(Arrays.asList(
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
||||||
0 /* targetSdkVersion */)));
|
0 /* targetSdkVersion */)));
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
|
assertThat(mCallable.call()).hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_CapitalCase_DoestMatchSecondWord() {
|
public void query_CapitalCase_DoestMatchSecondWord() throws Exception {
|
||||||
final String query = "Apples";
|
final String query = "Apples";
|
||||||
final String packageName = "BananasApples";
|
final String packageName = "BananasApples";
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
||||||
.thenReturn(Arrays.asList(
|
.thenReturn(Arrays.asList(
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
||||||
0 /* targetSdkVersion */)));
|
0 /* targetSdkVersion */)));
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground().size()).isEqualTo(0);
|
assertThat(mCallable.call()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_TwoWords_MatchesFirstWord() {
|
public void query_TwoWords_MatchesFirstWord() throws Exception {
|
||||||
final String query = "Banana";
|
final String query = "Banana";
|
||||||
final String packageName = "Bananas Apples";
|
final String packageName = "Bananas Apples";
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
||||||
.thenReturn(Arrays.asList(
|
.thenReturn(Arrays.asList(
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
||||||
0 /* targetSdkVersion */)));
|
0 /* targetSdkVersion */)));
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
|
assertThat(mCallable.call()).hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_TwoWords_MatchesSecondWord() {
|
public void query_TwoWords_MatchesSecondWord() throws Exception {
|
||||||
final String query = "Apple";
|
final String query = "Apple";
|
||||||
final String packageName = "Bananas Apples";
|
final String packageName = "Bananas Apples";
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
||||||
.thenReturn(Arrays.asList(
|
.thenReturn(Arrays.asList(
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
||||||
0 /* targetSdkVersion */)));
|
0 /* targetSdkVersion */)));
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
|
assertThat(mCallable.call()).hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_ThreeWords_MatchesThirdWord() {
|
public void query_ThreeWords_MatchesThirdWord() throws Exception {
|
||||||
final String query = "Pear";
|
final String query = "Pear";
|
||||||
final String packageName = "Bananas Apples Pears";
|
final String packageName = "Bananas Apples Pears";
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
||||||
.thenReturn(Arrays.asList(
|
.thenReturn(Arrays.asList(
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
||||||
0 /* targetSdkVersion */)));
|
0 /* targetSdkVersion */)));
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
|
assertThat(mCallable.call()).hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_DoubleSpacedWords_MatchesSecondWord() {
|
public void query_DoubleSpacedWords_MatchesSecondWord() throws Exception {
|
||||||
final String query = "Apple";
|
final String query = "Apple";
|
||||||
final String packageName = "Bananas Apples";
|
final String packageName = "Bananas Apples";
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
||||||
.thenReturn(Arrays.asList(
|
.thenReturn(Arrays.asList(
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
||||||
0 /* targetSdkVersion */)));
|
0 /* targetSdkVersion */)));
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
|
assertThat(mCallable.call()).hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_SpecialChar_MatchesSecondWord() {
|
public void query_SpecialChar_MatchesSecondWord() throws Exception {
|
||||||
final String query = "Apple";
|
final String query = "Apple";
|
||||||
final String packageName = "Bananas & Apples";
|
final String packageName = "Bananas & Apples";
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
||||||
.thenReturn(Arrays.asList(
|
.thenReturn(Arrays.asList(
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
||||||
0 /* targetSdkVersion */)));
|
0 /* targetSdkVersion */)));
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
|
assertThat(mCallable.call()).hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_TabSeparated_MatchesSecondWord() {
|
public void query_TabSeparated_MatchesSecondWord() throws Exception {
|
||||||
final String query = "Apple";
|
final String query = "Apple";
|
||||||
final String packageName = "Bananas\tApples";
|
final String packageName = "Bananas\tApples";
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
||||||
.thenReturn(Arrays.asList(
|
.thenReturn(Arrays.asList(
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
||||||
0 /* targetSdkVersion */)));
|
0 /* targetSdkVersion */)));
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
|
assertThat(mCallable.call()).hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_LeadingNumber_MatchesWord() {
|
public void query_LeadingNumber_MatchesWord() throws Exception {
|
||||||
final String query = "4";
|
final String query = "4";
|
||||||
final String packageName = "4Bananas";
|
final String packageName = "4Bananas";
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
||||||
.thenReturn(Arrays.asList(
|
.thenReturn(Arrays.asList(
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
||||||
0 /* targetSdkVersion */)));
|
0 /* targetSdkVersion */)));
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
|
assertThat(mCallable.call()).hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_FirstWordPrefixOfQuery_NoMatch() {
|
public void query_FirstWordPrefixOfQuery_NoMatch() throws Exception {
|
||||||
final String query = "Bananass";
|
final String query = "Bananass";
|
||||||
final String packageName = "Bananas Apples";
|
final String packageName = "Bananas Apples";
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
||||||
.thenReturn(Arrays.asList(
|
.thenReturn(Arrays.asList(
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
||||||
0 /* targetSdkVersion */)));
|
0 /* targetSdkVersion */)));
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground().size()).isEqualTo(0);
|
assertThat(mCallable.call()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_QueryLongerThanAppName_NoMatch() {
|
public void query_QueryLongerThanAppName_NoMatch() throws Exception {
|
||||||
final String query = "BananasApples";
|
final String query = "BananasApples";
|
||||||
final String packageName = "Bananas";
|
final String packageName = "Bananas";
|
||||||
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
|
||||||
.thenReturn(Arrays.asList(
|
.thenReturn(Arrays.asList(
|
||||||
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
|
||||||
0 /* targetSdkVersion */)));
|
0 /* targetSdkVersion */)));
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
|
|
||||||
assertThat(mLoader.loadInBackground().size()).isEqualTo(0);
|
assertThat(mCallable.call()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void query_appExistsInBothProfiles() {
|
public void query_appExistsInBothProfiles() throws Exception {
|
||||||
final String query = "carrot";
|
final String query = "carrot";
|
||||||
final String packageName = "carrot";
|
final String packageName = "carrot";
|
||||||
final int user1 = 0;
|
final int user1 = 0;
|
||||||
@@ -414,10 +427,11 @@ public class InstalledAppResultLoaderTest {
|
|||||||
packageName, 0 /* flags */,
|
packageName, 0 /* flags */,
|
||||||
0 /* targetSdkVersion */)));
|
0 /* targetSdkVersion */)));
|
||||||
|
|
||||||
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
|
mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
|
||||||
|
mPackageManagerWrapper, query,
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
|
|
||||||
Set<AppSearchResult> searchResults = (Set<AppSearchResult>) mLoader.loadInBackground();
|
List<AppSearchResult> searchResults = (List<AppSearchResult>) mCallable.call();
|
||||||
assertThat(searchResults).hasSize(2);
|
assertThat(searchResults).hasSize(2);
|
||||||
|
|
||||||
Set<Integer> uidResults = searchResults.stream().map(result -> result.info.uid).collect(
|
Set<Integer> uidResults = searchResults.stream().map(result -> result.info.uid).collect(
|
||||||
|
@@ -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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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) {
|
||||||
|
}
|
||||||
|
}
|
@@ -21,7 +21,6 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
|
|
||||||
import com.android.settings.TestConfig;
|
import com.android.settings.TestConfig;
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
import com.android.settings.dashboard.SiteMapManager;
|
||||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
||||||
@@ -33,6 +32,10 @@ import org.mockito.MockitoAnnotations;
|
|||||||
import org.robolectric.Robolectric;
|
import org.robolectric.Robolectric;
|
||||||
import org.robolectric.annotation.Config;
|
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)
|
@RunWith(SettingsRobolectricTestRunner.class)
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
||||||
public class SearchFeatureProviderImplTest {
|
public class SearchFeatureProviderImplTest {
|
||||||
@@ -43,7 +46,7 @@ public class SearchFeatureProviderImplTest {
|
|||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
mActivity = Robolectric.buildActivity(Activity.class).create().visible().get();
|
mActivity = Robolectric.buildActivity(Activity.class).create().visible().get();
|
||||||
mProvider = new SearchFeatureProviderImpl();
|
mProvider = spy(new SearchFeatureProviderImpl());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -57,18 +60,19 @@ public class SearchFeatureProviderImplTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getDatabaseSearchLoader_shouldCleanupQuery() {
|
public void getDatabaseSearchLoader_shouldCleanupQuery() {
|
||||||
final String query = " space ";
|
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
|
@Test
|
||||||
public void getInstalledAppSearchLoader_shouldCleanupQuery() {
|
public void getInstalledAppSearchLoader_shouldCleanupQuery() {
|
||||||
final String query = " space ";
|
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)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
@@ -87,4 +91,12 @@ public class SearchFeatureProviderImplTest {
|
|||||||
final ComponentName cn = new ComponentName(mActivity.getPackageName(), "class");
|
final ComponentName cn = new ComponentName(mActivity.getPackageName(), "class");
|
||||||
mProvider.verifyLaunchSearchResultPageCaller(mActivity, cn);
|
mProvider.verifyLaunchSearchResultPageCaller(mActivity, cn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cleanQuery_trimsWhitespace() {
|
||||||
|
final String query = " space ";
|
||||||
|
final String cleanQuery = "space";
|
||||||
|
|
||||||
|
assertThat(mProvider.cleanQuery(query)).isEqualTo(cleanQuery);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@
|
|||||||
package com.android.settings.search;
|
package com.android.settings.search;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.nullable;
|
import static org.mockito.ArgumentMatchers.nullable;
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
import static org.mockito.Matchers.anyInt;
|
import static org.mockito.Matchers.anyInt;
|
||||||
@@ -54,9 +55,7 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Answers;
|
import org.mockito.Answers;
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.ArgumentMatcher;
|
import org.mockito.ArgumentMatcher;
|
||||||
import org.mockito.Captor;
|
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
import org.robolectric.Robolectric;
|
import org.robolectric.Robolectric;
|
||||||
@@ -65,7 +64,7 @@ import org.robolectric.android.controller.ActivityController;
|
|||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
import org.robolectric.util.ReflectionHelpers;
|
import org.robolectric.util.ReflectionHelpers;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.List;
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
@RunWith(SettingsRobolectricTestRunner.class)
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH,
|
@Config(manifest = TestConfig.MANIFEST_PATH,
|
||||||
@@ -79,22 +78,13 @@ public class SearchFragmentTest {
|
|||||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
@Mock
|
@Mock
|
||||||
private DatabaseResultLoader mDatabaseResultLoader;
|
private SearchResultLoader mSearchResultLoader;
|
||||||
@Mock
|
|
||||||
private InstalledAppResultLoader mInstalledAppResultLoader;
|
|
||||||
@Mock
|
|
||||||
private AccessibilityServiceResultLoader mAccessibilityServiceResultLoader;
|
|
||||||
@Mock
|
|
||||||
private InputDeviceResultLoader mInputDeviceResultLoader;
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private SavedQueryLoader mSavedQueryLoader;
|
private SavedQueryLoader mSavedQueryLoader;
|
||||||
@Mock
|
@Mock
|
||||||
private SavedQueryController mSavedQueryController;
|
private SavedQueryController mSavedQueryController;
|
||||||
@Mock
|
@Mock
|
||||||
private SearchResultsAdapter mSearchResultsAdapter;
|
private SearchResultsAdapter mSearchResultsAdapter;
|
||||||
@Captor
|
|
||||||
private ArgumentCaptor<String> mQueryCaptor = ArgumentCaptor.forClass(String.class);
|
|
||||||
|
|
||||||
private FakeFeatureFactory mFeatureFactory;
|
private FakeFeatureFactory mFeatureFactory;
|
||||||
|
|
||||||
@@ -113,17 +103,8 @@ public class SearchFragmentTest {
|
|||||||
@Test
|
@Test
|
||||||
public void screenRotate_shouldPersistQuery() {
|
public void screenRotate_shouldPersistQuery() {
|
||||||
when(mFeatureFactory.searchFeatureProvider
|
when(mFeatureFactory.searchFeatureProvider
|
||||||
.getDatabaseSearchLoader(any(Context.class), anyString()))
|
.getSearchResultLoader(any(Context.class), anyString()))
|
||||||
.thenReturn(mDatabaseResultLoader);
|
.thenReturn(new MockSearchResultLoader(RuntimeEnvironment.application));
|
||||||
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)))
|
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
|
||||||
.thenReturn(mSavedQueryLoader);
|
.thenReturn(mSavedQueryLoader);
|
||||||
|
|
||||||
@@ -168,25 +149,16 @@ public class SearchFragmentTest {
|
|||||||
activityController.setup(bundle);
|
activityController.setup(bundle);
|
||||||
|
|
||||||
verify(mFeatureFactory.searchFeatureProvider, never())
|
verify(mFeatureFactory.searchFeatureProvider, never())
|
||||||
.getDatabaseSearchLoader(any(Context.class), anyString());
|
.getStaticSearchResultTask(any(Context.class), anyString());
|
||||||
verify(mFeatureFactory.searchFeatureProvider, never())
|
verify(mFeatureFactory.searchFeatureProvider, never())
|
||||||
.getInstalledAppSearchLoader(any(Context.class), anyString());
|
.getInstalledAppSearchTask(any(Context.class), anyString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void queryTextChange_shouldTriggerLoaderAndInitializeSearch() {
|
public void queryTextChange_shouldTriggerLoader() {
|
||||||
when(mFeatureFactory.searchFeatureProvider
|
when(mFeatureFactory.searchFeatureProvider
|
||||||
.getDatabaseSearchLoader(any(Context.class), anyString()))
|
.getSearchResultLoader(any(Context.class), anyString()))
|
||||||
.thenReturn(mDatabaseResultLoader);
|
.thenReturn(mSearchResultLoader);
|
||||||
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)))
|
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
|
||||||
.thenReturn(mSavedQueryLoader);
|
.thenReturn(mSavedQueryLoader);
|
||||||
|
|
||||||
@@ -199,7 +171,6 @@ public class SearchFragmentTest {
|
|||||||
when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
|
when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
|
||||||
.thenReturn(true);
|
.thenReturn(true);
|
||||||
|
|
||||||
ReflectionHelpers.setField(fragment, "mSearchAdapter", mSearchResultsAdapter);
|
|
||||||
fragment.onQueryTextChange(testQuery);
|
fragment.onQueryTextChange(testQuery);
|
||||||
activityController.get().onBackPressed();
|
activityController.get().onBackPressed();
|
||||||
|
|
||||||
@@ -209,11 +180,7 @@ public class SearchFragmentTest {
|
|||||||
any(Context.class),
|
any(Context.class),
|
||||||
eq(MetricsProto.MetricsEvent.ACTION_LEAVE_SEARCH_RESULT_WITHOUT_QUERY));
|
eq(MetricsProto.MetricsEvent.ACTION_LEAVE_SEARCH_RESULT_WITHOUT_QUERY));
|
||||||
verify(mFeatureFactory.searchFeatureProvider)
|
verify(mFeatureFactory.searchFeatureProvider)
|
||||||
.getDatabaseSearchLoader(any(Context.class), anyString());
|
.getSearchResultLoader(any(Context.class), anyString());
|
||||||
verify(mFeatureFactory.searchFeatureProvider)
|
|
||||||
.getInstalledAppSearchLoader(any(Context.class), anyString());
|
|
||||||
verify(mSearchResultsAdapter).initializeSearch(mQueryCaptor.capture());
|
|
||||||
assertThat(mQueryCaptor.getValue()).isEqualTo(testQuery);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -238,18 +205,6 @@ public class SearchFragmentTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void queryTextChangeToEmpty_shouldLoadSavedQueryAndNotInitializeSearch() {
|
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)))
|
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
|
||||||
.thenReturn(mSavedQueryLoader);
|
.thenReturn(mSavedQueryLoader);
|
||||||
ActivityController<SearchActivity> activityController =
|
ActivityController<SearchActivity> activityController =
|
||||||
@@ -266,27 +221,14 @@ public class SearchFragmentTest {
|
|||||||
fragment.onQueryTextChange("");
|
fragment.onQueryTextChange("");
|
||||||
|
|
||||||
verify(mFeatureFactory.searchFeatureProvider, never())
|
verify(mFeatureFactory.searchFeatureProvider, never())
|
||||||
.getDatabaseSearchLoader(any(Context.class), anyString());
|
.getStaticSearchResultTask(any(Context.class), anyString());
|
||||||
verify(mFeatureFactory.searchFeatureProvider, never())
|
verify(mFeatureFactory.searchFeatureProvider, never())
|
||||||
.getInstalledAppSearchLoader(any(Context.class), anyString());
|
.getInstalledAppSearchTask(any(Context.class), anyString());
|
||||||
verify(mSavedQueryController).loadSavedQueries();
|
verify(mSavedQueryController).loadSavedQueries();
|
||||||
verify(mSearchResultsAdapter, never()).initializeSearch(anyString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateIndex_TriggerOnCreate() {
|
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)))
|
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
|
||||||
.thenReturn(mSavedQueryLoader);
|
.thenReturn(mSavedQueryLoader);
|
||||||
|
|
||||||
@@ -303,41 +245,11 @@ public class SearchFragmentTest {
|
|||||||
any(IndexingCallback.class));
|
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
|
@Test
|
||||||
public void whenNoQuery_HideFeedbackIsCalled() {
|
public void whenNoQuery_HideFeedbackIsCalled() {
|
||||||
when(mFeatureFactory.searchFeatureProvider
|
when(mFeatureFactory.searchFeatureProvider
|
||||||
.getDatabaseSearchLoader(any(Context.class), anyString()))
|
.getSearchResultLoader(any(Context.class), anyString()))
|
||||||
.thenReturn(new MockDBLoader(RuntimeEnvironment.application));
|
.thenReturn(new MockSearchResultLoader(RuntimeEnvironment.application));
|
||||||
when(mFeatureFactory.searchFeatureProvider
|
|
||||||
.getInstalledAppSearchLoader(any(Context.class), anyString()))
|
|
||||||
.thenReturn(new MockAppLoader(RuntimeEnvironment.application));
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
|
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
|
||||||
.thenReturn(mSavedQueryLoader);
|
.thenReturn(mSavedQueryLoader);
|
||||||
|
|
||||||
@@ -359,17 +271,8 @@ public class SearchFragmentTest {
|
|||||||
@Test
|
@Test
|
||||||
public void onLoadFinished_ShowsFeedback() {
|
public void onLoadFinished_ShowsFeedback() {
|
||||||
when(mFeatureFactory.searchFeatureProvider
|
when(mFeatureFactory.searchFeatureProvider
|
||||||
.getDatabaseSearchLoader(any(Context.class), anyString()))
|
.getSearchResultLoader(any(Context.class), anyString()))
|
||||||
.thenReturn(new MockDBLoader(RuntimeEnvironment.application));
|
.thenReturn(new MockSearchResultLoader(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));
|
|
||||||
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
|
when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
|
||||||
.thenReturn(mSavedQueryLoader);
|
.thenReturn(mSavedQueryLoader);
|
||||||
ActivityController<SearchActivity> activityController =
|
ActivityController<SearchActivity> activityController =
|
||||||
@@ -413,9 +316,7 @@ public class SearchFragmentTest {
|
|||||||
|
|
||||||
fragment.onIndexingFinished();
|
fragment.onIndexingFinished();
|
||||||
|
|
||||||
verify(loaderManager).initLoader(eq(SearchFragment.SearchLoaderId.DATABASE),
|
verify(loaderManager).initLoader(eq(SearchFragment.SearchLoaderId.SEARCH_RESULT),
|
||||||
eq(null), any(LoaderManager.LoaderCallbacks.class));
|
|
||||||
verify(loaderManager).initLoader(eq(SearchFragment.SearchLoaderId.INSTALLED_APPS),
|
|
||||||
eq(null), any(LoaderManager.LoaderCallbacks.class));
|
eq(null), any(LoaderManager.LoaderCallbacks.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,16 +381,13 @@ public class SearchFragmentTest {
|
|||||||
eq("test_setting"),
|
eq("test_setting"),
|
||||||
argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_COUNT)),
|
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_RANK)),
|
||||||
argThat(pairMatches(MetricsProto.MetricsEvent
|
|
||||||
.FIELD_SETTINGS_SEARCH_RESULT_ASYNC_RANKING_STATE)),
|
|
||||||
argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_QUERY_LENGTH)));
|
argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_QUERY_LENGTH)));
|
||||||
|
|
||||||
verify(mFeatureFactory.searchFeatureProvider).searchResultClicked(nullable(Context.class),
|
verify(mFeatureFactory.searchFeatureProvider).searchResultClicked(nullable(Context.class),
|
||||||
nullable(String.class), eq(searchResult));
|
nullable(String.class), eq(searchResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onResume_shouldCallSearchRankingWarmupIfSmartSearchRankingEnabled(){
|
public void onResume_shouldCallSearchRankingWarmupIfSmartSearchRankingEnabled() {
|
||||||
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any(Context.class)))
|
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any(Context.class)))
|
||||||
.thenReturn(true);
|
.thenReturn(true);
|
||||||
|
|
||||||
@@ -504,7 +402,7 @@ public class SearchFragmentTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onResume_shouldNotCallSearchRankingWarmupIfSmartSearchRankingDisabled(){
|
public void onResume_shouldNotCallSearchRankingWarmupIfSmartSearchRankingDisabled() {
|
||||||
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any(Context.class)))
|
when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any(Context.class)))
|
||||||
.thenReturn(false);
|
.thenReturn(false);
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -18,44 +18,32 @@
|
|||||||
package com.android.settings.search;
|
package com.android.settings.search;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
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.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.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.util.Pair;
|
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.TestConfig;
|
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 com.android.settings.testutils.SettingsRobolectricTestRunner;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.Captor;
|
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
import org.robolectric.Robolectric;
|
import org.robolectric.Robolectric;
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
import org.robolectric.shadows.ShadowLooper;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
@RunWith(SettingsRobolectricTestRunner.class)
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
||||||
@@ -67,25 +55,18 @@ public class SearchResultsAdapterTest {
|
|||||||
private SearchFeatureProvider mSearchFeatureProvider;
|
private SearchFeatureProvider mSearchFeatureProvider;
|
||||||
@Mock
|
@Mock
|
||||||
private Context mMockContext;
|
private Context mMockContext;
|
||||||
@Captor
|
|
||||||
private ArgumentCaptor<Integer> mSearchResultsCountCaptor =
|
|
||||||
ArgumentCaptor.forClass(Integer.class);
|
|
||||||
private SearchResultsAdapter mAdapter;
|
private SearchResultsAdapter mAdapter;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private String mLoaderClassName;
|
|
||||||
|
|
||||||
private String[] TITLES = {"alpha", "bravo", "charlie", "appAlpha", "appBravo", "appCharlie"};
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
mContext = Robolectric.buildActivity(Activity.class).get();
|
mContext = Robolectric.buildActivity(Activity.class).get();
|
||||||
mLoaderClassName = DatabaseResultLoader.class.getName();
|
|
||||||
when(mFragment.getContext()).thenReturn(mMockContext);
|
when(mFragment.getContext()).thenReturn(mMockContext);
|
||||||
when(mMockContext.getApplicationContext()).thenReturn(mContext);
|
when(mMockContext.getApplicationContext()).thenReturn(mContext);
|
||||||
when(mSearchFeatureProvider.smartSearchRankingTimeoutMs(any(Context.class)))
|
when(mSearchFeatureProvider.smartSearchRankingTimeoutMs(any(Context.class)))
|
||||||
.thenReturn(300L);
|
.thenReturn(300L);
|
||||||
mAdapter = new SearchResultsAdapter(mFragment, mSearchFeatureProvider);
|
mAdapter = new SearchResultsAdapter(mFragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -94,17 +75,6 @@ public class SearchResultsAdapterTest {
|
|||||||
assertThat(updatedResults).isEmpty();
|
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
|
@Test
|
||||||
public void testCreateViewHolder_returnsIntentResult() {
|
public void testCreateViewHolder_returnsIntentResult() {
|
||||||
ViewGroup group = new FrameLayout(mContext);
|
ViewGroup group = new FrameLayout(mContext);
|
||||||
@@ -123,387 +93,13 @@ public class SearchResultsAdapterTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEndToEndSearch_properResultsMerged_correctOrder() {
|
public void testPostSearchResults_addsDataAndDisplays() {
|
||||||
mAdapter.initializeSearch("");
|
List<SearchResult> results = getDummyDbResults();
|
||||||
mAdapter.addSearchResults(new HashSet<>(getDummyAppResults()),
|
|
||||||
InstalledAppResultLoader.class.getName());
|
|
||||||
mAdapter.addSearchResults(new HashSet<>(getDummyDbResults()),
|
|
||||||
DatabaseResultLoader.class.getName());
|
|
||||||
mAdapter.notifyResultsLoaded();
|
|
||||||
|
|
||||||
List<SearchResult> results = mAdapter.getSearchResults();
|
mAdapter.postSearchResults(results);
|
||||||
assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
|
|
||||||
assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
|
|
||||||
assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
|
|
||||||
assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
|
|
||||||
assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
|
|
||||||
assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
|
|
||||||
verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
|
|
||||||
assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
assertThat(mAdapter.getSearchResults()).containsExactlyElementsIn(results);
|
||||||
public void testEndToEndSearch_addResults_resultsAddedInOrder() {
|
verify(mFragment).onSearchResultsDisplayed(anyInt());
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<SearchResult> getDummyDbResults() {
|
private List<SearchResult> getDummyDbResults() {
|
||||||
@@ -511,78 +107,21 @@ public class SearchResultsAdapterTest {
|
|||||||
ResultPayload payload = new ResultPayload(new Intent());
|
ResultPayload payload = new ResultPayload(new Intent());
|
||||||
SearchResult.Builder builder = new SearchResult.Builder();
|
SearchResult.Builder builder = new SearchResult.Builder();
|
||||||
builder.setPayload(payload)
|
builder.setPayload(payload)
|
||||||
.setTitle(TITLES[0])
|
.setTitle("one")
|
||||||
.setRank(1)
|
.setRank(1)
|
||||||
.setStableId(Objects.hash(TITLES[0], "db"));
|
.setStableId(Objects.hash("one", "db"));
|
||||||
results.add(builder.build());
|
results.add(builder.build());
|
||||||
|
|
||||||
builder.setTitle(TITLES[1])
|
builder.setTitle("two")
|
||||||
.setRank(3)
|
.setRank(3)
|
||||||
.setStableId(Objects.hash(TITLES[1], "db"));
|
.setStableId(Objects.hash("two", "db"));
|
||||||
results.add(builder.build());
|
results.add(builder.build());
|
||||||
|
|
||||||
builder.setTitle(TITLES[2])
|
builder.setTitle("three")
|
||||||
.setRank(6)
|
.setRank(6)
|
||||||
.setStableId(Objects.hash(TITLES[2], "db"));
|
.setStableId(Objects.hash("three", "db"));
|
||||||
results.add(builder.build());
|
results.add(builder.build());
|
||||||
|
|
||||||
return results;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -21,9 +21,11 @@ import android.content.ContentValues;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import com.android.settings.TestConfig;
|
import com.android.settings.TestConfig;
|
||||||
import com.android.settings.dashboard.SiteMapManager;
|
import com.android.settings.dashboard.SiteMapManager;
|
||||||
|
import com.android.settings.search.DatabaseResultLoader.StaticSearchResultCallable;
|
||||||
import com.android.settings.search.indexing.IndexData;
|
import com.android.settings.search.indexing.IndexData;
|
||||||
import com.android.settings.testutils.DatabaseTestUtils;
|
import com.android.settings.testutils.DatabaseTestUtils;
|
||||||
import com.android.settings.testutils.FakeFeatureFactory;
|
import com.android.settings.testutils.FakeFeatureFactory;
|
||||||
@@ -39,37 +41,54 @@ import org.mockito.MockitoAnnotations;
|
|||||||
import org.robolectric.RuntimeEnvironment;
|
import org.robolectric.RuntimeEnvironment;
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
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 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.anyString;
|
||||||
import static org.mockito.Matchers.eq;
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
@RunWith(SettingsRobolectricTestRunner.class)
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
||||||
public class DatabaseResultLoaderTest {
|
public class StaticSearchResultFutureTaskTest {
|
||||||
|
|
||||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||||
private Context mMockContext;
|
private Context mMockContext;
|
||||||
@Mock
|
@Mock
|
||||||
private SiteMapManager mSiteMapManager;
|
private SiteMapManager mSiteMapManager;
|
||||||
|
@Mock
|
||||||
|
private ExecutorService mService;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
|
|
||||||
SQLiteDatabase mDb;
|
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
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
mContext = RuntimeEnvironment.application;
|
mContext = RuntimeEnvironment.application;
|
||||||
FakeFeatureFactory.setupForTest(mMockContext);
|
mFeatureFactory = FakeFeatureFactory.setupForTest(mMockContext);
|
||||||
FakeFeatureFactory factory =
|
when(mFeatureFactory.searchFeatureProvider.getExecutorService()).thenReturn(mService);
|
||||||
(FakeFeatureFactory) FakeFeatureFactory.getFactory(mMockContext);
|
when(mFeatureFactory.searchFeatureProvider.getSiteMapManager())
|
||||||
when(factory.searchFeatureProvider.getSiteMapManager())
|
|
||||||
.thenReturn(mSiteMapManager);
|
.thenReturn(mSiteMapManager);
|
||||||
mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
|
mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
|
||||||
setUpDb();
|
setUpDb();
|
||||||
@@ -81,159 +100,252 @@ public class DatabaseResultLoaderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMatchTitle() {
|
public void testMatchTitle() throws Exception {
|
||||||
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "title", mSiteMapManager);
|
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "title",
|
||||||
assertThat(loader.loadInBackground().size()).isEqualTo(2);
|
mSiteMapManager);
|
||||||
|
|
||||||
|
assertThat(loader.call()).hasSize(2);
|
||||||
verify(mSiteMapManager, times(2)).buildBreadCrumb(eq(mContext), anyString(), anyString());
|
verify(mSiteMapManager, times(2)).buildBreadCrumb(eq(mContext), anyString(), anyString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMatchSummary() {
|
public void testMatchSummary() throws Exception {
|
||||||
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "summary",
|
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "summary",
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
assertThat(loader.loadInBackground().size()).isEqualTo(2);
|
|
||||||
|
assertThat(loader.call()).hasSize(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMatchKeywords() {
|
public void testMatchKeywords() throws Exception {
|
||||||
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "keywords",
|
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "keywords",
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
assertThat(loader.loadInBackground().size()).isEqualTo(2);
|
|
||||||
|
assertThat(loader.call()).hasSize(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMatchEntries() {
|
public void testMatchEntries() throws Exception {
|
||||||
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "entries",
|
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "entries",
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
assertThat(loader.loadInBackground().size()).isEqualTo(2);
|
|
||||||
|
assertThat(loader.call()).hasSize(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSpecialCaseWord_matchesNonPrefix() {
|
public void testSpecialCaseWord_matchesNonPrefix() throws Exception {
|
||||||
insertSpecialCase("Data usage");
|
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
|
@Test
|
||||||
public void testSpecialCaseDash_matchesWordNoDash() {
|
public void testSpecialCaseDash_matchesWordNoDash() throws Exception {
|
||||||
insertSpecialCase("wi-fi calling");
|
insertSpecialCase("wi-fi calling");
|
||||||
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "wifi", mSiteMapManager);
|
|
||||||
assertThat(loader.loadInBackground().size()).isEqualTo(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "wifi",
|
||||||
public void testSpecialCaseDash_matchesWordWithDash() {
|
|
||||||
insertSpecialCase("priorités seulment");
|
|
||||||
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "priorités",
|
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
assertThat(loader.loadInBackground().size()).isEqualTo(1);
|
|
||||||
|
assertThat(loader.call()).hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSpecialCaseDash_matchesWordWithoutDash() {
|
public void testSpecialCaseDash_matchesWordWithDash() throws Exception {
|
||||||
insertSpecialCase("priorités seulment");
|
insertSpecialCase("priorités seulment");
|
||||||
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "priorites",
|
|
||||||
|
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "priorités",
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
assertThat(loader.loadInBackground().size()).isEqualTo(1);
|
|
||||||
|
assertThat(loader.call()).hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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");
|
insertSpecialCase("wi-fi calling");
|
||||||
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "wifi calling",
|
|
||||||
|
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "wifi calling",
|
||||||
mSiteMapManager);
|
mSiteMapManager);
|
||||||
assertThat(loader.loadInBackground().size()).isEqualTo(1);
|
|
||||||
|
assertThat(loader.call()).hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSpecialCasePrefix_matchesPrefixOfEntry() {
|
public void testSpecialCasePrefix_matchesPrefixOfEntry() throws Exception {
|
||||||
insertSpecialCase("Photos");
|
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
|
@Test
|
||||||
public void testSpecialCasePrefix_DoesNotMatchNonPrefixSubstring() {
|
public void testSpecialCasePrefix_DoesNotMatchNonPrefixSubstring() throws Exception {
|
||||||
insertSpecialCase("Photos");
|
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
|
@Test
|
||||||
public void testSpecialCaseMultiWordPrefix_matchesPrefixOfEntry() {
|
public void testSpecialCaseMultiWordPrefix_matchesPrefixOfEntry() throws Exception {
|
||||||
insertSpecialCase("Apps Notifications");
|
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
|
@Test
|
||||||
public void testSpecialCaseMultiWordPrefix_matchesSecondWordPrefixOfEntry() {
|
public void testSpecialCaseMultiWordPrefix_matchesSecondWordPrefixOfEntry() throws Exception {
|
||||||
insertSpecialCase("Apps Notifications");
|
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
|
@Test
|
||||||
public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfFirstEntry() {
|
public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfFirstEntry()
|
||||||
|
throws Exception {
|
||||||
insertSpecialCase("Apps Notifications");
|
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
|
@Test
|
||||||
public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfSecondEntry() {
|
public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfSecondEntry()
|
||||||
|
throws Exception {
|
||||||
insertSpecialCase("Apps Notifications");
|
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
|
@Test
|
||||||
public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfEntry() {
|
public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfEntry() throws
|
||||||
|
Exception {
|
||||||
insertSpecialCase("Apps & Notifications");
|
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
|
@Test
|
||||||
public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfSecondEntry() {
|
public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfSecondEntry()
|
||||||
|
throws Exception {
|
||||||
insertSpecialCase("Apps & Notifications");
|
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
|
@Test
|
||||||
public void testResultMatchedByMultipleQueries_duplicatesRemoved() {
|
public void testResultMatchedByMultipleQueries_duplicatesRemoved() throws Exception {
|
||||||
String key = "durr";
|
String key = "durr";
|
||||||
insertSameValueAllFieldsCase(key);
|
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
|
@Test
|
||||||
public void testSpecialCaseTwoWords_multipleResults() {
|
public void testSpecialCaseTwoWords_multipleResults() throws Exception {
|
||||||
final String caseOne = "Apple pear";
|
final String caseOne = "Apple pear";
|
||||||
final String caseTwo = "Banana apple";
|
final String caseTwo = "Banana apple";
|
||||||
insertSpecialCase(caseOne);
|
insertSpecialCase(caseOne);
|
||||||
insertSpecialCase(caseTwo);
|
insertSpecialCase(caseTwo);
|
||||||
DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "App", null);
|
StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "App", null);
|
||||||
Set<? extends SearchResult> results = loader.loadInBackground();
|
|
||||||
Set<CharSequence> expectedTitles = new HashSet<>(Arrays.asList(caseOne, caseTwo));
|
List<? extends SearchResult> results = loader.call();
|
||||||
Set<CharSequence> actualTitles = new HashSet<>();
|
|
||||||
|
Set<String> actualTitles = new HashSet<>();
|
||||||
for (SearchResult result : results) {
|
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) {
|
private void insertSpecialCase(String specialCase) {
|
||||||
|
insertSpecialCase(specialCase, specialCase.hashCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertSpecialCase(String specialCase, int docId) {
|
||||||
String normalized = IndexData.normalizeHyphen(specialCase);
|
String normalized = IndexData.normalizeHyphen(specialCase);
|
||||||
normalized = IndexData.normalizeString(normalized);
|
normalized = IndexData.normalizeString(normalized);
|
||||||
final ResultPayload payload = new ResultPayload(new Intent());
|
final ResultPayload payload = new ResultPayload(new Intent());
|
||||||
|
|
||||||
ContentValues values = new ContentValues();
|
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.LOCALE, "en-us");
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1);
|
values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1);
|
||||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, specialCase);
|
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, specialCase);
|
||||||
@@ -373,4 +485,33 @@ public class DatabaseResultLoaderTest {
|
|||||||
|
|
||||||
mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user