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 /**
38  * This is an abstract cursor class that handles a lot of the common code
39  * that all cursors need to deal with and is provided for convenience reasons.
40  */
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 = CloseGuard.get();
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         mCloseGuard.close();
188     }
189 
190     /**
191      * This function is called every time the cursor is successfully scrolled
192      * to a new position, giving the subclass a chance to update any state it
193      * may have. If it returns false the move function will also do so and the
194      * cursor will scroll to the beforeFirst position.
195      *
196      * @param oldPosition the position that we're moving from
197      * @param newPosition the position that we're moving to
198      * @return true if the move is successful, false otherwise
199      */
200     @Override
onMove(int oldPosition, int newPosition)201     public boolean onMove(int oldPosition, int newPosition) {
202         return true;
203     }
204 
205 
206     @Override
copyStringToBuffer(int columnIndex, CharArrayBuffer buffer)207     public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
208         // Default implementation, uses getString
209         String result = getString(columnIndex);
210         if (result != null) {
211             char[] data = buffer.data;
212             if (data == null || data.length < result.length()) {
213                 buffer.data = result.toCharArray();
214             } else {
215                 result.getChars(0, result.length(), data, 0);
216             }
217             buffer.sizeCopied = result.length();
218         } else {
219             buffer.sizeCopied = 0;
220         }
221     }
222 
223     /* -------------------------------------------------------- */
224     /* Implementation */
AbstractCursor()225     public AbstractCursor() {
226         mPos = -1;
227         mCloseGuard.open("AbstractCursor.close");
228     }
229 
230     @Override
getPosition()231     public final int getPosition() {
232         return mPos;
233     }
234 
235     @Override
moveToPosition(int position)236     public final boolean moveToPosition(int position) {
237         // Make sure position isn't past the end of the cursor
238         final int count = getCount();
239         if (position >= count) {
240             mPos = count;
241             return false;
242         }
243 
244         // Make sure position isn't before the beginning of the cursor
245         if (position < 0) {
246             mPos = -1;
247             return false;
248         }
249 
250         // Check for no-op moves, and skip the rest of the work for them
251         if (position == mPos) {
252             return true;
253         }
254 
255         boolean result = onMove(mPos, position);
256         if (result == false) {
257             mPos = -1;
258         } else {
259             mPos = position;
260         }
261 
262         return result;
263     }
264 
265     @Override
fillWindow(int position, CursorWindow window)266     public void fillWindow(int position, CursorWindow window) {
267         DatabaseUtils.cursorFillWindow(this, position, window);
268     }
269 
270     @Override
move(int offset)271     public final boolean move(int offset) {
272         return moveToPosition(mPos + offset);
273     }
274 
275     @Override
moveToFirst()276     public final boolean moveToFirst() {
277         return moveToPosition(0);
278     }
279 
280     @Override
moveToLast()281     public final boolean moveToLast() {
282         return moveToPosition(getCount() - 1);
283     }
284 
285     @Override
moveToNext()286     public final boolean moveToNext() {
287         return moveToPosition(mPos + 1);
288     }
289 
290     @Override
moveToPrevious()291     public final boolean moveToPrevious() {
292         return moveToPosition(mPos - 1);
293     }
294 
295     @Override
isFirst()296     public final boolean isFirst() {
297         return mPos == 0 && getCount() != 0;
298     }
299 
300     @Override
isLast()301     public final boolean isLast() {
302         int cnt = getCount();
303         return mPos == (cnt - 1) && cnt != 0;
304     }
305 
306     @Override
isBeforeFirst()307     public final boolean isBeforeFirst() {
308         if (getCount() == 0) {
309             return true;
310         }
311         return mPos == -1;
312     }
313 
314     @Override
isAfterLast()315     public final boolean isAfterLast() {
316         if (getCount() == 0) {
317             return true;
318         }
319         return mPos == getCount();
320     }
321 
322     @Override
getColumnIndex(String columnName)323     public int getColumnIndex(String columnName) {
324         // Hack according to bug 903852
325         final int periodIndex = columnName.lastIndexOf('.');
326         if (periodIndex != -1) {
327             Exception e = new Exception();
328             Log.e(TAG, "requesting column name with table name -- " + columnName, e);
329             columnName = columnName.substring(periodIndex + 1);
330         }
331 
332         String columnNames[] = getColumnNames();
333         int length = columnNames.length;
334         for (int i = 0; i < length; i++) {
335             if (columnNames[i].equalsIgnoreCase(columnName)) {
336                 return i;
337             }
338         }
339 
340         if (false) {
341             if (getCount() > 0) {
342                 Log.w("AbstractCursor", "Unknown column " + columnName);
343             }
344         }
345         return -1;
346     }
347 
348     @Override
getColumnIndexOrThrow(String columnName)349     public int getColumnIndexOrThrow(String columnName) {
350         final int index = getColumnIndex(columnName);
351         if (index < 0) {
352             String availableColumns = "";
353             try {
354                 availableColumns = Arrays.toString(getColumnNames());
355             } catch (Exception e) {
356                 Log.d(TAG, "Cannot collect column names for debug purposes", e);
357             }
358             throw new IllegalArgumentException("column '" + columnName
359                     + "' does not exist. Available columns: " + availableColumns);
360         }
361         return index;
362     }
363 
364     @Override
getColumnName(int columnIndex)365     public String getColumnName(int columnIndex) {
366         return getColumnNames()[columnIndex];
367     }
368 
369     @Override
registerContentObserver(ContentObserver observer)370     public void registerContentObserver(ContentObserver observer) {
371         mContentObservable.registerObserver(observer);
372     }
373 
374     @Override
unregisterContentObserver(ContentObserver observer)375     public void unregisterContentObserver(ContentObserver observer) {
376         // cursor will unregister all observers when it close
377         if (!mClosed) {
378             mContentObservable.unregisterObserver(observer);
379         }
380     }
381 
382     @Override
registerDataSetObserver(DataSetObserver observer)383     public void registerDataSetObserver(DataSetObserver observer) {
384         mDataSetObservable.registerObserver(observer);
385     }
386 
387     @Override
unregisterDataSetObserver(DataSetObserver observer)388     public void unregisterDataSetObserver(DataSetObserver observer) {
389         mDataSetObservable.unregisterObserver(observer);
390     }
391 
392     /**
393      * Subclasses must call this method when they finish committing updates to notify all
394      * observers.
395      *
396      * @param selfChange
397      */
onChange(boolean selfChange)398     protected void onChange(boolean selfChange) {
399         synchronized (mSelfObserverLock) {
400             mContentObservable.dispatchChange(selfChange, null);
401             if (mNotifyUris != null && selfChange) {
402                 final int size = mNotifyUris.size();
403                 for (int i = 0; i < size; ++i) {
404                     final Uri notifyUri = mNotifyUris.get(i);
405                     mContentResolver.notifyChange(notifyUri, mSelfObserver);
406                 }
407             }
408         }
409     }
410 
411     /**
412      * Specifies a content URI to watch for changes.
413      *
414      * @param cr The content resolver from the caller's context.
415      * @param notifyUri The URI to watch for changes. This can be a
416      * specific row URI, or a base URI for a whole class of content.
417      */
418     @Override
setNotificationUri(ContentResolver cr, Uri notifyUri)419     public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
420         setNotificationUris(cr, Arrays.asList(notifyUri));
421     }
422 
423     @Override
setNotificationUris(@onNull ContentResolver cr, @NonNull List<Uri> notifyUris)424     public void setNotificationUris(@NonNull ContentResolver cr, @NonNull List<Uri> notifyUris) {
425         Objects.requireNonNull(cr);
426         Objects.requireNonNull(notifyUris);
427 
428         setNotificationUris(cr, notifyUris, cr.getUserId(), true);
429     }
430 
431     /**
432      * Set the notification uri but with an observer for a particular user's view. Also allows
433      * disabling the use of a self observer, which is sensible if either
434      * a) the cursor's owner calls {@link #onChange(boolean)} whenever the content changes, or
435      * b) the cursor is known not to have any content observers.
436      * @hide
437      */
setNotificationUris(ContentResolver cr, List<Uri> notifyUris, int userHandle, boolean registerSelfObserver)438     public void setNotificationUris(ContentResolver cr, List<Uri> notifyUris, int userHandle,
439             boolean registerSelfObserver) {
440         synchronized (mSelfObserverLock) {
441             mNotifyUris = notifyUris;
442             mNotifyUri = mNotifyUris.get(0);
443             mContentResolver = cr;
444             if (mSelfObserver != null) {
445                 mContentResolver.unregisterContentObserver(mSelfObserver);
446                 mSelfObserverRegistered = false;
447             }
448             if (registerSelfObserver) {
449                 mSelfObserver = new SelfContentObserver(this);
450                 final int size = mNotifyUris.size();
451                 for (int i = 0; i < size; ++i) {
452                     final Uri notifyUri = mNotifyUris.get(i);
453                     mContentResolver.registerContentObserver(
454                             notifyUri, true, mSelfObserver, userHandle);
455                 }
456                 mSelfObserverRegistered = true;
457             }
458         }
459     }
460 
461     @Override
getNotificationUri()462     public Uri getNotificationUri() {
463         synchronized (mSelfObserverLock) {
464             return mNotifyUri;
465         }
466     }
467 
468     @Override
getNotificationUris()469     public List<Uri> getNotificationUris() {
470         synchronized (mSelfObserverLock) {
471             return mNotifyUris;
472         }
473     }
474 
475     @Override
getWantsAllOnMoveCalls()476     public boolean getWantsAllOnMoveCalls() {
477         return false;
478     }
479 
480     @Override
setExtras(Bundle extras)481     public void setExtras(Bundle extras) {
482         mExtras = (extras == null) ? Bundle.EMPTY : extras;
483     }
484 
485     @Override
getExtras()486     public Bundle getExtras() {
487         return mExtras;
488     }
489 
490     @Override
respond(Bundle extras)491     public Bundle respond(Bundle extras) {
492         return Bundle.EMPTY;
493     }
494 
495     /**
496      * @deprecated Always returns false since Cursors do not support updating rows
497      */
498     @Deprecated
isFieldUpdated(int columnIndex)499     protected boolean isFieldUpdated(int columnIndex) {
500         return false;
501     }
502 
503     /**
504      * @deprecated Always returns null since Cursors do not support updating rows
505      */
506     @Deprecated
getUpdatedField(int columnIndex)507     protected Object getUpdatedField(int columnIndex) {
508         return null;
509     }
510 
511     /**
512      * This function throws CursorIndexOutOfBoundsException if
513      * the cursor position is out of bounds. Subclass implementations of
514      * the get functions should call this before attempting
515      * to retrieve data.
516      *
517      * @throws CursorIndexOutOfBoundsException
518      */
checkPosition()519     protected void checkPosition() {
520         if (-1 == mPos || getCount() == mPos) {
521             throw new CursorIndexOutOfBoundsException(mPos, getCount());
522         }
523     }
524 
525     @Override
finalize()526     protected void finalize() {
527         if (mSelfObserver != null && mSelfObserverRegistered == true) {
528             mContentResolver.unregisterContentObserver(mSelfObserver);
529         }
530         try {
531             if (mCloseGuard != null) mCloseGuard.warnIfOpen();
532             if (!mClosed) close();
533         } catch(Exception e) { }
534     }
535 
536     /**
537      * Cursors use this class to track changes others make to their URI.
538      */
539     protected static class SelfContentObserver extends ContentObserver {
540         WeakReference<AbstractCursor> mCursor;
541 
SelfContentObserver(AbstractCursor cursor)542         public SelfContentObserver(AbstractCursor cursor) {
543             super(null);
544             mCursor = new WeakReference<AbstractCursor>(cursor);
545         }
546 
547         @Override
deliverSelfNotifications()548         public boolean deliverSelfNotifications() {
549             return false;
550         }
551 
552         @Override
onChange(boolean selfChange)553         public void onChange(boolean selfChange) {
554             AbstractCursor cursor = mCursor.get();
555             if (cursor != null) {
556                 cursor.onChange(false);
557             }
558         }
559     }
560 }
561