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 17 package com.android.quicksearchbox; 18 19 import com.android.quicksearchbox.util.BatchingNamedTaskExecutor; 20 import com.android.quicksearchbox.util.Consumer; 21 import com.android.quicksearchbox.util.NamedTaskExecutor; 22 import com.android.quicksearchbox.util.Util; 23 24 import android.os.Handler; 25 import android.util.Log; 26 27 import java.util.ArrayList; 28 import java.util.List; 29 import java.util.Set; 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 canceled. 36 */ 37 public 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 Logger mLogger; 53 54 private final ShouldQueryStrategy mShouldQueryStrategy = new ShouldQueryStrategy(); 55 56 private final Corpora mCorpora; 57 58 private BatchingNamedTaskExecutor mBatchingExecutor; 59 60 public SuggestionsProviderImpl(Config config, 61 NamedTaskExecutor queryExecutor, 62 Handler publishThread, 63 Promoter promoter, 64 ShortcutRepository shortcutRepo, 65 Corpora corpora, 66 Logger logger) { 67 mConfig = config; 68 mQueryExecutor = queryExecutor; 69 mPublishThread = publishThread; 70 mPromoter = promoter; 71 mShortcutRepo = shortcutRepo; 72 mLogger = logger; 73 mCorpora = corpora; 74 } 75 76 public void close() { 77 cancelPendingTasks(); 78 } 79 80 /** 81 * Cancels all pending query tasks. 82 */ 83 private void cancelPendingTasks() { 84 if (mBatchingExecutor != null) { 85 mBatchingExecutor.cancelPendingTasks(); 86 mBatchingExecutor = null; 87 } 88 } 89 90 protected SuggestionCursor getShortcutsForQuery(String query, List<Corpus> corpora, 91 int maxShortcuts) { 92 if (mShortcutRepo == null) return null; 93 return mShortcutRepo.getShortcutsForQuery(query, corpora, maxShortcuts); 94 } 95 96 /** 97 * Gets the sources that should be queried for the given query. 98 */ 99 private List<Corpus> getCorporaToQuery(String query, List<Corpus> orderedCorpora) { 100 ArrayList<Corpus> corporaToQuery = new ArrayList<Corpus>(orderedCorpora.size()); 101 for (Corpus corpus : orderedCorpora) { 102 if (shouldQueryCorpus(corpus, query)) { 103 corporaToQuery.add(corpus); 104 } 105 } 106 return corporaToQuery; 107 } 108 109 protected boolean shouldQueryCorpus(Corpus corpus, String query) { 110 if (query.length() == 0 && !corpus.isWebCorpus()) { 111 // Only the web corpus sees zero length queries. 112 return false; 113 } 114 return mShouldQueryStrategy.shouldQueryCorpus(corpus, query); 115 } 116 117 private void updateShouldQueryStrategy(CorpusResult cursor) { 118 if (cursor.getCount() == 0) { 119 mShouldQueryStrategy.onZeroResults(cursor.getCorpus(), 120 cursor.getUserQuery()); 121 } 122 } 123 124 public Suggestions getSuggestions(String query, List<Corpus> corpora, int maxSuggestions) { 125 if (DBG) Log.d(TAG, "getSuggestions(" + query + ")"); 126 cancelPendingTasks(); 127 List<Corpus> corporaToQuery = getCorporaToQuery(query, corpora); 128 final Suggestions suggestions = new Suggestions(mPromoter, 129 maxSuggestions, 130 query, 131 corporaToQuery.size()); 132 int maxShortcuts = mConfig.getMaxShortcutsReturned(); 133 SuggestionCursor shortcuts = getShortcutsForQuery(query, corpora, maxShortcuts); 134 if (shortcuts != null) { 135 suggestions.setShortcuts(shortcuts); 136 } 137 138 // Fast path for the zero sources case 139 if (corporaToQuery.size() == 0) { 140 return suggestions; 141 } 142 143 int initialBatchSize = countDefaultCorpora(corporaToQuery); 144 initialBatchSize = Math.min(initialBatchSize, mConfig.getNumPromotedSources()); 145 if (initialBatchSize == 0) { 146 initialBatchSize = mConfig.getNumPromotedSources(); 147 } 148 149 mBatchingExecutor = new BatchingNamedTaskExecutor(mQueryExecutor); 150 151 SuggestionCursorReceiver receiver = new SuggestionCursorReceiver( 152 mBatchingExecutor, suggestions, initialBatchSize); 153 154 int maxResultsPerSource = mConfig.getMaxResultsPerSource(); 155 QueryTask.startQueries(query, maxResultsPerSource, corporaToQuery, mBatchingExecutor, 156 mPublishThread, receiver); 157 mBatchingExecutor.executeNextBatch(initialBatchSize); 158 159 return suggestions; 160 } 161 162 private int countDefaultCorpora(List<Corpus> corpora) { 163 int count = 0; 164 for (Corpus corpus : corpora) { 165 if (mCorpora.isCorpusDefaultEnabled(corpus)) { 166 count++; 167 } 168 } 169 return count; 170 } 171 172 private class SuggestionCursorReceiver implements Consumer<CorpusResult> { 173 private final BatchingNamedTaskExecutor mExecutor; 174 private final Suggestions mSuggestions; 175 176 private int mCountAtWhichToExecuteNextBatch; 177 178 public SuggestionCursorReceiver(BatchingNamedTaskExecutor executor, 179 Suggestions suggestions, int initialBatchSize) { 180 mExecutor = executor; 181 mSuggestions = suggestions; 182 mCountAtWhichToExecuteNextBatch = initialBatchSize; 183 } 184 185 public boolean consume(CorpusResult cursor) { 186 updateShouldQueryStrategy(cursor); 187 mSuggestions.addCorpusResult(cursor); 188 if (!mSuggestions.isClosed()) { 189 executeNextBatchIfNeeded(); 190 } 191 if (cursor != null && mLogger != null) { 192 mLogger.logLatency(cursor); 193 } 194 return true; 195 } 196 197 private void executeNextBatchIfNeeded() { 198 if (mSuggestions.getSourceCount() == mCountAtWhichToExecuteNextBatch) { 199 // We've just finished one batch 200 if (mSuggestions.getPromoted().getCount() < mConfig.getMaxPromotedSuggestions()) { 201 // But we still don't have enough results, ask for more 202 int nextBatchSize = mConfig.getNumPromotedSources(); 203 mCountAtWhichToExecuteNextBatch += nextBatchSize; 204 mExecutor.executeNextBatch(nextBatchSize); 205 } 206 } 207 } 208 } 209 210 } 211