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