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 17 package android.database; 18 19 import android.content.ContentResolver; 20 import android.net.Uri; 21 import android.util.Config; 22 import android.util.Log; 23 import android.os.Bundle; 24 import android.os.Handler; 25 import android.os.HandlerThread; 26 import android.os.Looper; 27 import android.os.Message; 28 29 import java.lang.ref.WeakReference; 30 import java.lang.UnsupportedOperationException; 31 import java.util.HashMap; 32 import java.util.Map; 33 34 35 /** 36 * This is an abstract cursor class that handles a lot of the common code 37 * that all cursors need to deal with and is provided for convenience reasons. 38 */ 39 public abstract class AbstractCursor implements CrossProcessCursor { 40 private static final String TAG = "Cursor"; 41 42 DataSetObservable mDataSetObservable = new DataSetObservable(); 43 ContentObservable mContentObservable = new ContentObservable(); 44 45 /* -------------------------------------------------------- */ 46 /* These need to be implemented by subclasses */ 47 abstract public int getCount(); 48 49 abstract public String[] getColumnNames(); 50 51 abstract public String getString(int column); 52 abstract public short getShort(int column); 53 abstract public int getInt(int column); 54 abstract public long getLong(int column); 55 abstract public float getFloat(int column); 56 abstract public double getDouble(int column); 57 abstract public boolean isNull(int column); 58 59 // TODO implement getBlob in all cursor types 60 public byte[] getBlob(int column) { 61 throw new UnsupportedOperationException("getBlob is not supported"); 62 } 63 /* -------------------------------------------------------- */ 64 /* Methods that may optionally be implemented by subclasses */ 65 66 /** 67 * returns a pre-filled window, return NULL if no such window 68 */ 69 public CursorWindow getWindow() { 70 return null; 71 } 72 73 public int getColumnCount() { 74 return getColumnNames().length; 75 } 76 77 public void deactivate() { 78 deactivateInternal(); 79 } 80 81 /** 82 * @hide 83 */ 84 public void deactivateInternal() { 85 if (mSelfObserver != null) { 86 mContentResolver.unregisterContentObserver(mSelfObserver); 87 mSelfObserverRegistered = false; 88 } 89 mDataSetObservable.notifyInvalidated(); 90 } 91 92 public boolean requery() { 93 if (mSelfObserver != null && mSelfObserverRegistered == false) { 94 mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver); 95 mSelfObserverRegistered = true; 96 } 97 mDataSetObservable.notifyChanged(); 98 return true; 99 } 100 101 public boolean isClosed() { 102 return mClosed; 103 } 104 105 public void close() { 106 mClosed = true; 107 mContentObservable.unregisterAll(); 108 deactivateInternal(); 109 } 110 111 /** 112 * @hide 113 * @deprecated 114 */ 115 public boolean commitUpdates(Map<? extends Long,? extends Map<String,Object>> values) { 116 return false; 117 } 118 119 /** 120 * @hide 121 * @deprecated 122 */ 123 public boolean deleteRow() { 124 return false; 125 } 126 127 /** 128 * This function is called every time the cursor is successfully scrolled 129 * to a new position, giving the subclass a chance to update any state it 130 * may have. If it returns false the move function will also do so and the 131 * cursor will scroll to the beforeFirst position. 132 * 133 * @param oldPosition the position that we're moving from 134 * @param newPosition the position that we're moving to 135 * @return true if the move is successful, false otherwise 136 */ 137 public boolean onMove(int oldPosition, int newPosition) { 138 return true; 139 } 140 141 142 public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { 143 // Default implementation, uses getString 144 String result = getString(columnIndex); 145 if (result != null) { 146 char[] data = buffer.data; 147 if (data == null || data.length < result.length()) { 148 buffer.data = result.toCharArray(); 149 } else { 150 result.getChars(0, result.length(), data, 0); 151 } 152 buffer.sizeCopied = result.length(); 153 } 154 } 155 156 /* -------------------------------------------------------- */ 157 /* Implementation */ 158 public AbstractCursor() { 159 mPos = -1; 160 mRowIdColumnIndex = -1; 161 mCurrentRowID = null; 162 mUpdatedRows = new HashMap<Long, Map<String, Object>>(); 163 } 164 165 public final int getPosition() { 166 return mPos; 167 } 168 169 public final boolean moveToPosition(int position) { 170 // Make sure position isn't past the end of the cursor 171 final int count = getCount(); 172 if (position >= count) { 173 mPos = count; 174 return false; 175 } 176 177 // Make sure position isn't before the beginning of the cursor 178 if (position < 0) { 179 mPos = -1; 180 return false; 181 } 182 183 // Check for no-op moves, and skip the rest of the work for them 184 if (position == mPos) { 185 return true; 186 } 187 188 boolean result = onMove(mPos, position); 189 if (result == false) { 190 mPos = -1; 191 } else { 192 mPos = position; 193 if (mRowIdColumnIndex != -1) { 194 mCurrentRowID = Long.valueOf(getLong(mRowIdColumnIndex)); 195 } 196 } 197 198 return result; 199 } 200 201 /** 202 * Copy data from cursor to CursorWindow 203 * @param position start position of data 204 * @param window 205 */ 206 public void fillWindow(int position, CursorWindow window) { 207 if (position < 0 || position > getCount()) { 208 return; 209 } 210 window.acquireReference(); 211 try { 212 int oldpos = mPos; 213 mPos = position - 1; 214 window.clear(); 215 window.setStartPosition(position); 216 int columnNum = getColumnCount(); 217 window.setNumColumns(columnNum); 218 while (moveToNext() && window.allocRow()) { 219 for (int i = 0; i < columnNum; i++) { 220 String field = getString(i); 221 if (field != null) { 222 if (!window.putString(field, mPos, i)) { 223 window.freeLastRow(); 224 break; 225 } 226 } else { 227 if (!window.putNull(mPos, i)) { 228 window.freeLastRow(); 229 break; 230 } 231 } 232 } 233 } 234 235 mPos = oldpos; 236 } catch (IllegalStateException e){ 237 // simply ignore it 238 } finally { 239 window.releaseReference(); 240 } 241 } 242 243 public final boolean move(int offset) { 244 return moveToPosition(mPos + offset); 245 } 246 247 public final boolean moveToFirst() { 248 return moveToPosition(0); 249 } 250 251 public final boolean moveToLast() { 252 return moveToPosition(getCount() - 1); 253 } 254 255 public final boolean moveToNext() { 256 return moveToPosition(mPos + 1); 257 } 258 259 public final boolean moveToPrevious() { 260 return moveToPosition(mPos - 1); 261 } 262 263 public final boolean isFirst() { 264 return mPos == 0 && getCount() != 0; 265 } 266 267 public final boolean isLast() { 268 int cnt = getCount(); 269 return mPos == (cnt - 1) && cnt != 0; 270 } 271 272 public final boolean isBeforeFirst() { 273 if (getCount() == 0) { 274 return true; 275 } 276 return mPos == -1; 277 } 278 279 public final boolean isAfterLast() { 280 if (getCount() == 0) { 281 return true; 282 } 283 return mPos == getCount(); 284 } 285 286 public int getColumnIndex(String columnName) { 287 // Hack according to bug 903852 288 final int periodIndex = columnName.lastIndexOf('.'); 289 if (periodIndex != -1) { 290 Exception e = new Exception(); 291 Log.e(TAG, "requesting column name with table name -- " + columnName, e); 292 columnName = columnName.substring(periodIndex + 1); 293 } 294 295 String columnNames[] = getColumnNames(); 296 int length = columnNames.length; 297 for (int i = 0; i < length; i++) { 298 if (columnNames[i].equalsIgnoreCase(columnName)) { 299 return i; 300 } 301 } 302 303 if (Config.LOGV) { 304 if (getCount() > 0) { 305 Log.w("AbstractCursor", "Unknown column " + columnName); 306 } 307 } 308 return -1; 309 } 310 311 public int getColumnIndexOrThrow(String columnName) { 312 final int index = getColumnIndex(columnName); 313 if (index < 0) { 314 throw new IllegalArgumentException("column '" + columnName + "' does not exist"); 315 } 316 return index; 317 } 318 319 public String getColumnName(int columnIndex) { 320 return getColumnNames()[columnIndex]; 321 } 322 323 /** 324 * @hide 325 * @deprecated 326 */ 327 public boolean updateBlob(int columnIndex, byte[] value) { 328 return update(columnIndex, value); 329 } 330 331 /** 332 * @hide 333 * @deprecated 334 */ 335 public boolean updateString(int columnIndex, String value) { 336 return update(columnIndex, value); 337 } 338 339 /** 340 * @hide 341 * @deprecated 342 */ 343 public boolean updateShort(int columnIndex, short value) { 344 return update(columnIndex, Short.valueOf(value)); 345 } 346 347 /** 348 * @hide 349 * @deprecated 350 */ 351 public boolean updateInt(int columnIndex, int value) { 352 return update(columnIndex, Integer.valueOf(value)); 353 } 354 355 /** 356 * @hide 357 * @deprecated 358 */ 359 public boolean updateLong(int columnIndex, long value) { 360 return update(columnIndex, Long.valueOf(value)); 361 } 362 363 /** 364 * @hide 365 * @deprecated 366 */ 367 public boolean updateFloat(int columnIndex, float value) { 368 return update(columnIndex, Float.valueOf(value)); 369 } 370 371 /** 372 * @hide 373 * @deprecated 374 */ 375 public boolean updateDouble(int columnIndex, double value) { 376 return update(columnIndex, Double.valueOf(value)); 377 } 378 379 /** 380 * @hide 381 * @deprecated 382 */ 383 public boolean updateToNull(int columnIndex) { 384 return update(columnIndex, null); 385 } 386 387 /** 388 * @hide 389 * @deprecated 390 */ 391 public boolean update(int columnIndex, Object obj) { 392 if (!supportsUpdates()) { 393 return false; 394 } 395 396 // Long.valueOf() returns null sometimes! 397 // Long rowid = Long.valueOf(getLong(mRowIdColumnIndex)); 398 Long rowid = new Long(getLong(mRowIdColumnIndex)); 399 if (rowid == null) { 400 throw new IllegalStateException("null rowid. mRowIdColumnIndex = " + mRowIdColumnIndex); 401 } 402 403 synchronized(mUpdatedRows) { 404 Map<String, Object> row = mUpdatedRows.get(rowid); 405 if (row == null) { 406 row = new HashMap<String, Object>(); 407 mUpdatedRows.put(rowid, row); 408 } 409 row.put(getColumnNames()[columnIndex], obj); 410 } 411 412 return true; 413 } 414 415 /** 416 * Returns <code>true</code> if there are pending updates that have not yet been committed. 417 * 418 * @return <code>true</code> if there are pending updates that have not yet been committed. 419 * @hide 420 * @deprecated 421 */ 422 public boolean hasUpdates() { 423 synchronized(mUpdatedRows) { 424 return mUpdatedRows.size() > 0; 425 } 426 } 427 428 /** 429 * @hide 430 * @deprecated 431 */ 432 public void abortUpdates() { 433 synchronized(mUpdatedRows) { 434 mUpdatedRows.clear(); 435 } 436 } 437 438 /** 439 * @hide 440 * @deprecated 441 */ 442 public boolean commitUpdates() { 443 return commitUpdates(null); 444 } 445 446 /** 447 * @hide 448 * @deprecated 449 */ 450 public boolean supportsUpdates() { 451 return mRowIdColumnIndex != -1; 452 } 453 454 public void registerContentObserver(ContentObserver observer) { 455 mContentObservable.registerObserver(observer); 456 } 457 458 public void unregisterContentObserver(ContentObserver observer) { 459 // cursor will unregister all observers when it close 460 if (!mClosed) { 461 mContentObservable.unregisterObserver(observer); 462 } 463 } 464 465 /** 466 * This is hidden until the data set change model has been re-evaluated. 467 * @hide 468 */ 469 protected void notifyDataSetChange() { 470 mDataSetObservable.notifyChanged(); 471 } 472 473 /** 474 * This is hidden until the data set change model has been re-evaluated. 475 * @hide 476 */ 477 protected DataSetObservable getDataSetObservable() { 478 return mDataSetObservable; 479 480 } 481 public void registerDataSetObserver(DataSetObserver observer) { 482 mDataSetObservable.registerObserver(observer); 483 484 } 485 486 public void unregisterDataSetObserver(DataSetObserver observer) { 487 mDataSetObservable.unregisterObserver(observer); 488 } 489 490 /** 491 * Subclasses must call this method when they finish committing updates to notify all 492 * observers. 493 * 494 * @param selfChange 495 */ 496 protected void onChange(boolean selfChange) { 497 synchronized (mSelfObserverLock) { 498 mContentObservable.dispatchChange(selfChange); 499 if (mNotifyUri != null && selfChange) { 500 mContentResolver.notifyChange(mNotifyUri, mSelfObserver); 501 } 502 } 503 } 504 505 /** 506 * Specifies a content URI to watch for changes. 507 * 508 * @param cr The content resolver from the caller's context. 509 * @param notifyUri The URI to watch for changes. This can be a 510 * specific row URI, or a base URI for a whole class of content. 511 */ 512 public void setNotificationUri(ContentResolver cr, Uri notifyUri) { 513 synchronized (mSelfObserverLock) { 514 mNotifyUri = notifyUri; 515 mContentResolver = cr; 516 if (mSelfObserver != null) { 517 mContentResolver.unregisterContentObserver(mSelfObserver); 518 } 519 mSelfObserver = new SelfContentObserver(this); 520 mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver); 521 mSelfObserverRegistered = true; 522 } 523 } 524 525 public boolean getWantsAllOnMoveCalls() { 526 return false; 527 } 528 529 public Bundle getExtras() { 530 return Bundle.EMPTY; 531 } 532 533 public Bundle respond(Bundle extras) { 534 return Bundle.EMPTY; 535 } 536 537 /** 538 * This function returns true if the field has been updated and is 539 * used in conjunction with {@link #getUpdatedField} to allow subclasses to 540 * support reading uncommitted updates. NOTE: This function and 541 * {@link #getUpdatedField} should be called together inside of a 542 * block synchronized on mUpdatedRows. 543 * 544 * @param columnIndex the column index of the field to check 545 * @return true if the field has been updated, false otherwise 546 */ 547 protected boolean isFieldUpdated(int columnIndex) { 548 if (mRowIdColumnIndex != -1 && mUpdatedRows.size() > 0) { 549 Map<String, Object> updates = mUpdatedRows.get(mCurrentRowID); 550 if (updates != null && updates.containsKey(getColumnNames()[columnIndex])) { 551 return true; 552 } 553 } 554 return false; 555 } 556 557 /** 558 * This function returns the uncommitted updated value for the field 559 * at columnIndex. NOTE: This function and {@link #isFieldUpdated} should 560 * be called together inside of a block synchronized on mUpdatedRows. 561 * 562 * @param columnIndex the column index of the field to retrieve 563 * @return the updated value 564 */ 565 protected Object getUpdatedField(int columnIndex) { 566 Map<String, Object> updates = mUpdatedRows.get(mCurrentRowID); 567 return updates.get(getColumnNames()[columnIndex]); 568 } 569 570 /** 571 * This function throws CursorIndexOutOfBoundsException if 572 * the cursor position is out of bounds. Subclass implementations of 573 * the get functions should call this before attempting 574 * to retrieve data. 575 * 576 * @throws CursorIndexOutOfBoundsException 577 */ 578 protected void checkPosition() { 579 if (-1 == mPos || getCount() == mPos) { 580 throw new CursorIndexOutOfBoundsException(mPos, getCount()); 581 } 582 } 583 584 @Override 585 protected void finalize() { 586 if (mSelfObserver != null && mSelfObserverRegistered == true) { 587 mContentResolver.unregisterContentObserver(mSelfObserver); 588 } 589 } 590 591 /** 592 * Cursors use this class to track changes others make to their URI. 593 */ 594 protected static class SelfContentObserver extends ContentObserver { 595 WeakReference<AbstractCursor> mCursor; 596 597 public SelfContentObserver(AbstractCursor cursor) { 598 super(null); 599 mCursor = new WeakReference<AbstractCursor>(cursor); 600 } 601 602 @Override 603 public boolean deliverSelfNotifications() { 604 return false; 605 } 606 607 @Override 608 public void onChange(boolean selfChange) { 609 AbstractCursor cursor = mCursor.get(); 610 if (cursor != null) { 611 cursor.onChange(false); 612 } 613 } 614 } 615 616 /** 617 * This HashMap contains a mapping from Long rowIDs to another Map 618 * that maps from String column names to new values. A NULL value means to 619 * remove an existing value, and all numeric values are in their class 620 * forms, i.e. Integer, Long, Float, etc. 621 */ 622 protected HashMap<Long, Map<String, Object>> mUpdatedRows; 623 624 /** 625 * This must be set to the index of the row ID column by any 626 * subclass that wishes to support updates. 627 */ 628 protected int mRowIdColumnIndex; 629 630 protected int mPos; 631 protected Long mCurrentRowID; 632 protected ContentResolver mContentResolver; 633 protected boolean mClosed = false; 634 private Uri mNotifyUri; 635 private ContentObserver mSelfObserver; 636 final private Object mSelfObserverLock = new Object(); 637 private boolean mSelfObserverRegistered; 638 } 639