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