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