CursorToBulkCursorAdaptor.java revision b4009c73819e871bba369cdb7dbe56a55db23fc9
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        try {
93            mCursor = (CrossProcessCursor) cursor;
94        } catch (ClassCastException e) {
95            throw new UnsupportedOperationException(
96                    "Only CrossProcessCursor cursors are supported across process for now", e);
97        }
98        mProviderName = providerName;
99
100        synchronized (mLock) {
101            createAndRegisterObserverProxyLocked(observer);
102        }
103    }
104
105    private void closeFilledWindowLocked() {
106        if (mFilledWindow != null) {
107            mFilledWindow.close();
108            mFilledWindow = null;
109        }
110    }
111
112    private void disposeLocked() {
113        if (mCursor != null) {
114            unregisterObserverProxyLocked();
115            mCursor.close();
116            mCursor = null;
117        }
118
119        closeFilledWindowLocked();
120    }
121
122    private void throwIfCursorIsClosed() {
123        if (mCursor == null) {
124            throw new StaleDataException("Attempted to access a cursor after it has been closed.");
125        }
126    }
127
128    @Override
129    public void binderDied() {
130        synchronized (mLock) {
131            disposeLocked();
132        }
133    }
134
135    @Override
136    public CursorWindow getWindow(int startPos) {
137        synchronized (mLock) {
138            throwIfCursorIsClosed();
139
140            if (!mCursor.moveToPosition(startPos)) {
141                closeFilledWindowLocked();
142                return null;
143            }
144
145            CursorWindow window = mCursor.getWindow();
146            if (window != null) {
147                closeFilledWindowLocked();
148            } else {
149                window = mFilledWindow;
150                if (window == null) {
151                    mFilledWindow = new CursorWindow(mProviderName);
152                    window = mFilledWindow;
153                    mCursor.fillWindow(startPos, window);
154                } else if (startPos < window.getStartPosition()
155                        || startPos >= window.getStartPosition() + window.getNumRows()) {
156                    window.clear();
157                    mCursor.fillWindow(startPos, window);
158                }
159            }
160
161            // Acquire a reference before returning from this RPC.
162            // The Binder proxy will decrement the reference count again as part of writing
163            // the CursorWindow to the reply parcel as a return value.
164            if (window != null) {
165                window.acquireReference();
166            }
167            return window;
168        }
169    }
170
171    @Override
172    public void onMove(int position) {
173        synchronized (mLock) {
174            throwIfCursorIsClosed();
175
176            mCursor.onMove(mCursor.getPosition(), position);
177        }
178    }
179
180    @Override
181    public int count() {
182        synchronized (mLock) {
183            throwIfCursorIsClosed();
184
185            return mCursor.getCount();
186        }
187    }
188
189    @Override
190    public String[] getColumnNames() {
191        synchronized (mLock) {
192            throwIfCursorIsClosed();
193
194            return mCursor.getColumnNames();
195        }
196    }
197
198    @Override
199    public void deactivate() {
200        synchronized (mLock) {
201            if (mCursor != null) {
202                unregisterObserverProxyLocked();
203                mCursor.deactivate();
204            }
205
206            closeFilledWindowLocked();
207        }
208    }
209
210    @Override
211    public void close() {
212        synchronized (mLock) {
213            disposeLocked();
214        }
215    }
216
217    @Override
218    public int requery(IContentObserver observer) {
219        synchronized (mLock) {
220            throwIfCursorIsClosed();
221
222            closeFilledWindowLocked();
223
224            try {
225                if (!mCursor.requery()) {
226                    return -1;
227                }
228            } catch (IllegalStateException e) {
229                IllegalStateException leakProgram = new IllegalStateException(
230                        mProviderName + " Requery misuse db, mCursor isClosed:" +
231                        mCursor.isClosed(), e);
232                throw leakProgram;
233            }
234
235            unregisterObserverProxyLocked();
236            createAndRegisterObserverProxyLocked(observer);
237            return mCursor.getCount();
238        }
239    }
240
241    @Override
242    public boolean getWantsAllOnMoveCalls() {
243        synchronized (mLock) {
244            throwIfCursorIsClosed();
245
246            return mCursor.getWantsAllOnMoveCalls();
247        }
248    }
249
250    /**
251     * Create a ContentObserver from the observer and register it as an observer on the
252     * underlying cursor.
253     * @param observer the IContentObserver that wants to monitor the cursor
254     * @throws IllegalStateException if an observer is already registered
255     */
256    private void createAndRegisterObserverProxyLocked(IContentObserver observer) {
257        if (mObserver != null) {
258            throw new IllegalStateException("an observer is already registered");
259        }
260        mObserver = new ContentObserverProxy(observer, this);
261        mCursor.registerContentObserver(mObserver);
262    }
263
264    /** Unregister the observer if it is already registered. */
265    private void unregisterObserverProxyLocked() {
266        if (mObserver != null) {
267            mCursor.unregisterContentObserver(mObserver);
268            mObserver.unlinkToDeath(this);
269            mObserver = null;
270        }
271    }
272
273    @Override
274    public Bundle getExtras() {
275        synchronized (mLock) {
276            throwIfCursorIsClosed();
277
278            return mCursor.getExtras();
279        }
280    }
281
282    @Override
283    public Bundle respond(Bundle extras) {
284        synchronized (mLock) {
285            throwIfCursorIsClosed();
286
287            return mCursor.respond(extras);
288        }
289    }
290}
291