Move search querying into a single API
Settings now collects search results from a single loader which fetches from an aggregator. This is to facilitate the separation of search functionalitiy, where "query" becomes a single synchronous call. In this case, the aggregator will move to the unbundled app and would be called on the other end of the Query call. i.e. the new search result loader will just call query, and unbundled search will handle everything else. An important implication is that the results will be returned in a ranked order. Thus the ranking and merging logic has been moved out of the RecyclerView adapter (which is a good clean-up, anyway). The SearchResultAggregator starts a Future for each of the data sources: - Static Results - Installed Apps - Input Devices - Accessibility Services We allow up to 500ms to collect the static results, and then an additional 150ms for each subsequent loader. In my quick tests, the static results take about 20-30ms to load. The longest loader is installed apps which takes roughly 50-60ms seconds (note that this will be improved with dynamic result caching). To handle the ranking in DatabaseResultLoader, we start a Future to collect the dynamic ranking before we start the SQL queries. When the SQL is done, we wait the same timeout as before. Then we merge the results, as before. For now we have not changed how the Dynamic results are collected, but eventually they will be a cache of dynamic results. Bug: 33577327 Bug: 67360547 Test: robotests Change-Id: I91fb03f9fd059672a970f48bea21c8d655007fa3
This commit is contained in:
@@ -26,6 +26,7 @@ import android.content.pm.ServiceInfo;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.hardware.input.KeyboardLayout;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.util.Log;
|
||||
import android.view.InputDevice;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
@@ -35,20 +36,24 @@ import com.android.settings.R;
|
||||
import com.android.settings.dashboard.SiteMapManager;
|
||||
import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
|
||||
import com.android.settings.inputmethod.PhysicalKeyboardFragment;
|
||||
import com.android.settings.utils.AsyncLoader;
|
||||
import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
/**
|
||||
* Search result for input devices (physical/virtual keyboard, game controllers, etc)
|
||||
*/
|
||||
public class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchResult>> {
|
||||
private static final int NAME_NO_MATCH = -1;
|
||||
|
||||
public class InputDeviceResultLoader extends FutureTask<List<? extends SearchResult>> {
|
||||
|
||||
private static final String TAG = "InputResultFutureTask";
|
||||
|
||||
@VisibleForTesting
|
||||
static final String PHYSICAL_KEYBOARD_FRAGMENT = PhysicalKeyboardFragment.class.getName();
|
||||
@@ -56,145 +61,151 @@ public class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchRes
|
||||
static final String VIRTUAL_KEYBOARD_FRAGMENT =
|
||||
AvailableVirtualKeyboardFragment.class.getName();
|
||||
|
||||
private final SiteMapManager mSiteMapManager;
|
||||
private final InputManager mInputManager;
|
||||
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();
|
||||
public InputDeviceResultLoader(Context context, String query, SiteMapManager manager) {
|
||||
super(new InputDeviceResultCallable(context, query, manager));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDiscardResult(Set<? extends SearchResult> result) {
|
||||
}
|
||||
static class InputDeviceResultCallable implements
|
||||
Callable<List<? extends SearchResult>> {
|
||||
private static final int NAME_NO_MATCH = -1;
|
||||
|
||||
@Override
|
||||
public Set<? extends SearchResult> loadInBackground() {
|
||||
final Set<SearchResult> results = new HashSet<>();
|
||||
results.addAll(buildPhysicalKeyboardSearchResults());
|
||||
results.addAll(buildVirtualKeyboardSearchResults());
|
||||
return results;
|
||||
}
|
||||
private final Context mContext;
|
||||
private final SiteMapManager mSiteMapManager;
|
||||
private final InputManager mInputManager;
|
||||
private final InputMethodManager mImm;
|
||||
private final PackageManager mPackageManager;
|
||||
@VisibleForTesting
|
||||
final String mQuery;
|
||||
|
||||
private Set<SearchResult> buildPhysicalKeyboardSearchResults() {
|
||||
final Set<SearchResult> results = new HashSet<>();
|
||||
final Context context = getContext();
|
||||
final String screenTitle = context.getString(R.string.physical_keyboard_title);
|
||||
private List<String> mPhysicalKeyboardBreadcrumb;
|
||||
private List<String> mVirtualKeyboardBreadcrumb;
|
||||
|
||||
for (final InputDevice device : getPhysicalFullKeyboards()) {
|
||||
final String deviceName = device.getName();
|
||||
final int wordDiff = InstalledAppResultLoader.getWordDifference(deviceName, mQuery);
|
||||
if (wordDiff == NAME_NO_MATCH) {
|
||||
continue;
|
||||
public InputDeviceResultCallable(Context context, String query, SiteMapManager mapManager) {
|
||||
mContext = context;
|
||||
mQuery = query;
|
||||
mSiteMapManager = mapManager;
|
||||
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
|
||||
.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;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private Set<SearchResult> buildVirtualKeyboardSearchResults() {
|
||||
final Set<SearchResult> results = new HashSet<>();
|
||||
final Context context = getContext();
|
||||
final String screenTitle = context.getString(R.string.add_virtual_keyboard);
|
||||
final List<InputMethodInfo> inputMethods = mImm.getInputMethodList();
|
||||
for (InputMethodInfo info : inputMethods) {
|
||||
final String title = info.loadLabel(mPackageManager).toString();
|
||||
final String summary = InputMethodAndSubtypeUtil
|
||||
.getSubtypeLocaleNameListAsSentence(getAllSubtypesOf(info), context, info);
|
||||
int wordDiff = InstalledAppResultLoader.getWordDifference(title, mQuery);
|
||||
if (wordDiff == NAME_NO_MATCH) {
|
||||
wordDiff = InstalledAppResultLoader.getWordDifference(summary, mQuery);
|
||||
private Set<SearchResult> buildVirtualKeyboardSearchResults() {
|
||||
final Set<SearchResult> results = new HashSet<>();
|
||||
final String screenTitle = mContext.getString(R.string.add_virtual_keyboard);
|
||||
final List<InputMethodInfo> inputMethods = mImm.getInputMethodList();
|
||||
for (InputMethodInfo info : inputMethods) {
|
||||
final String title = info.loadLabel(mPackageManager).toString();
|
||||
final String summary = InputMethodAndSubtypeUtil
|
||||
.getSubtypeLocaleNameListAsSentence(getAllSubtypesOf(info), mContext, info);
|
||||
int wordDiff = InstalledAppResultLoader.getWordDifference(title, mQuery);
|
||||
if (wordDiff == NAME_NO_MATCH) {
|
||||
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) {
|
||||
continue;
|
||||
return results;
|
||||
}
|
||||
|
||||
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();
|
||||
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 mPhysicalKeyboardBreadcrumb;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private List<String> getPhysicalKeyboardBreadCrumb() {
|
||||
if (mPhysicalKeyboardBreadcrumb == null || mPhysicalKeyboardBreadcrumb.isEmpty()) {
|
||||
final Context context = getContext();
|
||||
mPhysicalKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
|
||||
context, PHYSICAL_KEYBOARD_FRAGMENT,
|
||||
context.getString(R.string.physical_keyboard_title));
|
||||
|
||||
private List<String> getVirtualKeyboardBreadCrumb() {
|
||||
if (mVirtualKeyboardBreadcrumb == null || mVirtualKeyboardBreadcrumb.isEmpty()) {
|
||||
final Context context = mContext;
|
||||
mVirtualKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
|
||||
context, VIRTUAL_KEYBOARD_FRAGMENT,
|
||||
context.getString(R.string.add_virtual_keyboard));
|
||||
}
|
||||
return mVirtualKeyboardBreadcrumb;
|
||||
}
|
||||
return mPhysicalKeyboardBreadcrumb;
|
||||
}
|
||||
|
||||
|
||||
private List<String> getVirtualKeyboardBreadCrumb() {
|
||||
if (mVirtualKeyboardBreadcrumb == null || mVirtualKeyboardBreadcrumb.isEmpty()) {
|
||||
final Context context = getContext();
|
||||
mVirtualKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
|
||||
context, VIRTUAL_KEYBOARD_FRAGMENT,
|
||||
context.getString(R.string.add_virtual_keyboard));
|
||||
}
|
||||
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);
|
||||
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) {
|
||||
final int subtypeCount = imi.getSubtypeCount();
|
||||
final List<InputMethodSubtype> allSubtypes = new ArrayList<>(subtypeCount);
|
||||
for (int index = 0; index < subtypeCount; index++) {
|
||||
allSubtypes.add(imi.getSubtypeAt(index));
|
||||
private static List<InputMethodSubtype> getAllSubtypesOf(final InputMethodInfo imi) {
|
||||
final int subtypeCount = imi.getSubtypeCount();
|
||||
final List<InputMethodSubtype> allSubtypes = new ArrayList<>(subtypeCount);
|
||||
for (int index = 0; index < subtypeCount; index++) {
|
||||
allSubtypes.add(imi.getSubtypeAt(index));
|
||||
}
|
||||
return allSubtypes;
|
||||
}
|
||||
return allSubtypes;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user