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