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.net.Uri;
20import android.os.*;
21
22
23/**
24 * Wraps a BulkCursor around an existing Cursor making it remotable.
25 * <p>
26 * If the wrapped cursor returns non-null from {@link CrossProcessCursor#getWindow}
27 * then it is assumed to own the window.  Otherwise, the adaptor provides a
28 * window to be filled and ensures it gets closed as needed during deactivation
29 * and requeries.
30 * </p>
31 *
32 * {@hide}
33 */
34public final class CursorToBulkCursorAdaptor extends BulkCursorNative
35        implements IBinder.DeathRecipient {
36    private static final String TAG = "Cursor";
37
38    private final Object mLock = new Object();
39    private final String mProviderName;
40    private ContentObserverProxy mObserver;
41
42    /**
43     * The cursor that is being adapted.
44     * This field is set to null when the cursor is closed.
45     */
46    private CrossProcessCursor mCursor;
47
48    /**
49     * The cursor window that was filled by the cross process cursor in the
50     * case where the cursor does not support getWindow.
51     * This field is only ever non-null when the window has actually be filled.
52     */
53    private CursorWindow mFilledWindow;
54
55    private static final class ContentObserverProxy extends ContentObserver {
56        protected IContentObserver mRemote;
57
58        public ContentObserverProxy(IContentObserver remoteObserver, DeathRecipient recipient) {
59            super(null);
60            mRemote = remoteObserver;
61            try {
62                remoteObserver.asBinder().linkToDeath(recipient, 0);
63            } catch (RemoteException e) {
64                // Do nothing, the far side is dead
65            }
66        }
67
68        public boolean unlinkToDeath(DeathRecipient recipient) {
69            return mRemote.asBinder().unlinkToDeath(recipient, 0);
70        }
71
72        @Override
73        public boolean deliverSelfNotifications() {
74            // The far side handles the self notifications.
75            return false;
76        }
77
78        @Override
79        public void onChange(boolean selfChange, Uri uri) {
80            try {
81                mRemote.onChange(selfChange, uri, android.os.Process.myUid());
82            } catch (RemoteException ex) {
83                // Do nothing, the far side is dead
84            }
85        }
86    }
87
88    public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer,
89            String providerName) {
90        if (cursor instanceof CrossProcessCursor) {
91            mCursor = (CrossProcessCursor)cursor;
92        } else {
93            mCursor = new CrossProcessCursorWrapper(cursor);
94        }
95        mProviderName = providerName;
96
97        synchronized (mLock) {
98            createAndRegisterObserverProxyLocked(observer);
99        }
100    }
101
102    private void closeFilledWindowLocked() {
103        if (mFilledWindow != null) {
104            mFilledWindow.close();
105            mFilledWindow = null;
106        }
107    }
108
109    private void disposeLocked() {
110        if (mCursor != null) {
111            unregisterObserverProxyLocked();
112            mCursor.close();
113            mCursor = null;
114        }
115
116        closeFilledWindowLocked();
117    }
118
119    private void throwIfCursorIsClosed() {
120        if (mCursor == null) {
121            throw new StaleDataException("Attempted to access a cursor after it has been closed.");
122        }
123    }
124
125    @Override
126    public void binderDied() {
127        synchronized (mLock) {
128            disposeLocked();
129        }
130    }
131
132    /**
133     * Returns an object that contains sufficient metadata to reconstruct
134     * the cursor remotely.  May throw if an error occurs when executing the query
135     * and obtaining the row count.
136     */
137    public BulkCursorDescriptor getBulkCursorDescriptor() {
138        synchronized (mLock) {
139            throwIfCursorIsClosed();
140
141            BulkCursorDescriptor d = new BulkCursorDescriptor();
142            d.cursor = this;
143            d.columnNames = mCursor.getColumnNames();
144            d.wantsAllOnMoveCalls = mCursor.getWantsAllOnMoveCalls();
145            d.count = mCursor.getCount();
146            d.window = mCursor.getWindow();
147            if (d.window != null) {
148                // Acquire a reference to the window because its reference count will be
149                // decremented when it is returned as part of the binder call reply parcel.
150                d.window.acquireReference();
151            }
152            return d;
153        }
154    }
155
156    @Override
157    public CursorWindow getWindow(int position) {
158        synchronized (mLock) {
159            throwIfCursorIsClosed();
160
161            if (!mCursor.moveToPosition(position)) {
162                closeFilledWindowLocked();
163                return null;
164            }
165
166            CursorWindow window = mCursor.getWindow();
167            if (window != null) {
168                closeFilledWindowLocked();
169            } else {
170                window = mFilledWindow;
171                if (window == null) {
172                    mFilledWindow = new CursorWindow(mProviderName);
173                    window = mFilledWindow;
174                } else if (position < window.getStartPosition()
175                        || position >= window.getStartPosition() + window.getNumRows()) {
176                    window.clear();
177                }
178                mCursor.fillWindow(position, window);
179            }
180
181            if (window != null) {
182                // Acquire a reference to the window because its reference count will be
183                // decremented when it is returned as part of the binder call reply parcel.
184                window.acquireReference();
185            }
186            return window;
187        }
188    }
189
190    @Override
191    public void onMove(int position) {
192        synchronized (mLock) {
193            throwIfCursorIsClosed();
194
195            mCursor.onMove(mCursor.getPosition(), position);
196        }
197    }
198
199    @Override
200    public void deactivate() {
201        synchronized (mLock) {
202            if (mCursor != null) {
203                unregisterObserverProxyLocked();
204                mCursor.deactivate();
205            }
206
207            closeFilledWindowLocked();
208        }
209    }
210
211    @Override
212    public void close() {
213        synchronized (mLock) {
214            disposeLocked();
215        }
216    }
217
218    @Override
219    public int requery(IContentObserver observer) {
220        synchronized (mLock) {
221            throwIfCursorIsClosed();
222
223            closeFilledWindowLocked();
224
225            try {
226                if (!mCursor.requery()) {
227                    return -1;
228                }
229            } catch (IllegalStateException e) {
230                IllegalStateException leakProgram = new IllegalStateException(
231                        mProviderName + " Requery misuse db, mCursor isClosed:" +
232                        mCursor.isClosed(), e);
233                throw leakProgram;
234            }
235
236            unregisterObserverProxyLocked();
237            createAndRegisterObserverProxyLocked(observer);
238            return mCursor.getCount();
239        }
240    }
241
242    /**
243     * Create a ContentObserver from the observer and register it as an observer on the
244     * underlying cursor.
245     * @param observer the IContentObserver that wants to monitor the cursor
246     * @throws IllegalStateException if an observer is already registered
247     */
248    private void createAndRegisterObserverProxyLocked(IContentObserver observer) {
249        if (mObserver != null) {
250            throw new IllegalStateException("an observer is already registered");
251        }
252        mObserver = new ContentObserverProxy(observer, this);
253        mCursor.registerContentObserver(mObserver);
254    }
255
256    /** Unregister the observer if it is already registered. */
257    private void unregisterObserverProxyLocked() {
258        if (mObserver != null) {
259            mCursor.unregisterContentObserver(mObserver);
260            mObserver.unlinkToDeath(this);
261            mObserver = null;
262        }
263    }
264
265    @Override
266    public Bundle getExtras() {
267        synchronized (mLock) {
268            throwIfCursorIsClosed();
269
270            return mCursor.getExtras();
271        }
272    }
273
274    @Override
275    public Bundle respond(Bundle extras) {
276        synchronized (mLock) {
277            throwIfCursorIsClosed();
278
279            return mCursor.respond(extras);
280        }
281    }
282}
283