SuggestionsAdapter.java revision 8b2936607176720172aee068abc5631bdf77e843
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 mMaxPromoted = maxPromoted; 103 onSuggestionsChanged(); 104 } 105 106 public boolean isClosed() { 107 return mClosed; 108 } 109 110 public void close() { 111 setSuggestions(null); 112 mCorpora.unregisterDataSetObserver(mCorporaObserver); 113 mClosed = true; 114 } 115 116 public void setPromoter(Promoter promoter) { 117 mPromoter = promoter; 118 onSuggestionsChanged(); 119 } 120 121 public void setSuggestionClickListener(SuggestionClickListener listener) { 122 mSuggestionClickListener = listener; 123 } 124 125 public void setOnFocusChangeListener(OnFocusChangeListener l) { 126 mOnFocusChangeListener = l; 127 } 128 129 public void setSuggestions(Suggestions suggestions) { 130 if (mSuggestions == suggestions) { 131 return; 132 } 133 if (mClosed) { 134 if (suggestions != null) { 135 suggestions.release(); 136 } 137 return; 138 } 139 if (mDataSetObserver == null) { 140 mDataSetObserver = new MySuggestionsObserver(); 141 } 142 // TODO: delay the change if there are no suggestions for the currently visible tab. 143 if (mSuggestions != null) { 144 mSuggestions.unregisterDataSetObserver(mDataSetObserver); 145 mSuggestions.release(); 146 } 147 mSuggestions = suggestions; 148 if (mSuggestions != null) { 149 mSuggestions.registerDataSetObserver(mDataSetObserver); 150 } 151 onSuggestionsChanged(); 152 } 153 154 public Suggestions getSuggestions() { 155 return mSuggestions; 156 } 157 158 public int getCount() { 159 return mCursor == null ? 0 : mCursor.getCount(); 160 } 161 162 public SuggestionPosition getItem(int position) { 163 if (mCursor == null) return null; 164 return new SuggestionPosition(mCursor, position); 165 } 166 167 public long getItemId(int position) { 168 return position; 169 } 170 171 @Override 172 public int getViewTypeCount() { 173 return mViewTypeMap.size(); 174 } 175 176 private String currentSuggestionViewType() { 177 String viewType = mCursor.getSuggestionSource().getSuggestionViewFactory() 178 .getViewType(mCursor); 179 if (!mViewTypeMap.containsKey(viewType)) { 180 throw new IllegalStateException("Unknown viewType " + viewType); 181 } 182 return viewType; 183 } 184 185 @Override 186 public int getItemViewType(int position) { 187 if (DBG) Log.d(TAG, "getItemViewType(" + position + ") mCursor=" + mCursor); 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) Log.d(TAG, "changeCursor(" + newCursor + ")"); 250 if (newCursor == mCursor) { 251 if (newCursor != null) { 252 // Shortcuts may have changed without the cursor changing. 253 notifyDataSetChanged(); 254 } 255 return; 256 } 257 mCursor = newCursor; 258 if (mCursor != null) { 259 notifyDataSetChanged(); 260 } else { 261 notifyDataSetInvalidated(); 262 } 263 } 264 265 public void onSuggestionClicked(int position) { 266 if (mSuggestionClickListener != null) { 267 mSuggestionClickListener.onSuggestionClicked(this, position); 268 } 269 } 270 271 public void onSuggestionQuickContactClicked(int position) { 272 if (mSuggestionClickListener != null) { 273 mSuggestionClickListener.onSuggestionQuickContactClicked(this, position); 274 } 275 } 276 277 public boolean onSuggestionLongClicked(int position) { 278 if (mSuggestionClickListener != null) { 279 return mSuggestionClickListener.onSuggestionLongClicked(this, position); 280 } 281 return false; 282 } 283 284 public void onSuggestionQueryRefineClicked(int position) { 285 if (mSuggestionClickListener != null) { 286 mSuggestionClickListener.onSuggestionQueryRefineClicked(this, position); 287 } 288 } 289 290 private class MySuggestionsObserver extends DataSetObserver { 291 @Override 292 public void onChanged() { 293 onSuggestionsChanged(); 294 } 295 } 296 297 private class CorporaObserver extends DataSetObserver { 298 @Override 299 public void onChanged() { 300 if (buildViewTypeMap()) { 301 if (mListener != null) { 302 mListener.onSuggestionAdapterChanged(); 303 } 304 } 305 } 306 } 307 308 private class SuggestionViewClickListener 309 implements View.OnClickListener, View.OnLongClickListener { 310 private final int mPosition; 311 public SuggestionViewClickListener(int position) { 312 mPosition = position; 313 } 314 public void onClick(View v) { 315 onSuggestionClicked(mPosition); 316 } 317 public boolean onLongClick(View v) { 318 return onSuggestionLongClicked(mPosition); 319 } 320 } 321 322 /** 323 * Callback interface used to notify the view when the adapter has changed (i.e. the number and 324 * type of views returned has changed). 325 */ 326 public interface SuggestionsAdapterChangeListener { 327 void onSuggestionAdapterChanged(); 328 } 329 330} 331