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