SuggestionsProviderImpl.java revision 3cb8178193a41f6c74ee396c318385a50dd624e1
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.quicksearchbox; 18 19import com.android.quicksearchbox.util.BatchingNamedTaskExecutor; 20import com.android.quicksearchbox.util.Consumer; 21import com.android.quicksearchbox.util.NamedTaskExecutor; 22 23import android.os.Handler; 24import android.util.Log; 25 26import java.util.ArrayList; 27import java.util.Collection; 28import java.util.Collections; 29import java.util.List; 30 31/** 32 * Suggestions provider implementation. 33 * 34 * The provider will only handle a single query at a time. If a new query comes 35 * in, the old one is cancelled. 36 */ 37public class SuggestionsProviderImpl implements SuggestionsProvider { 38 39 private static final boolean DBG = false; 40 private static final String TAG = "QSB.SuggestionsProviderImpl"; 41 42 private final Config mConfig; 43 44 private final NamedTaskExecutor mQueryExecutor; 45 46 private final Handler mPublishThread; 47 48 private final Promoter mPromoter; 49 50 private final ShortcutRepository mShortcutRepo; 51 52 private final ShouldQueryStrategy mShouldQueryStrategy = new ShouldQueryStrategy(); 53 54 private final Corpora mCorpora; 55 56 private final CorpusRanker mCorpusRanker; 57 58 private final Logger mLogger; 59 60 private BatchingNamedTaskExecutor mBatchingExecutor; 61 62 public SuggestionsProviderImpl(Config config, 63 NamedTaskExecutor queryExecutor, 64 Handler publishThread, 65 Promoter promoter, 66 ShortcutRepository shortcutRepo, 67 Corpora corpora, 68 CorpusRanker corpusRanker, 69 Logger logger) { 70 mConfig = config; 71 mQueryExecutor = queryExecutor; 72 mPublishThread = publishThread; 73 mPromoter = promoter; 74 mShortcutRepo = shortcutRepo; 75 mCorpora = corpora; 76 mCorpusRanker = corpusRanker; 77 mLogger = logger; 78 } 79 80 public void close() { 81 cancelPendingTasks(); 82 } 83 84 /** 85 * Cancels all pending query tasks. 86 */ 87 private void cancelPendingTasks() { 88 if (mBatchingExecutor != null) { 89 mBatchingExecutor.cancelPendingTasks(); 90 mBatchingExecutor = null; 91 } 92 } 93 94 protected SuggestionCursor getShortcutsForQuery(String query, Corpus singleCorpus, 95 int maxShortcuts) { 96 if (mShortcutRepo == null) return null; 97 Collection<Corpus> allowedCorpora; 98 if (singleCorpus == null) { 99 allowedCorpora = mCorpora.getEnabledCorpora(); 100 } else { 101 allowedCorpora = Collections.singletonList(singleCorpus); 102 } 103 return mShortcutRepo.getShortcutsForQuery(query, allowedCorpora, maxShortcuts); 104 } 105 106 /** 107 * Gets the sources that should be queried for the given query. 108 */ 109 private List<Corpus> getCorporaToQuery(String query, Corpus singleCorpus) { 110 if (singleCorpus != null) return Collections.singletonList(singleCorpus); 111 List<Corpus> orderedCorpora = mCorpusRanker.getRankedCorpora(); 112 ArrayList<Corpus> corporaToQuery = new ArrayList<Corpus>(orderedCorpora.size()); 113 for (Corpus corpus : orderedCorpora) { 114 if (shouldQueryCorpus(corpus, query)) { 115 corporaToQuery.add(corpus); 116 } 117 } 118 return corporaToQuery; 119 } 120 121 protected boolean shouldQueryCorpus(Corpus corpus, String query) { 122 if (query.length() == 0 && !corpus.isWebCorpus()) { 123 // Only the web corpus sees zero length queries. 124 return false; 125 } 126 return mShouldQueryStrategy.shouldQueryCorpus(corpus, query); 127 } 128 129 private void updateShouldQueryStrategy(CorpusResult cursor) { 130 if (cursor.getCount() == 0) { 131 mShouldQueryStrategy.onZeroResults(cursor.getCorpus(), 132 cursor.getUserQuery()); 133 } 134 } 135 136 public Suggestions getSuggestions(String query, Corpus singleCorpus, int maxSuggestions) { 137 if (DBG) Log.d(TAG, "getSuggestions(" + query + ")"); 138 cancelPendingTasks(); 139 List<Corpus> corporaToQuery = getCorporaToQuery(query, singleCorpus); 140 final Suggestions suggestions = new Suggestions(mPromoter, 141 maxSuggestions, 142 query, 143 corporaToQuery); 144 int maxShortcuts = mConfig.getMaxShortcutsReturned(); 145 SuggestionCursor shortcuts = getShortcutsForQuery(query, singleCorpus, maxShortcuts); 146 if (shortcuts != null) { 147 suggestions.setShortcuts(shortcuts); 148 } 149 150 // Fast path for the zero sources case 151 if (corporaToQuery.size() == 0) { 152 return suggestions; 153 } 154 155 int initialBatchSize = countDefaultCorpora(corporaToQuery); 156 if (initialBatchSize == 0) { 157 initialBatchSize = mConfig.getNumPromotedSources(); 158 } 159 160 mBatchingExecutor = new BatchingNamedTaskExecutor(mQueryExecutor); 161 162 SuggestionCursorReceiver receiver = new SuggestionCursorReceiver( 163 mBatchingExecutor, suggestions, initialBatchSize); 164 165 int maxResultsPerSource = mConfig.getMaxResultsPerSource(); 166 QueryTask.startQueries(query, maxResultsPerSource, corporaToQuery, mBatchingExecutor, 167 mPublishThread, receiver); 168 mBatchingExecutor.executeNextBatch(initialBatchSize); 169 170 return suggestions; 171 } 172 173 private int countDefaultCorpora(List<Corpus> corpora) { 174 int count = 0; 175 for (Corpus corpus : corpora) { 176 if (corpus.isCorpusDefaultEnabled()) { 177 count++; 178 } 179 } 180 return count; 181 } 182 183 private class SuggestionCursorReceiver implements Consumer<CorpusResult> { 184 private final BatchingNamedTaskExecutor mExecutor; 185 private final Suggestions mSuggestions; 186 187 private int mCountAtWhichToExecuteNextBatch; 188 189 public SuggestionCursorReceiver(BatchingNamedTaskExecutor executor, 190 Suggestions suggestions, int initialBatchSize) { 191 mExecutor = executor; 192 mSuggestions = suggestions; 193 mCountAtWhichToExecuteNextBatch = initialBatchSize; 194 } 195 196 public boolean consume(CorpusResult cursor) { 197 updateShouldQueryStrategy(cursor); 198 mSuggestions.addCorpusResult(cursor); 199 if (!mSuggestions.isClosed()) { 200 executeNextBatchIfNeeded(); 201 } 202 if (cursor != null && mLogger != null) { 203 mLogger.logLatency(cursor); 204 } 205 return true; 206 } 207 208 private void executeNextBatchIfNeeded() { 209 if (mSuggestions.getSourceCount() == mCountAtWhichToExecuteNextBatch) { 210 // We've just finished one batch 211 if (mSuggestions.getPromoted().getCount() < mConfig.getMaxPromotedSuggestions()) { 212 // But we still don't have enough results, ask for more 213 int nextBatchSize = mConfig.getNumPromotedSources(); 214 mCountAtWhichToExecuteNextBatch += nextBatchSize; 215 mExecutor.executeNextBatch(nextBatchSize); 216 } 217 } 218 } 219 } 220 221} 222