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