• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 mIncludeInAll;
44 
MultiSourceCorpus(Context context, Config config, Executor executor, Source... sources)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 
addSource(Source source)57     protected void addSource(Source source) {
58         if (source != null) {
59             mSources.add(source);
60             // invalidate calculated values:
61             mSourcePropertiesValid = false;
62         }
63     }
64 
getSources()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      */
createResult(String query, ArrayList<SourceResult> results, int latency)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      */
getSourcesToQuery(String query, boolean onlyCorpus)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 
updateSourceProperties()99     private void updateSourceProperties() {
100         if (mSourcePropertiesValid) return;
101         mQueryThreshold = Integer.MAX_VALUE;
102         mQueryAfterZeroResults = false;
103         mVoiceSearchEnabled = false;
104         mIncludeInAll = false;
105         for (Source s : getSources()) {
106             mQueryThreshold = Math.min(mQueryThreshold, s.getQueryThreshold());
107             mQueryAfterZeroResults |= s.queryAfterZeroResults();
108             mVoiceSearchEnabled |= s.voiceSearchEnabled();
109             mIncludeInAll |= s.includeInAll();
110         }
111         if (mQueryThreshold == Integer.MAX_VALUE) {
112             mQueryThreshold = 0;
113         }
114         mSourcePropertiesValid = true;
115     }
116 
getQueryThreshold()117     public int getQueryThreshold() {
118         updateSourceProperties();
119         return mQueryThreshold;
120     }
121 
queryAfterZeroResults()122     public boolean queryAfterZeroResults() {
123         updateSourceProperties();
124         return mQueryAfterZeroResults;
125     }
126 
voiceSearchEnabled()127     public boolean voiceSearchEnabled() {
128         updateSourceProperties();
129         return mVoiceSearchEnabled;
130     }
131 
includeInAll()132     public boolean includeInAll() {
133         updateSourceProperties();
134         return mIncludeInAll;
135     }
136 
getSuggestions(String query, int queryLimit, boolean onlyCorpus)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 
Result(String userQuery, ArrayList<SourceResult> results, int latency)167         public Result(String userQuery, ArrayList<SourceResult> results, int latency) {
168             super(userQuery);
169             mResults = results;
170             mLatency = latency;
171         }
172 
getResults()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          */
fill()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 
getCorpus()191         public Corpus getCorpus() {
192             return MultiSourceCorpus.this;
193         }
194 
getLatency()195         public int getLatency() {
196             return mLatency;
197         }
198 
199         @Override
close()200         public void close() {
201             super.close();
202             for (SourceResult result : mResults) {
203                 result.close();
204             }
205         }
206 
207         @Override
toString()208         public String toString() {
209             return "{" + getCorpus() + "[" + getUserQuery() + "]" + ";n=" + getCount() + "}";
210         }
211     }
212 
213 }
214