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