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