Suggestions.java revision 3673ad4f2b3893f15675820ab2240ec0a64b7970
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        }
161    }
162
163    /**
164     * Checks whether all sources have reported.
165     * Must be called on the UI thread, or before this object is seen by the UI thread.
166     */
167    public boolean isDone() {
168        // TODO: Handle early completion because we have all the results we want.
169        return mCorpusResults.size() >= mExpectedCorpusCount;
170    }
171
172    /**
173     * Sets the shortcut suggestions.
174     * Must be called on the UI thread, or before this object is seen by the UI thread.
175     *
176     * @param shortcuts The shortcuts.
177     */
178    public void setShortcuts(SuggestionCursor shortcuts) {
179        if (DBG) Log.d(TAG, "setShortcuts(" + shortcuts + ")");
180        mShortcuts = shortcuts;
181        if (shortcuts != null) {
182            mShortcuts.registerDataSetObserver(mShortcutsObserver);
183        }
184    }
185
186    /**
187     * Adds a corpus result. Must be called on the UI thread, or before this
188     * object is seen by the UI thread.
189     */
190    public void addCorpusResult(CorpusResult corpusResult) {
191        if (mClosed) {
192            corpusResult.close();
193            return;
194        }
195        if (!mQuery.equals(corpusResult.getUserQuery())) {
196          throw new IllegalArgumentException("Got result for wrong query: "
197                + mQuery + " != " + corpusResult.getUserQuery());
198        }
199        mCorpusResults.add(corpusResult);
200        mResultsByCorpus.put(corpusResult.getCorpus(), corpusResult);
201        mPromoted = null;
202        notifyDataSetChanged();
203    }
204
205    private void updatePromoted() {
206        mPromoted = new ListSuggestionCursorNoDuplicates(mQuery);
207        if (mPromoter == null) {
208            return;
209        }
210        mPromoter.pickPromoted(mShortcuts, mCorpusResults, mMaxPromoted, mPromoted);
211    }
212
213    /**
214     * Gets a given corpus result.
215     * Must be called on the UI thread, or before this object is seen by the UI thread.
216     *
217     * @param corpus corpus
218     * @return The source result for the given source. {@code null} if the source has not
219     *         yet returned.
220     */
221    public CorpusResult getCorpusResult(Corpus corpus) {
222        if (mClosed) {
223            throw new IllegalStateException("getCorpusResult(" + corpus + ") when closed.");
224        }
225        return mResultsByCorpus.get(corpus);
226    }
227
228    /**
229     * Gets the number of source results.
230     * Must be called on the UI thread, or before this object is seen by the UI thread.
231     */
232    public int getSourceCount() {
233        if (mClosed) {
234            throw new IllegalStateException("Called getSourceCount() when closed.");
235        }
236        return mCorpusResults == null ? 0 : mCorpusResults.size();
237    }
238
239    private class MyShortcutsObserver extends DataSetObserver {
240        @Override
241        public void onChanged() {
242            mPromoted = null;
243            notifyDataSetChanged();
244        }
245    }
246
247}
248