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 startPos) {
136        synchronized (mLock) {
137            throwIfCursorIsClosed();
138
139            if (!mCursor.moveToPosition(startPos)) {
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                    mCursor.fillWindow(startPos, window);
153                } else if (startPos < window.getStartPosition()
154                        || startPos >= window.getStartPosition() + window.getNumRows()) {
155                    window.clear();
156                    mCursor.fillWindow(startPos, window);
157                }
158            }
159
160            // Acquire a reference before returning from this RPC.
161            // The Binder proxy will decrement the reference count again as part of writing
162            // the CursorWindow to the reply parcel as a return value.
163            if (window != null) {
164                window.acquireReference();
165            }
166            return window;
167        }
168    }
169
170    @Override
171    public void onMove(int position) {
172        synchronized (mLock) {
173            throwIfCursorIsClosed();
174
175            mCursor.onMove(mCursor.getPosition(), position);
176        }
177    }
178
179    @Override
180    public int count() {
181        synchronized (mLock) {
182            throwIfCursorIsClosed();
183
184            return mCursor.getCount();
185        }
186    }
187
188    @Override
189    public String[] getColumnNames() {
190        synchronized (mLock) {
191            throwIfCursorIsClosed();
192
193            return mCursor.getColumnNames();
194        }
195    }
196
197    @Override
198    public void deactivate() {
199        synchronized (mLock) {
200            if (mCursor != null) {
201                unregisterObserverProxyLocked();
202                mCursor.deactivate();
203            }
204
205            closeFilledWindowLocked();
206        }
207    }
208
209    @Override
210    public void close() {
211        synchronized (mLock) {
212            disposeLocked();
213        }
214    }
215
216    @Override
217    public int requery(IContentObserver observer) {
218        synchronized (mLock) {
219            throwIfCursorIsClosed();
220
221            closeFilledWindowLocked();
222
223            try {
224                if (!mCursor.requery()) {
225                    return -1;
226                }
227            } catch (IllegalStateException e) {
228                IllegalStateException leakProgram = new IllegalStateException(
229                        mProviderName + " Requery misuse db, mCursor isClosed:" +
230                        mCursor.isClosed(), e);
231                throw leakProgram;
232            }
233
234            unregisterObserverProxyLocked();
235            createAndRegisterObserverProxyLocked(observer);
236            return mCursor.getCount();
237        }
238    }
239
240    @Override
241    public boolean getWantsAllOnMoveCalls() {
242        synchronized (mLock) {
243            throwIfCursorIsClosed();
244
245            return mCursor.getWantsAllOnMoveCalls();
246        }
247    }
248
249    /**
250     * Create a ContentObserver from the observer and register it as an observer on the
251     * underlying cursor.
252     * @param observer the IContentObserver that wants to monitor the cursor
253     * @throws IllegalStateException if an observer is already registered
254     */
255    private void createAndRegisterObserverProxyLocked(IContentObserver observer) {
256        if (mObserver != null) {
257            throw new IllegalStateException("an observer is already registered");
258        }
259        mObserver = new ContentObserverProxy(observer, this);
260        mCursor.registerContentObserver(mObserver);
261    }
262
263    /** Unregister the observer if it is already registered. */
264    private void unregisterObserverProxyLocked() {
265        if (mObserver != null) {
266            mCursor.unregisterContentObserver(mObserver);
267            mObserver.unlinkToDeath(this);
268            mObserver = null;
269        }
270    }
271
272    @Override
273    public Bundle getExtras() {
274        synchronized (mLock) {
275            throwIfCursorIsClosed();
276
277            return mCursor.getExtras();
278        }
279    }
280
281    @Override
282    public Bundle respond(Bundle extras) {
283        synchronized (mLock) {
284            throwIfCursorIsClosed();
285
286            return mCursor.respond(extras);
287        }
288    }
289}
290