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