1 /* 2 * Copyright (C) 2011 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 17 package android.support.v4.widget; 18 19 import android.content.Context; 20 import android.database.ContentObserver; 21 import android.database.Cursor; 22 import android.database.DataSetObserver; 23 import android.os.Handler; 24 import android.util.Log; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.widget.BaseAdapter; 28 import android.widget.Filter; 29 import android.widget.FilterQueryProvider; 30 import android.widget.Filterable; 31 32 /** 33 * Static library support version of the framework's {@link android.widget.CursorAdapter}. 34 * Used to write apps that run on platforms prior to Android 3.0. When running 35 * on Android 3.0 or above, this implementation is still used; it does not try 36 * to switch to the framework's implementation. See the framework SDK 37 * documentation for a class overview. 38 */ 39 public abstract class CursorAdapter extends BaseAdapter implements Filterable, 40 CursorFilter.CursorFilterClient { 41 /** 42 * This field should be made private, so it is hidden from the SDK. 43 * {@hide} 44 */ 45 protected boolean mDataValid; 46 /** 47 * This field should be made private, so it is hidden from the SDK. 48 * {@hide} 49 */ 50 protected boolean mAutoRequery; 51 /** 52 * This field should be made private, so it is hidden from the SDK. 53 * {@hide} 54 */ 55 protected Cursor mCursor; 56 /** 57 * This field should be made private, so it is hidden from the SDK. 58 * {@hide} 59 */ 60 protected Context mContext; 61 /** 62 * This field should be made private, so it is hidden from the SDK. 63 * {@hide} 64 */ 65 protected int mRowIDColumn; 66 /** 67 * This field should be made private, so it is hidden from the SDK. 68 * {@hide} 69 */ 70 protected ChangeObserver mChangeObserver; 71 /** 72 * This field should be made private, so it is hidden from the SDK. 73 * {@hide} 74 */ 75 protected DataSetObserver mDataSetObserver; 76 /** 77 * This field should be made private, so it is hidden from the SDK. 78 * {@hide} 79 */ 80 protected CursorFilter mCursorFilter; 81 /** 82 * This field should be made private, so it is hidden from the SDK. 83 * {@hide} 84 */ 85 protected FilterQueryProvider mFilterQueryProvider; 86 87 /** 88 * If set the adapter will call requery() on the cursor whenever a content change 89 * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}. 90 * 91 * @deprecated This option is discouraged, as it results in Cursor queries 92 * being performed on the application's UI thread and thus can cause poor 93 * responsiveness or even Application Not Responding errors. As an alternative, 94 * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}. 95 */ 96 @Deprecated 97 public static final int FLAG_AUTO_REQUERY = 0x01; 98 99 /** 100 * If set the adapter will register a content observer on the cursor and will call 101 * {@link #onContentChanged()} when a notification comes in. Be careful when 102 * using this flag: you will need to unset the current Cursor from the adapter 103 * to avoid leaks due to its registered observers. This flag is not needed 104 * when using a CursorAdapter with a 105 * {@link android.content.CursorLoader}. 106 */ 107 public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02; 108 109 /** 110 * Constructor that always enables auto-requery. 111 * 112 * @deprecated This option is discouraged, as it results in Cursor queries 113 * being performed on the application's UI thread and thus can cause poor 114 * responsiveness or even Application Not Responding errors. As an alternative, 115 * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}. 116 * 117 * @param c The cursor from which to get the data. 118 * @param context The context 119 */ 120 @Deprecated CursorAdapter(Context context, Cursor c)121 public CursorAdapter(Context context, Cursor c) { 122 init(context, c, FLAG_AUTO_REQUERY); 123 } 124 125 /** 126 * Constructor that allows control over auto-requery. It is recommended 127 * you not use this, but instead {@link #CursorAdapter(Context, Cursor, int)}. 128 * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER} 129 * will always be set. 130 * 131 * @param c The cursor from which to get the data. 132 * @param context The context 133 * @param autoRequery If true the adapter will call requery() on the 134 * cursor whenever it changes so the most recent 135 * data is always displayed. Using true here is discouraged. 136 */ CursorAdapter(Context context, Cursor c, boolean autoRequery)137 public CursorAdapter(Context context, Cursor c, boolean autoRequery) { 138 init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER); 139 } 140 141 /** 142 * Recommended constructor. 143 * 144 * @param c The cursor from which to get the data. 145 * @param context The context 146 * @param flags Flags used to determine the behavior of the adapter; may 147 * be any combination of {@link #FLAG_AUTO_REQUERY} and 148 * {@link #FLAG_REGISTER_CONTENT_OBSERVER}. 149 */ CursorAdapter(Context context, Cursor c, int flags)150 public CursorAdapter(Context context, Cursor c, int flags) { 151 init(context, c, flags); 152 } 153 154 /** 155 * @deprecated Don't use this, use the normal constructor. This will 156 * be removed in the future. 157 */ 158 @Deprecated init(Context context, Cursor c, boolean autoRequery)159 protected void init(Context context, Cursor c, boolean autoRequery) { 160 init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER); 161 } 162 init(Context context, Cursor c, int flags)163 void init(Context context, Cursor c, int flags) { 164 if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) { 165 flags |= FLAG_REGISTER_CONTENT_OBSERVER; 166 mAutoRequery = true; 167 } else { 168 mAutoRequery = false; 169 } 170 boolean cursorPresent = c != null; 171 mCursor = c; 172 mDataValid = cursorPresent; 173 mContext = context; 174 mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1; 175 if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) { 176 mChangeObserver = new ChangeObserver(); 177 mDataSetObserver = new MyDataSetObserver(); 178 } else { 179 mChangeObserver = null; 180 mDataSetObserver = null; 181 } 182 183 if (cursorPresent) { 184 if (mChangeObserver != null) c.registerContentObserver(mChangeObserver); 185 if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver); 186 } 187 } 188 189 /** 190 * Returns the cursor. 191 * @return the cursor. 192 */ 193 @Override getCursor()194 public Cursor getCursor() { 195 return mCursor; 196 } 197 198 /** 199 * @see android.widget.ListAdapter#getCount() 200 */ 201 @Override getCount()202 public int getCount() { 203 if (mDataValid && mCursor != null) { 204 return mCursor.getCount(); 205 } else { 206 return 0; 207 } 208 } 209 210 /** 211 * @see android.widget.ListAdapter#getItem(int) 212 */ 213 @Override getItem(int position)214 public Object getItem(int position) { 215 if (mDataValid && mCursor != null) { 216 mCursor.moveToPosition(position); 217 return mCursor; 218 } else { 219 return null; 220 } 221 } 222 223 /** 224 * @see android.widget.ListAdapter#getItemId(int) 225 */ 226 @Override getItemId(int position)227 public long getItemId(int position) { 228 if (mDataValid && mCursor != null) { 229 if (mCursor.moveToPosition(position)) { 230 return mCursor.getLong(mRowIDColumn); 231 } else { 232 return 0; 233 } 234 } else { 235 return 0; 236 } 237 } 238 239 @Override hasStableIds()240 public boolean hasStableIds() { 241 return true; 242 } 243 244 /** 245 * @see android.widget.ListAdapter#getView(int, View, ViewGroup) 246 */ 247 @Override getView(int position, View convertView, ViewGroup parent)248 public View getView(int position, View convertView, ViewGroup parent) { 249 if (!mDataValid) { 250 throw new IllegalStateException("this should only be called when the cursor is valid"); 251 } 252 if (!mCursor.moveToPosition(position)) { 253 throw new IllegalStateException("couldn't move cursor to position " + position); 254 } 255 View v; 256 if (convertView == null) { 257 v = newView(mContext, mCursor, parent); 258 } else { 259 v = convertView; 260 } 261 bindView(v, mContext, mCursor); 262 return v; 263 } 264 265 @Override getDropDownView(int position, View convertView, ViewGroup parent)266 public View getDropDownView(int position, View convertView, ViewGroup parent) { 267 if (mDataValid) { 268 mCursor.moveToPosition(position); 269 View v; 270 if (convertView == null) { 271 v = newDropDownView(mContext, mCursor, parent); 272 } else { 273 v = convertView; 274 } 275 bindView(v, mContext, mCursor); 276 return v; 277 } else { 278 return null; 279 } 280 } 281 282 /** 283 * Makes a new view to hold the data pointed to by cursor. 284 * @param context Interface to application's global information 285 * @param cursor The cursor from which to get the data. The cursor is already 286 * moved to the correct position. 287 * @param parent The parent to which the new view is attached to 288 * @return the newly created view. 289 */ newView(Context context, Cursor cursor, ViewGroup parent)290 public abstract View newView(Context context, Cursor cursor, ViewGroup parent); 291 292 /** 293 * Makes a new drop down view to hold the data pointed to by cursor. 294 * @param context Interface to application's global information 295 * @param cursor The cursor from which to get the data. The cursor is already 296 * moved to the correct position. 297 * @param parent The parent to which the new view is attached to 298 * @return the newly created view. 299 */ newDropDownView(Context context, Cursor cursor, ViewGroup parent)300 public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) { 301 return newView(context, cursor, parent); 302 } 303 304 /** 305 * Bind an existing view to the data pointed to by cursor 306 * @param view Existing view, returned earlier by newView 307 * @param context Interface to application's global information 308 * @param cursor The cursor from which to get the data. The cursor is already 309 * moved to the correct position. 310 */ bindView(View view, Context context, Cursor cursor)311 public abstract void bindView(View view, Context context, Cursor cursor); 312 313 /** 314 * Change the underlying cursor to a new cursor. If there is an existing cursor it will be 315 * closed. 316 * 317 * @param cursor The new cursor to be used 318 */ 319 @Override changeCursor(Cursor cursor)320 public void changeCursor(Cursor cursor) { 321 Cursor old = swapCursor(cursor); 322 if (old != null) { 323 old.close(); 324 } 325 } 326 327 /** 328 * Swap in a new Cursor, returning the old Cursor. Unlike 329 * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em> 330 * closed. 331 * 332 * @param newCursor The new cursor to be used. 333 * @return Returns the previously set Cursor, or null if there was not one. 334 * If the given new Cursor is the same instance is the previously set 335 * Cursor, null is also returned. 336 */ swapCursor(Cursor newCursor)337 public Cursor swapCursor(Cursor newCursor) { 338 if (newCursor == mCursor) { 339 return null; 340 } 341 Cursor oldCursor = mCursor; 342 if (oldCursor != null) { 343 if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver); 344 if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver); 345 } 346 mCursor = newCursor; 347 if (newCursor != null) { 348 if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver); 349 if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver); 350 mRowIDColumn = newCursor.getColumnIndexOrThrow("_id"); 351 mDataValid = true; 352 // notify the observers about the new cursor 353 notifyDataSetChanged(); 354 } else { 355 mRowIDColumn = -1; 356 mDataValid = false; 357 // notify the observers about the lack of a data set 358 notifyDataSetInvalidated(); 359 } 360 return oldCursor; 361 } 362 363 /** 364 * <p>Converts the cursor into a CharSequence. Subclasses should override this 365 * method to convert their results. The default implementation returns an 366 * empty String for null values or the default String representation of 367 * the value.</p> 368 * 369 * @param cursor the cursor to convert to a CharSequence 370 * @return a CharSequence representing the value 371 */ 372 @Override convertToString(Cursor cursor)373 public CharSequence convertToString(Cursor cursor) { 374 return cursor == null ? "" : cursor.toString(); 375 } 376 377 /** 378 * Runs a query with the specified constraint. This query is requested 379 * by the filter attached to this adapter. 380 * 381 * The query is provided by a 382 * {@link android.widget.FilterQueryProvider}. 383 * If no provider is specified, the current cursor is not filtered and returned. 384 * 385 * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)} 386 * and the previous cursor is closed. 387 * 388 * This method is always executed on a background thread, not on the 389 * application's main thread (or UI thread.) 390 * 391 * Contract: when constraint is null or empty, the original results, 392 * prior to any filtering, must be returned. 393 * 394 * @param constraint the constraint with which the query must be filtered 395 * 396 * @return a Cursor representing the results of the new query 397 * 398 * @see #getFilter() 399 * @see #getFilterQueryProvider() 400 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider) 401 */ 402 @Override runQueryOnBackgroundThread(CharSequence constraint)403 public Cursor runQueryOnBackgroundThread(CharSequence constraint) { 404 if (mFilterQueryProvider != null) { 405 return mFilterQueryProvider.runQuery(constraint); 406 } 407 408 return mCursor; 409 } 410 411 @Override getFilter()412 public Filter getFilter() { 413 if (mCursorFilter == null) { 414 mCursorFilter = new CursorFilter(this); 415 } 416 return mCursorFilter; 417 } 418 419 /** 420 * Returns the query filter provider used for filtering. When the 421 * provider is null, no filtering occurs. 422 * 423 * @return the current filter query provider or null if it does not exist 424 * 425 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider) 426 * @see #runQueryOnBackgroundThread(CharSequence) 427 */ getFilterQueryProvider()428 public FilterQueryProvider getFilterQueryProvider() { 429 return mFilterQueryProvider; 430 } 431 432 /** 433 * Sets the query filter provider used to filter the current Cursor. 434 * The provider's 435 * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)} 436 * method is invoked when filtering is requested by a client of 437 * this adapter. 438 * 439 * @param filterQueryProvider the filter query provider or null to remove it 440 * 441 * @see #getFilterQueryProvider() 442 * @see #runQueryOnBackgroundThread(CharSequence) 443 */ setFilterQueryProvider(FilterQueryProvider filterQueryProvider)444 public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) { 445 mFilterQueryProvider = filterQueryProvider; 446 } 447 448 /** 449 * Called when the {@link ContentObserver} on the cursor receives a change notification. 450 * The default implementation provides the auto-requery logic, but may be overridden by 451 * sub classes. 452 * 453 * @see ContentObserver#onChange(boolean) 454 */ onContentChanged()455 protected void onContentChanged() { 456 if (mAutoRequery && mCursor != null && !mCursor.isClosed()) { 457 if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update"); 458 mDataValid = mCursor.requery(); 459 } 460 } 461 462 private class ChangeObserver extends ContentObserver { ChangeObserver()463 ChangeObserver() { 464 super(new Handler()); 465 } 466 467 @Override deliverSelfNotifications()468 public boolean deliverSelfNotifications() { 469 return true; 470 } 471 472 @Override onChange(boolean selfChange)473 public void onChange(boolean selfChange) { 474 onContentChanged(); 475 } 476 } 477 478 private class MyDataSetObserver extends DataSetObserver { 479 @Override onChanged()480 public void onChanged() { 481 mDataValid = true; 482 notifyDataSetChanged(); 483 } 484 485 @Override onInvalidated()486 public void onInvalidated() { 487 mDataValid = false; 488 notifyDataSetInvalidated(); 489 } 490 } 491 492 } 493