13e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert/*
2848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani * Copyright (C) 2010 The Android Open Source Project
33e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert *
43e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * Licensed under the Apache License, Version 2.0 (the "License");
53e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * you may not use this file except in compliance with the License.
63e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * You may obtain a copy of the License at
73e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert *
83e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert *      http://www.apache.org/licenses/LICENSE-2.0
93e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert *
103e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * Unless required by applicable law or agreed to in writing, software
113e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * distributed under the License is distributed on an "AS IS" BASIS,
123e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
133e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * See the License for the specific language governing permissions and
143e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * limitations under the License.
153e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert */
163e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
173e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertpackage com.android.quicksearchbox;
183e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
19b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringertimport com.google.common.annotations.VisibleForTesting;
20b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
213e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.database.DataSetObservable;
223e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.database.DataSetObserver;
233e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.util.Log;
243e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
25b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringertimport java.util.ArrayList;
2600efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwoodimport java.util.Arrays;
2700efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwoodimport java.util.HashMap;
28b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringertimport java.util.HashSet;
29b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringertimport java.util.List;
30b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringertimport java.util.Set;
31b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
32b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert/**
33b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert * Collects all corpus results for a single query.
34b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert */
35b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringertpublic class Suggestions {
36cef2c4c9d54f513babd74801dbed5cbf709b9b79Bjorn Bringert    private static final boolean DBG = false;
373e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private static final String TAG = "QSB.Suggestions";
383e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
39848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani    /** True if {@link Suggestions#close} has been called. */
40848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani    private boolean mClosed = false;
41848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani    protected final String mQuery;
423e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
434572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood    private ShortcutCursor mShortcuts;
444572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood
454572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood    private final MyShortcutsObserver mShortcutsObserver = new MyShortcutsObserver();
464572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood
473e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
483e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * The observers that want notifications of changes to the published suggestions.
493e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * This object may be accessed on any thread.
503e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
513e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private final DataSetObservable mDataSetObservable = new DataSetObservable();
523e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
53b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    /** The sources that are expected to report. */
54b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    private final List<Corpus> mExpectedCorpora;
5500efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood    private final HashMap<String, Integer> mCorpusPositions;
56b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
57b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    /**
58b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     * All {@link SuggestionCursor} objects that have been published so far,
5900efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood     * in the same order as {@link #mExpectedCorpora}. There may be {@code null} items
6000efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood     * in the array, if not all corpora have published yet.
61b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     * This object may only be accessed on the UI thread.
62b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     * */
6300efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood    private final CorpusResult[] mCorpusResults;
64b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
65b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    private CorpusResult mWebResult;
66b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
67b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    private int mRefCount = 0;
68b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
693540e5a9eab6ff33e6d45f1d908bcaf4c4f49152Bjorn Bringert    private boolean mDone = false;
703540e5a9eab6ff33e6d45f1d908bcaf4c4f49152Bjorn Bringert
71b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    public Suggestions(String query, List<Corpus> expectedCorpora) {
723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mQuery = query;
73b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        mExpectedCorpora = expectedCorpora;
7400efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        mCorpusResults = new CorpusResult[mExpectedCorpora.size()];
7500efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        // create a map of corpus name -> position in mExpectedCorpora for sorting later
7600efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        // (we want to keep the ordering of corpora in mCorpusResults).
7700efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        mCorpusPositions = new HashMap<String, Integer>();
7800efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        for (int i = 0; i < mExpectedCorpora.size(); ++i) {
7900efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood            mCorpusPositions.put(mExpectedCorpora.get(i).getName(), i);
8000efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        }
81b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        if (DBG) {
82b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert            Log.d(TAG, "new Suggestions [" + hashCode() + "] query \"" + query
83b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert                    + "\" expected corpora: " + mExpectedCorpora);
84b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        }
85b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    }
86b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
87b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    public void acquire() {
88b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        mRefCount++;
89b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    }
90b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
91b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    public void release() {
92b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        mRefCount--;
93b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        if (mRefCount <= 0) {
94b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert            close();
95b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        }
96b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    }
97b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
98b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    public List<Corpus> getExpectedCorpora() {
99b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        return mExpectedCorpora;
1004572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood    }
1014572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood
102b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    /**
103b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     * Gets the number of corpora that are expected to report.
104b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     */
105b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    @VisibleForTesting
106b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    public int getExpectedResultCount() {
107b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        return mExpectedCorpora.size();
108b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    }
109b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
110dfc1772caf35942837d83331d787eb10734c37cbBjorn Bringert    public boolean expectsCorpus(Corpus corpus) {
111dfc1772caf35942837d83331d787eb10734c37cbBjorn Bringert        for (Corpus expectedCorpus : mExpectedCorpora) {
112dfc1772caf35942837d83331d787eb10734c37cbBjorn Bringert            if (expectedCorpus.equals(corpus)) return true;
113dfc1772caf35942837d83331d787eb10734c37cbBjorn Bringert        }
114dfc1772caf35942837d83331d787eb10734c37cbBjorn Bringert        return false;
115dfc1772caf35942837d83331d787eb10734c37cbBjorn Bringert    }
116dfc1772caf35942837d83331d787eb10734c37cbBjorn Bringert
117b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    /**
118b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     * Gets the set of corpora that have reported results to this suggestions set.
119b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     *
120b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     * @return A collection of corpora.
121b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     */
122b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    public Set<Corpus> getIncludedCorpora() {
123b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        HashSet<Corpus> corpora = new HashSet<Corpus>();
124b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        for (CorpusResult result : mCorpusResults) {
125116affd19a834a2df4226a1ae37afcc1113a230eMathew Inwood            if (result != null) {
126116affd19a834a2df4226a1ae37afcc1113a230eMathew Inwood                corpora.add(result.getCorpus());
127116affd19a834a2df4226a1ae37afcc1113a230eMathew Inwood            }
1284572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood        }
129b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        return corpora;
1304572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood    }
1314572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood
1324572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood    /**
1334572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood     * Sets the shortcut suggestions.
1344572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood     * Must be called on the UI thread, or before this object is seen by the UI thread.
1354572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood     *
1364572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood     * @param shortcuts The shortcuts.
1374572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood     */
1384572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood    public void setShortcuts(ShortcutCursor shortcuts) {
1394572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood        if (DBG) Log.d(TAG, "setShortcuts(" + shortcuts + ")");
140b42184f1e6a1b7bb22797ff92cae696753aca770Bjorn Bringert        if (mShortcuts != null) {
141b42184f1e6a1b7bb22797ff92cae696753aca770Bjorn Bringert            throw new IllegalStateException("Got duplicate shortcuts: old: " + mShortcuts
142b42184f1e6a1b7bb22797ff92cae696753aca770Bjorn Bringert                    + ", new: " + shortcuts);
143b42184f1e6a1b7bb22797ff92cae696753aca770Bjorn Bringert        }
144b42184f1e6a1b7bb22797ff92cae696753aca770Bjorn Bringert        if (shortcuts == null) return;
145b42184f1e6a1b7bb22797ff92cae696753aca770Bjorn Bringert        if (isClosed()) {
146b42184f1e6a1b7bb22797ff92cae696753aca770Bjorn Bringert            shortcuts.close();
147b42184f1e6a1b7bb22797ff92cae696753aca770Bjorn Bringert            return;
148b42184f1e6a1b7bb22797ff92cae696753aca770Bjorn Bringert        }
149b42184f1e6a1b7bb22797ff92cae696753aca770Bjorn Bringert        if (!mQuery.equals(shortcuts.getUserQuery())) {
150b42184f1e6a1b7bb22797ff92cae696753aca770Bjorn Bringert            throw new IllegalArgumentException("Got shortcuts for wrong query: "
151b42184f1e6a1b7bb22797ff92cae696753aca770Bjorn Bringert                    + mQuery + " != " + shortcuts.getUserQuery());
152b42184f1e6a1b7bb22797ff92cae696753aca770Bjorn Bringert        }
1534572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood        mShortcuts = shortcuts;
1544572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood        if (shortcuts != null) {
1554572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood            mShortcuts.registerDataSetObserver(mShortcutsObserver);
1564572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood        }
157b42184f1e6a1b7bb22797ff92cae696753aca770Bjorn Bringert        notifyDataSetChanged();
1583e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
1593e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
1603e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
1613540e5a9eab6ff33e6d45f1d908bcaf4c4f49152Bjorn Bringert     * Marks the suggestions set as complete, regardless of whether all corpora have
1623540e5a9eab6ff33e6d45f1d908bcaf4c4f49152Bjorn Bringert     * returned.
1633540e5a9eab6ff33e6d45f1d908bcaf4c4f49152Bjorn Bringert     */
1643540e5a9eab6ff33e6d45f1d908bcaf4c4f49152Bjorn Bringert    public void done() {
1653540e5a9eab6ff33e6d45f1d908bcaf4c4f49152Bjorn Bringert        mDone = true;
1663540e5a9eab6ff33e6d45f1d908bcaf4c4f49152Bjorn Bringert    }
1673540e5a9eab6ff33e6d45f1d908bcaf4c4f49152Bjorn Bringert
1683540e5a9eab6ff33e6d45f1d908bcaf4c4f49152Bjorn Bringert    /**
169b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     * Checks whether all sources have reported.
170b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     * Must be called on the UI thread, or before this object is seen by the UI thread.
171b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     */
172b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    public boolean isDone() {
173b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        // TODO: Handle early completion because we have all the results we want.
17400efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        return mDone || countCorpusResults() >= mExpectedCorpora.size();
17500efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood    }
17600efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood
17700efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood    private int countCorpusResults() {
17800efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        int count = 0;
17900efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        for (int i = 0; i < mCorpusResults.length; ++i) {
18000efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood            if (mCorpusResults[i] != null) {
18100efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood                count++;
18200efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood            }
18300efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        }
18400efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        return count;
185b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    }
186b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
187b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    /**
188b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     * Adds a list of corpus results. Must be called on the UI thread, or before this
189b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     * object is seen by the UI thread.
190b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     */
191b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    public void addCorpusResults(List<CorpusResult> corpusResults) {
192b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        if (isClosed()) {
193b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert            for (CorpusResult corpusResult : corpusResults) {
194b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert                corpusResult.close();
195b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert            }
196b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert            return;
197b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        }
198b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
199b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        for (CorpusResult corpusResult : corpusResults) {
200b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert            if (DBG) {
201b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert                Log.d(TAG, "addCorpusResult["+ hashCode() + "] corpus:" +
202b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert                        corpusResult.getCorpus().getName() + " results:" + corpusResult.getCount());
203b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert            }
204b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert            if (!mQuery.equals(corpusResult.getUserQuery())) {
205b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert              throw new IllegalArgumentException("Got result for wrong query: "
206b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert                    + mQuery + " != " + corpusResult.getUserQuery());
207b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert            }
20800efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood            Integer pos = mCorpusPositions.get(corpusResult.getCorpus().getName());
20900efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood            if (pos == null) {
21000efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood                Log.w(TAG, "Got unexpected CorpusResult from corpus " +
21100efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood                        corpusResult.getCorpus().getName());
21200efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood                corpusResult.close();
21300efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood            } else {
21400efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood                mCorpusResults[pos] = corpusResult;
21500efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood                if (corpusResult.getCorpus().isWebCorpus()) {
21600efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood                    mWebResult = corpusResult;
21700efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood                }
218b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert            }
219b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        }
220b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        notifyDataSetChanged();
221b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    }
222b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
223b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    /**
2243e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Registers an observer that will be notified when the reported results or
2253e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * the done status changes.
2263e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
2273e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public void registerDataSetObserver(DataSetObserver observer) {
2283e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (mClosed) {
2293e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            throw new IllegalStateException("registerDataSetObserver() when closed");
2303e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
2313e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mDataSetObservable.registerObserver(observer);
2323e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2333e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
234848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani
2353e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
2363e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Unregisters an observer.
2373e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
2383e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public void unregisterDataSetObserver(DataSetObserver observer) {
2393e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mDataSetObservable.unregisterObserver(observer);
2403e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2413e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
242e06b7cbf55301a24cfd7525a91107e3cd2c9f48eBjorn Bringert    /**
2433e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Calls {@link DataSetObserver#onChanged()} on all observers.
2443e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
245848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani    protected void notifyDataSetChanged() {
2463e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) Log.d(TAG, "notifyDataSetChanged()");
2473e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mDataSetObservable.notifyChanged();
2483e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2493e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
250b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    /**
251b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     * Closes all the source results and unregisters all observers.
252b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     */
253b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    private void close() {
254b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        if (DBG) Log.d(TAG, "close() [" + hashCode() + "]");
2553e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (mClosed) {
256185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert            throw new IllegalStateException("Double close()");
2573e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
2583e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mClosed = true;
259848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani        mDataSetObservable.unregisterAll();
2604572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood        if (mShortcuts != null) {
2614572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood            mShortcuts.close();
2624572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood            mShortcuts = null;
2634572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood        }
264b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
265b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        for (CorpusResult result : mCorpusResults) {
26600efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood            if (result != null) {
26700efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood                result.close();
26800efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood            }
269b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        }
27000efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        Arrays.fill(mCorpusResults, null);
2713e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
27327d3d4eb3b0414cf7001020d8ddcfdde81fd516bBryan Mawhinney    public boolean isClosed() {
27427d3d4eb3b0414cf7001020d8ddcfdde81fd516bBryan Mawhinney        return mClosed;
27527d3d4eb3b0414cf7001020d8ddcfdde81fd516bBryan Mawhinney    }
27627d3d4eb3b0414cf7001020d8ddcfdde81fd516bBryan Mawhinney
2778b2936607176720172aee068abc5631bdf77e843Bjorn Bringert    public ShortcutCursor getShortcuts() {
2784572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood        return mShortcuts;
2794572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood    }
2804572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood
281b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    private void refreshShortcuts(SuggestionCursor promoted) {
282b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        if (DBG) Log.d(TAG, "refreshShortcuts(" + promoted + ")");
283b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        for (int i = 0; i < promoted.getCount(); ++i) {
284b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert            promoted.moveTo(i);
285b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert            if (promoted.isSuggestionShortcut()) {
286b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert                getShortcuts().refresh(promoted);
2874572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood            }
2884572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood        }
2894572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood    }
2904572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood
2913e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    @Override
2923e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected void finalize() {
2933e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (!mClosed) {
294848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani            Log.e(TAG, "LEAK! Finalized without being closed: Suggestions[" + getQuery() + "]");
2953e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
2963e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2973e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
298848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani    public String getQuery() {
299848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani        return mQuery;
300ced9f76b761536341d739e9a243c98a4bf90638cBjorn Bringert    }
301ced9f76b761536341d739e9a243c98a4bf90638cBjorn Bringert
302b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    public SuggestionCursor getPromoted(Promoter promoter, int maxPromoted) {
303b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        SuggestionCursor promoted = buildPromoted(promoter, maxPromoted);
304b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        refreshShortcuts(promoted);
305b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        return promoted;
306b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    }
307b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
308b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    protected SuggestionCursor buildPromoted(Promoter promoter, int maxPromoted) {
309b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        ListSuggestionCursor promoted = new ListSuggestionCursorNoDuplicates(mQuery);
310b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        if (promoter == null) {
311b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert            return promoted;
3124572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood        }
313b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        promoter.pickPromoted(this, maxPromoted, promoted);
314b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        if (DBG) {
315b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert            Log.d(TAG, "pickPromoted(" + getShortcuts() + "," + mCorpusResults + ","
316b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert                    + maxPromoted + ") = " + promoted);
317b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        }
318b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        return promoted;
3194572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood    }
3204572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood
321b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    /**
322b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     * Gets the list of corpus results reported so far. Do not modify or hang on to
323b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     * the returned iterator.
324b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     */
325b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    public Iterable<CorpusResult> getCorpusResults() {
32600efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        ArrayList<CorpusResult> results = new ArrayList<CorpusResult>(mCorpusResults.length);
32700efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        for (int i = 0; i < mCorpusResults.length; ++i) {
32800efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood            if (mCorpusResults[i] != null) {
32900efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood                results.add(mCorpusResults[i]);
33000efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood            }
33100efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        }
33200efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        return results;
333b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    }
334b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
335b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    public CorpusResult getCorpusResult(Corpus corpus) {
336b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        for (CorpusResult result : mCorpusResults) {
337723b496d757c34d8ea745c4f650b353f5791e1ceMathew Inwood            if (result != null && corpus.equals(result.getCorpus())) {
338b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert                return result;
339b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert            }
340b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        }
341b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        return null;
342b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    }
343b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
344b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    public CorpusResult getWebResult() {
345b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        return mWebResult;
346b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    }
34794e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney
348b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    /**
349b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     * Gets the number of source results.
350b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     * Must be called on the UI thread, or before this object is seen by the UI thread.
351b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert     */
352b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    public int getResultCount() {
353b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        if (isClosed()) {
354b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert            throw new IllegalStateException("Called getSourceCount() when closed.");
355b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        }
35600efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood        return countCorpusResults();
357b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    }
358b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert
359b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    @Override
360b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    public String toString() {
361b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert        return "Suggestions@" + hashCode() + "{expectedCorpora=" + mExpectedCorpora
36200efc7712fd9fce49f1c8e1a4de71784958f8a35Mathew Inwood                + ",countCorpusResults()=" + countCorpusResults() + "}";
363b83882b9efa37ec0f20a0f1c85cf5ccc93194aeeBjorn Bringert    }
3644572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood
3654572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood    private class MyShortcutsObserver extends DataSetObserver {
3664572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood        @Override
3674572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood        public void onChanged() {
3684572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood            notifyDataSetChanged();
3694572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood        }
3704572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood    }
3714572856ac85bb53ea06e65d00beebdf336af9b27Mathew Inwood
3723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert}
373