/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.quicksearchbox; import com.android.quicksearchbox.util.BarrierConsumer; import android.content.Context; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; /** * Base class for corpora backed by multiple sources. */ public abstract class MultiSourceCorpus extends AbstractCorpus { private final Executor mExecutor; private final ArrayList<Source> mSources; // calculated values based on properties of sources: private boolean mSourcePropertiesValid; private int mQueryThreshold; private boolean mQueryAfterZeroResults; private boolean mVoiceSearchEnabled; private boolean mIncludeInAll; public MultiSourceCorpus(Context context, Config config, Executor executor, Source... sources) { super(context, config); mExecutor = executor; mSources = new ArrayList<Source>(); for (Source source : sources) { addSource(source); } } protected void addSource(Source source) { if (source != null) { mSources.add(source); // invalidate calculated values: mSourcePropertiesValid = false; } } public Collection<Source> getSources() { return mSources; } /** * Creates a corpus result object for a set of source results. * This method should not call {@link Result#fill}. * * @param query The query text. * @param results The results of the queries. * @param latency Latency in milliseconds of the suggestion queries. * @return An instance of {@link Result} or a subclass of it. */ protected Result createResult(String query, ArrayList<SourceResult> results, int latency) { return new Result(query, results, latency); } /** * Gets the sources to query for suggestions for the given input. * * @param query The current input. * @param onlyCorpus If true, this is the only corpus being queried. * @return The sources to query. */ protected List<Source> getSourcesToQuery(String query, boolean onlyCorpus) { List<Source> sources = new ArrayList<Source>(); for (Source candidate : getSources()) { if (candidate.getQueryThreshold() <= query.length()) { sources.add(candidate); } } return sources; } private void updateSourceProperties() { if (mSourcePropertiesValid) return; mQueryThreshold = Integer.MAX_VALUE; mQueryAfterZeroResults = false; mVoiceSearchEnabled = false; mIncludeInAll = false; for (Source s : getSources()) { mQueryThreshold = Math.min(mQueryThreshold, s.getQueryThreshold()); mQueryAfterZeroResults |= s.queryAfterZeroResults(); mVoiceSearchEnabled |= s.voiceSearchEnabled(); mIncludeInAll |= s.includeInAll(); } if (mQueryThreshold == Integer.MAX_VALUE) { mQueryThreshold = 0; } mSourcePropertiesValid = true; } public int getQueryThreshold() { updateSourceProperties(); return mQueryThreshold; } public boolean queryAfterZeroResults() { updateSourceProperties(); return mQueryAfterZeroResults; } public boolean voiceSearchEnabled() { updateSourceProperties(); return mVoiceSearchEnabled; } public boolean includeInAll() { updateSourceProperties(); return mIncludeInAll; } public CorpusResult getSuggestions(String query, int queryLimit, boolean onlyCorpus) { LatencyTracker latencyTracker = new LatencyTracker(); List<Source> sources = getSourcesToQuery(query, onlyCorpus); BarrierConsumer<SourceResult> consumer = new BarrierConsumer<SourceResult>(sources.size()); boolean onlySource = sources.size() == 1; for (Source source : sources) { QueryTask<SourceResult> task = new QueryTask<SourceResult>(query, queryLimit, source, null, consumer, onlySource); mExecutor.execute(task); } ArrayList<SourceResult> results = consumer.getValues(); int latency = latencyTracker.getLatency(); Result result = createResult(query, results, latency); result.fill(); return result; } /** * Base class for results returned by {@link MultiSourceCorpus#getSuggestions}. * Subclasses of {@link MultiSourceCorpus} should override * {@link MultiSourceCorpus#createResult} and return an instance of this class or a * subclass. */ protected class Result extends ListSuggestionCursor implements CorpusResult { private final ArrayList<SourceResult> mResults; private final int mLatency; public Result(String userQuery, ArrayList<SourceResult> results, int latency) { super(userQuery); mResults = results; mLatency = latency; } protected ArrayList<SourceResult> getResults() { return mResults; } /** * Fills the list of suggestions using the list of results. * The default implementation concatenates the results. */ public void fill() { for (SourceResult result : getResults()) { int count = result.getCount(); for (int i = 0; i < count; i++) { result.moveTo(i); add(new SuggestionPosition(result)); } } } public Corpus getCorpus() { return MultiSourceCorpus.this; } public int getLatency() { return mLatency; } @Override public void close() { super.close(); for (SourceResult result : mResults) { result.close(); } } @Override public String toString() { return "{" + getCorpus() + "[" + getUserQuery() + "]" + ";n=" + getCount() + "}"; } } }