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