1 package com.android.settings.intelligence.search;
2 
3 import android.content.Context;
4 import androidx.annotation.NonNull;
5 import android.util.ArrayMap;
6 import android.util.Log;
7 
8 import com.android.settings.intelligence.overlay.FeatureFactory;
9 import com.android.settings.intelligence.search.query.DatabaseResultTask;
10 import com.android.settings.intelligence.search.query.SearchQueryTask;
11 
12 import java.util.ArrayList;
13 import java.util.Collections;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.PriorityQueue;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.ExecutorService;
19 import java.util.concurrent.TimeUnit;
20 import java.util.concurrent.TimeoutException;
21 
22 /**
23  * Collects the sorted list of all setting search results.
24  */
25 public class SearchResultAggregator {
26 
27     private static final String TAG = "SearchResultAggregator";
28 
29     /**
30      * Timeout for subsequent tasks to allow for fast returning tasks.
31      * TODO(70164062): Tweak the timeout values.
32      */
33     private static final long SHORT_CHECK_TASK_TIMEOUT_MS = 600;
34 
35     private static SearchResultAggregator sResultAggregator;
36 
SearchResultAggregator()37     private SearchResultAggregator() {
38     }
39 
getInstance()40     public static SearchResultAggregator getInstance() {
41         if (sResultAggregator == null) {
42             sResultAggregator = new SearchResultAggregator();
43         }
44 
45         return sResultAggregator;
46     }
47 
48     @NonNull
fetchResults(Context context, String query)49     public synchronized List<? extends SearchResult> fetchResults(Context context, String query) {
50         final SearchFeatureProvider mFeatureProvider = FeatureFactory.get(context)
51                 .searchFeatureProvider();
52         final ExecutorService executorService = mFeatureProvider.getExecutorService();
53 
54         final List<SearchQueryTask> tasks =
55                 mFeatureProvider.getSearchQueryTasks(context, query);
56         // Start tasks
57         for (SearchQueryTask task : tasks) {
58             executorService.execute(task);
59         }
60 
61         // Collect results
62         final Map<Integer, List<? extends SearchResult>> taskResults = new ArrayMap<>();
63         final long allTasksStart = System.currentTimeMillis();
64         for (SearchQueryTask task : tasks) {
65             final int taskId = task.getTaskId();
66             try {
67                 taskResults.put(taskId,
68                         task.get(SHORT_CHECK_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
69             } catch (TimeoutException | InterruptedException | ExecutionException e) {
70                 Log.d(TAG, "Could not retrieve result in time: " + taskId, e);
71                 taskResults.put(taskId, Collections.EMPTY_LIST);
72             }
73         }
74 
75         // Merge results
76         final long mergeStartTime = System.currentTimeMillis();
77         if (SearchFeatureProvider.DEBUG) {
78             Log.d(TAG, "Total result loader time: " + (mergeStartTime - allTasksStart));
79         }
80         final List<? extends SearchResult> mergedResults = mergeSearchResults(taskResults);
81         if (SearchFeatureProvider.DEBUG) {
82             Log.d(TAG, "Total merge time: " + (System.currentTimeMillis() - mergeStartTime));
83             Log.d(TAG, "Total aggregator time: " + (System.currentTimeMillis() - allTasksStart));
84         }
85 
86         return mergedResults;
87     }
88 
89     // TODO (b/68255021) scale the dynamic search results ranks
mergeSearchResults( Map<Integer, List<? extends SearchResult>> taskResults)90     private List<? extends SearchResult> mergeSearchResults(
91             Map<Integer, List<? extends SearchResult>> taskResults) {
92 
93         final List<SearchResult> searchResults = new ArrayList<>();
94         // First add db results as a special case
95         searchResults.addAll(taskResults.remove(DatabaseResultTask.QUERY_WORKER_ID));
96 
97         // Merge the rest into result list: add everything to heap then pop them out one by one.
98         final PriorityQueue<SearchResult> heap = new PriorityQueue<>();
99         for (List<? extends SearchResult> taskResult : taskResults.values()) {
100             heap.addAll(taskResult);
101         }
102         while (!heap.isEmpty()) {
103             searchResults.add(heap.poll());
104         }
105         return searchResults;
106     }
107 }
108