blob: b5b89dd17a5d285f5db83c4fbe89fb26d8d9c1cf [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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;
Makoto Onuki25897162010-04-29 11:26:20 -070021import android.os.Bundle;
Christopher Tateafccaa82012-10-03 17:41:51 -070022import android.os.UserHandle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024
25import java.lang.ref.WeakReference;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import 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
Jeff Brownfb5a4962012-03-14 17:38:59 -070037 /**
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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042
Jeff Brownfb5a4962012-03-14 17:38:59 -070043 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.
Jeff Brown7873d5b2012-05-09 14:32:14 -070048 *
49 * @deprecated This field should not be used.
Jeff Brownfb5a4962012-03-14 17:38:59 -070050 */
Jeff Brown7873d5b2012-05-09 14:32:14 -070051 @Deprecated
Jeff Brownfb5a4962012-03-14 17:38:59 -070052 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.
Jeff Brown7873d5b2012-05-09 14:32:14 -070058 *
59 * @deprecated This field should not be used.
Jeff Brownfb5a4962012-03-14 17:38:59 -070060 */
Jeff Brown7873d5b2012-05-09 14:32:14 -070061 @Deprecated
Jeff Brownfb5a4962012-03-14 17:38:59 -070062 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
Jeff Brown7873d5b2012-05-09 14:32:14 -070072 private final DataSetObservable mDataSetObservable = new DataSetObservable();
73 private final ContentObservable mContentObservable = new ContentObservable();
Jeff Brownfb5a4962012-03-14 17:38:59 -070074
75 private Bundle mExtras = Bundle.EMPTY;
Makoto Onuki630f6b12011-08-30 18:02:37 -070076
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077 /* -------------------------------------------------------- */
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
Vasu Nori8b0dd7d2010-05-18 11:54:31 -070091 public int getType(int column) {
Jeff Brown80e7b802011-10-12 17:42:41 -070092 // 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;
Vasu Nori8b0dd7d2010-05-18 11:54:31 -070096 }
97
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080098 // 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 /**
Jeff Brown7ce74522011-10-07 13:29:37 -0700106 * 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.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 */
111 public CursorWindow getWindow() {
112 return null;
113 }
114
115 public int getColumnCount() {
116 return getColumnNames().length;
117 }
Makoto Onuki630f6b12011-08-30 18:02:37 -0700118
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119 public void deactivate() {
Jeff Brownd2183652011-10-09 12:39:53 -0700120 onDeactivateOrClose();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800121 }
Makoto Onuki630f6b12011-08-30 18:02:37 -0700122
Jeff Brownd2183652011-10-09 12:39:53 -0700123 /** @hide */
124 protected void onDeactivateOrClose() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 if (mSelfObserver != null) {
126 mContentResolver.unregisterContentObserver(mSelfObserver);
127 mSelfObserverRegistered = false;
128 }
129 mDataSetObservable.notifyInvalidated();
130 }
Vasu Nori20f549f2010-04-15 11:25:51 -0700131
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132 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 }
Makoto Onuki630f6b12011-08-30 18:02:37 -0700144
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145 public void close() {
146 mClosed = true;
147 mContentObservable.unregisterAll();
Jeff Brownd2183652011-10-09 12:39:53 -0700148 onDeactivateOrClose();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800149 }
150
151 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152 * 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
Makoto Onuki630f6b12011-08-30 18:02:37 -0700165
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800166 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();
Dmitri Plotnikov9480efe2011-02-16 12:03:10 -0800177 } else {
178 buffer.sizeCopied = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800179 }
180 }
Makoto Onuki630f6b12011-08-30 18:02:37 -0700181
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800182 /* -------------------------------------------------------- */
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 }
Makoto Onuki630f6b12011-08-30 18:02:37 -0700226
Jeff Brown80e7b802011-10-12 17:42:41 -0700227 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 public void fillWindow(int position, CursorWindow window) {
Jeff Brown80e7b802011-10-12 17:42:41 -0700229 DatabaseUtils.cursorFillWindow(this, position, window);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 }
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
Joe Onorato43a17652011-04-06 19:22:23 -0700292 if (false) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800293 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800312 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 }
Makoto Onuki630f6b12011-08-30 18:02:37 -0700322
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800323 public void registerDataSetObserver(DataSetObserver observer) {
324 mDataSetObservable.registerObserver(observer);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800325 }
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) {
Jeff Brown655e66b2012-01-23 15:51:41 -0800339 mContentObservable.dispatchChange(selfChange, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800340 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) {
Christopher Tateafccaa82012-10-03 17:41:51 -0700354 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) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800359 synchronized (mSelfObserverLock) {
360 mNotifyUri = notifyUri;
361 mContentResolver = cr;
362 if (mSelfObserver != null) {
363 mContentResolver.unregisterContentObserver(mSelfObserver);
364 }
365 mSelfObserver = new SelfContentObserver(this);
Christopher Tateafccaa82012-10-03 17:41:51 -0700366 mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver, userHandle);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800367 mSelfObserverRegistered = true;
368 }
369 }
370
Makoto Onuki90bf7c72010-09-15 10:13:07 -0700371 public Uri getNotificationUri() {
Dianne Hackbornc87c92e2013-05-14 13:44:29 -0700372 synchronized (mSelfObserverLock) {
373 return mNotifyUri;
374 }
Makoto Onuki90bf7c72010-09-15 10:13:07 -0700375 }
376
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 public boolean getWantsAllOnMoveCalls() {
378 return false;
379 }
380
Makoto Onuki630f6b12011-08-30 18:02:37 -0700381 /**
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800392 public Bundle getExtras() {
Makoto Onuki630f6b12011-08-30 18:02:37 -0700393 return mExtras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800394 }
395
396 public Bundle respond(Bundle extras) {
397 return Bundle.EMPTY;
398 }
399
400 /**
Jeff Hamiltonf1a4a0a2010-06-30 15:10:24 -0500401 * @deprecated Always returns false since Cursors do not support updating rows
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800402 */
Jeff Hamiltonf1a4a0a2010-06-30 15:10:24 -0500403 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800404 protected boolean isFieldUpdated(int columnIndex) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800405 return false;
406 }
407
408 /**
Jeff Hamiltonf1a4a0a2010-06-30 15:10:24 -0500409 * @deprecated Always returns null since Cursors do not support updating rows
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800410 */
Jeff Hamiltonf1a4a0a2010-06-30 15:10:24 -0500411 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800412 protected Object getUpdatedField(int columnIndex) {
Jeff Hamiltonf1a4a0a2010-06-30 15:10:24 -0500413 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800414 }
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 }
Catherine Liu3f824c02012-06-11 16:38:17 -0500435 try {
436 if (!mClosed) close();
437 } catch(Exception e) { }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800438 }
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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800464}