AbstractCursor.java revision 8faab36fdd2d7dd038885a5073eeb8354d8996d4
1/* 2 * Copyright (C) 2006 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 android.database; 18 19import android.content.ContentResolver; 20import android.net.Uri; 21import android.os.Bundle; 22import android.os.UserHandle; 23import android.util.Log; 24 25import java.lang.ref.WeakReference; 26import java.util.HashMap; 27import java.util.Map; 28 29 30/** 31 * This is an abstract cursor class that handles a lot of the common code 32 * that all cursors need to deal with and is provided for convenience reasons. 33 */ 34public abstract class AbstractCursor implements CrossProcessCursor { 35 private static final String TAG = "Cursor"; 36 37 /** 38 * @removed This field should not be used. 39 */ 40 protected HashMap<Long, Map<String, Object>> mUpdatedRows; 41 42 /** 43 * @removed This field should not be used. 44 */ 45 protected int mRowIdColumnIndex; 46 47 /** 48 * @removed This field should not be used. 49 */ 50 protected Long mCurrentRowID; 51 52 /** 53 * @deprecated Use {@link #getPosition()} instead. 54 */ 55 @Deprecated 56 protected int mPos; 57 58 /** 59 * @deprecated Use {@link #isClosed()} instead. 60 */ 61 @Deprecated 62 protected boolean mClosed; 63 64 /** 65 * @deprecated Do not use. 66 */ 67 @Deprecated 68 protected ContentResolver mContentResolver; 69 70 private Uri mNotifyUri; 71 72 private final Object mSelfObserverLock = new Object(); 73 private ContentObserver mSelfObserver; 74 private boolean mSelfObserverRegistered; 75 76 private final DataSetObservable mDataSetObservable = new DataSetObservable(); 77 private final ContentObservable mContentObservable = new ContentObservable(); 78 79 private Bundle mExtras = Bundle.EMPTY; 80 81 /* -------------------------------------------------------- */ 82 /* These need to be implemented by subclasses */ 83 @Override 84 abstract public int getCount(); 85 86 @Override 87 abstract public String[] getColumnNames(); 88 89 @Override 90 abstract public String getString(int column); 91 @Override 92 abstract public short getShort(int column); 93 @Override 94 abstract public int getInt(int column); 95 @Override 96 abstract public long getLong(int column); 97 @Override 98 abstract public float getFloat(int column); 99 @Override 100 abstract public double getDouble(int column); 101 @Override 102 abstract public boolean isNull(int column); 103 104 @Override 105 public int getType(int column) { 106 // Reflects the assumption that all commonly used field types (meaning everything 107 // but blobs) are convertible to strings so it should be safe to call 108 // getString to retrieve them. 109 return FIELD_TYPE_STRING; 110 } 111 112 // TODO implement getBlob in all cursor types 113 @Override 114 public byte[] getBlob(int column) { 115 throw new UnsupportedOperationException("getBlob is not supported"); 116 } 117 /* -------------------------------------------------------- */ 118 /* Methods that may optionally be implemented by subclasses */ 119 120 /** 121 * If the cursor is backed by a {@link CursorWindow}, returns a pre-filled 122 * window with the contents of the cursor, otherwise null. 123 * 124 * @return The pre-filled window that backs this cursor, or null if none. 125 */ 126 @Override 127 public CursorWindow getWindow() { 128 return null; 129 } 130 131 @Override 132 public int getColumnCount() { 133 return getColumnNames().length; 134 } 135 136 @Override 137 public void deactivate() { 138 onDeactivateOrClose(); 139 } 140 141 /** @hide */ 142 protected void onDeactivateOrClose() { 143 if (mSelfObserver != null) { 144 mContentResolver.unregisterContentObserver(mSelfObserver); 145 mSelfObserverRegistered = false; 146 } 147 mDataSetObservable.notifyInvalidated(); 148 } 149 150 @Override 151 public boolean requery() { 152 if (mSelfObserver != null && mSelfObserverRegistered == false) { 153 mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver); 154 mSelfObserverRegistered = true; 155 } 156 mDataSetObservable.notifyChanged(); 157 return true; 158 } 159 160 @Override 161 public boolean isClosed() { 162 return mClosed; 163 } 164 165 @Override 166 public void close() { 167 mClosed = true; 168 mContentObservable.unregisterAll(); 169 onDeactivateOrClose(); 170 } 171 172 /** 173 * This function is called every time the cursor is successfully scrolled 174 * to a new position, giving the subclass a chance to update any state it 175 * may have. If it returns false the move function will also do so and the 176 * cursor will scroll to the beforeFirst position. 177 * 178 * @param oldPosition the position that we're moving from 179 * @param newPosition the position that we're moving to 180 * @return true if the move is successful, false otherwise 181 */ 182 @Override 183 public boolean onMove(int oldPosition, int newPosition) { 184 return true; 185 } 186 187 188 @Override 189 public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { 190 // Default implementation, uses getString 191 String result = getString(columnIndex); 192 if (result != null) { 193 char[] data = buffer.data; 194 if (data == null || data.length < result.length()) { 195 buffer.data = result.toCharArray(); 196 } else { 197 result.getChars(0, result.length(), data, 0); 198 } 199 buffer.sizeCopied = result.length(); 200 } else { 201 buffer.sizeCopied = 0; 202 } 203 } 204 205 /* -------------------------------------------------------- */ 206 /* Implementation */ 207 public AbstractCursor() { 208 mPos = -1; 209 } 210 211 @Override 212 public final int getPosition() { 213 return mPos; 214 } 215 216 @Override 217 public final boolean moveToPosition(int position) { 218 // Make sure position isn't past the end of the cursor 219 final int count = getCount(); 220 if (position >= count) { 221 mPos = count; 222 return false; 223 } 224 225 // Make sure position isn't before the beginning of the cursor 226 if (position < 0) { 227 mPos = -1; 228 return false; 229 } 230 231 // Check for no-op moves, and skip the rest of the work for them 232 if (position == mPos) { 233 return true; 234 } 235 236 boolean result = onMove(mPos, position); 237 if (result == false) { 238 mPos = -1; 239 } else { 240 mPos = position; 241 } 242 243 return result; 244 } 245 246 @Override 247 public void fillWindow(int position, CursorWindow window) { 248 DatabaseUtils.cursorFillWindow(this, position, window); 249 } 250 251 @Override 252 public final boolean move(int offset) { 253 return moveToPosition(mPos + offset); 254 } 255 256 @Override 257 public final boolean moveToFirst() { 258 return moveToPosition(0); 259 } 260 261 @Override 262 public final boolean moveToLast() { 263 return moveToPosition(getCount() - 1); 264 } 265 266 @Override 267 public final boolean moveToNext() { 268 return moveToPosition(mPos + 1); 269 } 270 271 @Override 272 public final boolean moveToPrevious() { 273 return moveToPosition(mPos - 1); 274 } 275 276 @Override 277 public final boolean isFirst() { 278 return mPos == 0 && getCount() != 0; 279 } 280 281 @Override 282 public final boolean isLast() { 283 int cnt = getCount(); 284 return mPos == (cnt - 1) && cnt != 0; 285 } 286 287 @Override 288 public final boolean isBeforeFirst() { 289 if (getCount() == 0) { 290 return true; 291 } 292 return mPos == -1; 293 } 294 295 @Override 296 public final boolean isAfterLast() { 297 if (getCount() == 0) { 298 return true; 299 } 300 return mPos == getCount(); 301 } 302 303 @Override 304 public int getColumnIndex(String columnName) { 305 // Hack according to bug 903852 306 final int periodIndex = columnName.lastIndexOf('.'); 307 if (periodIndex != -1) { 308 Exception e = new Exception(); 309 Log.e(TAG, "requesting column name with table name -- " + columnName, e); 310 columnName = columnName.substring(periodIndex + 1); 311 } 312 313 String columnNames[] = getColumnNames(); 314 int length = columnNames.length; 315 for (int i = 0; i < length; i++) { 316 if (columnNames[i].equalsIgnoreCase(columnName)) { 317 return i; 318 } 319 } 320 321 if (false) { 322 if (getCount() > 0) { 323 Log.w("AbstractCursor", "Unknown column " + columnName); 324 } 325 } 326 return -1; 327 } 328 329 @Override 330 public int getColumnIndexOrThrow(String columnName) { 331 final int index = getColumnIndex(columnName); 332 if (index < 0) { 333 throw new IllegalArgumentException("column '" + columnName + "' does not exist"); 334 } 335 return index; 336 } 337 338 @Override 339 public String getColumnName(int columnIndex) { 340 return getColumnNames()[columnIndex]; 341 } 342 343 @Override 344 public void registerContentObserver(ContentObserver observer) { 345 mContentObservable.registerObserver(observer); 346 } 347 348 @Override 349 public void unregisterContentObserver(ContentObserver observer) { 350 // cursor will unregister all observers when it close 351 if (!mClosed) { 352 mContentObservable.unregisterObserver(observer); 353 } 354 } 355 356 @Override 357 public void registerDataSetObserver(DataSetObserver observer) { 358 mDataSetObservable.registerObserver(observer); 359 } 360 361 @Override 362 public void unregisterDataSetObserver(DataSetObserver observer) { 363 mDataSetObservable.unregisterObserver(observer); 364 } 365 366 /** 367 * Subclasses must call this method when they finish committing updates to notify all 368 * observers. 369 * 370 * @param selfChange 371 */ 372 protected void onChange(boolean selfChange) { 373 synchronized (mSelfObserverLock) { 374 mContentObservable.dispatchChange(selfChange, null); 375 if (mNotifyUri != null && selfChange) { 376 mContentResolver.notifyChange(mNotifyUri, mSelfObserver); 377 } 378 } 379 } 380 381 /** 382 * Specifies a content URI to watch for changes. 383 * 384 * @param cr The content resolver from the caller's context. 385 * @param notifyUri The URI to watch for changes. This can be a 386 * specific row URI, or a base URI for a whole class of content. 387 */ 388 @Override 389 public void setNotificationUri(ContentResolver cr, Uri notifyUri) { 390 setNotificationUri(cr, notifyUri, UserHandle.myUserId()); 391 } 392 393 /** @hide - set the notification uri but with an observer for a particular user's view */ 394 public void setNotificationUri(ContentResolver cr, Uri notifyUri, int userHandle) { 395 synchronized (mSelfObserverLock) { 396 mNotifyUri = notifyUri; 397 mContentResolver = cr; 398 if (mSelfObserver != null) { 399 mContentResolver.unregisterContentObserver(mSelfObserver); 400 } 401 mSelfObserver = new SelfContentObserver(this); 402 mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver, userHandle); 403 mSelfObserverRegistered = true; 404 } 405 } 406 407 public Uri getNotificationUri() { 408 synchronized (mSelfObserverLock) { 409 return mNotifyUri; 410 } 411 } 412 413 public boolean getWantsAllOnMoveCalls() { 414 return false; 415 } 416 417 /** 418 * Sets a {@link Bundle} that will be returned by {@link #getExtras()}. <code>null</code> will 419 * be converted into {@link Bundle#EMPTY}. 420 * 421 * @param extras {@link Bundle} to set. 422 * @hide 423 */ 424 public void setExtras(Bundle extras) { 425 mExtras = (extras == null) ? Bundle.EMPTY : extras; 426 } 427 428 public Bundle getExtras() { 429 return mExtras; 430 } 431 432 public Bundle respond(Bundle extras) { 433 return Bundle.EMPTY; 434 } 435 436 /** 437 * @deprecated Always returns false since Cursors do not support updating rows 438 */ 439 @Deprecated 440 protected boolean isFieldUpdated(int columnIndex) { 441 return false; 442 } 443 444 /** 445 * @deprecated Always returns null since Cursors do not support updating rows 446 */ 447 @Deprecated 448 protected Object getUpdatedField(int columnIndex) { 449 return null; 450 } 451 452 /** 453 * This function throws CursorIndexOutOfBoundsException if 454 * the cursor position is out of bounds. Subclass implementations of 455 * the get functions should call this before attempting 456 * to retrieve data. 457 * 458 * @throws CursorIndexOutOfBoundsException 459 */ 460 protected void checkPosition() { 461 if (-1 == mPos || getCount() == mPos) { 462 throw new CursorIndexOutOfBoundsException(mPos, getCount()); 463 } 464 } 465 466 @Override 467 protected void finalize() { 468 if (mSelfObserver != null && mSelfObserverRegistered == true) { 469 mContentResolver.unregisterContentObserver(mSelfObserver); 470 } 471 try { 472 if (!mClosed) close(); 473 } catch(Exception e) { } 474 } 475 476 /** 477 * Cursors use this class to track changes others make to their URI. 478 */ 479 protected static class SelfContentObserver extends ContentObserver { 480 WeakReference<AbstractCursor> mCursor; 481 482 public SelfContentObserver(AbstractCursor cursor) { 483 super(null); 484 mCursor = new WeakReference<AbstractCursor>(cursor); 485 } 486 487 @Override 488 public boolean deliverSelfNotifications() { 489 return false; 490 } 491 492 @Override 493 public void onChange(boolean selfChange) { 494 AbstractCursor cursor = mCursor.get(); 495 if (cursor != null) { 496 cursor.onChange(false); 497 } 498 } 499 } 500} 501