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 /** 38 * This is an abstract cursor class that handles a lot of the common code 39 * that all cursors need to deal with and is provided for convenience reasons. 40 */ 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 = CloseGuard.get(); 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 mCloseGuard.close(); 188 } 189 190 /** 191 * This function is called every time the cursor is successfully scrolled 192 * to a new position, giving the subclass a chance to update any state it 193 * may have. If it returns false the move function will also do so and the 194 * cursor will scroll to the beforeFirst position. 195 * 196 * @param oldPosition the position that we're moving from 197 * @param newPosition the position that we're moving to 198 * @return true if the move is successful, false otherwise 199 */ 200 @Override onMove(int oldPosition, int newPosition)201 public boolean onMove(int oldPosition, int newPosition) { 202 return true; 203 } 204 205 206 @Override copyStringToBuffer(int columnIndex, CharArrayBuffer buffer)207 public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { 208 // Default implementation, uses getString 209 String result = getString(columnIndex); 210 if (result != null) { 211 char[] data = buffer.data; 212 if (data == null || data.length < result.length()) { 213 buffer.data = result.toCharArray(); 214 } else { 215 result.getChars(0, result.length(), data, 0); 216 } 217 buffer.sizeCopied = result.length(); 218 } else { 219 buffer.sizeCopied = 0; 220 } 221 } 222 223 /* -------------------------------------------------------- */ 224 /* Implementation */ AbstractCursor()225 public AbstractCursor() { 226 mPos = -1; 227 mCloseGuard.open("AbstractCursor.close"); 228 } 229 230 @Override getPosition()231 public final int getPosition() { 232 return mPos; 233 } 234 235 @Override moveToPosition(int position)236 public final boolean moveToPosition(int position) { 237 // Make sure position isn't past the end of the cursor 238 final int count = getCount(); 239 if (position >= count) { 240 mPos = count; 241 return false; 242 } 243 244 // Make sure position isn't before the beginning of the cursor 245 if (position < 0) { 246 mPos = -1; 247 return false; 248 } 249 250 // Check for no-op moves, and skip the rest of the work for them 251 if (position == mPos) { 252 return true; 253 } 254 255 boolean result = onMove(mPos, position); 256 if (result == false) { 257 mPos = -1; 258 } else { 259 mPos = position; 260 } 261 262 return result; 263 } 264 265 @Override fillWindow(int position, CursorWindow window)266 public void fillWindow(int position, CursorWindow window) { 267 DatabaseUtils.cursorFillWindow(this, position, window); 268 } 269 270 @Override move(int offset)271 public final boolean move(int offset) { 272 return moveToPosition(mPos + offset); 273 } 274 275 @Override moveToFirst()276 public final boolean moveToFirst() { 277 return moveToPosition(0); 278 } 279 280 @Override moveToLast()281 public final boolean moveToLast() { 282 return moveToPosition(getCount() - 1); 283 } 284 285 @Override moveToNext()286 public final boolean moveToNext() { 287 return moveToPosition(mPos + 1); 288 } 289 290 @Override moveToPrevious()291 public final boolean moveToPrevious() { 292 return moveToPosition(mPos - 1); 293 } 294 295 @Override isFirst()296 public final boolean isFirst() { 297 return mPos == 0 && getCount() != 0; 298 } 299 300 @Override isLast()301 public final boolean isLast() { 302 int cnt = getCount(); 303 return mPos == (cnt - 1) && cnt != 0; 304 } 305 306 @Override isBeforeFirst()307 public final boolean isBeforeFirst() { 308 if (getCount() == 0) { 309 return true; 310 } 311 return mPos == -1; 312 } 313 314 @Override isAfterLast()315 public final boolean isAfterLast() { 316 if (getCount() == 0) { 317 return true; 318 } 319 return mPos == getCount(); 320 } 321 322 @Override getColumnIndex(String columnName)323 public int getColumnIndex(String columnName) { 324 // Hack according to bug 903852 325 final int periodIndex = columnName.lastIndexOf('.'); 326 if (periodIndex != -1) { 327 Exception e = new Exception(); 328 Log.e(TAG, "requesting column name with table name -- " + columnName, e); 329 columnName = columnName.substring(periodIndex + 1); 330 } 331 332 String columnNames[] = getColumnNames(); 333 int length = columnNames.length; 334 for (int i = 0; i < length; i++) { 335 if (columnNames[i].equalsIgnoreCase(columnName)) { 336 return i; 337 } 338 } 339 340 if (false) { 341 if (getCount() > 0) { 342 Log.w("AbstractCursor", "Unknown column " + columnName); 343 } 344 } 345 return -1; 346 } 347 348 @Override getColumnIndexOrThrow(String columnName)349 public int getColumnIndexOrThrow(String columnName) { 350 final int index = getColumnIndex(columnName); 351 if (index < 0) { 352 String availableColumns = ""; 353 try { 354 availableColumns = Arrays.toString(getColumnNames()); 355 } catch (Exception e) { 356 Log.d(TAG, "Cannot collect column names for debug purposes", e); 357 } 358 throw new IllegalArgumentException("column '" + columnName 359 + "' does not exist. Available columns: " + availableColumns); 360 } 361 return index; 362 } 363 364 @Override getColumnName(int columnIndex)365 public String getColumnName(int columnIndex) { 366 return getColumnNames()[columnIndex]; 367 } 368 369 @Override registerContentObserver(ContentObserver observer)370 public void registerContentObserver(ContentObserver observer) { 371 mContentObservable.registerObserver(observer); 372 } 373 374 @Override unregisterContentObserver(ContentObserver observer)375 public void unregisterContentObserver(ContentObserver observer) { 376 // cursor will unregister all observers when it close 377 if (!mClosed) { 378 mContentObservable.unregisterObserver(observer); 379 } 380 } 381 382 @Override registerDataSetObserver(DataSetObserver observer)383 public void registerDataSetObserver(DataSetObserver observer) { 384 mDataSetObservable.registerObserver(observer); 385 } 386 387 @Override unregisterDataSetObserver(DataSetObserver observer)388 public void unregisterDataSetObserver(DataSetObserver observer) { 389 mDataSetObservable.unregisterObserver(observer); 390 } 391 392 /** 393 * Subclasses must call this method when they finish committing updates to notify all 394 * observers. 395 * 396 * @param selfChange 397 */ onChange(boolean selfChange)398 protected void onChange(boolean selfChange) { 399 synchronized (mSelfObserverLock) { 400 mContentObservable.dispatchChange(selfChange, null); 401 if (mNotifyUris != null && selfChange) { 402 final int size = mNotifyUris.size(); 403 for (int i = 0; i < size; ++i) { 404 final Uri notifyUri = mNotifyUris.get(i); 405 mContentResolver.notifyChange(notifyUri, mSelfObserver); 406 } 407 } 408 } 409 } 410 411 /** 412 * Specifies a content URI to watch for changes. 413 * 414 * @param cr The content resolver from the caller's context. 415 * @param notifyUri The URI to watch for changes. This can be a 416 * specific row URI, or a base URI for a whole class of content. 417 */ 418 @Override setNotificationUri(ContentResolver cr, Uri notifyUri)419 public void setNotificationUri(ContentResolver cr, Uri notifyUri) { 420 setNotificationUris(cr, Arrays.asList(notifyUri)); 421 } 422 423 @Override setNotificationUris(@onNull ContentResolver cr, @NonNull List<Uri> notifyUris)424 public void setNotificationUris(@NonNull ContentResolver cr, @NonNull List<Uri> notifyUris) { 425 Objects.requireNonNull(cr); 426 Objects.requireNonNull(notifyUris); 427 428 setNotificationUris(cr, notifyUris, cr.getUserId(), true); 429 } 430 431 /** 432 * Set the notification uri but with an observer for a particular user's view. Also allows 433 * disabling the use of a self observer, which is sensible if either 434 * a) the cursor's owner calls {@link #onChange(boolean)} whenever the content changes, or 435 * b) the cursor is known not to have any content observers. 436 * @hide 437 */ setNotificationUris(ContentResolver cr, List<Uri> notifyUris, int userHandle, boolean registerSelfObserver)438 public void setNotificationUris(ContentResolver cr, List<Uri> notifyUris, int userHandle, 439 boolean registerSelfObserver) { 440 synchronized (mSelfObserverLock) { 441 mNotifyUris = notifyUris; 442 mNotifyUri = mNotifyUris.get(0); 443 mContentResolver = cr; 444 if (mSelfObserver != null) { 445 mContentResolver.unregisterContentObserver(mSelfObserver); 446 mSelfObserverRegistered = false; 447 } 448 if (registerSelfObserver) { 449 mSelfObserver = new SelfContentObserver(this); 450 final int size = mNotifyUris.size(); 451 for (int i = 0; i < size; ++i) { 452 final Uri notifyUri = mNotifyUris.get(i); 453 mContentResolver.registerContentObserver( 454 notifyUri, true, mSelfObserver, userHandle); 455 } 456 mSelfObserverRegistered = true; 457 } 458 } 459 } 460 461 @Override getNotificationUri()462 public Uri getNotificationUri() { 463 synchronized (mSelfObserverLock) { 464 return mNotifyUri; 465 } 466 } 467 468 @Override getNotificationUris()469 public List<Uri> getNotificationUris() { 470 synchronized (mSelfObserverLock) { 471 return mNotifyUris; 472 } 473 } 474 475 @Override getWantsAllOnMoveCalls()476 public boolean getWantsAllOnMoveCalls() { 477 return false; 478 } 479 480 @Override setExtras(Bundle extras)481 public void setExtras(Bundle extras) { 482 mExtras = (extras == null) ? Bundle.EMPTY : extras; 483 } 484 485 @Override getExtras()486 public Bundle getExtras() { 487 return mExtras; 488 } 489 490 @Override respond(Bundle extras)491 public Bundle respond(Bundle extras) { 492 return Bundle.EMPTY; 493 } 494 495 /** 496 * @deprecated Always returns false since Cursors do not support updating rows 497 */ 498 @Deprecated isFieldUpdated(int columnIndex)499 protected boolean isFieldUpdated(int columnIndex) { 500 return false; 501 } 502 503 /** 504 * @deprecated Always returns null since Cursors do not support updating rows 505 */ 506 @Deprecated getUpdatedField(int columnIndex)507 protected Object getUpdatedField(int columnIndex) { 508 return null; 509 } 510 511 /** 512 * This function throws CursorIndexOutOfBoundsException if 513 * the cursor position is out of bounds. Subclass implementations of 514 * the get functions should call this before attempting 515 * to retrieve data. 516 * 517 * @throws CursorIndexOutOfBoundsException 518 */ checkPosition()519 protected void checkPosition() { 520 if (-1 == mPos || getCount() == mPos) { 521 throw new CursorIndexOutOfBoundsException(mPos, getCount()); 522 } 523 } 524 525 @Override finalize()526 protected void finalize() { 527 if (mSelfObserver != null && mSelfObserverRegistered == true) { 528 mContentResolver.unregisterContentObserver(mSelfObserver); 529 } 530 try { 531 if (mCloseGuard != null) mCloseGuard.warnIfOpen(); 532 if (!mClosed) close(); 533 } catch(Exception e) { } 534 } 535 536 /** 537 * Cursors use this class to track changes others make to their URI. 538 */ 539 protected static class SelfContentObserver extends ContentObserver { 540 WeakReference<AbstractCursor> mCursor; 541 SelfContentObserver(AbstractCursor cursor)542 public SelfContentObserver(AbstractCursor cursor) { 543 super(null); 544 mCursor = new WeakReference<AbstractCursor>(cursor); 545 } 546 547 @Override deliverSelfNotifications()548 public boolean deliverSelfNotifications() { 549 return false; 550 } 551 552 @Override onChange(boolean selfChange)553 public void onChange(boolean selfChange) { 554 AbstractCursor cursor = mCursor.get(); 555 if (cursor != null) { 556 cursor.onChange(false); 557 } 558 } 559 } 560 } 561