SuggestionsAdapter.java revision b83882b9efa37ec0f20a0f1c85cf5ccc93194aee
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 android.database.DataSetObserver;
20import android.util.Log;
21import android.view.View;
22import android.view.View.OnFocusChangeListener;
23import android.view.ViewGroup;
24import android.widget.BaseAdapter;
25
26import com.android.quicksearchbox.Corpus;
27import com.android.quicksearchbox.Promoter;
28import com.android.quicksearchbox.SuggestionCursor;
29import com.android.quicksearchbox.SuggestionPosition;
30import com.android.quicksearchbox.Suggestions;
31
32/**
33 * Uses a {@link Suggestions} object to back a {@link SuggestionsView}.
34 */
35public class SuggestionsAdapter extends BaseAdapter {
36
37    private static final boolean DBG = false;
38    private static final String TAG = "QSB.SuggestionsAdapter";
39
40    private DataSetObserver mDataSetObserver;
41
42    private final Promoter mPromoter;
43
44    private final SuggestionViewFactory mViewFactory;
45
46    private final int mMaxPromoted;
47
48    private SuggestionCursor mCursor;
49
50    private Corpus mCorpus = null;
51
52    private Suggestions mSuggestions;
53
54    private SuggestionClickListener mSuggestionClickListener;
55    private OnFocusChangeListener mOnFocusChangeListener;
56
57    private boolean mClosed = false;
58
59    public SuggestionsAdapter(Promoter promoter, SuggestionViewFactory viewFactory,
60            int maxPromoted) {
61        mPromoter = promoter;
62        mViewFactory = viewFactory;
63        mMaxPromoted = maxPromoted;
64    }
65
66    public boolean isClosed() {
67        return mClosed;
68    }
69
70    public void close() {
71        setSuggestions(null);
72        mCorpus = null;
73        mClosed = true;
74    }
75
76    public void setSuggestionClickListener(SuggestionClickListener listener) {
77        mSuggestionClickListener = listener;
78    }
79
80    public void setOnFocusChangeListener(OnFocusChangeListener l) {
81        mOnFocusChangeListener = l;
82    }
83
84    public void setSuggestions(Suggestions suggestions) {
85        if (mSuggestions == suggestions) {
86            return;
87        }
88        if (mClosed) {
89            if (suggestions != null) {
90                suggestions.release();
91            }
92            return;
93        }
94        if (mDataSetObserver == null) {
95            mDataSetObserver = new MySuggestionsObserver();
96        }
97        // TODO: delay the change if there are no suggestions for the currently visible tab.
98        if (mSuggestions != null) {
99            mSuggestions.unregisterDataSetObserver(mDataSetObserver);
100            mSuggestions.release();
101        }
102        mSuggestions = suggestions;
103        if (mSuggestions != null) {
104            mSuggestions.registerDataSetObserver(mDataSetObserver);
105        }
106        onSuggestionsChanged();
107    }
108
109    public Suggestions getSuggestions() {
110        return mSuggestions;
111    }
112
113    /**
114     * Gets the source whose results are displayed.
115     */
116    public Corpus getCorpus() {
117        return mCorpus;
118    }
119
120    /**
121     * Sets the source whose results are displayed.
122     */
123    public void setCorpus(Corpus corpus) {
124        if (mSuggestions != null) {
125            // TODO: if (mCorpus == null && corpus != null)
126            // we've just switched from the 'All' corpus to a specific corpus
127            // we can filter the existing results immediately.
128            if (corpus != null) {
129                // Note, when switching from a specific corpus to 'All' we do not change the
130                // suggestions, since they're still relevant for 'All'. Hence 'corpus != null'
131                if (DBG) Log.d(TAG, "setCorpus(" + corpus.getName() + ") Clear suggestions");
132                mSuggestions.unregisterDataSetObserver(mDataSetObserver);
133                mSuggestions.release();
134                mSuggestions = null;
135            }
136        }
137        mCorpus = corpus;
138        onSuggestionsChanged();
139    }
140
141    public int getCount() {
142        return mCursor == null ? 0 : mCursor.getCount();
143    }
144
145    public SuggestionPosition getItem(int position) {
146        if (mCursor == null) return null;
147        return new SuggestionPosition(mCursor, position);
148    }
149
150    public long getItemId(int position) {
151        return position;
152    }
153
154    @Override
155    public int getViewTypeCount() {
156        return mViewFactory.getSuggestionViewTypeCount();
157    }
158
159    @Override
160    public int getItemViewType(int position) {
161        if (mCursor == null) {
162            return 0;
163        }
164        mCursor.moveTo(position);
165        return mViewFactory.getSuggestionViewType(mCursor);
166    }
167
168    // Implements Adapter#getView()
169    public View getView(int position, View convertView, ViewGroup parent) {
170        if (mCursor == null) {
171            throw new IllegalStateException("getView() called with null cursor");
172        }
173        mCursor.moveTo(position);
174        int viewType = mViewFactory.getSuggestionViewType(mCursor);
175        SuggestionView view = mViewFactory.getSuggestionView(viewType, convertView, parent);
176        view.bindAsSuggestion(mCursor, mSuggestionClickListener);
177        View v = (View) view;
178        if (mOnFocusChangeListener != null) {
179            v.setOnFocusChangeListener(mOnFocusChangeListener);
180        }
181        return v;
182    }
183
184    protected void onSuggestionsChanged() {
185        if (DBG) Log.d(TAG, "onSuggestionsChanged(" + mSuggestions + ")");
186        SuggestionCursor cursor = getCorpusCursor(mSuggestions, mCorpus);
187        changeCursor(cursor);
188    }
189
190    /**
191     * Gets the cursor containing the currently shown suggestions. The caller should not hold
192     * on to or modify the returned cursor.
193     */
194    public SuggestionCursor getCurrentSuggestions() {
195        return mCursor;
196    }
197
198    /**
199     * Gets the cursor for the given source.
200     */
201    protected SuggestionCursor getCorpusCursor(Suggestions suggestions, Corpus corpus) {
202        if (suggestions == null) return null;
203        return suggestions.getPromoted(mPromoter, mMaxPromoted);
204    }
205
206    /**
207     * Replace the cursor.
208     *
209     * This does not close the old cursor. Instead, all the cursors are closed in
210     * {@link #setSuggestions(Suggestions)}.
211     */
212    private void changeCursor(SuggestionCursor newCursor) {
213        if (DBG) Log.d(TAG, "changeCursor(" + newCursor + ")");
214        if (newCursor == mCursor) {
215            // Shortcuts may have changed without the cursor changing.
216            notifyDataSetChanged();
217            return;
218        }
219        mCursor = newCursor;
220        if (mCursor != null) {
221            notifyDataSetChanged();
222        } else {
223            notifyDataSetInvalidated();
224        }
225    }
226
227    public void onIcon2Clicked(int position) {
228        if (mSuggestionClickListener != null) {
229            mSuggestionClickListener.onSuggestionQueryRefineClicked(position);
230        }
231    }
232
233    private class MySuggestionsObserver extends DataSetObserver {
234        @Override
235        public void onChanged() {
236            onSuggestionsChanged();
237        }
238    }
239
240}
241