SuggestionsAdapterBase.java revision fd4a4cbc1143a734d357897531daa7105db6459b
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    private boolean addViewTypes(SuggestionViewFactory f) {
81        boolean changed = false;
82        for (String viewType : f.getSuggestionViewTypes()) {
83            if (!mViewTypeMap.containsKey(viewType)) {
84                mViewTypeMap.put(viewType, mViewTypeMap.size());
85                changed = true;
86            }
87        }
88        return changed;
89    }
90
91    private boolean buildViewTypeMap() {
92        boolean changed = addViewTypes(mFallback);
93        for (Corpus c : mCorpora.getEnabledCorpora()) {
94            for (Source s : c.getSources()) {
95                SuggestionViewFactory f = s.getSuggestionViewFactory();
96                changed |= addViewTypes(f);
97            }
98        }
99        return changed;
100    }
101
102    public void setMaxPromoted(int maxPromoted) {
103        if (DBG) Log.d(TAG, "setMaxPromoted " + maxPromoted);
104        mMaxPromoted = maxPromoted;
105        onSuggestionsChanged();
106    }
107
108    public void setIcon1Enabled(boolean enabled) {
109        mIcon1Enabled = enabled;
110    }
111
112    public boolean isClosed() {
113        return mClosed;
114    }
115
116    public void close() {
117        setSuggestions(null);
118        mCorpora.unregisterDataSetObserver(mCorporaObserver);
119        mClosed = true;
120    }
121
122    public void setPromoter(Promoter promoter) {
123        mPromoter = promoter;
124        onSuggestionsChanged();
125    }
126
127    public void setSuggestionClickListener(SuggestionClickListener listener) {
128        mSuggestionClickListener = listener;
129    }
130
131    public void setOnFocusChangeListener(OnFocusChangeListener l) {
132        mOnFocusChangeListener = l;
133    }
134
135    public void setSuggestions(Suggestions suggestions) {
136        if (mSuggestions == suggestions) {
137            return;
138        }
139        if (mClosed) {
140            if (suggestions != null) {
141                suggestions.release();
142            }
143            return;
144        }
145        if (mDataSetObserver == null) {
146            mDataSetObserver = new MySuggestionsObserver();
147        }
148        // TODO: delay the change if there are no suggestions for the currently visible tab.
149        if (mSuggestions != null) {
150            mSuggestions.unregisterDataSetObserver(mDataSetObserver);
151            mSuggestions.release();
152        }
153        mSuggestions = suggestions;
154        if (mSuggestions != null) {
155            mSuggestions.registerDataSetObserver(mDataSetObserver);
156        }
157        onSuggestionsChanged();
158    }
159
160    public Suggestions getSuggestions() {
161        return mSuggestions;
162    }
163
164    protected int getPromotedCount() {
165        return mPromotedSuggestions == null ? 0 : mPromotedSuggestions.getCount();
166    }
167
168    protected SuggestionPosition getPromotedSuggestion(int position) {
169        if (mPromotedSuggestions == null) return null;
170        return new SuggestionPosition(mPromotedSuggestions, position);
171    }
172
173    protected int getViewTypeCount() {
174        return mViewTypeMap.size();
175    }
176
177    private String suggestionViewType(Suggestion suggestion) {
178        String viewType = suggestion.getSuggestionSource().getSuggestionViewFactory()
179                .getViewType(suggestion);
180        if (!mViewTypeMap.containsKey(viewType)) {
181            throw new IllegalStateException("Unknown viewType " + viewType);
182        }
183        return viewType;
184    }
185
186    protected int getSuggestionViewType(SuggestionCursor cursor, int position) {
187        if (cursor == null) {
188            return 0;
189        }
190        cursor.moveTo(position);
191        return mViewTypeMap.get(suggestionViewType(cursor));
192    }
193
194    protected int getSuggestionViewTypeCount() {
195        return mViewTypeMap.size();
196    }
197
198    protected View getView(SuggestionCursor suggestions, int position,
199            View convertView, ViewGroup parent) {
200        suggestions.moveTo(position);
201        SuggestionViewFactory factory = suggestions.getSuggestionSource().getSuggestionViewFactory();
202        View v = factory.getView(suggestions, suggestions.getUserQuery(), convertView, parent);
203        if (v == null) {
204            v = mFallback.getView(suggestions, suggestions.getUserQuery(), convertView, parent);
205        }
206        if (v instanceof SuggestionView) {
207            ((SuggestionView) v).setIcon1Enabled(mIcon1Enabled);
208            ((SuggestionView) v).bindAdapter(this, position);
209        } else {
210            SuggestionViewClickListener l = new SuggestionViewClickListener(position);
211            v.setOnClickListener(l);
212        }
213
214        if (mOnFocusChangeListener != null) {
215            v.setOnFocusChangeListener(mOnFocusChangeListener);
216        }
217        return v;
218    }
219
220    protected void onSuggestionsChanged() {
221        if (DBG) Log.d(TAG, "onSuggestionsChanged(" + mSuggestions + ")");
222        SuggestionCursor cursor = getPromoted(mSuggestions);
223        changePromoted(cursor);
224    }
225
226    /**
227     * Gets the cursor containing the currently shown suggestions. The caller should not hold
228     * on to or modify the returned cursor.
229     */
230    public SuggestionCursor getCurrentPromotedSuggestions() {
231        return mPromotedSuggestions;
232    }
233
234    /**
235     * Gets the cursor for the given source.
236     */
237    protected SuggestionCursor getPromoted(Suggestions suggestions) {
238        if (suggestions == null) return null;
239        return suggestions.getPromoted(mPromoter, mMaxPromoted);
240    }
241
242    /**
243     * Replace the cursor.
244     *
245     * This does not close the old cursor. Instead, all the cursors are closed in
246     * {@link #setSuggestions(Suggestions)}.
247     */
248    private void changePromoted(SuggestionCursor newCursor) {
249        if (DBG) {
250            Log.d(TAG, "changeCursor(" + newCursor + ") count=" +
251                    (newCursor == null ? 0 : newCursor.getCount()));
252        }
253        if (newCursor == mPromotedSuggestions) {
254            if (newCursor != null) {
255                // Shortcuts may have changed without the cursor changing.
256                notifyDataSetChanged();
257            }
258            return;
259        }
260        mPromotedSuggestions = newCursor;
261        if (mPromotedSuggestions != null) {
262            notifyDataSetChanged();
263        } else {
264            notifyDataSetInvalidated();
265        }
266    }
267
268    public void onSuggestionClicked(int position) {
269        if (mSuggestionClickListener != null) {
270            mSuggestionClickListener.onSuggestionClicked(this, position);
271        }
272    }
273
274    public void onSuggestionQuickContactClicked(int position) {
275        if (mSuggestionClickListener != null) {
276            mSuggestionClickListener.onSuggestionQuickContactClicked(this, position);
277        }
278    }
279
280    public void onSuggestionRemoveFromHistoryClicked(int position) {
281        if (mSuggestionClickListener != null) {
282            mSuggestionClickListener.onSuggestionRemoveFromHistoryClicked(this, position);
283        }
284    }
285
286    public void onSuggestionQueryRefineClicked(int position) {
287        if (mSuggestionClickListener != null) {
288            mSuggestionClickListener.onSuggestionQueryRefineClicked(this, position);
289        }
290    }
291
292    public abstract A getListAdapter();
293
294    protected abstract void notifyDataSetInvalidated();
295
296    protected abstract void notifyDataSetChanged();
297
298    private class MySuggestionsObserver extends DataSetObserver {
299        @Override
300        public void onChanged() {
301            onSuggestionsChanged();
302        }
303    }
304
305    private class CorporaObserver extends DataSetObserver {
306        @Override
307        public void onChanged() {
308            if (buildViewTypeMap()) {
309                if (mListener != null) {
310                    mListener.onSuggestionAdapterChanged();
311                }
312            }
313        }
314    }
315
316    private class SuggestionViewClickListener implements View.OnClickListener {
317        private final int mPosition;
318        public SuggestionViewClickListener(int position) {
319            mPosition = position;
320        }
321        public void onClick(View v) {
322            onSuggestionClicked(mPosition);
323        }
324    }
325
326}
327