Files
app_Settings/src/com/android/settings/search/SearchFeatureProviderImpl.java
Matthew Fritze fb772248b1 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
2017-10-30 14:20:49 -07:00

160 lines
5.4 KiB
Java

/*
* 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.ComponentName;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.indexing.IndexData;
import com.android.settingslib.wrapper.PackageManagerWrapper;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* FeatureProvider for the refactored search code.
*/
public class SearchFeatureProviderImpl implements SearchFeatureProvider {
private static final String TAG = "SearchFeatureProvider";
private static final String METRICS_ACTION_SETTINGS_INDEX = "search_synchronous_indexing";
private DatabaseIndexingManager mDatabaseIndexingManager;
private SiteMapManager mSiteMapManager;
private ExecutorService mExecutorService;
@Override
public boolean isEnabled(Context context) {
return true;
}
@Override
public void verifyLaunchSearchResultPageCaller(Context context, ComponentName caller) {
if (caller == null) {
throw new IllegalArgumentException("ExternalSettingsTrampoline intents "
+ "must be called with startActivityForResult");
}
final String packageName = caller.getPackageName();
if (!TextUtils.equals(packageName, context.getPackageName())) {
throw new SecurityException("Only Settings app can launch search result page");
}
}
@Override
public SearchResultLoader getSearchResultLoader(Context context, String query) {
return new SearchResultLoader(context, cleanQuery(query));
}
@Override
public DatabaseResultLoader getStaticSearchResultTask(Context context, String query) {
return new DatabaseResultLoader(context, cleanQuery(query), getSiteMapManager());
}
@Override
public InstalledAppResultLoader getInstalledAppSearchTask(Context context, String query) {
return new InstalledAppResultLoader(
context, new PackageManagerWrapper(context.getPackageManager()),
cleanQuery(query), getSiteMapManager());
}
@Override
public AccessibilityServiceResultLoader getAccessibilityServiceResultTask(Context context,
String query) {
return new AccessibilityServiceResultLoader(context, cleanQuery(query),
getSiteMapManager());
}
@Override
public InputDeviceResultLoader getInputDeviceResultTask(Context context, String query) {
return new InputDeviceResultLoader(context, cleanQuery(query), getSiteMapManager());
}
@Override
public SavedQueryLoader getSavedQueryLoader(Context context) {
return new SavedQueryLoader(context);
}
@Override
public DatabaseIndexingManager getIndexingManager(Context context) {
if (mDatabaseIndexingManager == null) {
mDatabaseIndexingManager = new DatabaseIndexingManager(context.getApplicationContext());
}
return mDatabaseIndexingManager;
}
@Override
public boolean isIndexingComplete(Context context) {
return getIndexingManager(context).isIndexingComplete();
}
public SiteMapManager getSiteMapManager() {
if (mSiteMapManager == null) {
mSiteMapManager = new SiteMapManager();
}
return mSiteMapManager;
}
@Override
public void updateIndexAsync(Context context, IndexingCallback callback) {
if (SettingsSearchIndexablesProvider.DEBUG) {
Log.d(TAG, "updating index async");
}
getIndexingManager(context).indexDatabase(callback);
}
@Override
public void updateIndex(Context context) {
long indexStartTime = System.currentTimeMillis();
getIndexingManager(context).performIndexing();
int indexingTime = (int) (System.currentTimeMillis() - indexStartTime);
FeatureFactory.getFactory(context).getMetricsFeatureProvider()
.histogram(context, METRICS_ACTION_SETTINGS_INDEX, indexingTime);
}
@Override
public ExecutorService getExecutorService() {
if (mExecutorService == null) {
mExecutorService = Executors.newCachedThreadPool();
}
return mExecutorService;
}
/**
* A generic method to make the query suitable for searching the database.
*
* @return the cleaned query string
*/
@VisibleForTesting
String cleanQuery(String query) {
if (TextUtils.isEmpty(query)) {
return null;
}
if (Locale.getDefault().equals(Locale.JAPAN)) {
query = IndexData.normalizeJapaneseString(query);
}
return query.trim();
}
}