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