153bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney/*
253bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney * Copyright (C) 2010 The Android Open Source Project
353bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney *
453bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney * Licensed under the Apache License, Version 2.0 (the "License");
553bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney * you may not use this file except in compliance with the License.
653bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney * You may obtain a copy of the License at
753bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney *
853bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney *      http://www.apache.org/licenses/LICENSE-2.0
953bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney *
1053bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney * Unless required by applicable law or agreed to in writing, software
1153bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney * distributed under the License is distributed on an "AS IS" BASIS,
1253bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1353bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney * See the License for the specific language governing permissions and
1453bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney * limitations under the License.
1553bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney */
1653bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney
1753bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinneypackage com.android.quicksearchbox;
1853bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney
19a48053f425e7a65f48bcabe1e0cf0cfb54162167Mathew Inwoodimport android.text.TextUtils;
2053bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinneyimport android.util.Log;
2153bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney
2253bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinneyimport java.util.HashMap;
2353bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinneyimport java.util.Iterator;
2453bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinneyimport java.util.Map;
2553bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney
2653bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney/**
2753bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney * Decides whether a given source should be queried for a given query, taking
2853bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney * into account the source's query threshold and query after zero results flag.
2953bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney *
3053bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney * This class is thread safe.
3153bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney */
3253bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinneyclass ShouldQueryStrategy {
33b5fc08b7f16a32d3865f44b7f26d8aaa5304a2adBjorn Bringert
34b5fc08b7f16a32d3865f44b7f26d8aaa5304a2adBjorn Bringert    private static final boolean DBG = false;
3553bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney    private static final String TAG = "QSB.ShouldQueryStrategy";
3653bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney
3753bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney    // The last query we've seen
3853bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney    private String mLastQuery = "";
3953bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney
40e15fe38f0142174d223bfda0cd87d1a749a85facMathew Inwood    private final Config mConfig;
41e15fe38f0142174d223bfda0cd87d1a749a85facMathew Inwood
42fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    // The current implementation keeps a record of those corpora that have
43fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    // returned zero results for some prefix of the current query. mEmptyCorpora
44fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    // maps from corpus to the length of the query which returned
4553bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney    // zero results.  When a query is shortened (e.g., by deleting characters)
46fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    // or changed entirely, mEmptyCorpora is pruned (in updateQuery)
47fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private final HashMap<Corpus, Integer> mEmptyCorpora
48fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            = new HashMap<Corpus, Integer>();
4953bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney
50e15fe38f0142174d223bfda0cd87d1a749a85facMathew Inwood    public ShouldQueryStrategy(Config config) {
51e15fe38f0142174d223bfda0cd87d1a749a85facMathew Inwood        mConfig = config;
52e15fe38f0142174d223bfda0cd87d1a749a85facMathew Inwood    }
53e15fe38f0142174d223bfda0cd87d1a749a85facMathew Inwood
5453bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney    /**
5553bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney     * Returns whether we should query the given source for the given query.
5653bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney     */
57a65408ff321345557f93effa41395174640870ebBjorn Bringert    public boolean shouldQueryCorpus(Corpus corpus, String query) {
5853bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney        updateQuery(query);
59e15fe38f0142174d223bfda0cd87d1a749a85facMathew Inwood        if (query.length() == 0
60e15fe38f0142174d223bfda0cd87d1a749a85facMathew Inwood                && !corpus.isWebCorpus() // always query web, to warm up connection
61e15fe38f0142174d223bfda0cd87d1a749a85facMathew Inwood                && !mConfig.showSuggestionsForZeroQuery()) {
62e15fe38f0142174d223bfda0cd87d1a749a85facMathew Inwood                return false;
63e15fe38f0142174d223bfda0cd87d1a749a85facMathew Inwood        }
64fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (query.length() >= corpus.getQueryThreshold()) {
65fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            if (!corpus.queryAfterZeroResults() && mEmptyCorpora.containsKey(corpus)) {
66fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                if (DBG) Log.i(TAG, "Not querying " + corpus + ", returned 0 after "
67fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                        + mEmptyCorpora.get(corpus));
6853bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney                return false;
6953bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney            }
7053bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney            return true;
7153bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney        }
72a48053f425e7a65f48bcabe1e0cf0cfb54162167Mathew Inwood        if (DBG) Log.d(TAG, "Query too short for corpus " + corpus);
7353bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney        return false;
7453bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney    }
7553bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney
7653bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney    /**
7753bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney     * Called to notify ShouldQueryStrategy when a source reports no results for a query.
7853bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney     */
79a65408ff321345557f93effa41395174640870ebBjorn Bringert    public void onZeroResults(Corpus corpus, String query) {
8053bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney        // Make sure this result is actually for a prefix of the current query.
81a48053f425e7a65f48bcabe1e0cf0cfb54162167Mathew Inwood        if (mLastQuery.startsWith(query) && !corpus.queryAfterZeroResults()
82a48053f425e7a65f48bcabe1e0cf0cfb54162167Mathew Inwood                && !TextUtils.isEmpty(query)) {
83a48053f425e7a65f48bcabe1e0cf0cfb54162167Mathew Inwood            if (DBG) Log.d(TAG, corpus + " returned 0 results for '" + query + "'");
84fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            mEmptyCorpora.put(corpus, query.length());
8553bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney        }
8653bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney    }
8753bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney
8853bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney    private void updateQuery(String query) {
8953bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney        if (query.startsWith(mLastQuery)) {
90a65408ff321345557f93effa41395174640870ebBjorn Bringert            // This is a refinement of the last query, no changes to mEmptyCorpora needed
9153bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney        } else if (mLastQuery.startsWith(query)) {
9253bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney            // This is a widening of the last query: clear out any sources
9353bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney            // that reported zero results after this query.
94fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            Iterator<Map.Entry<Corpus, Integer>> iter = mEmptyCorpora.entrySet().iterator();
9553bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney            while (iter.hasNext()) {
9653bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney                if (iter.next().getValue() > query.length()) {
9753bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney                    iter.remove();
9853bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney                }
9953bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney            }
10053bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney        } else {
10153bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney            // This is a completely different query, clear everything.
102fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            mEmptyCorpora.clear();
10353bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney        }
104a65408ff321345557f93effa41395174640870ebBjorn Bringert        mLastQuery = query;
10553bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney    }
10653bf1409474f26ab0f3754ee9b4d2de901a6be00Bryan Mawhinney}
107