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