SuggestionsProviderImpl.java revision 96c7058210699c82445169048b7c0fdfb16f59ee
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 = mCorpora.getEnabledCorpora();
98        return mShortcutRepo.getShortcutsForQuery(query, allowedCorpora, maxShortcuts);
99    }
100
101    /**
102     * Gets the sources that should be queried for the given query.
103     */
104    private List<Corpus> getCorporaToQuery(String query, Corpus singleCorpus) {
105        if (singleCorpus != null) return Collections.singletonList(singleCorpus);
106        List<Corpus> orderedCorpora = mCorpusRanker.getRankedCorpora();
107        ArrayList<Corpus> corporaToQuery = new ArrayList<Corpus>(orderedCorpora.size());
108        for (Corpus corpus : orderedCorpora) {
109            if (shouldQueryCorpus(corpus, query)) {
110                corporaToQuery.add(corpus);
111            }
112        }
113        return corporaToQuery;
114    }
115
116    protected boolean shouldQueryCorpus(Corpus corpus, String query) {
117        if (query.length() == 0 && !corpus.isWebCorpus()) {
118            // Only the web corpus sees zero length queries.
119            return false;
120        }
121        return mShouldQueryStrategy.shouldQueryCorpus(corpus, query);
122    }
123
124    private void updateShouldQueryStrategy(CorpusResult cursor) {
125        if (cursor.getCount() == 0) {
126            mShouldQueryStrategy.onZeroResults(cursor.getCorpus(),
127                    cursor.getUserQuery());
128        }
129    }
130
131    public Suggestions getSuggestions(String query, Corpus singleCorpus, int maxSuggestions) {
132        if (DBG) Log.d(TAG, "getSuggestions(" + query + ")");
133        cancelPendingTasks();
134        List<Corpus> corporaToQuery = getCorporaToQuery(query, singleCorpus);
135        final Suggestions suggestions = new Suggestions(mPromoter,
136                maxSuggestions,
137                query,
138                corporaToQuery.size());
139        int maxShortcuts = mConfig.getMaxShortcutsReturned();
140        SuggestionCursor shortcuts = getShortcutsForQuery(query, singleCorpus, maxShortcuts);
141        if (shortcuts != null) {
142            suggestions.setShortcuts(shortcuts);
143        }
144
145        // Fast path for the zero sources case
146        if (corporaToQuery.size() == 0) {
147            return suggestions;
148        }
149
150        int initialBatchSize = countDefaultCorpora(corporaToQuery);
151        initialBatchSize = Math.min(initialBatchSize, mConfig.getNumPromotedSources());
152        if (initialBatchSize == 0) {
153            initialBatchSize = mConfig.getNumPromotedSources();
154        }
155
156        mBatchingExecutor = new BatchingNamedTaskExecutor(mQueryExecutor);
157
158        SuggestionCursorReceiver receiver = new SuggestionCursorReceiver(
159                mBatchingExecutor, suggestions, initialBatchSize);
160
161        int maxResultsPerSource = mConfig.getMaxResultsPerSource();
162        QueryTask.startQueries(query, maxResultsPerSource, corporaToQuery, mBatchingExecutor,
163                mPublishThread, receiver);
164        mBatchingExecutor.executeNextBatch(initialBatchSize);
165
166        return suggestions;
167    }
168
169    private int countDefaultCorpora(List<Corpus> corpora) {
170        int count = 0;
171        for (Corpus corpus : corpora) {
172            if (corpus.isCorpusDefaultEnabled()) {
173                count++;
174            }
175        }
176        return count;
177    }
178
179    private class SuggestionCursorReceiver implements Consumer<CorpusResult> {
180        private final BatchingNamedTaskExecutor mExecutor;
181        private final Suggestions mSuggestions;
182
183        private int mCountAtWhichToExecuteNextBatch;
184
185        public SuggestionCursorReceiver(BatchingNamedTaskExecutor executor,
186                Suggestions suggestions, int initialBatchSize) {
187            mExecutor = executor;
188            mSuggestions = suggestions;
189            mCountAtWhichToExecuteNextBatch = initialBatchSize;
190        }
191
192        public boolean consume(CorpusResult cursor) {
193            updateShouldQueryStrategy(cursor);
194            mSuggestions.addCorpusResult(cursor);
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 executeNextBatchIfNeeded() {
205            if (mSuggestions.getSourceCount() == mCountAtWhichToExecuteNextBatch) {
206                // We've just finished one batch
207                if (mSuggestions.getPromoted().getCount() < mConfig.getMaxPromotedSuggestions()) {
208                    // But we still don't have enough results, ask for more
209                    int nextBatchSize = mConfig.getNumPromotedSources();
210                    mCountAtWhichToExecuteNextBatch += nextBatchSize;
211                    mExecutor.executeNextBatch(nextBatchSize);
212                }
213            }
214        }
215    }
216
217}
218