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 
17 package android.database;
18 
19 import android.annotation.NonNull;
20 import android.annotation.UserIdInt;
21 import android.content.ContentResolver.NotifyFlags;
22 import android.net.Uri;
23 import android.os.Bundle;
24 import android.os.IBinder;
25 import android.os.RemoteException;
26 
27 import java.util.ArrayList;
28 import java.util.Collection;
29 
30 /**
31  * Wraps a BulkCursor around an existing Cursor making it remotable.
32  * <p>
33  * If the wrapped cursor returns non-null from {@link CrossProcessCursor#getWindow}
34  * then it is assumed to own the window.  Otherwise, the adaptor provides a
35  * window to be filled and ensures it gets closed as needed during deactivation
36  * and requeries.
37  * </p>
38  *
39  * {@hide}
40  */
41 public final class CursorToBulkCursorAdaptor extends BulkCursorNative
42         implements IBinder.DeathRecipient {
43     private static final String TAG = "Cursor";
44 
45     private final Object mLock = new Object();
46     private final String mProviderName;
47     private ContentObserverProxy mObserver;
48 
49     /**
50      * The cursor that is being adapted.
51      * This field is set to null when the cursor is closed.
52      */
53     private CrossProcessCursor mCursor;
54 
55     /**
56      * The cursor window that was filled by the cross process cursor in the
57      * case where the cursor does not support getWindow.
58      * This field is only ever non-null when the window has actually be filled.
59      */
60     private CursorWindow mFilledWindow;
61 
62     private static final class ContentObserverProxy extends ContentObserver {
63         protected IContentObserver mRemote;
64 
ContentObserverProxy(IContentObserver remoteObserver, DeathRecipient recipient)65         public ContentObserverProxy(IContentObserver remoteObserver, DeathRecipient recipient) {
66             super(null);
67             mRemote = remoteObserver;
68             try {
69                 remoteObserver.asBinder().linkToDeath(recipient, 0);
70             } catch (RemoteException e) {
71                 // Do nothing, the far side is dead
72             }
73         }
74 
unlinkToDeath(DeathRecipient recipient)75         public boolean unlinkToDeath(DeathRecipient recipient) {
76             return mRemote.asBinder().unlinkToDeath(recipient, 0);
77         }
78 
79         @Override
deliverSelfNotifications()80         public boolean deliverSelfNotifications() {
81             // The far side handles the self notifications.
82             return false;
83         }
84 
85         @Override
onChange(boolean selfChange, @NonNull Collection<Uri> uris, @NotifyFlags int flags, @UserIdInt int userId)86         public void onChange(boolean selfChange, @NonNull Collection<Uri> uris,
87                 @NotifyFlags int flags, @UserIdInt int userId) {
88             // Since we deliver changes from the most-specific to least-specific
89             // overloads, we only need to redirect from the most-specific local
90             // method to the most-specific remote method
91 
92             final ArrayList<Uri> asList = new ArrayList<>();
93             uris.forEach(asList::add);
94             final Uri[] asArray = asList.toArray(new Uri[asList.size()]);
95 
96             try {
97                 mRemote.onChangeEtc(selfChange, asArray, flags, userId);
98             } catch (RemoteException ex) {
99                 // Do nothing, the far side is dead
100             }
101         }
102     }
103 
CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer, String providerName)104     public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer,
105             String providerName) {
106         if (cursor instanceof CrossProcessCursor) {
107             mCursor = (CrossProcessCursor)cursor;
108         } else {
109             mCursor = new CrossProcessCursorWrapper(cursor);
110         }
111         mProviderName = providerName;
112 
113         synchronized (mLock) {
114             createAndRegisterObserverProxyLocked(observer);
115         }
116     }
117 
closeFilledWindowLocked()118     private void closeFilledWindowLocked() {
119         if (mFilledWindow != null) {
120             mFilledWindow.close();
121             mFilledWindow = null;
122         }
123     }
124 
disposeLocked()125     private void disposeLocked() {
126         if (mCursor != null) {
127             unregisterObserverProxyLocked();
128             mCursor.close();
129             mCursor = null;
130         }
131 
132         closeFilledWindowLocked();
133     }
134 
throwIfCursorIsClosed()135     private void throwIfCursorIsClosed() {
136         if (mCursor == null) {
137             throw new StaleDataException("Attempted to access a cursor after it has been closed.");
138         }
139     }
140 
141     @Override
binderDied()142     public void binderDied() {
143         synchronized (mLock) {
144             disposeLocked();
145         }
146     }
147 
148     /**
149      * Returns an object that contains sufficient metadata to reconstruct
150      * the cursor remotely.  May throw if an error occurs when executing the query
151      * and obtaining the row count.
152      */
getBulkCursorDescriptor()153     public BulkCursorDescriptor getBulkCursorDescriptor() {
154         synchronized (mLock) {
155             throwIfCursorIsClosed();
156 
157             BulkCursorDescriptor d = new BulkCursorDescriptor();
158             d.cursor = this;
159             d.columnNames = mCursor.getColumnNames();
160             d.wantsAllOnMoveCalls = mCursor.getWantsAllOnMoveCalls();
161             d.count = mCursor.getCount();
162             d.window = mCursor.getWindow();
163             if (d.window != null) {
164                 // Acquire a reference to the window because its reference count will be
165                 // decremented when it is returned as part of the binder call reply parcel.
166                 d.window.acquireReference();
167             }
168             return d;
169         }
170     }
171 
172     @Override
getWindow(int position)173     public CursorWindow getWindow(int position) {
174         synchronized (mLock) {
175             throwIfCursorIsClosed();
176 
177             if (!mCursor.moveToPosition(position)) {
178                 closeFilledWindowLocked();
179                 return null;
180             }
181 
182             CursorWindow window = mCursor.getWindow();
183             if (window != null) {
184                 closeFilledWindowLocked();
185             } else {
186                 window = mFilledWindow;
187                 if (window == null) {
188                     mFilledWindow = new CursorWindow(mProviderName);
189                     window = mFilledWindow;
190                 } else if (position < window.getStartPosition()
191                         || position >= window.getStartPosition() + window.getNumRows()) {
192                     window.clear();
193                 }
194                 mCursor.fillWindow(position, window);
195             }
196 
197             if (window != null) {
198                 // Acquire a reference to the window because its reference count will be
199                 // decremented when it is returned as part of the binder call reply parcel.
200                 window.acquireReference();
201             }
202             return window;
203         }
204     }
205 
206     @Override
onMove(int position)207     public void onMove(int position) {
208         synchronized (mLock) {
209             throwIfCursorIsClosed();
210 
211             mCursor.onMove(mCursor.getPosition(), position);
212         }
213     }
214 
215     @Override
deactivate()216     public void deactivate() {
217         synchronized (mLock) {
218             if (mCursor != null) {
219                 unregisterObserverProxyLocked();
220                 mCursor.deactivate();
221             }
222 
223             closeFilledWindowLocked();
224         }
225     }
226 
227     @Override
close()228     public void close() {
229         synchronized (mLock) {
230             disposeLocked();
231         }
232     }
233 
234     @Override
requery(IContentObserver observer)235     public int requery(IContentObserver observer) {
236         synchronized (mLock) {
237             throwIfCursorIsClosed();
238 
239             closeFilledWindowLocked();
240 
241             try {
242                 if (!mCursor.requery()) {
243                     return -1;
244                 }
245             } catch (IllegalStateException e) {
246                 IllegalStateException leakProgram = new IllegalStateException(
247                         mProviderName + " Requery misuse db, mCursor isClosed:" +
248                         mCursor.isClosed(), e);
249                 throw leakProgram;
250             }
251 
252             unregisterObserverProxyLocked();
253             createAndRegisterObserverProxyLocked(observer);
254             return mCursor.getCount();
255         }
256     }
257 
258     /**
259      * Create a ContentObserver from the observer and register it as an observer on the
260      * underlying cursor.
261      * @param observer the IContentObserver that wants to monitor the cursor
262      * @throws IllegalStateException if an observer is already registered
263      */
createAndRegisterObserverProxyLocked(IContentObserver observer)264     private void createAndRegisterObserverProxyLocked(IContentObserver observer) {
265         if (mObserver != null) {
266             throw new IllegalStateException("an observer is already registered");
267         }
268         mObserver = new ContentObserverProxy(observer, this);
269         mCursor.registerContentObserver(mObserver);
270     }
271 
272     /** Unregister the observer if it is already registered. */
unregisterObserverProxyLocked()273     private void unregisterObserverProxyLocked() {
274         if (mObserver != null) {
275             mCursor.unregisterContentObserver(mObserver);
276             mObserver.unlinkToDeath(this);
277             mObserver = null;
278         }
279     }
280 
281     @Override
getExtras()282     public Bundle getExtras() {
283         synchronized (mLock) {
284             throwIfCursorIsClosed();
285 
286             return mCursor.getExtras();
287         }
288     }
289 
290     @Override
respond(Bundle extras)291     public Bundle respond(Bundle extras) {
292         synchronized (mLock) {
293             throwIfCursorIsClosed();
294 
295             return mCursor.respond(extras);
296         }
297     }
298 }
299