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