blob: d0e96a063242896b9792e6872508bd75dd37b90d [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quicksearchbox;
import com.android.quicksearchbox.util.BatchingNamedTaskExecutor;
import com.android.quicksearchbox.util.Consumer;
import com.android.quicksearchbox.util.NamedTaskExecutor;
import com.android.quicksearchbox.util.NoOpConsumer;
import android.os.Handler;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Suggestions provider implementation.
*
* The provider will only handle a single query at a time. If a new query comes
* in, the old one is cancelled.
*/
public class SuggestionsProviderImpl implements SuggestionsProvider {
private static final boolean DBG = false;
private static final String TAG = "QSB.SuggestionsProviderImpl";
private final Config mConfig;
private final NamedTaskExecutor mQueryExecutor;
private final Handler mPublishThread;
private final ShouldQueryStrategy mShouldQueryStrategy;
private final Logger mLogger;
private BatchingNamedTaskExecutor mBatchingExecutor;
public SuggestionsProviderImpl(Config config,
NamedTaskExecutor queryExecutor,
Handler publishThread,
Logger logger) {
mConfig = config;
mQueryExecutor = queryExecutor;
mPublishThread = publishThread;
mLogger = logger;
mShouldQueryStrategy = new ShouldQueryStrategy(mConfig);
}
public void close() {
cancelPendingTasks();
}
/**
* Cancels all pending query tasks.
*/
private void cancelPendingTasks() {
if (mBatchingExecutor != null) {
mBatchingExecutor.cancelPendingTasks();
mBatchingExecutor = null;
}
}
/**
* Gets the sources that should be queried for the given query.
*/
private List<Corpus> filterCorpora(String query, List<Corpus> corpora) {
// If there is only one corpus, always query it
if (corpora.size() <= 1) return corpora;
ArrayList<Corpus> corporaToQuery = new ArrayList<Corpus>(corpora.size());
for (Corpus corpus : corpora) {
if (shouldQueryCorpus(corpus, query)) {
if (DBG) Log.d(TAG, "should query corpus " + corpus);
corporaToQuery.add(corpus);
} else {
if (DBG) Log.d(TAG, "should NOT query corpus " + corpus);
}
}
if (DBG) Log.d(TAG, "getCorporaToQuery corporaToQuery=" + corporaToQuery);
return corporaToQuery;
}
protected boolean shouldQueryCorpus(Corpus corpus, String query) {
return mShouldQueryStrategy.shouldQueryCorpus(corpus, query);
}
private void updateShouldQueryStrategy(CorpusResult cursor) {
if (cursor.getCount() == 0) {
mShouldQueryStrategy.onZeroResults(cursor.getCorpus(),
cursor.getUserQuery());
}
}
public Suggestions getSuggestions(String query, List<Corpus> corporaToQuery) {
if (DBG) Log.d(TAG, "getSuggestions(" + query + ")");
corporaToQuery = filterCorpora(query, corporaToQuery);
final Suggestions suggestions = new Suggestions(query, corporaToQuery);
Log.i(TAG, "chars:" + query.length() + ",corpora:" + corporaToQuery);
// Fast path for the zero sources case
if (corporaToQuery.size() == 0) {
return suggestions;
}
int initialBatchSize = countDefaultCorpora(corporaToQuery);
if (initialBatchSize == 0) {
initialBatchSize = mConfig.getNumPromotedSources();
}
mBatchingExecutor = new BatchingNamedTaskExecutor(mQueryExecutor);
long publishResultDelayMillis = mConfig.getPublishResultDelayMillis();
Consumer<CorpusResult> receiver;
if (shouldDisplayResults(query)) {
receiver = new SuggestionCursorReceiver(
mBatchingExecutor, suggestions, initialBatchSize,
publishResultDelayMillis);
} else {
receiver = new NoOpConsumer<CorpusResult>();
suggestions.done();
}
int maxResultsPerSource = mConfig.getMaxResultsPerSource();
QueryTask.startQueries(query, maxResultsPerSource, corporaToQuery, mBatchingExecutor,
mPublishThread, receiver, corporaToQuery.size() == 1);
mBatchingExecutor.executeNextBatch(initialBatchSize);
return suggestions;
}
private int countDefaultCorpora(List<Corpus> corpora) {
int count = 0;
for (Corpus corpus : corpora) {
if (corpus.isCorpusDefaultEnabled()) {
count++;
}
}
return count;
}
private boolean shouldDisplayResults(String query) {
if (query.length() == 0 && !mConfig.showSuggestionsForZeroQuery()) {
// Note that even though we don't display such results, it's
// useful to run the query itself because it warms up the network
// connection.
return false;
}
return true;
}
private class SuggestionCursorReceiver implements Consumer<CorpusResult> {
private final BatchingNamedTaskExecutor mExecutor;
private final Suggestions mSuggestions;
private final long mResultPublishDelayMillis;
private final ArrayList<CorpusResult> mPendingResults;
private final Runnable mResultPublishTask = new Runnable () {
public void run() {
if (DBG) Log.d(TAG, "Publishing delayed results");
publishPendingResults();
}
};
private int mCountAtWhichToExecuteNextBatch;
public SuggestionCursorReceiver(BatchingNamedTaskExecutor executor,
Suggestions suggestions, int initialBatchSize,
long publishResultDelayMillis) {
mExecutor = executor;
mSuggestions = suggestions;
mCountAtWhichToExecuteNextBatch = initialBatchSize;
mResultPublishDelayMillis = publishResultDelayMillis;
mPendingResults = new ArrayList<CorpusResult>();
}
public boolean consume(CorpusResult cursor) {
if (DBG) {
Log.d(TAG, "SuggestionCursorReceiver.consume(" + cursor + ") corpus=" +
cursor.getCorpus() + " count = " + cursor.getCount());
}
updateShouldQueryStrategy(cursor);
mPendingResults.add(cursor);
if (mResultPublishDelayMillis > 0
&& !mSuggestions.isClosed()
&& mSuggestions.getResultCount() + mPendingResults.size()
< mCountAtWhichToExecuteNextBatch) {
// This is not the last result of the batch, delay publishing
if (DBG) Log.d(TAG, "Delaying result by " + mResultPublishDelayMillis + " ms");
mPublishThread.removeCallbacks(mResultPublishTask);
mPublishThread.postDelayed(mResultPublishTask, mResultPublishDelayMillis);
} else {
// This is the last result, publish immediately
if (DBG) Log.d(TAG, "Publishing result immediately");
mPublishThread.removeCallbacks(mResultPublishTask);
publishPendingResults();
}
if (!mSuggestions.isClosed()) {
executeNextBatchIfNeeded();
}
if (cursor != null && mLogger != null) {
mLogger.logLatency(cursor);
}
return true;
}
private void publishPendingResults() {
mSuggestions.addCorpusResults(mPendingResults);
mPendingResults.clear();
}
private void executeNextBatchIfNeeded() {
if (mSuggestions.getResultCount() == mCountAtWhichToExecuteNextBatch) {
// We've just finished one batch, ask for more
int nextBatchSize = mConfig.getNumPromotedSources();
mCountAtWhichToExecuteNextBatch += nextBatchSize;
mExecutor.executeNextBatch(nextBatchSize);
}
}
}
}