SuggestionsAdapterBase.java revision fd4a4cbc1143a734d357897531daa7105db6459b
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 private boolean addViewTypes(SuggestionViewFactory f) { 81 boolean changed = false; 82 for (String viewType : f.getSuggestionViewTypes()) { 83 if (!mViewTypeMap.containsKey(viewType)) { 84 mViewTypeMap.put(viewType, mViewTypeMap.size()); 85 changed = true; 86 } 87 } 88 return changed; 89 } 90 91 private boolean buildViewTypeMap() { 92 boolean changed = addViewTypes(mFallback); 93 for (Corpus c : mCorpora.getEnabledCorpora()) { 94 for (Source s : c.getSources()) { 95 SuggestionViewFactory f = s.getSuggestionViewFactory(); 96 changed |= addViewTypes(f); 97 } 98 } 99 return changed; 100 } 101 102 public void setMaxPromoted(int maxPromoted) { 103 if (DBG) Log.d(TAG, "setMaxPromoted " + maxPromoted); 104 mMaxPromoted = maxPromoted; 105 onSuggestionsChanged(); 106 } 107 108 public void setIcon1Enabled(boolean enabled) { 109 mIcon1Enabled = enabled; 110 } 111 112 public boolean isClosed() { 113 return mClosed; 114 } 115 116 public void close() { 117 setSuggestions(null); 118 mCorpora.unregisterDataSetObserver(mCorporaObserver); 119 mClosed = true; 120 } 121 122 public void setPromoter(Promoter promoter) { 123 mPromoter = promoter; 124 onSuggestionsChanged(); 125 } 126 127 public void setSuggestionClickListener(SuggestionClickListener listener) { 128 mSuggestionClickListener = listener; 129 } 130 131 public void setOnFocusChangeListener(OnFocusChangeListener l) { 132 mOnFocusChangeListener = l; 133 } 134 135 public void setSuggestions(Suggestions suggestions) { 136 if (mSuggestions == suggestions) { 137 return; 138 } 139 if (mClosed) { 140 if (suggestions != null) { 141 suggestions.release(); 142 } 143 return; 144 } 145 if (mDataSetObserver == null) { 146 mDataSetObserver = new MySuggestionsObserver(); 147 } 148 // TODO: delay the change if there are no suggestions for the currently visible tab. 149 if (mSuggestions != null) { 150 mSuggestions.unregisterDataSetObserver(mDataSetObserver); 151 mSuggestions.release(); 152 } 153 mSuggestions = suggestions; 154 if (mSuggestions != null) { 155 mSuggestions.registerDataSetObserver(mDataSetObserver); 156 } 157 onSuggestionsChanged(); 158 } 159 160 public Suggestions getSuggestions() { 161 return mSuggestions; 162 } 163 164 protected int getPromotedCount() { 165 return mPromotedSuggestions == null ? 0 : mPromotedSuggestions.getCount(); 166 } 167 168 protected SuggestionPosition getPromotedSuggestion(int position) { 169 if (mPromotedSuggestions == null) return null; 170 return new SuggestionPosition(mPromotedSuggestions, position); 171 } 172 173 protected int getViewTypeCount() { 174 return mViewTypeMap.size(); 175 } 176 177 private String suggestionViewType(Suggestion suggestion) { 178 String viewType = suggestion.getSuggestionSource().getSuggestionViewFactory() 179 .getViewType(suggestion); 180 if (!mViewTypeMap.containsKey(viewType)) { 181 throw new IllegalStateException("Unknown viewType " + viewType); 182 } 183 return viewType; 184 } 185 186 protected int getSuggestionViewType(SuggestionCursor cursor, int position) { 187 if (cursor == null) { 188 return 0; 189 } 190 cursor.moveTo(position); 191 return mViewTypeMap.get(suggestionViewType(cursor)); 192 } 193 194 protected int getSuggestionViewTypeCount() { 195 return mViewTypeMap.size(); 196 } 197 198 protected View getView(SuggestionCursor suggestions, int position, 199 View convertView, ViewGroup parent) { 200 suggestions.moveTo(position); 201 SuggestionViewFactory factory = suggestions.getSuggestionSource().getSuggestionViewFactory(); 202 View v = factory.getView(suggestions, suggestions.getUserQuery(), convertView, parent); 203 if (v == null) { 204 v = mFallback.getView(suggestions, suggestions.getUserQuery(), convertView, parent); 205 } 206 if (v instanceof SuggestionView) { 207 ((SuggestionView) v).setIcon1Enabled(mIcon1Enabled); 208 ((SuggestionView) v).bindAdapter(this, position); 209 } else { 210 SuggestionViewClickListener l = new SuggestionViewClickListener(position); 211 v.setOnClickListener(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 changePromoted(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 getCurrentPromotedSuggestions() { 231 return mPromotedSuggestions; 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 changePromoted(SuggestionCursor newCursor) { 249 if (DBG) { 250 Log.d(TAG, "changeCursor(" + newCursor + ") count=" + 251 (newCursor == null ? 0 : newCursor.getCount())); 252 } 253 if (newCursor == mPromotedSuggestions) { 254 if (newCursor != null) { 255 // Shortcuts may have changed without the cursor changing. 256 notifyDataSetChanged(); 257 } 258 return; 259 } 260 mPromotedSuggestions = newCursor; 261 if (mPromotedSuggestions != null) { 262 notifyDataSetChanged(); 263 } else { 264 notifyDataSetInvalidated(); 265 } 266 } 267 268 public void onSuggestionClicked(int position) { 269 if (mSuggestionClickListener != null) { 270 mSuggestionClickListener.onSuggestionClicked(this, position); 271 } 272 } 273 274 public void onSuggestionQuickContactClicked(int position) { 275 if (mSuggestionClickListener != null) { 276 mSuggestionClickListener.onSuggestionQuickContactClicked(this, position); 277 } 278 } 279 280 public void onSuggestionRemoveFromHistoryClicked(int position) { 281 if (mSuggestionClickListener != null) { 282 mSuggestionClickListener.onSuggestionRemoveFromHistoryClicked(this, position); 283 } 284 } 285 286 public void onSuggestionQueryRefineClicked(int position) { 287 if (mSuggestionClickListener != null) { 288 mSuggestionClickListener.onSuggestionQueryRefineClicked(this, position); 289 } 290 } 291 292 public abstract A getListAdapter(); 293 294 protected abstract void notifyDataSetInvalidated(); 295 296 protected abstract void notifyDataSetChanged(); 297 298 private class MySuggestionsObserver extends DataSetObserver { 299 @Override 300 public void onChanged() { 301 onSuggestionsChanged(); 302 } 303 } 304 305 private class CorporaObserver extends DataSetObserver { 306 @Override 307 public void onChanged() { 308 if (buildViewTypeMap()) { 309 if (mListener != null) { 310 mListener.onSuggestionAdapterChanged(); 311 } 312 } 313 } 314 } 315 316 private class SuggestionViewClickListener implements View.OnClickListener { 317 private final int mPosition; 318 public SuggestionViewClickListener(int position) { 319 mPosition = position; 320 } 321 public void onClick(View v) { 322 onSuggestionClicked(mPosition); 323 } 324 } 325 326} 327