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