SuggestionsAdapter.java revision 49f8d487114bdbe5e0cdd6da923e1e92e5ce1bbf
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 private boolean mIcon1Enabled = true; 68 69 public SuggestionsAdapter(SuggestionViewFactory fallbackFactory, Corpora corpora) { 70 mCorpora = corpora; 71 mFallback = fallbackFactory; 72 mViewTypeMap = new HashMap<String, Integer>(); 73 mCorpora.registerDataSetObserver(mCorporaObserver); 74 buildViewTypeMap(); 75 } 76 77 public void setSuggestionAdapterChangeListener(SuggestionsAdapterChangeListener l) { 78 mListener = l; 79 } 80 81 private boolean addViewTypes(SuggestionViewFactory f) { 82 boolean changed = false; 83 for (String viewType : f.getSuggestionViewTypes()) { 84 if (!mViewTypeMap.containsKey(viewType)) { 85 mViewTypeMap.put(viewType, mViewTypeMap.size()); 86 changed = true; 87 } 88 } 89 return changed; 90 } 91 92 private boolean buildViewTypeMap() { 93 boolean changed = addViewTypes(mFallback); 94 for (Corpus c : mCorpora.getEnabledCorpora()) { 95 for (Source s : c.getSources()) { 96 SuggestionViewFactory f = s.getSuggestionViewFactory(); 97 changed |= addViewTypes(f); 98 } 99 } 100 return changed; 101 } 102 103 public void setMaxPromoted(int maxPromoted) { 104 if (DBG) Log.d(TAG, "setMaxPromoted " + maxPromoted); 105 mMaxPromoted = maxPromoted; 106 onSuggestionsChanged(); 107 } 108 109 public void setIcon1Enabled(boolean enabled) { 110 mIcon1Enabled = enabled; 111 } 112 113 public boolean isClosed() { 114 return mClosed; 115 } 116 117 public void close() { 118 setSuggestions(null); 119 mCorpora.unregisterDataSetObserver(mCorporaObserver); 120 mClosed = true; 121 } 122 123 public void setPromoter(Promoter promoter) { 124 mPromoter = promoter; 125 onSuggestionsChanged(); 126 } 127 128 public void setSuggestionClickListener(SuggestionClickListener listener) { 129 mSuggestionClickListener = listener; 130 } 131 132 public void setOnFocusChangeListener(OnFocusChangeListener l) { 133 mOnFocusChangeListener = l; 134 } 135 136 public void setSuggestions(Suggestions suggestions) { 137 if (mSuggestions == suggestions) { 138 return; 139 } 140 if (mClosed) { 141 if (suggestions != null) { 142 suggestions.release(); 143 } 144 return; 145 } 146 if (mDataSetObserver == null) { 147 mDataSetObserver = new MySuggestionsObserver(); 148 } 149 // TODO: delay the change if there are no suggestions for the currently visible tab. 150 if (mSuggestions != null) { 151 mSuggestions.unregisterDataSetObserver(mDataSetObserver); 152 mSuggestions.release(); 153 } 154 mSuggestions = suggestions; 155 if (mSuggestions != null) { 156 mSuggestions.registerDataSetObserver(mDataSetObserver); 157 } 158 onSuggestionsChanged(); 159 } 160 161 public Suggestions getSuggestions() { 162 return mSuggestions; 163 } 164 165 public int getCount() { 166 return mCursor == null ? 0 : mCursor.getCount(); 167 } 168 169 public SuggestionPosition getItem(int position) { 170 if (mCursor == null) return null; 171 return new SuggestionPosition(mCursor, position); 172 } 173 174 public long getItemId(int position) { 175 return position; 176 } 177 178 @Override 179 public int getViewTypeCount() { 180 return mViewTypeMap.size(); 181 } 182 183 private String currentSuggestionViewType() { 184 String viewType = mCursor.getSuggestionSource().getSuggestionViewFactory() 185 .getViewType(mCursor); 186 if (!mViewTypeMap.containsKey(viewType)) { 187 throw new IllegalStateException("Unknown viewType " + viewType); 188 } 189 return viewType; 190 } 191 192 @Override 193 public int getItemViewType(int position) { 194 if (mCursor == null) { 195 return 0; 196 } 197 mCursor.moveTo(position); 198 return mViewTypeMap.get(currentSuggestionViewType()); 199 } 200 201 // Implements Adapter#getView() 202 public View getView(int position, View convertView, ViewGroup parent) { 203 if (mCursor == null) { 204 throw new IllegalStateException("getView() called with null cursor"); 205 } 206 mCursor.moveTo(position); 207 SuggestionViewFactory factory = mCursor.getSuggestionSource().getSuggestionViewFactory(); 208 View v = factory.getView(mCursor, mCursor.getUserQuery(), convertView, parent); 209 if (v == null) { 210 v = mFallback.getView(mCursor, mCursor.getUserQuery(), convertView, parent); 211 } 212 if (v instanceof SuggestionView) { 213 ((SuggestionView) v).setIcon1Enabled(mIcon1Enabled); 214 ((SuggestionView) v).bindAdapter(this, position); 215 } else { 216 SuggestionViewClickListener l = new SuggestionViewClickListener(position); 217 v.setOnClickListener(l); 218 } 219 220 if (mOnFocusChangeListener != null) { 221 v.setOnFocusChangeListener(mOnFocusChangeListener); 222 } 223 return v; 224 } 225 226 protected void onSuggestionsChanged() { 227 if (DBG) Log.d(TAG, "onSuggestionsChanged(" + mSuggestions + ")"); 228 SuggestionCursor cursor = getPromoted(mSuggestions); 229 changeCursor(cursor); 230 } 231 232 /** 233 * Gets the cursor containing the currently shown suggestions. The caller should not hold 234 * on to or modify the returned cursor. 235 */ 236 public SuggestionCursor getCurrentSuggestions() { 237 return mCursor; 238 } 239 240 /** 241 * Gets the cursor for the given source. 242 */ 243 protected SuggestionCursor getPromoted(Suggestions suggestions) { 244 if (suggestions == null) return null; 245 return suggestions.getPromoted(mPromoter, mMaxPromoted); 246 } 247 248 /** 249 * Replace the cursor. 250 * 251 * This does not close the old cursor. Instead, all the cursors are closed in 252 * {@link #setSuggestions(Suggestions)}. 253 */ 254 private void changeCursor(SuggestionCursor newCursor) { 255 if (DBG) { 256 Log.d(TAG, "changeCursor(" + newCursor + ") count=" + 257 (newCursor == null ? 0 : newCursor.getCount())); 258 } 259 if (newCursor == mCursor) { 260 if (newCursor != null) { 261 // Shortcuts may have changed without the cursor changing. 262 notifyDataSetChanged(); 263 } 264 return; 265 } 266 mCursor = newCursor; 267 if (mCursor != null) { 268 notifyDataSetChanged(); 269 } else { 270 notifyDataSetInvalidated(); 271 } 272 } 273 274 public void onSuggestionClicked(int position) { 275 if (mSuggestionClickListener != null) { 276 mSuggestionClickListener.onSuggestionClicked(this, position); 277 } 278 } 279 280 public void onSuggestionQuickContactClicked(int position) { 281 if (mSuggestionClickListener != null) { 282 mSuggestionClickListener.onSuggestionQuickContactClicked(this, position); 283 } 284 } 285 286 public void onSuggestionRemoveFromHistoryClicked(int position) { 287 if (mSuggestionClickListener != null) { 288 mSuggestionClickListener.onSuggestionRemoveFromHistoryClicked(this, position); 289 } 290 } 291 292 public void onSuggestionQueryRefineClicked(int position) { 293 if (mSuggestionClickListener != null) { 294 mSuggestionClickListener.onSuggestionQueryRefineClicked(this, position); 295 } 296 } 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 * Callback interface used to notify the view when the adapter has changed (i.e. the number and 328 * type of views returned has changed). 329 */ 330 public interface SuggestionsAdapterChangeListener { 331 void onSuggestionAdapterChanged(); 332 } 333 334} 335