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