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