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    /**
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