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