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