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 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 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 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 includeInAll() { 133 updateSourceProperties(); 134 return mIncludeInAll; 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() + "]" + ";n=" + getCount() + "}"; 210 } 211 } 212 213 } 214