Home | History | Annotate | Download | only in database
      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
     84     abstract public int getCount();
     85 
     86     @Override
     87     abstract public String[] getColumnNames();
     88 
     89     @Override
     90     abstract public String getString(int column);
     91     @Override
     92     abstract public short getShort(int column);
     93     @Override
     94     abstract public int getInt(int column);
     95     @Override
     96     abstract public long getLong(int column);
     97     @Override
     98     abstract public float getFloat(int column);
     99     @Override
    100     abstract public double getDouble(int column);
    101     @Override
    102     abstract public boolean isNull(int column);
    103 
    104     @Override
    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
    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
    127     public CursorWindow getWindow() {
    128         return null;
    129     }
    130 
    131     @Override
    132     public int getColumnCount() {
    133         return getColumnNames().length;
    134     }
    135 
    136     @Override
    137     public void deactivate() {
    138         onDeactivateOrClose();
    139     }
    140 
    141     /** @hide */
    142     protected void onDeactivateOrClose() {
    143         if (mSelfObserver != null) {
    144             mContentResolver.unregisterContentObserver(mSelfObserver);
    145             mSelfObserverRegistered = false;
    146         }
    147         mDataSetObservable.notifyInvalidated();
    148     }
    149 
    150     @Override
    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
    161     public boolean isClosed() {
    162         return mClosed;
    163     }
    164 
    165     @Override
    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
    183     public boolean onMove(int oldPosition, int newPosition) {
    184         return true;
    185     }
    186 
    187 
    188     @Override
    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 */
    207     public AbstractCursor() {
    208         mPos = -1;
    209     }
    210 
    211     @Override
    212     public final int getPosition() {
    213         return mPos;
    214     }
    215 
    216     @Override
    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
    247     public void fillWindow(int position, CursorWindow window) {
    248         DatabaseUtils.cursorFillWindow(this, position, window);
    249     }
    250 
    251     @Override
    252     public final boolean move(int offset) {
    253         return moveToPosition(mPos + offset);
    254     }
    255 
    256     @Override
    257     public final boolean moveToFirst() {
    258         return moveToPosition(0);
    259     }
    260 
    261     @Override
    262     public final boolean moveToLast() {
    263         return moveToPosition(getCount() - 1);
    264     }
    265 
    266     @Override
    267     public final boolean moveToNext() {
    268         return moveToPosition(mPos + 1);
    269     }
    270 
    271     @Override
    272     public final boolean moveToPrevious() {
    273         return moveToPosition(mPos - 1);
    274     }
    275 
    276     @Override
    277     public final boolean isFirst() {
    278         return mPos == 0 && getCount() != 0;
    279     }
    280 
    281     @Override
    282     public final boolean isLast() {
    283         int cnt = getCount();
    284         return mPos == (cnt - 1) && cnt != 0;
    285     }
    286 
    287     @Override
    288     public final boolean isBeforeFirst() {
    289         if (getCount() == 0) {
    290             return true;
    291         }
    292         return mPos == -1;
    293     }
    294 
    295     @Override
    296     public final boolean isAfterLast() {
    297         if (getCount() == 0) {
    298             return true;
    299         }
    300         return mPos == getCount();
    301     }
    302 
    303     @Override
    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
    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
    339     public String getColumnName(int columnIndex) {
    340         return getColumnNames()[columnIndex];
    341     }
    342 
    343     @Override
    344     public void registerContentObserver(ContentObserver observer) {
    345         mContentObservable.registerObserver(observer);
    346     }
    347 
    348     @Override
    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
    357     public void registerDataSetObserver(DataSetObserver observer) {
    358         mDataSetObservable.registerObserver(observer);
    359     }
    360 
    361     @Override
    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      */
    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
    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 */
    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
    408     public Uri getNotificationUri() {
    409         synchronized (mSelfObserverLock) {
    410             return mNotifyUri;
    411         }
    412     }
    413 
    414     @Override
    415     public boolean getWantsAllOnMoveCalls() {
    416         return false;
    417     }
    418 
    419     @Override
    420     public void setExtras(Bundle extras) {
    421         mExtras = (extras == null) ? Bundle.EMPTY : extras;
    422     }
    423 
    424     @Override
    425     public Bundle getExtras() {
    426         return mExtras;
    427     }
    428 
    429     @Override
    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
    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
    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      */
    458     protected void checkPosition() {
    459         if (-1 == mPos || getCount() == mPos) {
    460             throw new CursorIndexOutOfBoundsException(mPos, getCount());
    461         }
    462     }
    463 
    464     @Override
    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 
    480         public SelfContentObserver(AbstractCursor cursor) {
    481             super(null);
    482             mCursor = new WeakReference<AbstractCursor>(cursor);
    483         }
    484 
    485         @Override
    486         public boolean deliverSelfNotifications() {
    487             return false;
    488         }
    489 
    490         @Override
    491         public void onChange(boolean selfChange) {
    492             AbstractCursor cursor = mCursor.get();
    493             if (cursor != null) {
    494                 cursor.onChange(false);
    495             }
    496         }
    497     }
    498 }
    499