SuggestionsAdapter.java revision 8b2936607176720172aee068abc5631bdf77e843
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        mMaxPromoted = maxPromoted;
103        onSuggestionsChanged();
104    }
105
106    public boolean isClosed() {
107        return mClosed;
108    }
109
110    public void close() {
111        setSuggestions(null);
112        mCorpora.unregisterDataSetObserver(mCorporaObserver);
113        mClosed = true;
114    }
115
116    public void setPromoter(Promoter promoter) {
117        mPromoter = promoter;
118        onSuggestionsChanged();
119    }
120
121    public void setSuggestionClickListener(SuggestionClickListener listener) {
122        mSuggestionClickListener = listener;
123    }
124
125    public void setOnFocusChangeListener(OnFocusChangeListener l) {
126        mOnFocusChangeListener = l;
127    }
128
129    public void setSuggestions(Suggestions suggestions) {
130        if (mSuggestions == suggestions) {
131            return;
132        }
133        if (mClosed) {
134            if (suggestions != null) {
135                suggestions.release();
136            }
137            return;
138        }
139        if (mDataSetObserver == null) {
140            mDataSetObserver = new MySuggestionsObserver();
141        }
142        // TODO: delay the change if there are no suggestions for the currently visible tab.
143        if (mSuggestions != null) {
144            mSuggestions.unregisterDataSetObserver(mDataSetObserver);
145            mSuggestions.release();
146        }
147        mSuggestions = suggestions;
148        if (mSuggestions != null) {
149            mSuggestions.registerDataSetObserver(mDataSetObserver);
150        }
151        onSuggestionsChanged();
152    }
153
154    public Suggestions getSuggestions() {
155        return mSuggestions;
156    }
157
158    public int getCount() {
159        return mCursor == null ? 0 : mCursor.getCount();
160    }
161
162    public SuggestionPosition getItem(int position) {
163        if (mCursor == null) return null;
164        return new SuggestionPosition(mCursor, position);
165    }
166
167    public long getItemId(int position) {
168        return position;
169    }
170
171    @Override
172    public int getViewTypeCount() {
173        return mViewTypeMap.size();
174    }
175
176    private String currentSuggestionViewType() {
177        String viewType = mCursor.getSuggestionSource().getSuggestionViewFactory()
178                .getViewType(mCursor);
179        if (!mViewTypeMap.containsKey(viewType)) {
180            throw new IllegalStateException("Unknown viewType " + viewType);
181        }
182        return viewType;
183    }
184
185    @Override
186    public int getItemViewType(int position) {
187        if (DBG) Log.d(TAG, "getItemViewType(" + position + ") mCursor=" + mCursor);
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) Log.d(TAG, "changeCursor(" + newCursor + ")");
250        if (newCursor == mCursor) {
251            if (newCursor != null) {
252                // Shortcuts may have changed without the cursor changing.
253                notifyDataSetChanged();
254            }
255            return;
256        }
257        mCursor = newCursor;
258        if (mCursor != null) {
259            notifyDataSetChanged();
260        } else {
261            notifyDataSetInvalidated();
262        }
263    }
264
265    public void onSuggestionClicked(int position) {
266        if (mSuggestionClickListener != null) {
267            mSuggestionClickListener.onSuggestionClicked(this, position);
268        }
269    }
270
271    public void onSuggestionQuickContactClicked(int position) {
272        if (mSuggestionClickListener != null) {
273            mSuggestionClickListener.onSuggestionQuickContactClicked(this, position);
274        }
275    }
276
277    public boolean onSuggestionLongClicked(int position) {
278        if (mSuggestionClickListener != null) {
279            return mSuggestionClickListener.onSuggestionLongClicked(this, position);
280        }
281        return false;
282    }
283
284    public void onSuggestionQueryRefineClicked(int position) {
285        if (mSuggestionClickListener != null) {
286            mSuggestionClickListener.onSuggestionQueryRefineClicked(this, position);
287        }
288    }
289
290    private class MySuggestionsObserver extends DataSetObserver {
291        @Override
292        public void onChanged() {
293            onSuggestionsChanged();
294        }
295    }
296
297    private class CorporaObserver extends DataSetObserver {
298        @Override
299        public void onChanged() {
300            if (buildViewTypeMap()) {
301                if (mListener != null) {
302                    mListener.onSuggestionAdapterChanged();
303                }
304            }
305        }
306    }
307
308    private class SuggestionViewClickListener
309            implements View.OnClickListener, View.OnLongClickListener {
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        public boolean onLongClick(View v) {
318            return onSuggestionLongClicked(mPosition);
319        }
320    }
321
322    /**
323     * Callback interface used to notify the view when the adapter has changed (i.e. the number and
324     * type of views returned has changed).
325     */
326    public interface SuggestionsAdapterChangeListener {
327        void onSuggestionAdapterChanged();
328    }
329
330}
331