blob: b6487bd2f7f68d3407141cf24fe71698c3efb311 [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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023
24import java.lang.ref.WeakReference;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import 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 DataSetObservable mDataSetObservable = new DataSetObservable();
37 ContentObservable mContentObservable = new ContentObservable();
38
39 /* -------------------------------------------------------- */
40 /* These need to be implemented by subclasses */
41 abstract public int getCount();
42
43 abstract public String[] getColumnNames();
44
45 abstract public String getString(int column);
46 abstract public short getShort(int column);
47 abstract public int getInt(int column);
48 abstract public long getLong(int column);
49 abstract public float getFloat(int column);
50 abstract public double getDouble(int column);
51 abstract public boolean isNull(int column);
52
Vasu Nori8b0dd7d2010-05-18 11:54:31 -070053 public int getType(int column) {
54 throw new UnsupportedOperationException();
55 }
56
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057 // TODO implement getBlob in all cursor types
58 public byte[] getBlob(int column) {
59 throw new UnsupportedOperationException("getBlob is not supported");
60 }
61 /* -------------------------------------------------------- */
62 /* Methods that may optionally be implemented by subclasses */
63
64 /**
65 * returns a pre-filled window, return NULL if no such window
66 */
67 public CursorWindow getWindow() {
68 return null;
69 }
70
71 public int getColumnCount() {
72 return getColumnNames().length;
73 }
74
75 public void deactivate() {
76 deactivateInternal();
77 }
78
79 /**
80 * @hide
81 */
82 public void deactivateInternal() {
83 if (mSelfObserver != null) {
84 mContentResolver.unregisterContentObserver(mSelfObserver);
85 mSelfObserverRegistered = false;
86 }
87 mDataSetObservable.notifyInvalidated();
88 }
Vasu Nori20f549f2010-04-15 11:25:51 -070089
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090 public boolean requery() {
91 if (mSelfObserver != null && mSelfObserverRegistered == false) {
92 mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
93 mSelfObserverRegistered = true;
94 }
95 mDataSetObservable.notifyChanged();
96 return true;
97 }
98
99 public boolean isClosed() {
100 return mClosed;
101 }
102
103 public void close() {
104 mClosed = true;
105 mContentObservable.unregisterAll();
106 deactivateInternal();
107 }
108
109 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 * This function is called every time the cursor is successfully scrolled
111 * to a new position, giving the subclass a chance to update any state it
112 * may have. If it returns false the move function will also do so and the
113 * cursor will scroll to the beforeFirst position.
114 *
115 * @param oldPosition the position that we're moving from
116 * @param newPosition the position that we're moving to
117 * @return true if the move is successful, false otherwise
118 */
119 public boolean onMove(int oldPosition, int newPosition) {
120 return true;
121 }
122
123
124 public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
125 // Default implementation, uses getString
126 String result = getString(columnIndex);
127 if (result != null) {
128 char[] data = buffer.data;
129 if (data == null || data.length < result.length()) {
130 buffer.data = result.toCharArray();
131 } else {
132 result.getChars(0, result.length(), data, 0);
133 }
134 buffer.sizeCopied = result.length();
Dmitri Plotnikov9480efe2011-02-16 12:03:10 -0800135 } else {
136 buffer.sizeCopied = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800137 }
138 }
139
140 /* -------------------------------------------------------- */
141 /* Implementation */
142 public AbstractCursor() {
143 mPos = -1;
144 mRowIdColumnIndex = -1;
145 mCurrentRowID = null;
146 mUpdatedRows = new HashMap<Long, Map<String, Object>>();
147 }
148
149 public final int getPosition() {
150 return mPos;
151 }
152
153 public final boolean moveToPosition(int position) {
154 // Make sure position isn't past the end of the cursor
155 final int count = getCount();
156 if (position >= count) {
157 mPos = count;
158 return false;
159 }
160
161 // Make sure position isn't before the beginning of the cursor
162 if (position < 0) {
163 mPos = -1;
164 return false;
165 }
166
167 // Check for no-op moves, and skip the rest of the work for them
168 if (position == mPos) {
169 return true;
170 }
171
172 boolean result = onMove(mPos, position);
173 if (result == false) {
174 mPos = -1;
175 } else {
176 mPos = position;
177 if (mRowIdColumnIndex != -1) {
178 mCurrentRowID = Long.valueOf(getLong(mRowIdColumnIndex));
179 }
180 }
181
182 return result;
183 }
184
185 /**
186 * Copy data from cursor to CursorWindow
187 * @param position start position of data
188 * @param window
189 */
190 public void fillWindow(int position, CursorWindow window) {
Tatsuo Nagamatsu6d3acde2010-06-07 02:07:51 +0900191 if (position < 0 || position >= getCount()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800192 return;
193 }
194 window.acquireReference();
195 try {
196 int oldpos = mPos;
197 mPos = position - 1;
198 window.clear();
199 window.setStartPosition(position);
200 int columnNum = getColumnCount();
201 window.setNumColumns(columnNum);
202 while (moveToNext() && window.allocRow()) {
203 for (int i = 0; i < columnNum; i++) {
204 String field = getString(i);
205 if (field != null) {
206 if (!window.putString(field, mPos, i)) {
207 window.freeLastRow();
208 break;
209 }
210 } else {
211 if (!window.putNull(mPos, i)) {
212 window.freeLastRow();
213 break;
214 }
215 }
216 }
217 }
218
219 mPos = oldpos;
220 } catch (IllegalStateException e){
221 // simply ignore it
222 } finally {
223 window.releaseReference();
224 }
225 }
226
227 public final boolean move(int offset) {
228 return moveToPosition(mPos + offset);
229 }
230
231 public final boolean moveToFirst() {
232 return moveToPosition(0);
233 }
234
235 public final boolean moveToLast() {
236 return moveToPosition(getCount() - 1);
237 }
238
239 public final boolean moveToNext() {
240 return moveToPosition(mPos + 1);
241 }
242
243 public final boolean moveToPrevious() {
244 return moveToPosition(mPos - 1);
245 }
246
247 public final boolean isFirst() {
248 return mPos == 0 && getCount() != 0;
249 }
250
251 public final boolean isLast() {
252 int cnt = getCount();
253 return mPos == (cnt - 1) && cnt != 0;
254 }
255
256 public final boolean isBeforeFirst() {
257 if (getCount() == 0) {
258 return true;
259 }
260 return mPos == -1;
261 }
262
263 public final boolean isAfterLast() {
264 if (getCount() == 0) {
265 return true;
266 }
267 return mPos == getCount();
268 }
269
270 public int getColumnIndex(String columnName) {
271 // Hack according to bug 903852
272 final int periodIndex = columnName.lastIndexOf('.');
273 if (periodIndex != -1) {
274 Exception e = new Exception();
275 Log.e(TAG, "requesting column name with table name -- " + columnName, e);
276 columnName = columnName.substring(periodIndex + 1);
277 }
278
279 String columnNames[] = getColumnNames();
280 int length = columnNames.length;
281 for (int i = 0; i < length; i++) {
282 if (columnNames[i].equalsIgnoreCase(columnName)) {
283 return i;
284 }
285 }
286
Joe Onorato43a17652011-04-06 19:22:23 -0700287 if (false) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800288 if (getCount() > 0) {
289 Log.w("AbstractCursor", "Unknown column " + columnName);
290 }
291 }
292 return -1;
293 }
294
295 public int getColumnIndexOrThrow(String columnName) {
296 final int index = getColumnIndex(columnName);
297 if (index < 0) {
298 throw new IllegalArgumentException("column '" + columnName + "' does not exist");
299 }
300 return index;
301 }
302
303 public String getColumnName(int columnIndex) {
304 return getColumnNames()[columnIndex];
305 }
306
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800307 public void registerContentObserver(ContentObserver observer) {
308 mContentObservable.registerObserver(observer);
309 }
310
311 public void unregisterContentObserver(ContentObserver observer) {
312 // cursor will unregister all observers when it close
313 if (!mClosed) {
314 mContentObservable.unregisterObserver(observer);
315 }
316 }
317
318 /**
Dianne Hackborn935ae462009-04-13 16:11:55 -0700319 * This is hidden until the data set change model has been re-evaluated.
320 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800321 */
322 protected void notifyDataSetChange() {
323 mDataSetObservable.notifyChanged();
324 }
325
326 /**
Dianne Hackborn935ae462009-04-13 16:11:55 -0700327 * This is hidden until the data set change model has been re-evaluated.
328 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800329 */
330 protected DataSetObservable getDataSetObservable() {
331 return mDataSetObservable;
332
333 }
Jeff Hamiltonf1a4a0a2010-06-30 15:10:24 -0500334
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335 public void registerDataSetObserver(DataSetObserver observer) {
336 mDataSetObservable.registerObserver(observer);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800337 }
338
339 public void unregisterDataSetObserver(DataSetObserver observer) {
340 mDataSetObservable.unregisterObserver(observer);
341 }
342
343 /**
344 * Subclasses must call this method when they finish committing updates to notify all
345 * observers.
346 *
347 * @param selfChange
348 */
349 protected void onChange(boolean selfChange) {
350 synchronized (mSelfObserverLock) {
351 mContentObservable.dispatchChange(selfChange);
352 if (mNotifyUri != null && selfChange) {
353 mContentResolver.notifyChange(mNotifyUri, mSelfObserver);
354 }
355 }
356 }
357
358 /**
359 * Specifies a content URI to watch for changes.
360 *
361 * @param cr The content resolver from the caller's context.
362 * @param notifyUri The URI to watch for changes. This can be a
363 * specific row URI, or a base URI for a whole class of content.
364 */
365 public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
366 synchronized (mSelfObserverLock) {
367 mNotifyUri = notifyUri;
368 mContentResolver = cr;
369 if (mSelfObserver != null) {
370 mContentResolver.unregisterContentObserver(mSelfObserver);
371 }
372 mSelfObserver = new SelfContentObserver(this);
373 mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
374 mSelfObserverRegistered = true;
375 }
376 }
377
Makoto Onuki90bf7c72010-09-15 10:13:07 -0700378 public Uri getNotificationUri() {
379 return mNotifyUri;
380 }
381
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382 public boolean getWantsAllOnMoveCalls() {
383 return false;
384 }
385
386 public Bundle getExtras() {
387 return Bundle.EMPTY;
388 }
389
390 public Bundle respond(Bundle extras) {
391 return Bundle.EMPTY;
392 }
393
394 /**
Jeff Hamiltonf1a4a0a2010-06-30 15:10:24 -0500395 * @deprecated Always returns false since Cursors do not support updating rows
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800396 */
Jeff Hamiltonf1a4a0a2010-06-30 15:10:24 -0500397 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800398 protected boolean isFieldUpdated(int columnIndex) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800399 return false;
400 }
401
402 /**
Jeff Hamiltonf1a4a0a2010-06-30 15:10:24 -0500403 * @deprecated Always returns null since Cursors do not support updating rows
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800404 */
Jeff Hamiltonf1a4a0a2010-06-30 15:10:24 -0500405 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800406 protected Object getUpdatedField(int columnIndex) {
Jeff Hamiltonf1a4a0a2010-06-30 15:10:24 -0500407 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800408 }
409
410 /**
411 * This function throws CursorIndexOutOfBoundsException if
412 * the cursor position is out of bounds. Subclass implementations of
413 * the get functions should call this before attempting
414 * to retrieve data.
415 *
416 * @throws CursorIndexOutOfBoundsException
417 */
418 protected void checkPosition() {
419 if (-1 == mPos || getCount() == mPos) {
420 throw new CursorIndexOutOfBoundsException(mPos, getCount());
421 }
422 }
423
424 @Override
425 protected void finalize() {
426 if (mSelfObserver != null && mSelfObserverRegistered == true) {
427 mContentResolver.unregisterContentObserver(mSelfObserver);
428 }
429 }
430
431 /**
432 * Cursors use this class to track changes others make to their URI.
433 */
434 protected static class SelfContentObserver extends ContentObserver {
435 WeakReference<AbstractCursor> mCursor;
436
437 public SelfContentObserver(AbstractCursor cursor) {
438 super(null);
439 mCursor = new WeakReference<AbstractCursor>(cursor);
440 }
441
442 @Override
443 public boolean deliverSelfNotifications() {
444 return false;
445 }
446
447 @Override
448 public void onChange(boolean selfChange) {
449 AbstractCursor cursor = mCursor.get();
450 if (cursor != null) {
451 cursor.onChange(false);
452 }
453 }
454 }
455
456 /**
Jeff Hamiltonf1a4a0a2010-06-30 15:10:24 -0500457 * @deprecated This is never updated by this class and should not be used
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800458 */
Jeff Hamiltonf1a4a0a2010-06-30 15:10:24 -0500459 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800460 protected HashMap<Long, Map<String, Object>> mUpdatedRows;
461
462 /**
463 * This must be set to the index of the row ID column by any
464 * subclass that wishes to support updates.
465 */
466 protected int mRowIdColumnIndex;
467
468 protected int mPos;
Jeff Hamiltonf1a4a0a2010-06-30 15:10:24 -0500469 /**
Vasu Noricc08bb82010-07-01 18:55:30 -0700470 * If {@link #mRowIdColumnIndex} is not -1 this contains contains the value of
471 * the column at {@link #mRowIdColumnIndex} for the current row this cursor is
Jeff Hamiltonf1a4a0a2010-06-30 15:10:24 -0500472 * pointing at.
473 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800474 protected Long mCurrentRowID;
475 protected ContentResolver mContentResolver;
476 protected boolean mClosed = false;
477 private Uri mNotifyUri;
478 private ContentObserver mSelfObserver;
479 final private Object mSelfObserverLock = new Object();
480 private boolean mSelfObserverRegistered;
481}