SuggestionsAdapter.java revision fdb80c2962c88ac62dcd7ee7f2fab1857b61506b
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            v.setOnLongClickListener(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        changeCursor(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 getCurrentSuggestions() {
231        return mCursor;
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 changeCursor(SuggestionCursor newCursor) {
249        if (DBG) {
250            Log.d(TAG, "changeCursor(" + newCursor + ") count=" +
251                    (newCursor == null ? 0 : newCursor.getCount()));
252        }
253        if (newCursor == mCursor) {
254            if (newCursor != null) {
255                // Shortcuts may have changed without the cursor changing.
256                notifyDataSetChanged();
257            }
258            return;
259        }
260        mCursor = newCursor;
261        if (mCursor != 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 boolean onSuggestionLongClicked(int position) {
281        if (mSuggestionClickListener != null) {
282            return mSuggestionClickListener.onSuggestionLongClicked(this, position);
283        }
284        return false;
285    }
286
287    public void onSuggestionQueryRefineClicked(int position) {
288        if (mSuggestionClickListener != null) {
289            mSuggestionClickListener.onSuggestionQueryRefineClicked(this, position);
290        }
291    }
292
293    private class MySuggestionsObserver extends DataSetObserver {
294        @Override
295        public void onChanged() {
296            onSuggestionsChanged();
297        }
298    }
299
300    private class CorporaObserver extends DataSetObserver {
301        @Override
302        public void onChanged() {
303            if (buildViewTypeMap()) {
304                if (mListener != null) {
305                    mListener.onSuggestionAdapterChanged();
306                }
307            }
308        }
309    }
310
311    private class SuggestionViewClickListener
312            implements View.OnClickListener, View.OnLongClickListener {
313        private final int mPosition;
314        public SuggestionViewClickListener(int position) {
315            mPosition = position;
316        }
317        public void onClick(View v) {
318            onSuggestionClicked(mPosition);
319        }
320        public boolean onLongClick(View v) {
321            return onSuggestionLongClicked(mPosition);
322        }
323    }
324
325    /**
326     * Callback interface used to notify the view when the adapter has changed (i.e. the number and
327     * type of views returned has changed).
328     */
329    public interface SuggestionsAdapterChangeListener {
330        void onSuggestionAdapterChanged();
331    }
332
333}
334