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