Home | History | Annotate | Download | only in quicksearchbox
      1 /*
      2  * Copyright (C) 2010 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 
     20 import com.android.quicksearchbox.util.BarrierConsumer;
     21 
     22 import android.content.Context;
     23 
     24 import java.util.ArrayList;
     25 import java.util.Collection;
     26 import java.util.List;
     27 import java.util.concurrent.Executor;
     28 
     29 /**
     30  * Base class for corpora backed by multiple sources.
     31  */
     32 public abstract class MultiSourceCorpus extends AbstractCorpus {
     33 
     34     private final Executor mExecutor;
     35 
     36     private final ArrayList<Source> mSources;
     37 
     38     // calculated values based on properties of sources:
     39     private boolean mSourcePropertiesValid;
     40     private int mQueryThreshold;
     41     private boolean mQueryAfterZeroResults;
     42     private boolean mVoiceSearchEnabled;
     43     private boolean mIsLocationAware;
     44 
     45     public MultiSourceCorpus(Context context, Config config,
     46             Executor executor, Source... sources) {
     47         super(context, config);
     48         mExecutor = executor;
     49 
     50         mSources = new ArrayList<Source>();
     51         for (Source source : sources) {
     52             addSource(source);
     53         }
     54 
     55     }
     56 
     57     protected void addSource(Source source) {
     58         if (source != null) {
     59             mSources.add(source);
     60             // invalidate calculated values:
     61             mSourcePropertiesValid = false;
     62         }
     63     }
     64 
     65     public Collection<Source> getSources() {
     66         return mSources;
     67     }
     68 
     69     /**
     70      * Creates a corpus result object for a set of source results.
     71      * This method should not call {@link Result#fill}.
     72      *
     73      * @param query The query text.
     74      * @param results The results of the queries.
     75      * @param latency Latency in milliseconds of the suggestion queries.
     76      * @return An instance of {@link Result} or a subclass of it.
     77      */
     78     protected Result createResult(String query, ArrayList<SourceResult> results, int latency) {
     79         return new Result(query, results, latency);
     80     }
     81 
     82     /**
     83      * Gets the sources to query for suggestions for the given input.
     84      *
     85      * @param query The current input.
     86      * @param onlyCorpus If true, this is the only corpus being queried.
     87      * @return The sources to query.
     88      */
     89     protected List<Source> getSourcesToQuery(String query, boolean onlyCorpus) {
     90         List<Source> sources = new ArrayList<Source>();
     91         for (Source candidate : getSources()) {
     92             if (candidate.getQueryThreshold() <= query.length()) {
     93                 sources.add(candidate);
     94             }
     95         }
     96         return sources;
     97     }
     98 
     99     private void updateSourceProperties() {
    100         if (mSourcePropertiesValid) return;
    101         mQueryThreshold = Integer.MAX_VALUE;
    102         mQueryAfterZeroResults = false;
    103         mVoiceSearchEnabled = false;
    104         mIsLocationAware = false;
    105         for (Source s : getSources()) {
    106             mQueryThreshold = Math.min(mQueryThreshold, s.getQueryThreshold());
    107             mQueryAfterZeroResults |= s.queryAfterZeroResults();
    108             mVoiceSearchEnabled |= s.voiceSearchEnabled();
    109             mIsLocationAware |= s.isLocationAware();
    110         }
    111         if (mQueryThreshold == Integer.MAX_VALUE) {
    112             mQueryThreshold = 0;
    113         }
    114         mSourcePropertiesValid = true;
    115     }
    116 
    117     public int getQueryThreshold() {
    118         updateSourceProperties();
    119         return mQueryThreshold;
    120     }
    121 
    122     public boolean queryAfterZeroResults() {
    123         updateSourceProperties();
    124         return mQueryAfterZeroResults;
    125     }
    126 
    127     public boolean voiceSearchEnabled() {
    128         updateSourceProperties();
    129         return mVoiceSearchEnabled;
    130     }
    131 
    132     public boolean isLocationAware() {
    133         updateSourceProperties();
    134         return mIsLocationAware;
    135     }
    136 
    137     public CorpusResult getSuggestions(String query, int queryLimit, boolean onlyCorpus) {
    138         LatencyTracker latencyTracker = new LatencyTracker();
    139         List<Source> sources = getSourcesToQuery(query, onlyCorpus);
    140         BarrierConsumer<SourceResult> consumer =
    141                 new BarrierConsumer<SourceResult>(sources.size());
    142         boolean onlySource = sources.size() == 1;
    143         for (Source source : sources) {
    144             QueryTask<SourceResult> task = new QueryTask<SourceResult>(query, queryLimit,
    145                     source, null, consumer, onlySource);
    146             mExecutor.execute(task);
    147         }
    148         ArrayList<SourceResult> results = consumer.getValues();
    149         int latency = latencyTracker.getLatency();
    150         Result result = createResult(query, results, latency);
    151         result.fill();
    152         return result;
    153     }
    154 
    155     /**
    156      * Base class for results returned by {@link MultiSourceCorpus#getSuggestions}.
    157      * Subclasses of {@link MultiSourceCorpus} should override
    158      * {@link MultiSourceCorpus#createResult} and return an instance of this class or a
    159      * subclass.
    160      */
    161     protected class Result extends ListSuggestionCursor implements CorpusResult {
    162 
    163         private final ArrayList<SourceResult> mResults;
    164 
    165         private final int mLatency;
    166 
    167         public Result(String userQuery, ArrayList<SourceResult> results, int latency) {
    168             super(userQuery);
    169             mResults = results;
    170             mLatency = latency;
    171         }
    172 
    173         protected ArrayList<SourceResult> getResults() {
    174             return mResults;
    175         }
    176 
    177         /**
    178          * Fills the list of suggestions using the list of results.
    179          * The default implementation concatenates the results.
    180          */
    181         public void fill() {
    182             for (SourceResult result : getResults()) {
    183                 int count = result.getCount();
    184                 for (int i = 0; i < count; i++) {
    185                     result.moveTo(i);
    186                     add(new SuggestionPosition(result));
    187                 }
    188             }
    189         }
    190 
    191         public Corpus getCorpus() {
    192             return MultiSourceCorpus.this;
    193         }
    194 
    195         public int getLatency() {
    196             return mLatency;
    197         }
    198 
    199         @Override
    200         public void close() {
    201             super.close();
    202             for (SourceResult result : mResults) {
    203                 result.close();
    204             }
    205         }
    206 
    207         @Override
    208         public String toString() {
    209             return getCorpus() + "[" + getUserQuery() + "]";
    210         }
    211     }
    212 
    213 }
    214