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