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