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