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