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