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