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