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.Suggestion;
19import com.android.quicksearchbox.SuggestionCursor;
20import com.android.quicksearchbox.SuggestionPosition;
21import com.android.quicksearchbox.Suggestions;
22
23import android.database.DataSetObserver;
24import android.util.Log;
25import android.view.View;
26import android.view.View.OnFocusChangeListener;
27import android.view.ViewGroup;
28
29import java.util.HashMap;
30
31/**
32 * Base class for suggestions adapters. The templated class A is the list adapter class.
33 */
34public abstract class SuggestionsAdapterBase<A> implements SuggestionsAdapter<A> {
35
36    private static final boolean DBG = false;
37    private static final String TAG = "QSB.SuggestionsAdapter";
38
39    private DataSetObserver mDataSetObserver;
40
41    private SuggestionCursor mCurrentSuggestions;
42    private final HashMap<String, Integer> mViewTypeMap;
43    private final SuggestionViewFactory mViewFactory;
44
45    private Suggestions mSuggestions;
46
47    private SuggestionClickListener mSuggestionClickListener;
48    private OnFocusChangeListener mOnFocusChangeListener;
49
50    private boolean mClosed = false;
51
52    protected SuggestionsAdapterBase(SuggestionViewFactory viewFactory) {
53        mViewFactory = viewFactory;
54        mViewTypeMap = new HashMap<String, Integer>();
55        for (String viewType : mViewFactory.getSuggestionViewTypes()) {
56            if (!mViewTypeMap.containsKey(viewType)) {
57                mViewTypeMap.put(viewType, mViewTypeMap.size());
58            }
59        }
60    }
61
62    @Override
63    public abstract boolean isEmpty();
64
65    public boolean isClosed() {
66        return mClosed;
67    }
68
69    public void close() {
70        setSuggestions(null);
71        mClosed = true;
72    }
73
74    @Override
75    public void setSuggestionClickListener(SuggestionClickListener listener) {
76        mSuggestionClickListener = listener;
77    }
78
79    @Override
80    public void setOnFocusChangeListener(OnFocusChangeListener l) {
81        mOnFocusChangeListener = l;
82    }
83
84    @Override
85    public void setSuggestions(Suggestions suggestions) {
86        if (mSuggestions == suggestions) {
87            return;
88        }
89        if (mClosed) {
90            if (suggestions != null) {
91                suggestions.release();
92            }
93            return;
94        }
95        if (mDataSetObserver == null) {
96            mDataSetObserver = new MySuggestionsObserver();
97        }
98        // TODO: delay the change if there are no suggestions for the currently visible tab.
99        if (mSuggestions != null) {
100            mSuggestions.unregisterDataSetObserver(mDataSetObserver);
101            mSuggestions.release();
102        }
103        mSuggestions = suggestions;
104        if (mSuggestions != null) {
105            mSuggestions.registerDataSetObserver(mDataSetObserver);
106        }
107        onSuggestionsChanged();
108    }
109
110    @Override
111    public Suggestions getSuggestions() {
112        return mSuggestions;
113    }
114
115    @Override
116    public abstract SuggestionPosition getSuggestion(long suggestionId);
117
118    protected int getCount() {
119        return mCurrentSuggestions == null ? 0 : mCurrentSuggestions.getCount();
120    }
121
122    protected SuggestionPosition getSuggestion(int position) {
123        if (mCurrentSuggestions == null) return null;
124        return new SuggestionPosition(mCurrentSuggestions, position);
125    }
126
127    protected int getViewTypeCount() {
128        return mViewTypeMap.size();
129    }
130
131    private String suggestionViewType(Suggestion suggestion) {
132        String viewType = mViewFactory.getViewType(suggestion);
133        if (!mViewTypeMap.containsKey(viewType)) {
134            throw new IllegalStateException("Unknown viewType " + viewType);
135        }
136        return viewType;
137    }
138
139    protected int getSuggestionViewType(SuggestionCursor cursor, int position) {
140        if (cursor == null) {
141            return 0;
142        }
143        cursor.moveTo(position);
144        return mViewTypeMap.get(suggestionViewType(cursor));
145    }
146
147    protected int getSuggestionViewTypeCount() {
148        return mViewTypeMap.size();
149    }
150
151    protected View getView(SuggestionCursor suggestions, int position, long suggestionId,
152            View convertView, ViewGroup parent) {
153        suggestions.moveTo(position);
154        View v = mViewFactory.getView(suggestions, suggestions.getUserQuery(), convertView, parent);
155        if (v instanceof SuggestionView) {
156            ((SuggestionView) v).bindAdapter(this, suggestionId);
157        } else {
158            SuggestionViewClickListener l = new SuggestionViewClickListener(suggestionId);
159            v.setOnClickListener(l);
160        }
161
162        if (mOnFocusChangeListener != null) {
163            v.setOnFocusChangeListener(mOnFocusChangeListener);
164        }
165        return v;
166    }
167
168    protected void onSuggestionsChanged() {
169        if (DBG) Log.d(TAG, "onSuggestionsChanged(" + mSuggestions + ")");
170        SuggestionCursor cursor = null;
171        if (mSuggestions != null) {
172            cursor = mSuggestions.getResult();
173        }
174        changeSuggestions(cursor);
175    }
176
177    public SuggestionCursor getCurrentSuggestions() {
178        return mCurrentSuggestions;
179    }
180
181    /**
182     * Replace the cursor.
183     *
184     * This does not close the old cursor. Instead, all the cursors are closed in
185     * {@link #setSuggestions(Suggestions)}.
186     */
187    private void changeSuggestions(SuggestionCursor newCursor) {
188        if (DBG) {
189            Log.d(TAG, "changeCursor(" + newCursor + ") count=" +
190                    (newCursor == null ? 0 : newCursor.getCount()));
191        }
192        if (newCursor == mCurrentSuggestions) {
193            if (newCursor != null) {
194                // Shortcuts may have changed without the cursor changing.
195                notifyDataSetChanged();
196            }
197            return;
198        }
199        mCurrentSuggestions = newCursor;
200        if (mCurrentSuggestions != null) {
201            notifyDataSetChanged();
202        } else {
203            notifyDataSetInvalidated();
204        }
205    }
206
207    @Override
208    public void onSuggestionClicked(long suggestionId) {
209        if (mClosed) {
210            Log.w(TAG, "onSuggestionClicked after close");
211        } else if (mSuggestionClickListener != null) {
212            mSuggestionClickListener.onSuggestionClicked(this, suggestionId);
213        }
214    }
215
216    @Override
217    public void onSuggestionQueryRefineClicked(long suggestionId) {
218        if (mClosed) {
219            Log.w(TAG, "onSuggestionQueryRefineClicked after close");
220        } else if (mSuggestionClickListener != null) {
221            mSuggestionClickListener.onSuggestionQueryRefineClicked(this, suggestionId);
222        }
223    }
224
225    @Override
226    public abstract A getListAdapter();
227
228    protected abstract void notifyDataSetInvalidated();
229
230    protected abstract void notifyDataSetChanged();
231
232    private class MySuggestionsObserver extends DataSetObserver {
233        @Override
234        public void onChanged() {
235            onSuggestionsChanged();
236        }
237    }
238
239    private class SuggestionViewClickListener implements View.OnClickListener {
240        private final long mSuggestionId;
241        public SuggestionViewClickListener(long suggestionId) {
242            mSuggestionId = suggestionId;
243        }
244        @Override
245        public void onClick(View v) {
246            onSuggestionClicked(mSuggestionId);
247        }
248    }
249
250}
251