Suggestions.java revision fde948e69f59589cf0d217ea414af7947de600bb
1/*
2 * Copyright (C) 2009 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
17package com.android.quicksearchbox;
18
19import android.database.DataSetObservable;
20import android.database.DataSetObserver;
21import android.util.Log;
22
23import java.util.ArrayList;
24import java.util.HashMap;
25
26/**
27 * Contains all {@link SuggestionCursor} objects that have been reported.
28 */
29public class Suggestions {
30
31    private static final boolean DBG = false;
32    private static final String TAG = "QSB.Suggestions";
33
34    private final int mMaxPromoted;
35
36    private final String mQuery;
37
38    /** The number of sources that are expected to report. */
39    private final int mExpectedCorpusCount;
40
41    /**
42     * The observers that want notifications of changes to the published suggestions.
43     * This object may be accessed on any thread.
44     */
45    private final DataSetObservable mDataSetObservable = new DataSetObservable();
46
47    /**
48     * All {@link SuggestionCursor} objects that have been published so far,
49     * in the order that they were published.
50     * This object may only be accessed on the UI thread.
51     * */
52    private final ArrayList<CorpusResult> mCorpusResults;
53
54    /**
55     * All {@link SuggestionCursor} objects that have been published so far.
56     * This object may only be accessed on the UI thread.
57     * */
58    private final HashMap<Corpus,CorpusResult> mResultsByCorpus;
59
60    private SuggestionCursor mShortcuts;
61
62    private MyShortcutsObserver mShortcutsObserver = new MyShortcutsObserver();
63
64    /** True if {@link Suggestions#close} has been called. */
65    private boolean mClosed = false;
66
67    private final Promoter mPromoter;
68
69    private ListSuggestionCursor mPromoted;
70
71    /**
72     * Creates a new empty Suggestions.
73     *
74     * @param expectedCorpusCount The number of sources that are expected to report.
75     */
76    public Suggestions(Promoter promoter, int maxPromoted,
77            String query, int expectedCorpusCount) {
78        mPromoter = promoter;
79        mMaxPromoted = maxPromoted;
80        mQuery = query;
81        mExpectedCorpusCount = expectedCorpusCount;
82        mCorpusResults = new ArrayList<CorpusResult>(mExpectedCorpusCount);
83        mResultsByCorpus = new HashMap<Corpus,CorpusResult>(mExpectedCorpusCount);
84        mPromoted = null;  // will be set by updatePromoted()
85    }
86
87    public String getQuery() {
88        return mQuery;
89    }
90
91    /**
92     * Gets the number of sources that are expected to report.
93     */
94    public int getExpectedSourceCount() {
95        return mExpectedCorpusCount;
96    }
97
98    /**
99     * Registers an observer that will be notified when the reported results or
100     * the done status changes.
101     */
102    public void registerDataSetObserver(DataSetObserver observer) {
103        if (mClosed) {
104            throw new IllegalStateException("registerDataSetObserver() when closed");
105        }
106        mDataSetObservable.registerObserver(observer);
107    }
108
109    /**
110     * Unregisters an observer.
111     */
112    public void unregisterDataSetObserver(DataSetObserver observer) {
113        mDataSetObservable.unregisterObserver(observer);
114    }
115
116    public SuggestionCursor getPromoted() {
117        if (mPromoted == null) {
118            updatePromoted();
119        }
120        return mPromoted;
121    }
122
123    /**
124     * Calls {@link DataSetObserver#onChanged()} on all observers.
125     */
126    private void notifyDataSetChanged() {
127        if (DBG) Log.d(TAG, "notifyDataSetChanged()");
128        mDataSetObservable.notifyChanged();
129    }
130
131    /**
132     * Closes all the source results and unregisters all observers.
133     */
134    public void close() {
135        if (DBG) Log.d(TAG, "close()");
136        if (mClosed) {
137            throw new IllegalStateException("Double close()");
138        }
139        mDataSetObservable.unregisterAll();
140        mClosed = true;
141        if (mShortcuts != null) {
142            mShortcuts.close();
143            mShortcuts = null;
144        }
145        for (CorpusResult result : mCorpusResults) {
146            result.close();
147        }
148        mCorpusResults.clear();
149        mResultsByCorpus.clear();
150    }
151
152    public boolean isClosed() {
153        return mClosed;
154    }
155
156    @Override
157    protected void finalize() {
158        if (!mClosed) {
159            Log.e(TAG, "LEAK! Finalized without being closed: Suggestions[" + mQuery + "]");
160            close();
161        }
162    }
163
164    /**
165     * Checks whether all sources have reported.
166     * Must be called on the UI thread, or before this object is seen by the UI thread.
167     */
168    public boolean isDone() {
169        // TODO: Handle early completion because we have all the results we want.
170        return mCorpusResults.size() >= mExpectedCorpusCount;
171    }
172
173    /**
174     * Sets the shortcut suggestions.
175     * Must be called on the UI thread, or before this object is seen by the UI thread.
176     *
177     * @param shortcuts The shortcuts.
178     */
179    public void setShortcuts(SuggestionCursor shortcuts) {
180        if (DBG) Log.d(TAG, "setShortcuts(" + shortcuts + ")");
181        mShortcuts = shortcuts;
182        if (shortcuts != null) {
183            mShortcuts.registerDataSetObserver(mShortcutsObserver);
184        }
185    }
186
187    /**
188     * Adds a corpus result. Must be called on the UI thread, or before this
189     * object is seen by the UI thread.
190     */
191    public void addCorpusResult(CorpusResult corpusResult) {
192        if (mClosed) {
193            corpusResult.close();
194            return;
195        }
196        if (!mQuery.equals(corpusResult.getUserQuery())) {
197          throw new IllegalArgumentException("Got result for wrong query: "
198                + mQuery + " != " + corpusResult.getUserQuery());
199        }
200        mCorpusResults.add(corpusResult);
201        mResultsByCorpus.put(corpusResult.getCorpus(), corpusResult);
202        mPromoted = null;
203        notifyDataSetChanged();
204    }
205
206    private void updatePromoted() {
207        mPromoted = new ListSuggestionCursorNoDuplicates(mQuery);
208        if (mPromoter == null) {
209            return;
210        }
211        mPromoter.pickPromoted(mShortcuts, mCorpusResults, mMaxPromoted, mPromoted);
212    }
213
214    /**
215     * Gets a given corpus result.
216     * Must be called on the UI thread, or before this object is seen by the UI thread.
217     *
218     * @param corpus corpus
219     * @return The source result for the given source. {@code null} if the source has not
220     *         yet returned.
221     */
222    public CorpusResult getCorpusResult(Corpus corpus) {
223        if (mClosed) {
224            throw new IllegalStateException("getCorpusResult(" + corpus + ") when closed.");
225        }
226        return mResultsByCorpus.get(corpus);
227    }
228
229    /**
230     * Gets the number of source results.
231     * Must be called on the UI thread, or before this object is seen by the UI thread.
232     */
233    public int getSourceCount() {
234        if (mClosed) {
235            throw new IllegalStateException("Called getSourceCount() when closed.");
236        }
237        return mCorpusResults == null ? 0 : mCorpusResults.size();
238    }
239
240    private class MyShortcutsObserver extends DataSetObserver {
241        @Override
242        public void onChanged() {
243            mPromoted = null;
244            notifyDataSetChanged();
245        }
246    }
247
248}
249