Home | History | Annotate | Download | only in quicksearchbox
      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 
     17 package com.android.quicksearchbox;
     18 
     19 import com.android.quicksearchbox.util.BatchingNamedTaskExecutor;
     20 import com.android.quicksearchbox.util.Consumer;
     21 import com.android.quicksearchbox.util.NamedTaskExecutor;
     22 import com.android.quicksearchbox.util.Util;
     23 
     24 import android.os.Handler;
     25 import android.util.Log;
     26 
     27 import java.util.ArrayList;
     28 import java.util.List;
     29 import 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  */
     37 public 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