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     * @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        return mNotifyUri;
373    }
374
375    public boolean getWantsAllOnMoveCalls() {
376        return false;
377    }
378
379    /**
380     * Sets a {@link Bundle} that will be returned by {@link #getExtras()}.  <code>null</code> will
381     * be converted into {@link Bundle#EMPTY}.
382     *
383     * @param extras {@link Bundle} to set.
384     * @hide
385     */
386    public void setExtras(Bundle extras) {
387        mExtras = (extras == null) ? Bundle.EMPTY : extras;
388    }
389
390    public Bundle getExtras() {
391        return mExtras;
392    }
393
394    public Bundle respond(Bundle extras) {
395        return Bundle.EMPTY;
396    }
397
398    /**
399     * @deprecated Always returns false since Cursors do not support updating rows
400     */
401    @Deprecated
402    protected boolean isFieldUpdated(int columnIndex) {
403        return false;
404    }
405
406    /**
407     * @deprecated Always returns null since Cursors do not support updating rows
408     */
409    @Deprecated
410    protected Object getUpdatedField(int columnIndex) {
411        return null;
412    }
413
414    /**
415     * This function throws CursorIndexOutOfBoundsException if
416     * the cursor position is out of bounds. Subclass implementations of
417     * the get functions should call this before attempting
418     * to retrieve data.
419     *
420     * @throws CursorIndexOutOfBoundsException
421     */
422    protected void checkPosition() {
423        if (-1 == mPos || getCount() == mPos) {
424            throw new CursorIndexOutOfBoundsException(mPos, getCount());
425        }
426    }
427
428    @Override
429    protected void finalize() {
430        if (mSelfObserver != null && mSelfObserverRegistered == true) {
431            mContentResolver.unregisterContentObserver(mSelfObserver);
432        }
433        try {
434            if (!mClosed) close();
435        } catch(Exception e) { }
436    }
437
438    /**
439     * Cursors use this class to track changes others make to their URI.
440     */
441    protected static class SelfContentObserver extends ContentObserver {
442        WeakReference<AbstractCursor> mCursor;
443
444        public SelfContentObserver(AbstractCursor cursor) {
445            super(null);
446            mCursor = new WeakReference<AbstractCursor>(cursor);
447        }
448
449        @Override
450        public boolean deliverSelfNotifications() {
451            return false;
452        }
453
454        @Override
455        public void onChange(boolean selfChange) {
456            AbstractCursor cursor = mCursor.get();
457            if (cursor != null) {
458                cursor.onChange(false);
459            }
460        }
461    }
462}
463