SuggestionsAdapter.java revision fdb80c2962c88ac62dcd7ee7f2fab1857b61506b
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 com.android.quicksearchbox.Corpora; 20import com.android.quicksearchbox.Corpus; 21import com.android.quicksearchbox.Promoter; 22import com.android.quicksearchbox.Source; 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; 32import android.widget.BaseAdapter; 33 34import java.util.HashMap; 35 36/** 37 * Uses a {@link Suggestions} object to back a {@link SuggestionsView}. 38 */ 39public class SuggestionsAdapter extends BaseAdapter { 40 41 private static final boolean DBG = false; 42 private static final String TAG = "QSB.SuggestionsAdapter"; 43 44 private final Corpora mCorpora; 45 46 private DataSetObserver mDataSetObserver; 47 48 private Promoter mPromoter; 49 50 private int mMaxPromoted; 51 52 private SuggestionCursor mCursor; 53 private final HashMap<String, Integer> mViewTypeMap; 54 private final SuggestionViewFactory mFallback; 55 56 private Suggestions mSuggestions; 57 58 private SuggestionClickListener mSuggestionClickListener; 59 private OnFocusChangeListener mOnFocusChangeListener; 60 61 private SuggestionsAdapterChangeListener mListener; 62 63 private final CorporaObserver mCorporaObserver = new CorporaObserver(); 64 65 private boolean mClosed = false; 66 67 public SuggestionsAdapter(SuggestionViewFactory fallbackFactory, Corpora corpora) { 68 mCorpora = corpora; 69 mFallback = fallbackFactory; 70 mViewTypeMap = new HashMap<String, Integer>(); 71 mCorpora.registerDataSetObserver(mCorporaObserver); 72 buildViewTypeMap(); 73 } 74 75 public void setSuggestionAdapterChangeListener(SuggestionsAdapterChangeListener l) { 76 mListener = l; 77 } 78 79 private boolean addViewTypes(SuggestionViewFactory f) { 80 boolean changed = false; 81 for (String viewType : f.getSuggestionViewTypes()) { 82 if (!mViewTypeMap.containsKey(viewType)) { 83 mViewTypeMap.put(viewType, mViewTypeMap.size()); 84 changed = true; 85 } 86 } 87 return changed; 88 } 89 90 private boolean buildViewTypeMap() { 91 boolean changed = addViewTypes(mFallback); 92 for (Corpus c : mCorpora.getEnabledCorpora()) { 93 for (Source s : c.getSources()) { 94 SuggestionViewFactory f = s.getSuggestionViewFactory(); 95 changed |= addViewTypes(f); 96 } 97 } 98 return changed; 99 } 100 101 public void setMaxPromoted(int maxPromoted) { 102 if (DBG) Log.d(TAG, "setMaxPromoted " + maxPromoted); 103 mMaxPromoted = maxPromoted; 104 onSuggestionsChanged(); 105 } 106 107 public boolean isClosed() { 108 return mClosed; 109 } 110 111 public void close() { 112 setSuggestions(null); 113 mCorpora.unregisterDataSetObserver(mCorporaObserver); 114 mClosed = true; 115 } 116 117 public void setPromoter(Promoter promoter) { 118 mPromoter = promoter; 119 onSuggestionsChanged(); 120 } 121 122 public void setSuggestionClickListener(SuggestionClickListener listener) { 123 mSuggestionClickListener = listener; 124 } 125 126 public void setOnFocusChangeListener(OnFocusChangeListener l) { 127 mOnFocusChangeListener = l; 128 } 129 130 public void setSuggestions(Suggestions suggestions) { 131 if (mSuggestions == suggestions) { 132 return; 133 } 134 if (mClosed) { 135 if (suggestions != null) { 136 suggestions.release(); 137 } 138 return; 139 } 140 if (mDataSetObserver == null) { 141 mDataSetObserver = new MySuggestionsObserver(); 142 } 143 // TODO: delay the change if there are no suggestions for the currently visible tab. 144 if (mSuggestions != null) { 145 mSuggestions.unregisterDataSetObserver(mDataSetObserver); 146 mSuggestions.release(); 147 } 148 mSuggestions = suggestions; 149 if (mSuggestions != null) { 150 mSuggestions.registerDataSetObserver(mDataSetObserver); 151 } 152 onSuggestionsChanged(); 153 } 154 155 public Suggestions getSuggestions() { 156 return mSuggestions; 157 } 158 159 public int getCount() { 160 return mCursor == null ? 0 : mCursor.getCount(); 161 } 162 163 public SuggestionPosition getItem(int position) { 164 if (mCursor == null) return null; 165 return new SuggestionPosition(mCursor, position); 166 } 167 168 public long getItemId(int position) { 169 return position; 170 } 171 172 @Override 173 public int getViewTypeCount() { 174 return mViewTypeMap.size(); 175 } 176 177 private String currentSuggestionViewType() { 178 String viewType = mCursor.getSuggestionSource().getSuggestionViewFactory() 179 .getViewType(mCursor); 180 if (!mViewTypeMap.containsKey(viewType)) { 181 throw new IllegalStateException("Unknown viewType " + viewType); 182 } 183 return viewType; 184 } 185 186 @Override 187 public int getItemViewType(int position) { 188 if (mCursor == null) { 189 return 0; 190 } 191 mCursor.moveTo(position); 192 return mViewTypeMap.get(currentSuggestionViewType()); 193 } 194 195 // Implements Adapter#getView() 196 public View getView(int position, View convertView, ViewGroup parent) { 197 if (mCursor == null) { 198 throw new IllegalStateException("getView() called with null cursor"); 199 } 200 mCursor.moveTo(position); 201 SuggestionViewFactory factory = mCursor.getSuggestionSource().getSuggestionViewFactory(); 202 View v = factory.getView(mCursor, mCursor.getUserQuery(), convertView, parent); 203 if (v == null) { 204 v = mFallback.getView(mCursor, mCursor.getUserQuery(), convertView, parent); 205 } 206 if (v instanceof SuggestionView) { 207 ((SuggestionView) v).bindAdapter(this, position); 208 } else { 209 SuggestionViewClickListener l = new SuggestionViewClickListener(position); 210 v.setOnClickListener(l); 211 v.setOnLongClickListener(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 changeCursor(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 getCurrentSuggestions() { 231 return mCursor; 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 changeCursor(SuggestionCursor newCursor) { 249 if (DBG) { 250 Log.d(TAG, "changeCursor(" + newCursor + ") count=" + 251 (newCursor == null ? 0 : newCursor.getCount())); 252 } 253 if (newCursor == mCursor) { 254 if (newCursor != null) { 255 // Shortcuts may have changed without the cursor changing. 256 notifyDataSetChanged(); 257 } 258 return; 259 } 260 mCursor = newCursor; 261 if (mCursor != 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 boolean onSuggestionLongClicked(int position) { 281 if (mSuggestionClickListener != null) { 282 return mSuggestionClickListener.onSuggestionLongClicked(this, position); 283 } 284 return false; 285 } 286 287 public void onSuggestionQueryRefineClicked(int position) { 288 if (mSuggestionClickListener != null) { 289 mSuggestionClickListener.onSuggestionQueryRefineClicked(this, position); 290 } 291 } 292 293 private class MySuggestionsObserver extends DataSetObserver { 294 @Override 295 public void onChanged() { 296 onSuggestionsChanged(); 297 } 298 } 299 300 private class CorporaObserver extends DataSetObserver { 301 @Override 302 public void onChanged() { 303 if (buildViewTypeMap()) { 304 if (mListener != null) { 305 mListener.onSuggestionAdapterChanged(); 306 } 307 } 308 } 309 } 310 311 private class SuggestionViewClickListener 312 implements View.OnClickListener, View.OnLongClickListener { 313 private final int mPosition; 314 public SuggestionViewClickListener(int position) { 315 mPosition = position; 316 } 317 public void onClick(View v) { 318 onSuggestionClicked(mPosition); 319 } 320 public boolean onLongClick(View v) { 321 return onSuggestionLongClicked(mPosition); 322 } 323 } 324 325 /** 326 * Callback interface used to notify the view when the adapter has changed (i.e. the number and 327 * type of views returned has changed). 328 */ 329 public interface SuggestionsAdapterChangeListener { 330 void onSuggestionAdapterChanged(); 331 } 332 333} 334