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  */
16 package com.android.quicksearchbox.ui;
17 
18 import com.android.quicksearchbox.Suggestion;
19 import com.android.quicksearchbox.SuggestionCursor;
20 import com.android.quicksearchbox.SuggestionPosition;
21 import com.android.quicksearchbox.Suggestions;
22 
23 import android.database.DataSetObserver;
24 import android.util.Log;
25 import android.view.View;
26 import android.view.View.OnFocusChangeListener;
27 import android.view.ViewGroup;
28 
29 import java.util.HashMap;
30 
31 /**
32  * Base class for suggestions adapters. The templated class A is the list adapter class.
33  */
34 public 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 
SuggestionsAdapterBase(SuggestionViewFactory viewFactory)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
isEmpty()63     public abstract boolean isEmpty();
64 
isClosed()65     public boolean isClosed() {
66         return mClosed;
67     }
68 
close()69     public void close() {
70         setSuggestions(null);
71         mClosed = true;
72     }
73 
74     @Override
setSuggestionClickListener(SuggestionClickListener listener)75     public void setSuggestionClickListener(SuggestionClickListener listener) {
76         mSuggestionClickListener = listener;
77     }
78 
79     @Override
setOnFocusChangeListener(OnFocusChangeListener l)80     public void setOnFocusChangeListener(OnFocusChangeListener l) {
81         mOnFocusChangeListener = l;
82     }
83 
84     @Override
setSuggestions(Suggestions suggestions)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
getSuggestions()111     public Suggestions getSuggestions() {
112         return mSuggestions;
113     }
114 
115     @Override
getSuggestion(long suggestionId)116     public abstract SuggestionPosition getSuggestion(long suggestionId);
117 
getCount()118     protected int getCount() {
119         return mCurrentSuggestions == null ? 0 : mCurrentSuggestions.getCount();
120     }
121 
getSuggestion(int position)122     protected SuggestionPosition getSuggestion(int position) {
123         if (mCurrentSuggestions == null) return null;
124         return new SuggestionPosition(mCurrentSuggestions, position);
125     }
126 
getViewTypeCount()127     protected int getViewTypeCount() {
128         return mViewTypeMap.size();
129     }
130 
suggestionViewType(Suggestion suggestion)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 
getSuggestionViewType(SuggestionCursor cursor, int position)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 
getSuggestionViewTypeCount()147     protected int getSuggestionViewTypeCount() {
148         return mViewTypeMap.size();
149     }
150 
getView(SuggestionCursor suggestions, int position, long suggestionId, View convertView, ViewGroup parent)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 
onSuggestionsChanged()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 
getCurrentSuggestions()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      */
changeSuggestions(SuggestionCursor newCursor)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
onSuggestionClicked(long suggestionId)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
onSuggestionQueryRefineClicked(long suggestionId)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
getListAdapter()226     public abstract A getListAdapter();
227 
notifyDataSetInvalidated()228     protected abstract void notifyDataSetInvalidated();
229 
notifyDataSetChanged()230     protected abstract void notifyDataSetChanged();
231 
232     private class MySuggestionsObserver extends DataSetObserver {
233         @Override
onChanged()234         public void onChanged() {
235             onSuggestionsChanged();
236         }
237     }
238 
239     private class SuggestionViewClickListener implements View.OnClickListener {
240         private final long mSuggestionId;
SuggestionViewClickListener(long suggestionId)241         public SuggestionViewClickListener(long suggestionId) {
242             mSuggestionId = suggestionId;
243         }
244         @Override
onClick(View v)245         public void onClick(View v) {
246             onSuggestionClicked(mSuggestionId);
247         }
248     }
249 
250 }
251