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