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.Bundle;
21import android.os.IBinder;
22import android.os.RemoteException;
23import android.util.Log;
24
25
26/**
27 * Wraps a BulkCursor around an existing Cursor making it remotable.
28 * <p>
29 * If the wrapped cursor returns non-null from {@link CrossProcessCursor#getWindow}
30 * then it is assumed to own the window.  Otherwise, the adaptor provides a
31 * window to be filled and ensures it gets closed as needed during deactivation
32 * and requeries.
33 * </p>
34 *
35 * {@hide}
36 */
37public final class CursorToBulkCursorAdaptor extends BulkCursorNative
38        implements IBinder.DeathRecipient {
39    private static final String TAG = "Cursor";
40
41    private final Object mLock = new Object();
42    private final String mProviderName;
43    private ContentObserverProxy mObserver;
44
45    /**
46     * The cursor that is being adapted.
47     * This field is set to null when the cursor is closed.
48     */
49    private CrossProcessCursor mCursor;
50
51    /**
52     * The cursor window that was filled by the cross process cursor in the
53     * case where the cursor does not support getWindow.
54     * This field is only ever non-null when the window has actually be filled.
55     */
56    private CursorWindow mFilledWindow;
57
58    private static final class ContentObserverProxy extends ContentObserver {
59        protected IContentObserver mRemote;
60
61        public ContentObserverProxy(IContentObserver remoteObserver, DeathRecipient recipient) {
62            super(null);
63            mRemote = remoteObserver;
64            try {
65                remoteObserver.asBinder().linkToDeath(recipient, 0);
66            } catch (RemoteException e) {
67                // Do nothing, the far side is dead
68            }
69        }
70
71        public boolean unlinkToDeath(DeathRecipient recipient) {
72            return mRemote.asBinder().unlinkToDeath(recipient, 0);
73        }
74
75        @Override
76        public boolean deliverSelfNotifications() {
77            // The far side handles the self notifications.
78            return false;
79        }
80
81        @Override
82        public void onChange(boolean selfChange, Uri uri) {
83            try {
84                mRemote.onChange(selfChange, uri);
85            } catch (RemoteException ex) {
86                // Do nothing, the far side is dead
87            }
88        }
89    }
90
91    public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer,
92            String providerName) {
93        if (cursor instanceof CrossProcessCursor) {
94            mCursor = (CrossProcessCursor)cursor;
95        } else {
96            mCursor = new CrossProcessCursorWrapper(cursor);
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    public BulkCursorDescriptor getBulkCursorDescriptor() {
136        synchronized (mLock) {
137            throwIfCursorIsClosed();
138
139            BulkCursorDescriptor d = new BulkCursorDescriptor();
140            d.cursor = this;
141            d.columnNames = mCursor.getColumnNames();
142            d.wantsAllOnMoveCalls = mCursor.getWantsAllOnMoveCalls();
143            d.count = mCursor.getCount();
144            d.window = mCursor.getWindow();
145            if (d.window != null) {
146                // Acquire a reference to the window because its reference count will be
147                // decremented when it is returned as part of the binder call reply parcel.
148                d.window.acquireReference();
149            }
150            return d;
151        }
152    }
153
154    @Override
155    public CursorWindow getWindow(int position) {
156        synchronized (mLock) {
157            throwIfCursorIsClosed();
158
159            if (!mCursor.moveToPosition(position)) {
160                closeFilledWindowLocked();
161                return null;
162            }
163
164            CursorWindow window = mCursor.getWindow();
165            if (window != null) {
166                closeFilledWindowLocked();
167            } else {
168                window = mFilledWindow;
169                if (window == null) {
170                    mFilledWindow = new CursorWindow(mProviderName);
171                    window = mFilledWindow;
172                } else if (position < window.getStartPosition()
173                        || position >= window.getStartPosition() + window.getNumRows()) {
174                    window.clear();
175                }
176                mCursor.fillWindow(position, window);
177            }
178
179            if (window != null) {
180                // Acquire a reference to the window because its reference count will be
181                // decremented when it is returned as part of the binder call reply parcel.
182                window.acquireReference();
183            }
184            return window;
185        }
186    }
187
188    @Override
189    public void onMove(int position) {
190        synchronized (mLock) {
191            throwIfCursorIsClosed();
192
193            mCursor.onMove(mCursor.getPosition(), position);
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    /**
241     * Create a ContentObserver from the observer and register it as an observer on the
242     * underlying cursor.
243     * @param observer the IContentObserver that wants to monitor the cursor
244     * @throws IllegalStateException if an observer is already registered
245     */
246    private void createAndRegisterObserverProxyLocked(IContentObserver observer) {
247        if (mObserver != null) {
248            throw new IllegalStateException("an observer is already registered");
249        }
250        mObserver = new ContentObserverProxy(observer, this);
251        mCursor.registerContentObserver(mObserver);
252    }
253
254    /** Unregister the observer if it is already registered. */
255    private void unregisterObserverProxyLocked() {
256        if (mObserver != null) {
257            mCursor.unregisterContentObserver(mObserver);
258            mObserver.unlinkToDeath(this);
259            mObserver = null;
260        }
261    }
262
263    @Override
264    public Bundle getExtras() {
265        synchronized (mLock) {
266            throwIfCursorIsClosed();
267
268            return mCursor.getExtras();
269        }
270    }
271
272    @Override
273    public Bundle respond(Bundle extras) {
274        synchronized (mLock) {
275            throwIfCursorIsClosed();
276
277            return mCursor.respond(extras);
278        }
279    }
280}
281