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