blob: cc36143df03462fb7cc928663741a2015a654292 [file] [log] [blame]
package com.android.settings.intelligence.search;
import android.content.Context;
import androidx.annotation.NonNull;
import android.util.ArrayMap;
import android.util.Log;
import com.android.settings.intelligence.overlay.FeatureFactory;
import com.android.settings.intelligence.search.query.DatabaseResultTask;
import com.android.settings.intelligence.search.query.SearchQueryTask;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
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.
*/
public class SearchResultAggregator {
private static final String TAG = "SearchResultAggregator";
/**
* Timeout for subsequent tasks to allow for fast returning tasks.
* TODO(70164062): Tweak the timeout values.
*/
private static final long SHORT_CHECK_TASK_TIMEOUT_MS = 600;
private static SearchResultAggregator sResultAggregator;
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) {
final SearchFeatureProvider mFeatureProvider = FeatureFactory.get(context)
.searchFeatureProvider();
final ExecutorService executorService = mFeatureProvider.getExecutorService();
final List<SearchQueryTask> tasks =
mFeatureProvider.getSearchQueryTasks(context, query);
// Start tasks
for (SearchQueryTask task : tasks) {
executorService.execute(task);
}
// Collect results
final Map<Integer, List<? extends SearchResult>> taskResults = new ArrayMap<>();
final long allTasksStart = System.currentTimeMillis();
for (SearchQueryTask task : tasks) {
final int taskId = task.getTaskId();
try {
taskResults.put(taskId,
task.get(SHORT_CHECK_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
} catch (TimeoutException | InterruptedException | ExecutionException e) {
Log.d(TAG, "Could not retrieve result in time: " + taskId, e);
taskResults.put(taskId, Collections.EMPTY_LIST);
}
}
// Merge results
final long mergeStartTime = System.currentTimeMillis();
if (SearchFeatureProvider.DEBUG) {
Log.d(TAG, "Total result loader time: " + (mergeStartTime - allTasksStart));
}
final List<? extends SearchResult> mergedResults = mergeSearchResults(taskResults);
if (SearchFeatureProvider.DEBUG) {
Log.d(TAG, "Total merge time: " + (System.currentTimeMillis() - mergeStartTime));
Log.d(TAG, "Total aggregator time: " + (System.currentTimeMillis() - allTasksStart));
}
return mergedResults;
}
// TODO (b/68255021) scale the dynamic search results ranks
private List<? extends SearchResult> mergeSearchResults(
Map<Integer, List<? extends SearchResult>> taskResults) {
final List<SearchResult> searchResults = new ArrayList<>();
// First add db results as a special case
searchResults.addAll(taskResults.remove(DatabaseResultTask.QUERY_WORKER_ID));
// Merge the rest into result list: add everything to heap then pop them out one by one.
final PriorityQueue<SearchResult> heap = new PriorityQueue<>();
for (List<? extends SearchResult> taskResult : taskResults.values()) {
heap.addAll(taskResult);
}
while (!heap.isEmpty()) {
searchResults.add(heap.poll());
}
return searchResults;
}
}