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