SuggestionsProviderImpl.java revision b83882b9efa37ec0f20a0f1c85cf5ccc93194aee
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.List; 28 29/** 30 * Suggestions provider implementation. 31 * 32 * The provider will only handle a single query at a time. If a new query comes 33 * in, the old one is cancelled. 34 */ 35public class SuggestionsProviderImpl implements SuggestionsProvider { 36 37 private static final boolean DBG = false; 38 private static final String TAG = "QSB.SuggestionsProviderImpl"; 39 40 private final Config mConfig; 41 42 private final NamedTaskExecutor mQueryExecutor; 43 44 private final Handler mPublishThread; 45 46 private final ShouldQueryStrategy mShouldQueryStrategy = new ShouldQueryStrategy(); 47 48 private final Logger mLogger; 49 50 private BatchingNamedTaskExecutor mBatchingExecutor; 51 52 public SuggestionsProviderImpl(Config config, 53 NamedTaskExecutor queryExecutor, 54 Handler publishThread, 55 Logger logger) { 56 mConfig = config; 57 mQueryExecutor = queryExecutor; 58 mPublishThread = publishThread; 59 mLogger = logger; 60 } 61 62 public void close() { 63 cancelPendingTasks(); 64 } 65 66 /** 67 * Cancels all pending query tasks. 68 */ 69 private void cancelPendingTasks() { 70 if (mBatchingExecutor != null) { 71 mBatchingExecutor.cancelPendingTasks(); 72 mBatchingExecutor = null; 73 } 74 } 75 76 /** 77 * Gets the sources that should be queried for the given query. 78 */ 79 private List<Corpus> filterCorpora(String query, List<Corpus> corpora) { 80 // If there is only one corpus, always query it 81 if (corpora.size() <= 1) return corpora; 82 ArrayList<Corpus> corporaToQuery = new ArrayList<Corpus>(corpora.size()); 83 for (Corpus corpus : corpora) { 84 if (shouldQueryCorpus(corpus, query)) { 85 if (DBG) Log.d(TAG, "should query corpus " + corpus); 86 corporaToQuery.add(corpus); 87 } else { 88 if (DBG) Log.d(TAG, "should NOT query corpus " + corpus); 89 } 90 } 91 if (DBG) Log.d(TAG, "getCorporaToQuery corporaToQuery=" + corporaToQuery); 92 return corporaToQuery; 93 } 94 95 protected boolean shouldQueryCorpus(Corpus corpus, String query) { 96 if (query.length() == 0 && !corpus.isWebCorpus()) { 97 // Only the web corpus sees zero length queries. 98 return false; 99 } 100 return mShouldQueryStrategy.shouldQueryCorpus(corpus, query); 101 } 102 103 private void updateShouldQueryStrategy(CorpusResult cursor) { 104 if (cursor.getCount() == 0) { 105 mShouldQueryStrategy.onZeroResults(cursor.getCorpus(), 106 cursor.getUserQuery()); 107 } 108 } 109 110 public Suggestions getSuggestions(String query, List<Corpus> corporaToQuery) { 111 if (DBG) Log.d(TAG, "getSuggestions(" + query + ")"); 112 corporaToQuery = filterCorpora(query, corporaToQuery); 113 final Suggestions suggestions = new Suggestions(query, corporaToQuery); 114 115 // Fast path for the zero sources case 116 if (corporaToQuery.size() == 0) { 117 return suggestions; 118 } 119 120 int initialBatchSize = countDefaultCorpora(corporaToQuery); 121 if (initialBatchSize == 0) { 122 initialBatchSize = mConfig.getNumPromotedSources(); 123 } 124 125 mBatchingExecutor = new BatchingNamedTaskExecutor(mQueryExecutor); 126 127 long publishResultDelayMillis = mConfig.getPublishResultDelayMillis(); 128 SuggestionCursorReceiver receiver = new SuggestionCursorReceiver( 129 mBatchingExecutor, suggestions, initialBatchSize, 130 publishResultDelayMillis); 131 132 int maxResultsPerSource = mConfig.getMaxResultsPerSource(); 133 QueryTask.startQueries(query, maxResultsPerSource, corporaToQuery, mBatchingExecutor, 134 mPublishThread, receiver, corporaToQuery.size() == 1); 135 mBatchingExecutor.executeNextBatch(initialBatchSize); 136 137 return suggestions; 138 } 139 140 private int countDefaultCorpora(List<Corpus> corpora) { 141 int count = 0; 142 for (Corpus corpus : corpora) { 143 if (corpus.isCorpusDefaultEnabled()) { 144 count++; 145 } 146 } 147 return count; 148 } 149 150 private class SuggestionCursorReceiver implements Consumer<CorpusResult> { 151 private final BatchingNamedTaskExecutor mExecutor; 152 private final Suggestions mSuggestions; 153 private final long mResultPublishDelayMillis; 154 private final ArrayList<CorpusResult> mPendingResults; 155 private final Runnable mResultPublishTask = new Runnable () { 156 public void run() { 157 if (DBG) Log.d(TAG, "Publishing delayed results"); 158 publishPendingResults(); 159 } 160 }; 161 162 private int mCountAtWhichToExecuteNextBatch; 163 164 public SuggestionCursorReceiver(BatchingNamedTaskExecutor executor, 165 Suggestions suggestions, int initialBatchSize, 166 long publishResultDelayMillis) { 167 mExecutor = executor; 168 mSuggestions = suggestions; 169 mCountAtWhichToExecuteNextBatch = initialBatchSize; 170 mResultPublishDelayMillis = publishResultDelayMillis; 171 mPendingResults = new ArrayList<CorpusResult>(); 172 } 173 174 public boolean consume(CorpusResult cursor) { 175 if (DBG) { 176 Log.d(TAG, "SuggestionCursorReceiver.consume(" + cursor + ") corpus=" + 177 cursor.getCorpus() + " count = " + cursor.getCount()); 178 } 179 updateShouldQueryStrategy(cursor); 180 mPendingResults.add(cursor); 181 if (mResultPublishDelayMillis > 0 182 && !mSuggestions.isClosed() 183 && mSuggestions.getResultCount() + mPendingResults.size() 184 < mCountAtWhichToExecuteNextBatch) { 185 // This is not the last result of the batch, delay publishing 186 if (DBG) Log.d(TAG, "Delaying result by " + mResultPublishDelayMillis + " ms"); 187 mPublishThread.removeCallbacks(mResultPublishTask); 188 mPublishThread.postDelayed(mResultPublishTask, mResultPublishDelayMillis); 189 } else { 190 // This is the last result, publish immediately 191 if (DBG) Log.d(TAG, "Publishing result immediately"); 192 mPublishThread.removeCallbacks(mResultPublishTask); 193 publishPendingResults(); 194 } 195 if (!mSuggestions.isClosed()) { 196 executeNextBatchIfNeeded(); 197 } 198 if (cursor != null && mLogger != null) { 199 mLogger.logLatency(cursor); 200 } 201 return true; 202 } 203 204 private void publishPendingResults() { 205 mSuggestions.addCorpusResults(mPendingResults); 206 mPendingResults.clear(); 207 } 208 209 private void executeNextBatchIfNeeded() { 210 if (mSuggestions.getResultCount() == mCountAtWhichToExecuteNextBatch) { 211 // We've just finished one batch, ask for more 212 int nextBatchSize = mConfig.getNumPromotedSources(); 213 mCountAtWhichToExecuteNextBatch += nextBatchSize; 214 mExecutor.executeNextBatch(nextBatchSize); 215 } 216 } 217 } 218 219} 220