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.Corpus; 20import com.android.quicksearchbox.SuggestionCursor; 21import com.android.quicksearchbox.SuggestionPosition; 22import com.android.quicksearchbox.Suggestions; 23 24import android.database.DataSetObserver; 25import android.util.Log; 26import android.view.View; 27import android.view.ViewGroup; 28import android.view.View.OnFocusChangeListener; 29import android.widget.BaseAdapter; 30 31/** 32 * Uses a {@link Suggestions} object to back a {@link SuggestionsView}. 33 */ 34public class SuggestionsAdapter extends BaseAdapter { 35 36 private static final boolean DBG = false; 37 private static final String TAG = "QSB.SuggestionsAdapter"; 38 39 private DataSetObserver mDataSetObserver; 40 41 private final SuggestionViewFactory mViewFactory; 42 43 private SuggestionCursor mCursor; 44 45 private Corpus mCorpus = null; 46 47 private Suggestions mSuggestions; 48 49 private SuggestionClickListener mSuggestionClickListener; 50 private OnFocusChangeListener mOnFocusChangeListener; 51 52 private boolean mClosed = false; 53 54 public SuggestionsAdapter(SuggestionViewFactory viewFactory) { 55 mViewFactory = viewFactory; 56 } 57 58 public boolean isClosed() { 59 return mClosed; 60 } 61 62 public void close() { 63 setSuggestions(null); 64 mCorpus = null; 65 mClosed = true; 66 } 67 68 public void setSuggestionClickListener(SuggestionClickListener listener) { 69 mSuggestionClickListener = listener; 70 } 71 72 public void setOnFocusChangeListener(OnFocusChangeListener l) { 73 mOnFocusChangeListener = l; 74 } 75 76 public void setSuggestions(Suggestions suggestions) { 77 if (mSuggestions == suggestions) { 78 return; 79 } 80 if (mClosed) { 81 if (suggestions != null) { 82 suggestions.close(); 83 } 84 return; 85 } 86 if (mDataSetObserver == null) { 87 mDataSetObserver = new MySuggestionsObserver(); 88 } 89 // TODO: delay the change if there are no suggestions for the currently visible tab. 90 if (mSuggestions != null) { 91 mSuggestions.unregisterDataSetObserver(mDataSetObserver); 92 mSuggestions.close(); 93 } 94 mSuggestions = suggestions; 95 if (mSuggestions != null) { 96 mSuggestions.registerDataSetObserver(mDataSetObserver); 97 } 98 onSuggestionsChanged(); 99 } 100 101 public Suggestions getSuggestions() { 102 return mSuggestions; 103 } 104 105 /** 106 * Gets the source whose results are displayed. 107 */ 108 public Corpus getCorpus() { 109 return mCorpus; 110 } 111 112 /** 113 * Sets the source whose results are displayed. 114 */ 115 public void setCorpus(Corpus corpus) { 116 if (mSuggestions != null) { 117 if ((mCorpus == null) && (corpus != null)) { 118 // we've just switched from the 'All' corpus to a specific corpus 119 // we can filter the existing results immediately. 120 if (DBG) Log.d(TAG, "setCorpus(" + corpus.getName() + ") Filter suggestions"); 121 mSuggestions.filterByCorpus(corpus); 122 } else if (corpus != null) { 123 // Note, when switching from a specific corpus to 'All' we do not change the 124 // suggestions, since they're still relevant for 'All'. Hence 'corpus != null' 125 if (DBG) Log.d(TAG, "setCorpus(" + corpus.getName() + ") Clear suggestions"); 126 mSuggestions.unregisterDataSetObserver(mDataSetObserver); 127 mSuggestions.close(); 128 mSuggestions = null; 129 } 130 } 131 mCorpus = corpus; 132 onSuggestionsChanged(); 133 } 134 135 public int getCount() { 136 return mCursor == null ? 0 : mCursor.getCount(); 137 } 138 139 public SuggestionPosition getItem(int position) { 140 if (mCursor == null) return null; 141 return new SuggestionPosition(mCursor, position); 142 } 143 144 public long getItemId(int position) { 145 return position; 146 } 147 148 @Override 149 public int getViewTypeCount() { 150 return mViewFactory.getSuggestionViewTypeCount(); 151 } 152 153 @Override 154 public int getItemViewType(int position) { 155 if (mCursor == null) { 156 return 0; 157 } 158 mCursor.moveTo(position); 159 return mViewFactory.getSuggestionViewType(mCursor); 160 } 161 162 public View getView(int position, View convertView, ViewGroup parent) { 163 if (mCursor == null) { 164 throw new IllegalStateException("getView() called with null cursor"); 165 } 166 mCursor.moveTo(position); 167 int viewType = mViewFactory.getSuggestionViewType(mCursor); 168 SuggestionView view = mViewFactory.getSuggestionView(viewType, convertView, parent); 169 view.bindAsSuggestion(mCursor, mSuggestionClickListener); 170 View v = (View) view; 171 if (mOnFocusChangeListener != null) { 172 v.setOnFocusChangeListener(mOnFocusChangeListener); 173 } 174 return v; 175 } 176 177 protected void onSuggestionsChanged() { 178 if (DBG) Log.d(TAG, "onSuggestionsChanged(" + mSuggestions + ")"); 179 SuggestionCursor cursor = getCorpusCursor(mSuggestions, mCorpus); 180 changeCursor(cursor); 181 } 182 183 /** 184 * Gets the cursor containing the currently shown suggestions. The caller should not hold 185 * on to or modify the returned cursor. 186 */ 187 public SuggestionCursor getCurrentSuggestions() { 188 return mCursor; 189 } 190 191 /** 192 * Gets the cursor for the given source. 193 */ 194 protected SuggestionCursor getCorpusCursor(Suggestions suggestions, Corpus corpus) { 195 if (suggestions == null) return null; 196 return suggestions.getPromoted(); 197 } 198 199 /** 200 * Replace the cursor. 201 * 202 * This does not close the old cursor. Instead, all the cursors are closed in 203 * {@link #setSuggestions(Suggestions)}. 204 */ 205 private void changeCursor(SuggestionCursor newCursor) { 206 if (DBG) Log.d(TAG, "changeCursor(" + newCursor + ")"); 207 if (newCursor == mCursor) { 208 // Shortcuts may have changed without the cursor changing. 209 notifyDataSetChanged(); 210 return; 211 } 212 mCursor = newCursor; 213 if (mCursor != null) { 214 // TODO: Register observers here to watch for 215 // changes in the cursor, e.g. shortcut refreshes? 216 notifyDataSetChanged(); 217 } else { 218 notifyDataSetInvalidated(); 219 } 220 } 221 222 public void onIcon2Clicked(int position) { 223 if (mSuggestionClickListener != null) { 224 mSuggestionClickListener.onSuggestionQueryRefineClicked(position); 225 } 226 } 227 228 private class MySuggestionsObserver extends DataSetObserver { 229 @Override 230 public void onChanged() { 231 onSuggestionsChanged(); 232 } 233 } 234 235} 236