SuggestionsAdapterBase.java revision 116affd19a834a2df4226a1ae37afcc1113a230e
1/*
2 * Copyright (C) 2010 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 */
16package com.android.quicksearchbox.ui;
17
18import com.android.quicksearchbox.Corpora;
19import com.android.quicksearchbox.Corpus;
20import com.android.quicksearchbox.Promoter;
21import com.android.quicksearchbox.Source;
22import com.android.quicksearchbox.Suggestion;
23import com.android.quicksearchbox.SuggestionCursor;
24import com.android.quicksearchbox.SuggestionPosition;
25import com.android.quicksearchbox.Suggestions;
26
27import android.database.DataSetObserver;
28import android.util.Log;
29import android.view.View;
30import android.view.View.OnFocusChangeListener;
31import android.view.ViewGroup;
32
33import java.util.HashMap;
34
35/**
36 * Base class for suggestions adapters. The templated class A is the list adapter class.
37 */
38public abstract class SuggestionsAdapterBase<A> implements SuggestionsAdapter<A> {
39
40    private static final boolean DBG = false;
41    private static final String TAG = "QSB.SuggestionsAdapter";
42
43    private final Corpora mCorpora;
44
45    private DataSetObserver mDataSetObserver;
46
47    private Promoter mPromoter;
48
49    private int mMaxPromoted;
50
51    private SuggestionCursor mPromotedSuggestions;
52    private final HashMap<String, Integer> mViewTypeMap;
53    private final SuggestionViewFactory mFallback;
54
55    private Suggestions mSuggestions;
56
57    private SuggestionClickListener mSuggestionClickListener;
58    private OnFocusChangeListener mOnFocusChangeListener;
59
60    private SuggestionsAdapterChangeListener mListener;
61
62    private final CorporaObserver mCorporaObserver = new CorporaObserver();
63
64    private boolean mClosed = false;
65
66    private boolean mIcon1Enabled = true;
67
68    protected SuggestionsAdapterBase(SuggestionViewFactory fallbackFactory, Corpora corpora) {
69        mCorpora = corpora;
70        mFallback = fallbackFactory;
71        mViewTypeMap = new HashMap<String, Integer>();
72        mCorpora.registerDataSetObserver(mCorporaObserver);
73        buildViewTypeMap();
74    }
75
76    public void setSuggestionAdapterChangeListener(SuggestionsAdapterChangeListener l) {
77        mListener = l;
78    }
79
80    public abstract boolean isEmpty();
81
82    /**
83     * Indicates if this adapter will publish suggestions other than those in the promoted list.
84     */
85    public abstract boolean willPublishNonPromotedSuggestions();
86
87    private boolean addViewTypes(SuggestionViewFactory f) {
88        boolean changed = false;
89        for (String viewType : f.getSuggestionViewTypes()) {
90            if (!mViewTypeMap.containsKey(viewType)) {
91                mViewTypeMap.put(viewType, mViewTypeMap.size());
92                changed = true;
93            }
94        }
95        return changed;
96    }
97
98    private boolean buildViewTypeMap() {
99        boolean changed = addViewTypes(mFallback);
100        for (Corpus c : mCorpora.getEnabledCorpora()) {
101            for (Source s : c.getSources()) {
102                SuggestionViewFactory f = s.getSuggestionViewFactory();
103                changed |= addViewTypes(f);
104            }
105        }
106        return changed;
107    }
108
109    public void setMaxPromoted(int maxPromoted) {
110        if (DBG) Log.d(TAG, "setMaxPromoted " + maxPromoted);
111        mMaxPromoted = maxPromoted;
112        onSuggestionsChanged();
113    }
114
115    public void setIcon1Enabled(boolean enabled) {
116        mIcon1Enabled = enabled;
117    }
118
119    public boolean isClosed() {
120        return mClosed;
121    }
122
123    public void close() {
124        setSuggestions(null);
125        mCorpora.unregisterDataSetObserver(mCorporaObserver);
126        mClosed = true;
127    }
128
129    public void setPromoter(Promoter promoter) {
130        mPromoter = promoter;
131        onSuggestionsChanged();
132    }
133
134    public void setSuggestionClickListener(SuggestionClickListener listener) {
135        mSuggestionClickListener = listener;
136    }
137
138    public void setOnFocusChangeListener(OnFocusChangeListener l) {
139        mOnFocusChangeListener = l;
140    }
141
142    public void setSuggestions(Suggestions suggestions) {
143        if (mSuggestions == suggestions) {
144            return;
145        }
146        if (mClosed) {
147            if (suggestions != null) {
148                suggestions.release();
149            }
150            return;
151        }
152        if (mDataSetObserver == null) {
153            mDataSetObserver = new MySuggestionsObserver();
154        }
155        // TODO: delay the change if there are no suggestions for the currently visible tab.
156        if (mSuggestions != null) {
157            mSuggestions.unregisterDataSetObserver(mDataSetObserver);
158            mSuggestions.release();
159        }
160        mSuggestions = suggestions;
161        if (mSuggestions != null) {
162            mSuggestions.registerDataSetObserver(mDataSetObserver);
163        }
164        onSuggestionsChanged();
165    }
166
167    public Suggestions getSuggestions() {
168        return mSuggestions;
169    }
170
171    public abstract SuggestionPosition getSuggestion(long suggestionId);
172
173    protected int getPromotedCount() {
174        return mPromotedSuggestions == null ? 0 : mPromotedSuggestions.getCount();
175    }
176
177    protected SuggestionPosition getPromotedSuggestion(int position) {
178        if (mPromotedSuggestions == null) return null;
179        return new SuggestionPosition(mPromotedSuggestions, position);
180    }
181
182    protected int getViewTypeCount() {
183        return mViewTypeMap.size();
184    }
185
186    private String suggestionViewType(Suggestion suggestion) {
187        String viewType = suggestion.getSuggestionSource().getSuggestionViewFactory()
188                .getViewType(suggestion);
189        if (!mViewTypeMap.containsKey(viewType)) {
190            throw new IllegalStateException("Unknown viewType " + viewType);
191        }
192        return viewType;
193    }
194
195    protected int getSuggestionViewType(SuggestionCursor cursor, int position) {
196        if (cursor == null) {
197            return 0;
198        }
199        cursor.moveTo(position);
200        return mViewTypeMap.get(suggestionViewType(cursor));
201    }
202
203    protected int getSuggestionViewTypeCount() {
204        return mViewTypeMap.size();
205    }
206
207    protected View getView(SuggestionCursor suggestions, int position, long suggestionId,
208            View convertView, ViewGroup parent) {
209        suggestions.moveTo(position);
210        SuggestionViewFactory factory = suggestions.getSuggestionSource().getSuggestionViewFactory();
211        View v = factory.getView(suggestions, suggestions.getUserQuery(), convertView, parent);
212        if (v == null) {
213            v = mFallback.getView(suggestions, suggestions.getUserQuery(), convertView, parent);
214        }
215        if (v instanceof SuggestionView) {
216            ((SuggestionView) v).setIcon1Enabled(mIcon1Enabled);
217            ((SuggestionView) v).bindAdapter(this, suggestionId);
218        } else {
219            SuggestionViewClickListener l = new SuggestionViewClickListener(suggestionId);
220            v.setOnClickListener(l);
221        }
222
223        if (mOnFocusChangeListener != null) {
224            v.setOnFocusChangeListener(mOnFocusChangeListener);
225        }
226        return v;
227    }
228
229    protected void onSuggestionsChanged() {
230        if (DBG) Log.d(TAG, "onSuggestionsChanged(" + mSuggestions + ")");
231        SuggestionCursor cursor = getPromoted(mSuggestions);
232        changePromoted(cursor);
233    }
234
235    /**
236     * Gets the cursor containing the currently shown suggestions. The caller should not hold
237     * on to or modify the returned cursor.
238     */
239    public SuggestionCursor getCurrentPromotedSuggestions() {
240        return mPromotedSuggestions;
241    }
242
243    /**
244     * Gets the cursor for the given source.
245     */
246    protected SuggestionCursor getPromoted(Suggestions suggestions) {
247        if (suggestions == null) return null;
248        return suggestions.getPromoted(mPromoter, mMaxPromoted);
249    }
250
251    /**
252     * Replace the cursor.
253     *
254     * This does not close the old cursor. Instead, all the cursors are closed in
255     * {@link #setSuggestions(Suggestions)}.
256     */
257    private void changePromoted(SuggestionCursor newCursor) {
258        if (DBG) {
259            Log.d(TAG, "changeCursor(" + newCursor + ") count=" +
260                    (newCursor == null ? 0 : newCursor.getCount()));
261        }
262        if (newCursor == mPromotedSuggestions) {
263            if (newCursor != null) {
264                // Shortcuts may have changed without the cursor changing.
265                notifyDataSetChanged();
266            }
267            return;
268        }
269        mPromotedSuggestions = newCursor;
270        if (mPromotedSuggestions != null) {
271            notifyDataSetChanged();
272        } else {
273            notifyDataSetInvalidated();
274        }
275    }
276
277    public void onSuggestionClicked(long suggestionId) {
278        if (mSuggestionClickListener != null) {
279            mSuggestionClickListener.onSuggestionClicked(this, suggestionId);
280        }
281    }
282
283    public void onSuggestionQuickContactClicked(long suggestionId) {
284        if (mSuggestionClickListener != null) {
285            mSuggestionClickListener.onSuggestionQuickContactClicked(this, suggestionId);
286        }
287    }
288
289    public void onSuggestionRemoveFromHistoryClicked(long suggestionId) {
290        if (mSuggestionClickListener != null) {
291            mSuggestionClickListener.onSuggestionRemoveFromHistoryClicked(this, suggestionId);
292        }
293    }
294
295    public void onSuggestionQueryRefineClicked(long suggestionId) {
296        if (mSuggestionClickListener != null) {
297            mSuggestionClickListener.onSuggestionQueryRefineClicked(this, suggestionId);
298        }
299    }
300
301    public abstract A getListAdapter();
302
303    protected abstract void notifyDataSetInvalidated();
304
305    protected abstract void notifyDataSetChanged();
306
307    private class MySuggestionsObserver extends DataSetObserver {
308        @Override
309        public void onChanged() {
310            onSuggestionsChanged();
311        }
312    }
313
314    private class CorporaObserver extends DataSetObserver {
315        @Override
316        public void onChanged() {
317            if (buildViewTypeMap()) {
318                if (mListener != null) {
319                    mListener.onSuggestionAdapterChanged();
320                }
321            }
322        }
323    }
324
325    private class SuggestionViewClickListener implements View.OnClickListener {
326        private final long mSuggestionId;
327        public SuggestionViewClickListener(long suggestionId) {
328            mSuggestionId = suggestionId;
329        }
330        public void onClick(View v) {
331            onSuggestionClicked(mSuggestionId);
332        }
333    }
334
335}
336