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