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