SuggestionsAdapterBase.java revision 116affd19a834a2df4226a1ae37afcc1113a230e
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.Corpora; 19import com.android.quicksearchbox.Corpus; 20import com.android.quicksearchbox.Promoter; 21import com.android.quicksearchbox.Source; 22import com.android.quicksearchbox.Suggestion; 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; 32 33import java.util.HashMap; 34 35/** 36 * Base class for suggestions adapters. The templated class A is the list adapter class. 37 */ 38public abstract class SuggestionsAdapterBase<A> implements SuggestionsAdapter<A> { 39 40 private static final boolean DBG = false; 41 private static final String TAG = "QSB.SuggestionsAdapter"; 42 43 private final Corpora mCorpora; 44 45 private DataSetObserver mDataSetObserver; 46 47 private Promoter mPromoter; 48 49 private int mMaxPromoted; 50 51 private SuggestionCursor mPromotedSuggestions; 52 private final HashMap<String, Integer> mViewTypeMap; 53 private final SuggestionViewFactory mFallback; 54 55 private Suggestions mSuggestions; 56 57 private SuggestionClickListener mSuggestionClickListener; 58 private OnFocusChangeListener mOnFocusChangeListener; 59 60 private SuggestionsAdapterChangeListener mListener; 61 62 private final CorporaObserver mCorporaObserver = new CorporaObserver(); 63 64 private boolean mClosed = false; 65 66 private boolean mIcon1Enabled = true; 67 68 protected SuggestionsAdapterBase(SuggestionViewFactory fallbackFactory, Corpora corpora) { 69 mCorpora = corpora; 70 mFallback = fallbackFactory; 71 mViewTypeMap = new HashMap<String, Integer>(); 72 mCorpora.registerDataSetObserver(mCorporaObserver); 73 buildViewTypeMap(); 74 } 75 76 public void setSuggestionAdapterChangeListener(SuggestionsAdapterChangeListener l) { 77 mListener = l; 78 } 79 80 public abstract boolean isEmpty(); 81 82 /** 83 * Indicates if this adapter will publish suggestions other than those in the promoted list. 84 */ 85 public abstract boolean willPublishNonPromotedSuggestions(); 86 87 private boolean addViewTypes(SuggestionViewFactory f) { 88 boolean changed = false; 89 for (String viewType : f.getSuggestionViewTypes()) { 90 if (!mViewTypeMap.containsKey(viewType)) { 91 mViewTypeMap.put(viewType, mViewTypeMap.size()); 92 changed = true; 93 } 94 } 95 return changed; 96 } 97 98 private boolean buildViewTypeMap() { 99 boolean changed = addViewTypes(mFallback); 100 for (Corpus c : mCorpora.getEnabledCorpora()) { 101 for (Source s : c.getSources()) { 102 SuggestionViewFactory f = s.getSuggestionViewFactory(); 103 changed |= addViewTypes(f); 104 } 105 } 106 return changed; 107 } 108 109 public void setMaxPromoted(int maxPromoted) { 110 if (DBG) Log.d(TAG, "setMaxPromoted " + maxPromoted); 111 mMaxPromoted = maxPromoted; 112 onSuggestionsChanged(); 113 } 114 115 public void setIcon1Enabled(boolean enabled) { 116 mIcon1Enabled = enabled; 117 } 118 119 public boolean isClosed() { 120 return mClosed; 121 } 122 123 public void close() { 124 setSuggestions(null); 125 mCorpora.unregisterDataSetObserver(mCorporaObserver); 126 mClosed = true; 127 } 128 129 public void setPromoter(Promoter promoter) { 130 mPromoter = promoter; 131 onSuggestionsChanged(); 132 } 133 134 public void setSuggestionClickListener(SuggestionClickListener listener) { 135 mSuggestionClickListener = listener; 136 } 137 138 public void setOnFocusChangeListener(OnFocusChangeListener l) { 139 mOnFocusChangeListener = l; 140 } 141 142 public void setSuggestions(Suggestions suggestions) { 143 if (mSuggestions == suggestions) { 144 return; 145 } 146 if (mClosed) { 147 if (suggestions != null) { 148 suggestions.release(); 149 } 150 return; 151 } 152 if (mDataSetObserver == null) { 153 mDataSetObserver = new MySuggestionsObserver(); 154 } 155 // TODO: delay the change if there are no suggestions for the currently visible tab. 156 if (mSuggestions != null) { 157 mSuggestions.unregisterDataSetObserver(mDataSetObserver); 158 mSuggestions.release(); 159 } 160 mSuggestions = suggestions; 161 if (mSuggestions != null) { 162 mSuggestions.registerDataSetObserver(mDataSetObserver); 163 } 164 onSuggestionsChanged(); 165 } 166 167 public Suggestions getSuggestions() { 168 return mSuggestions; 169 } 170 171 public abstract SuggestionPosition getSuggestion(long suggestionId); 172 173 protected int getPromotedCount() { 174 return mPromotedSuggestions == null ? 0 : mPromotedSuggestions.getCount(); 175 } 176 177 protected SuggestionPosition getPromotedSuggestion(int position) { 178 if (mPromotedSuggestions == null) return null; 179 return new SuggestionPosition(mPromotedSuggestions, position); 180 } 181 182 protected int getViewTypeCount() { 183 return mViewTypeMap.size(); 184 } 185 186 private String suggestionViewType(Suggestion suggestion) { 187 String viewType = suggestion.getSuggestionSource().getSuggestionViewFactory() 188 .getViewType(suggestion); 189 if (!mViewTypeMap.containsKey(viewType)) { 190 throw new IllegalStateException("Unknown viewType " + viewType); 191 } 192 return viewType; 193 } 194 195 protected int getSuggestionViewType(SuggestionCursor cursor, int position) { 196 if (cursor == null) { 197 return 0; 198 } 199 cursor.moveTo(position); 200 return mViewTypeMap.get(suggestionViewType(cursor)); 201 } 202 203 protected int getSuggestionViewTypeCount() { 204 return mViewTypeMap.size(); 205 } 206 207 protected View getView(SuggestionCursor suggestions, int position, long suggestionId, 208 View convertView, ViewGroup parent) { 209 suggestions.moveTo(position); 210 SuggestionViewFactory factory = suggestions.getSuggestionSource().getSuggestionViewFactory(); 211 View v = factory.getView(suggestions, suggestions.getUserQuery(), convertView, parent); 212 if (v == null) { 213 v = mFallback.getView(suggestions, suggestions.getUserQuery(), convertView, parent); 214 } 215 if (v instanceof SuggestionView) { 216 ((SuggestionView) v).setIcon1Enabled(mIcon1Enabled); 217 ((SuggestionView) v).bindAdapter(this, suggestionId); 218 } else { 219 SuggestionViewClickListener l = new SuggestionViewClickListener(suggestionId); 220 v.setOnClickListener(l); 221 } 222 223 if (mOnFocusChangeListener != null) { 224 v.setOnFocusChangeListener(mOnFocusChangeListener); 225 } 226 return v; 227 } 228 229 protected void onSuggestionsChanged() { 230 if (DBG) Log.d(TAG, "onSuggestionsChanged(" + mSuggestions + ")"); 231 SuggestionCursor cursor = getPromoted(mSuggestions); 232 changePromoted(cursor); 233 } 234 235 /** 236 * Gets the cursor containing the currently shown suggestions. The caller should not hold 237 * on to or modify the returned cursor. 238 */ 239 public SuggestionCursor getCurrentPromotedSuggestions() { 240 return mPromotedSuggestions; 241 } 242 243 /** 244 * Gets the cursor for the given source. 245 */ 246 protected SuggestionCursor getPromoted(Suggestions suggestions) { 247 if (suggestions == null) return null; 248 return suggestions.getPromoted(mPromoter, mMaxPromoted); 249 } 250 251 /** 252 * Replace the cursor. 253 * 254 * This does not close the old cursor. Instead, all the cursors are closed in 255 * {@link #setSuggestions(Suggestions)}. 256 */ 257 private void changePromoted(SuggestionCursor newCursor) { 258 if (DBG) { 259 Log.d(TAG, "changeCursor(" + newCursor + ") count=" + 260 (newCursor == null ? 0 : newCursor.getCount())); 261 } 262 if (newCursor == mPromotedSuggestions) { 263 if (newCursor != null) { 264 // Shortcuts may have changed without the cursor changing. 265 notifyDataSetChanged(); 266 } 267 return; 268 } 269 mPromotedSuggestions = newCursor; 270 if (mPromotedSuggestions != null) { 271 notifyDataSetChanged(); 272 } else { 273 notifyDataSetInvalidated(); 274 } 275 } 276 277 public void onSuggestionClicked(long suggestionId) { 278 if (mSuggestionClickListener != null) { 279 mSuggestionClickListener.onSuggestionClicked(this, suggestionId); 280 } 281 } 282 283 public void onSuggestionQuickContactClicked(long suggestionId) { 284 if (mSuggestionClickListener != null) { 285 mSuggestionClickListener.onSuggestionQuickContactClicked(this, suggestionId); 286 } 287 } 288 289 public void onSuggestionRemoveFromHistoryClicked(long suggestionId) { 290 if (mSuggestionClickListener != null) { 291 mSuggestionClickListener.onSuggestionRemoveFromHistoryClicked(this, suggestionId); 292 } 293 } 294 295 public void onSuggestionQueryRefineClicked(long suggestionId) { 296 if (mSuggestionClickListener != null) { 297 mSuggestionClickListener.onSuggestionQueryRefineClicked(this, suggestionId); 298 } 299 } 300 301 public abstract A getListAdapter(); 302 303 protected abstract void notifyDataSetInvalidated(); 304 305 protected abstract void notifyDataSetChanged(); 306 307 private class MySuggestionsObserver extends DataSetObserver { 308 @Override 309 public void onChanged() { 310 onSuggestionsChanged(); 311 } 312 } 313 314 private class CorporaObserver extends DataSetObserver { 315 @Override 316 public void onChanged() { 317 if (buildViewTypeMap()) { 318 if (mListener != null) { 319 mListener.onSuggestionAdapterChanged(); 320 } 321 } 322 } 323 } 324 325 private class SuggestionViewClickListener implements View.OnClickListener { 326 private final long mSuggestionId; 327 public SuggestionViewClickListener(long suggestionId) { 328 mSuggestionId = suggestionId; 329 } 330 public void onClick(View v) { 331 onSuggestionClicked(mSuggestionId); 332 } 333 } 334 335} 336