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.NoOpConsumer;
     23 
     24 import android.os.Handler;
     25 import android.util.Log;
     26 
     27 import java.util.ArrayList;
     28 import java.util.List;
     29 
     30 /**
     31  * Suggestions provider implementation.
     32  *
     33  * The provider will only handle a single query at a time. If a new query comes
     34  * in, the old one is cancelled.
     35  */
     36 public class SuggestionsProviderImpl implements SuggestionsProvider {
     37 
     38     private static final boolean DBG = false;
     39     private static final String TAG = "QSB.SuggestionsProviderImpl";
     40 
     41     private final Config mConfig;
     42 
     43     private final NamedTaskExecutor mQueryExecutor;
     44 
     45     private final Handler mPublishThread;
     46 
     47     private final ShouldQueryStrategy mShouldQueryStrategy;
     48 
     49     private final Logger mLogger;
     50 
     51     private BatchingNamedTaskExecutor mBatchingExecutor;
     52 
     53     public SuggestionsProviderImpl(Config config,
     54             NamedTaskExecutor queryExecutor,
     55             Handler publishThread,
     56             Logger logger) {
     57         mConfig = config;
     58         mQueryExecutor = queryExecutor;
     59         mPublishThread = publishThread;
     60         mLogger = logger;
     61         mShouldQueryStrategy = new ShouldQueryStrategy(mConfig);
     62     }
     63 
     64     public void close() {
     65         cancelPendingTasks();
     66     }
     67 
     68     /**
     69      * Cancels all pending query tasks.
     70      */
     71     private void cancelPendingTasks() {
     72         if (mBatchingExecutor != null) {
     73             mBatchingExecutor.cancelPendingTasks();
     74             mBatchingExecutor = null;
     75         }
     76     }
     77 
     78     /**
     79      * Gets the sources that should be queried for the given query.
     80      */
     81     private List<Corpus> filterCorpora(String query, List<Corpus> corpora) {
     82         // If there is only one corpus, always query it
     83         if (corpora.size() <= 1) return corpora;
     84         ArrayList<Corpus> corporaToQuery = new ArrayList<Corpus>(corpora.size());
     85         for (Corpus corpus : corpora) {
     86             if (shouldQueryCorpus(corpus, query)) {
     87                 if (DBG) Log.d(TAG, "should query corpus " + corpus);
     88                 corporaToQuery.add(corpus);
     89             } else {
     90                 if (DBG) Log.d(TAG, "should NOT query corpus " + corpus);
     91             }
     92         }
     93         if (DBG) Log.d(TAG, "getCorporaToQuery corporaToQuery=" + corporaToQuery);
     94         return corporaToQuery;
     95     }
     96 
     97     protected boolean shouldQueryCorpus(Corpus corpus, String query) {
     98         return mShouldQueryStrategy.shouldQueryCorpus(corpus, query);
     99     }
    100 
    101     private void updateShouldQueryStrategy(CorpusResult cursor) {
    102         if (cursor.getCount() == 0) {
    103             mShouldQueryStrategy.onZeroResults(cursor.getCorpus(),
    104                     cursor.getUserQuery());
    105         }
    106     }
    107 
    108     public Suggestions getSuggestions(String query, List<Corpus> corporaToQuery) {
    109         if (DBG) Log.d(TAG, "getSuggestions(" + query + ")");
    110         corporaToQuery = filterCorpora(query, corporaToQuery);
    111         final Suggestions suggestions = new Suggestions(query, corporaToQuery);
    112         Log.i(TAG, "chars:" + query.length() + ",corpora:" + corporaToQuery);
    113 
    114         // Fast path for the zero sources case
    115         if (corporaToQuery.size() == 0) {
    116             return suggestions;
    117         }
    118 
    119         int initialBatchSize = countDefaultCorpora(corporaToQuery);
    120         if (initialBatchSize == 0) {
    121             initialBatchSize = mConfig.getNumPromotedSources();
    122         }
    123 
    124         mBatchingExecutor = new BatchingNamedTaskExecutor(mQueryExecutor);
    125 
    126         long publishResultDelayMillis = mConfig.getPublishResultDelayMillis();
    127 
    128         Consumer<CorpusResult> receiver;
    129         if (shouldDisplayResults(query)) {
    130             receiver = new SuggestionCursorReceiver(
    131                     mBatchingExecutor, suggestions, initialBatchSize,
    132                     publishResultDelayMillis);
    133         } else {
    134             receiver = new NoOpConsumer<CorpusResult>();
    135             suggestions.done();
    136         }
    137 
    138         int maxResultsPerSource = mConfig.getMaxResultsPerSource();
    139         QueryTask.startQueries(query, maxResultsPerSource, corporaToQuery, mBatchingExecutor,
    140                 mPublishThread, receiver, corporaToQuery.size() == 1);
    141         mBatchingExecutor.executeNextBatch(initialBatchSize);
    142 
    143         return suggestions;
    144     }
    145 
    146     private int countDefaultCorpora(List<Corpus> corpora) {
    147         int count = 0;
    148         for (Corpus corpus : corpora) {
    149             if (corpus.isCorpusDefaultEnabled()) {
    150                 count++;
    151             }
    152         }
    153         return count;
    154     }
    155 
    156     private boolean shouldDisplayResults(String query) {
    157         if (query.length() == 0 && !mConfig.showSuggestionsForZeroQuery()) {
    158             // Note that even though we don't display such results, it's
    159             // useful to run the query itself because it warms up the network
    160             // connection.
    161             return false;
    162         }
    163         return true;
    164     }
    165 
    166 
    167     private class SuggestionCursorReceiver implements Consumer<CorpusResult> {
    168         private final BatchingNamedTaskExecutor mExecutor;
    169         private final Suggestions mSuggestions;
    170         private final long mResultPublishDelayMillis;
    171         private final ArrayList<CorpusResult> mPendingResults;
    172         private final Runnable mResultPublishTask = new Runnable () {
    173             public void run() {
    174                 if (DBG) Log.d(TAG, "Publishing delayed results");
    175                 publishPendingResults();
    176             }
    177         };
    178 
    179         private int mCountAtWhichToExecuteNextBatch;
    180 
    181         public SuggestionCursorReceiver(BatchingNamedTaskExecutor executor,
    182                 Suggestions suggestions, int initialBatchSize,
    183                 long publishResultDelayMillis) {
    184             mExecutor = executor;
    185             mSuggestions = suggestions;
    186             mCountAtWhichToExecuteNextBatch = initialBatchSize;
    187             mResultPublishDelayMillis = publishResultDelayMillis;
    188             mPendingResults = new ArrayList<CorpusResult>();
    189         }
    190 
    191         public boolean consume(CorpusResult cursor) {
    192             if (DBG) {
    193                 Log.d(TAG, "SuggestionCursorReceiver.consume(" + cursor + ") corpus=" +
    194                         cursor.getCorpus() + " count = " + cursor.getCount());
    195             }
    196             updateShouldQueryStrategy(cursor);
    197             mPendingResults.add(cursor);
    198             if (mResultPublishDelayMillis > 0
    199                     && !mSuggestions.isClosed()
    200                     && mSuggestions.getResultCount() + mPendingResults.size()
    201                             < mCountAtWhichToExecuteNextBatch) {
    202                 // This is not the last result of the batch, delay publishing
    203                 if (DBG) Log.d(TAG, "Delaying result by " + mResultPublishDelayMillis + " ms");
    204                 mPublishThread.removeCallbacks(mResultPublishTask);
    205                 mPublishThread.postDelayed(mResultPublishTask, mResultPublishDelayMillis);
    206             } else {
    207                 // This is the last result, publish immediately
    208                 if (DBG) Log.d(TAG, "Publishing result immediately");
    209                 mPublishThread.removeCallbacks(mResultPublishTask);
    210                 publishPendingResults();
    211             }
    212             if (!mSuggestions.isClosed()) {
    213                 executeNextBatchIfNeeded();
    214             }
    215             if (cursor != null && mLogger != null) {
    216                 mLogger.logLatency(cursor);
    217             }
    218             return true;
    219         }
    220 
    221         private void publishPendingResults() {
    222             mSuggestions.addCorpusResults(mPendingResults);
    223             mPendingResults.clear();
    224         }
    225 
    226         private void executeNextBatchIfNeeded() {
    227             if (mSuggestions.getResultCount() == mCountAtWhichToExecuteNextBatch) {
    228                 // We've just finished one batch, ask for more
    229                 int nextBatchSize = mConfig.getNumPromotedSources();
    230                 mCountAtWhichToExecuteNextBatch += nextBatchSize;
    231                 mExecutor.executeNextBatch(nextBatchSize);
    232             }
    233         }
    234     }
    235 }
    236