diff options
author | Jeff Brown <jeffbrown@google.com> | 2011-10-09 12:39:53 -0700 |
---|---|---|
committer | Jeff Brown <jeffbrown@google.com> | 2011-10-09 22:10:36 -0700 |
commit | d2183654e03d589b120467f4e98da1b178ceeadb (patch) | |
tree | c52368d929521fd0d7182dc3cf53f8e4b37ed25f /core/java | |
parent | 1d8e7d640ad5ed6fe82bca017293dd89169f1c2e (diff) | |
download | frameworks_base-d2183654e03d589b120467f4e98da1b178ceeadb.zip frameworks_base-d2183654e03d589b120467f4e98da1b178ceeadb.tar.gz frameworks_base-d2183654e03d589b120467f4e98da1b178ceeadb.tar.bz2 |
Fix ownership of CursorWindows across processes.
Bug: 5332296
Ensure that there is always an owner for each CursorWindow
and that references to each window are acquired/released
appropriately at all times.
Added synchronization to CursorToBulkCursorAdaptor to
prevent the underlying Cursor and CursorWindow from being
remotely accessed in ways that might violate invariants,
resulting in leaks or other problems.
Ensured that CursorToBulkCursorAdaptor promptly releases
its references to the Cursor and CursorWindow when closed
so they don't stick around longer than they should, even
if the remote end hangs onto the IBulkCursor for some reason.
CursorWindow respects Parcelable.FLAG_WRITE_RETURN_VALUE
as an indication that one reference to the CursorWindow is
being released. Correspondingly, CursorToBulkCursorAdaptor
acquires a reference to the CursorWindow before returning
it to the caller. This change also prevents races from
resulting in the transfer of an invalid CursorWindow over
the wire.
Ensured that BulkCursorToCursorAdaptor promptly releases
its reference to the IBulkCursor when closed and throws
on attempts to access the cursor while closed.
Modified ContentProviderNative to handle both parts of
the wrapping and unwrapping of Cursors into IBulkCursors.
This makes it a lot easier to ensure that the right
things happen on both ends. Also, it turns out that
the only caller of IContentProvider.bulkQuery was
ContentProviderNative itself so there was no need
to support bulkQuery on ContentProviderProxy and it was
just getting in the way.
Implement CloseGuard on CursorWindow.
Change-Id: Ib3c8305d3cc62322f38a06698d404a2989bb6ef9
Diffstat (limited to 'core/java')
-rw-r--r-- | core/java/android/content/ContentProvider.java | 23 | ||||
-rw-r--r-- | core/java/android/content/ContentProviderNative.java | 178 | ||||
-rw-r--r-- | core/java/android/content/IContentProvider.java | 10 | ||||
-rw-r--r-- | core/java/android/database/AbstractCursor.java | 10 | ||||
-rw-r--r-- | core/java/android/database/AbstractWindowedCursor.java | 26 | ||||
-rw-r--r-- | core/java/android/database/BulkCursorNative.java | 8 | ||||
-rw-r--r-- | core/java/android/database/BulkCursorToCursorAdaptor.java | 112 | ||||
-rw-r--r-- | core/java/android/database/CrossProcessCursor.java | 2 | ||||
-rw-r--r-- | core/java/android/database/CursorToBulkCursorAdaptor.java | 213 | ||||
-rw-r--r-- | core/java/android/database/CursorWindow.java | 16 | ||||
-rw-r--r-- | core/java/android/database/sqlite/SQLiteCursor.java | 17 |
11 files changed, 350 insertions, 265 deletions
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 8057d4b..e452f1f 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -22,10 +22,6 @@ import android.content.pm.ProviderInfo; import android.content.res.AssetFileDescriptor; import android.content.res.Configuration; import android.database.Cursor; -import android.database.CursorToBulkCursorAdaptor; -import android.database.CursorWindow; -import android.database.IBulkCursor; -import android.database.IContentObserver; import android.database.SQLException; import android.net.Uri; import android.os.AsyncTask; @@ -168,22 +164,9 @@ public abstract class ContentProvider implements ComponentCallbacks2 { return ContentProvider.this; } - /** - * Remote version of a query, which returns an IBulkCursor. The bulk - * cursor should be wrapped with BulkCursorToCursorAdaptor before use. - */ - public IBulkCursor bulkQuery(Uri uri, String[] projection, - String selection, String[] selectionArgs, String sortOrder, - IContentObserver observer, CursorWindow window) { - enforceReadPermission(uri); - Cursor cursor = ContentProvider.this.query(uri, projection, - selection, selectionArgs, sortOrder); - if (cursor == null) { - return null; - } - return new CursorToBulkCursorAdaptor(cursor, observer, - ContentProvider.this.getClass().getName(), - hasWritePermission(uri), window); + @Override + public String getProviderName() { + return getContentProvider().getClass().getName(); } public Cursor query(Uri uri, String[] projection, diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index 9a20951..064755e 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -20,6 +20,7 @@ import android.content.res.AssetFileDescriptor; import android.database.BulkCursorNative; import android.database.BulkCursorToCursorAdaptor; import android.database.Cursor; +import android.database.CursorToBulkCursorAdaptor; import android.database.CursorWindow; import android.database.DatabaseUtils; import android.database.IBulkCursor; @@ -65,6 +66,13 @@ abstract public class ContentProviderNative extends Binder implements IContentPr return new ContentProviderProxy(obj); } + /** + * Gets the name of the content provider. + * Should probably be part of the {@link IContentProvider} interface. + * @return The content provider name. + */ + public abstract String getProviderName(); + @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { @@ -98,33 +106,23 @@ abstract public class ContentProviderNative extends Binder implements IContentPr } String sortOrder = data.readString(); - IContentObserver observer = IContentObserver.Stub. - asInterface(data.readStrongBinder()); + IContentObserver observer = IContentObserver.Stub.asInterface( + data.readStrongBinder()); CursorWindow window = CursorWindow.CREATOR.createFromParcel(data); - // Flag for whether caller wants the number of - // rows in the cursor and the position of the - // "_id" column index (or -1 if non-existent) - // Only to be returned if binder != null. - boolean wantsCursorMetadata = data.readInt() != 0; - - IBulkCursor bulkCursor = bulkQuery(url, projection, selection, - selectionArgs, sortOrder, observer, window); - if (bulkCursor != null) { - final IBinder binder = bulkCursor.asBinder(); - if (wantsCursorMetadata) { - final int count = bulkCursor.count(); - final int index = BulkCursorToCursorAdaptor.findRowIdColumnIndex( - bulkCursor.getColumnNames()); - - reply.writeNoException(); - reply.writeStrongBinder(binder); - reply.writeInt(count); - reply.writeInt(index); - } else { - reply.writeNoException(); - reply.writeStrongBinder(binder); - } + Cursor cursor = query(url, projection, selection, selectionArgs, sortOrder); + if (cursor != null) { + CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor( + cursor, observer, getProviderName(), window); + final IBinder binder = adaptor.asBinder(); + final int count = adaptor.count(); + final int index = BulkCursorToCursorAdaptor.findRowIdColumnIndex( + adaptor.getColumnNames()); + + reply.writeNoException(); + reply.writeStrongBinder(binder); + reply.writeInt(count); + reply.writeInt(index); } else { reply.writeNoException(); reply.writeStrongBinder(null); @@ -324,92 +322,70 @@ final class ContentProviderProxy implements IContentProvider return mRemote; } - // Like bulkQuery() but sets up provided 'adaptor' if not null. - private IBulkCursor bulkQueryInternal( - Uri url, String[] projection, - String selection, String[] selectionArgs, String sortOrder, - IContentObserver observer, CursorWindow window, - BulkCursorToCursorAdaptor adaptor) throws RemoteException { - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); + public Cursor query(Uri url, String[] projection, String selection, + String[] selectionArgs, String sortOrder) throws RemoteException { + CursorWindow window = new CursorWindow(false /* window will be used remotely */); try { - data.writeInterfaceToken(IContentProvider.descriptor); - - url.writeToParcel(data, 0); - int length = 0; - if (projection != null) { - length = projection.length; - } - data.writeInt(length); - for (int i = 0; i < length; i++) { - data.writeString(projection[i]); - } - data.writeString(selection); - if (selectionArgs != null) { - length = selectionArgs.length; - } else { - length = 0; - } - data.writeInt(length); - for (int i = 0; i < length; i++) { - data.writeString(selectionArgs[i]); - } - data.writeString(sortOrder); - data.writeStrongBinder(observer.asBinder()); - window.writeToParcel(data, 0); - - // Flag for whether or not we want the number of rows in the - // cursor and the position of the "_id" column index (or -1 if - // non-existent). Only to be returned if binder != null. - final boolean wantsCursorMetadata = (adaptor != null); - data.writeInt(wantsCursorMetadata ? 1 : 0); - - mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0); + BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor(); + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IContentProvider.descriptor); + + url.writeToParcel(data, 0); + int length = 0; + if (projection != null) { + length = projection.length; + } + data.writeInt(length); + for (int i = 0; i < length; i++) { + data.writeString(projection[i]); + } + data.writeString(selection); + if (selectionArgs != null) { + length = selectionArgs.length; + } else { + length = 0; + } + data.writeInt(length); + for (int i = 0; i < length; i++) { + data.writeString(selectionArgs[i]); + } + data.writeString(sortOrder); + data.writeStrongBinder(adaptor.getObserver().asBinder()); + window.writeToParcel(data, 0); - DatabaseUtils.readExceptionFromParcel(reply); + mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0); - IBulkCursor bulkCursor = null; - IBinder bulkCursorBinder = reply.readStrongBinder(); - if (bulkCursorBinder != null) { - bulkCursor = BulkCursorNative.asInterface(bulkCursorBinder); + DatabaseUtils.readExceptionFromParcel(reply); - if (wantsCursorMetadata) { + IBulkCursor bulkCursor = BulkCursorNative.asInterface(reply.readStrongBinder()); + if (bulkCursor != null) { int rowCount = reply.readInt(); int idColumnPosition = reply.readInt(); - if (bulkCursor != null) { - adaptor.set(bulkCursor, rowCount, idColumnPosition); - } + adaptor.initialize(bulkCursor, rowCount, idColumnPosition); + } else { + adaptor.close(); + adaptor = null; } + return adaptor; + } catch (RemoteException ex) { + adaptor.close(); + throw ex; + } catch (RuntimeException ex) { + adaptor.close(); + throw ex; + } finally { + data.recycle(); + reply.recycle(); } - return bulkCursor; } finally { - data.recycle(); - reply.recycle(); - } - } - - public IBulkCursor bulkQuery(Uri url, String[] projection, - String selection, String[] selectionArgs, String sortOrder, IContentObserver observer, - CursorWindow window) throws RemoteException { - return bulkQueryInternal( - url, projection, selection, selectionArgs, sortOrder, - observer, window, - null /* BulkCursorToCursorAdaptor */); - } - - public Cursor query(Uri url, String[] projection, String selection, - String[] selectionArgs, String sortOrder) throws RemoteException { - //TODO make a pool of windows so we can reuse memory dealers - CursorWindow window = new CursorWindow(false /* window will be used remotely */); - BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor(); - IBulkCursor bulkCursor = bulkQueryInternal( - url, projection, selection, selectionArgs, sortOrder, - adaptor.getObserver(), window, - adaptor); - if (bulkCursor == null) { - return null; + // We close the window now because the cursor adaptor does not + // take ownership of the window until the first call to onMove. + // The adaptor will obtain a fresh reference to the window when + // it is filled. + window.close(); } - return adaptor; } public String getType(Uri url) throws RemoteException diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index 72bc9c2..2a67ff8 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -18,9 +18,6 @@ package android.content; import android.content.res.AssetFileDescriptor; import android.database.Cursor; -import android.database.CursorWindow; -import android.database.IBulkCursor; -import android.database.IContentObserver; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; @@ -36,13 +33,6 @@ import java.util.ArrayList; * @hide */ public interface IContentProvider extends IInterface { - /** - * @hide - hide this because return type IBulkCursor and parameter - * IContentObserver are system private classes. - */ - public IBulkCursor bulkQuery(Uri url, String[] projection, - String selection, String[] selectionArgs, String sortOrder, IContentObserver observer, - CursorWindow window) throws RemoteException; public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sortOrder) throws RemoteException; public String getType(Uri url) throws RemoteException; diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java index 3f23b89..ee6aec6 100644 --- a/core/java/android/database/AbstractCursor.java +++ b/core/java/android/database/AbstractCursor.java @@ -78,13 +78,11 @@ public abstract class AbstractCursor implements CrossProcessCursor { } public void deactivate() { - deactivateInternal(); + onDeactivateOrClose(); } - /** - * @hide - */ - public void deactivateInternal() { + /** @hide */ + protected void onDeactivateOrClose() { if (mSelfObserver != null) { mContentResolver.unregisterContentObserver(mSelfObserver); mSelfObserverRegistered = false; @@ -108,7 +106,7 @@ public abstract class AbstractCursor implements CrossProcessCursor { public void close() { mClosed = true; mContentObservable.unregisterAll(); - deactivateInternal(); + onDeactivateOrClose(); } /** diff --git a/core/java/android/database/AbstractWindowedCursor.java b/core/java/android/database/AbstractWindowedCursor.java index bfc8123..5836265 100644 --- a/core/java/android/database/AbstractWindowedCursor.java +++ b/core/java/android/database/AbstractWindowedCursor.java @@ -19,6 +19,11 @@ package android.database; /** * A base class for Cursors that store their data in {@link CursorWindow}s. * <p> + * The cursor owns the cursor window it uses. When the cursor is closed, + * its window is also closed. Likewise, when the window used by the cursor is + * changed, its old window is closed. This policy of strict ownership ensures + * that cursor windows are not leaked. + * </p><p> * Subclasses are responsible for filling the cursor window with data during * {@link #onMove(int, int)}, allocating a new cursor window if necessary. * During {@link #requery()}, the existing cursor window should be cleared and @@ -180,4 +185,25 @@ public abstract class AbstractWindowedCursor extends AbstractCursor { mWindow = null; } } + + /** + * If there is a window, clear it. + * Otherwise, creates a local window. + * @hide + */ + protected void clearOrCreateLocalWindow() { + if (mWindow == null) { + // If there isn't a window set already it will only be accessed locally + mWindow = new CursorWindow(true /* the window is local only */); + } else { + mWindow.clear(); + } + } + + /** @hide */ + @Override + protected void onDeactivateOrClose() { + super.onDeactivateOrClose(); + closeWindow(); + } } diff --git a/core/java/android/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java index 9925a9a..4fada8c 100644 --- a/core/java/android/database/BulkCursorNative.java +++ b/core/java/android/database/BulkCursorNative.java @@ -62,13 +62,13 @@ public abstract class BulkCursorNative extends Binder implements IBulkCursor data.enforceInterface(IBulkCursor.descriptor); int startPos = data.readInt(); CursorWindow window = getWindow(startPos); + reply.writeNoException(); if (window == null) { reply.writeInt(0); - return true; + } else { + reply.writeInt(1); + window.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } - reply.writeNoException(); - reply.writeInt(1); - window.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); return true; } diff --git a/core/java/android/database/BulkCursorToCursorAdaptor.java b/core/java/android/database/BulkCursorToCursorAdaptor.java index 9c1b26d..cbdd07fb 100644 --- a/core/java/android/database/BulkCursorToCursorAdaptor.java +++ b/core/java/android/database/BulkCursorToCursorAdaptor.java @@ -21,40 +21,24 @@ import android.os.RemoteException; import android.util.Log; /** - * Adapts an {@link IBulkCursor} to a {@link Cursor} for use in the local - * process. + * Adapts an {@link IBulkCursor} to a {@link Cursor} for use in the local process. * * {@hide} */ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { private static final String TAG = "BulkCursor"; - private SelfContentObserver mObserverBridge; + private SelfContentObserver mObserverBridge = new SelfContentObserver(this); private IBulkCursor mBulkCursor; private int mCount; private String[] mColumns; private boolean mWantsAllOnMoveCalls; - public void set(IBulkCursor bulkCursor) { - mBulkCursor = bulkCursor; - - try { - mCount = mBulkCursor.count(); - mWantsAllOnMoveCalls = mBulkCursor.getWantsAllOnMoveCalls(); - - // Search for the rowID column index and set it for our parent - mColumns = mBulkCursor.getColumnNames(); - mRowIdColumnIndex = findRowIdColumnIndex(mColumns); - } catch (RemoteException ex) { - Log.e(TAG, "Setup failed because the remote process is dead"); - } - } - /** - * Version of set() that does fewer Binder calls if the caller - * already knows BulkCursorToCursorAdaptor's properties. + * Initializes the adaptor. + * Must be called before first use. */ - public void set(IBulkCursor bulkCursor, int count, int idIndex) { + public void initialize(IBulkCursor bulkCursor, int count, int idIndex) { mBulkCursor = bulkCursor; mColumns = null; // lazily retrieved mCount = count; @@ -80,31 +64,37 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { * * @return A SelfContentObserver hooked up to this Cursor */ - public synchronized IContentObserver getObserver() { - if (mObserverBridge == null) { - mObserverBridge = new SelfContentObserver(this); - } + public IContentObserver getObserver() { return mObserverBridge.getContentObserver(); } + private void throwIfCursorIsClosed() { + if (mBulkCursor == null) { + throw new StaleDataException("Attempted to access a cursor after it has been closed."); + } + } + @Override public int getCount() { + throwIfCursorIsClosed(); return mCount; } @Override public boolean onMove(int oldPosition, int newPosition) { + throwIfCursorIsClosed(); + try { // Make sure we have the proper window if (mWindow != null) { if (newPosition < mWindow.getStartPosition() || newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) { - mWindow = mBulkCursor.getWindow(newPosition); + setWindow(mBulkCursor.getWindow(newPosition)); } else if (mWantsAllOnMoveCalls) { mBulkCursor.onMove(newPosition); } } else { - mWindow = mBulkCursor.getWindow(newPosition); + setWindow(mBulkCursor.getWindow(newPosition)); } } catch (RemoteException ex) { // We tried to get a window and failed @@ -126,44 +116,54 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { // which is what actually makes the data set invalid. super.deactivate(); - try { - mBulkCursor.deactivate(); - } catch (RemoteException ex) { - Log.w(TAG, "Remote process exception when deactivating"); + if (mBulkCursor != null) { + try { + mBulkCursor.deactivate(); + } catch (RemoteException ex) { + Log.w(TAG, "Remote process exception when deactivating"); + } } - mWindow = null; } @Override public void close() { super.close(); - try { - mBulkCursor.close(); - } catch (RemoteException ex) { - Log.w(TAG, "Remote process exception when closing"); + + if (mBulkCursor != null) { + try { + mBulkCursor.close(); + } catch (RemoteException ex) { + Log.w(TAG, "Remote process exception when closing"); + } finally { + mBulkCursor = null; + } } - mWindow = null; } @Override public boolean requery() { + throwIfCursorIsClosed(); + try { - int oldCount = mCount; - //TODO get the window from a pool somewhere to avoid creating the memory dealer - mCount = mBulkCursor.requery(getObserver(), new CursorWindow( - false /* the window will be accessed across processes */)); - if (mCount != -1) { - mPos = -1; - closeWindow(); - - // super.requery() will call onChanged. Do it here instead of relying on the - // observer from the far side so that observers can see a correct value for mCount - // when responding to onChanged. - super.requery(); - return true; - } else { - deactivate(); - return false; + CursorWindow newWindow = new CursorWindow(false /* create a remote window */); + try { + mCount = mBulkCursor.requery(getObserver(), newWindow); + if (mCount != -1) { + mPos = -1; + closeWindow(); + + // super.requery() will call onChanged. Do it here instead of relying on the + // observer from the far side so that observers can see a correct value for mCount + // when responding to onChanged. + super.requery(); + return true; + } else { + deactivate(); + return false; + } + } finally { + // Don't take ownership of the window until the next call to onMove. + newWindow.close(); } } catch (Exception ex) { Log.e(TAG, "Unable to requery because the remote process exception " + ex.getMessage()); @@ -174,6 +174,8 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { @Override public String[] getColumnNames() { + throwIfCursorIsClosed(); + if (mColumns == null) { try { mColumns = mBulkCursor.getColumnNames(); @@ -187,6 +189,8 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { @Override public Bundle getExtras() { + throwIfCursorIsClosed(); + try { return mBulkCursor.getExtras(); } catch (RemoteException e) { @@ -198,6 +202,8 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { @Override public Bundle respond(Bundle extras) { + throwIfCursorIsClosed(); + try { return mBulkCursor.respond(extras); } catch (RemoteException e) { diff --git a/core/java/android/database/CrossProcessCursor.java b/core/java/android/database/CrossProcessCursor.java index 77ba3a5..8e6a5aa 100644 --- a/core/java/android/database/CrossProcessCursor.java +++ b/core/java/android/database/CrossProcessCursor.java @@ -16,7 +16,7 @@ package android.database; -public interface CrossProcessCursor extends Cursor{ +public interface CrossProcessCursor extends Cursor { /** * returns a pre-filled window, return NULL if no such window */ diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java index 8fa4d3b..a65b3b3 100644 --- a/core/java/android/database/CursorToBulkCursorAdaptor.java +++ b/core/java/android/database/CursorToBulkCursorAdaptor.java @@ -24,19 +24,37 @@ import android.util.Log; /** * Wraps a BulkCursor around an existing Cursor making it remotable. + * <p> + * If the wrapped cursor is a {@link AbstractWindowedCursor} then it owns + * the cursor window. Otherwise, the adaptor takes ownership of the + * cursor itself and ensures it gets closed as needed during deactivation + * and requeries. + * </p> * * {@hide} */ public final class CursorToBulkCursorAdaptor extends BulkCursorNative implements IBinder.DeathRecipient { private static final String TAG = "Cursor"; - private final CrossProcessCursor mCursor; - private CursorWindow mWindow; + + private final Object mLock = new Object(); private final String mProviderName; private ContentObserverProxy mObserver; - private static final class ContentObserverProxy extends ContentObserver - { + /** + * The cursor that is being adapted. + * This field is set to null when the cursor is closed. + */ + private CrossProcessCursor mCursor; + + /** + * The cursor window used by the cross process cursor. + * This field is always null for abstract windowed cursors since they are responsible + * for managing the lifetime of their window. + */ + private CursorWindow mWindowForNonWindowedCursor; + + private static final class ContentObserverProxy extends ContentObserver { protected IContentObserver mRemote; public ContentObserverProxy(IContentObserver remoteObserver, DeathRecipient recipient) { @@ -70,7 +88,7 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative } public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer, String providerName, - boolean allowWrite, CursorWindow window) { + CursorWindow window) { try { mCursor = (CrossProcessCursor) cursor; if (mCursor instanceof AbstractWindowedCursor) { @@ -81,90 +99,167 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative + providerName, new RuntimeException()); } } - windowedCursor.setWindow(window); + windowedCursor.setWindow(window); // cursor takes ownership of window } else { - mWindow = window; + mWindowForNonWindowedCursor = window; // we own the window mCursor.fillWindow(0, window); } } catch (ClassCastException e) { // TODO Implement this case. + window.close(); throw new UnsupportedOperationException( "Only CrossProcessCursor cursors are supported across process for now", e); } mProviderName = providerName; - createAndRegisterObserverProxy(observer); + synchronized (mLock) { + createAndRegisterObserverProxyLocked(observer); + } } - + + private void closeCursorAndWindowLocked() { + if (mCursor != null) { + unregisterObserverProxyLocked(); + mCursor.close(); + mCursor = null; + } + + if (mWindowForNonWindowedCursor != null) { + mWindowForNonWindowedCursor.close(); + mWindowForNonWindowedCursor = null; + } + } + + private void throwIfCursorIsClosed() { + if (mCursor == null) { + throw new StaleDataException("Attempted to access a cursor after it has been closed."); + } + } + + @Override public void binderDied() { - mCursor.close(); - if (mWindow != null) { - mWindow.close(); + synchronized (mLock) { + closeCursorAndWindowLocked(); } } - + + @Override public CursorWindow getWindow(int startPos) { - mCursor.moveToPosition(startPos); - - if (mWindow != null) { - if (startPos < mWindow.getStartPosition() || - startPos >= (mWindow.getStartPosition() + mWindow.getNumRows())) { - mCursor.fillWindow(startPos, mWindow); - } - return mWindow; - } else { - return ((AbstractWindowedCursor)mCursor).getWindow(); + synchronized (mLock) { + throwIfCursorIsClosed(); + + mCursor.moveToPosition(startPos); + + final CursorWindow window; + if (mCursor instanceof AbstractWindowedCursor) { + window = ((AbstractWindowedCursor)mCursor).getWindow(); + } else { + window = mWindowForNonWindowedCursor; + if (window != null + && (startPos < window.getStartPosition() || + startPos >= (window.getStartPosition() + window.getNumRows()))) { + mCursor.fillWindow(startPos, window); + } + } + + // Acquire a reference before returning from this RPC. + // The Binder proxy will decrement the reference count again as part of writing + // the CursorWindow to the reply parcel as a return value. + if (window != null) { + window.acquireReference(); + } + return window; } } + @Override public void onMove(int position) { - mCursor.onMove(mCursor.getPosition(), position); + synchronized (mLock) { + throwIfCursorIsClosed(); + + mCursor.onMove(mCursor.getPosition(), position); + } } + @Override public int count() { - return mCursor.getCount(); + synchronized (mLock) { + throwIfCursorIsClosed(); + + return mCursor.getCount(); + } } + @Override public String[] getColumnNames() { - return mCursor.getColumnNames(); + synchronized (mLock) { + throwIfCursorIsClosed(); + + return mCursor.getColumnNames(); + } } + @Override public void deactivate() { - maybeUnregisterObserverProxy(); - mCursor.deactivate(); + synchronized (mLock) { + if (mCursor != null) { + unregisterObserverProxyLocked(); + mCursor.deactivate(); + } + } } + @Override public void close() { - maybeUnregisterObserverProxy(); - mCursor.close(); + synchronized (mLock) { + closeCursorAndWindowLocked(); + } } + @Override public int requery(IContentObserver observer, CursorWindow window) { - if (mWindow == null) { - ((AbstractWindowedCursor)mCursor).setWindow(window); - } - try { - if (!mCursor.requery()) { - return -1; + synchronized (mLock) { + throwIfCursorIsClosed(); + + if (mCursor instanceof AbstractWindowedCursor) { + ((AbstractWindowedCursor) mCursor).setWindow(window); + } else { + if (mWindowForNonWindowedCursor != null) { + mWindowForNonWindowedCursor.close(); + } + mWindowForNonWindowedCursor = window; } - } catch (IllegalStateException e) { - IllegalStateException leakProgram = new IllegalStateException( - mProviderName + " Requery misuse db, mCursor isClosed:" + - mCursor.isClosed(), e); - throw leakProgram; - } - - if (mWindow != null) { - mCursor.fillWindow(0, window); - mWindow = window; + + try { + if (!mCursor.requery()) { + return -1; + } + } catch (IllegalStateException e) { + IllegalStateException leakProgram = new IllegalStateException( + mProviderName + " Requery misuse db, mCursor isClosed:" + + mCursor.isClosed(), e); + throw leakProgram; + } + + if (!(mCursor instanceof AbstractWindowedCursor)) { + if (window != null) { + mCursor.fillWindow(0, window); + } + } + + unregisterObserverProxyLocked(); + createAndRegisterObserverProxyLocked(observer); + return mCursor.getCount(); } - maybeUnregisterObserverProxy(); - createAndRegisterObserverProxy(observer); - return mCursor.getCount(); } + @Override public boolean getWantsAllOnMoveCalls() { - return mCursor.getWantsAllOnMoveCalls(); + synchronized (mLock) { + throwIfCursorIsClosed(); + + return mCursor.getWantsAllOnMoveCalls(); + } } /** @@ -173,7 +268,7 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative * @param observer the IContentObserver that wants to monitor the cursor * @throws IllegalStateException if an observer is already registered */ - private void createAndRegisterObserverProxy(IContentObserver observer) { + private void createAndRegisterObserverProxyLocked(IContentObserver observer) { if (mObserver != null) { throw new IllegalStateException("an observer is already registered"); } @@ -182,7 +277,7 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative } /** Unregister the observer if it is already registered. */ - private void maybeUnregisterObserverProxy() { + private void unregisterObserverProxyLocked() { if (mObserver != null) { mCursor.unregisterContentObserver(mObserver); mObserver.unlinkToDeath(this); @@ -190,11 +285,21 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative } } + @Override public Bundle getExtras() { - return mCursor.getExtras(); + synchronized (mLock) { + throwIfCursorIsClosed(); + + return mCursor.getExtras(); + } } + @Override public Bundle respond(Bundle extras) { - return mCursor.respond(extras); + synchronized (mLock) { + throwIfCursorIsClosed(); + + return mCursor.respond(extras); + } } } diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java index 2e3ef28..5a91b80 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -16,6 +16,8 @@ package android.database; +import dalvik.system.CloseGuard; + import android.content.res.Resources; import android.database.sqlite.SQLiteClosable; import android.database.sqlite.SQLiteException; @@ -48,6 +50,8 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { private int mStartPos; + private final CloseGuard mCloseGuard = CloseGuard.get(); + private static native int nativeInitializeEmpty(int cursorWindowSize, boolean localOnly); private static native int nativeInitializeFromBinder(IBinder nativeBinder); private static native void nativeDispose(int windowPtr); @@ -91,6 +95,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { throw new CursorWindowAllocationException("Cursor window allocation of " + (sCursorWindowSize / 1024) + " kb failed. " + printStats()); } + mCloseGuard.open("close"); recordNewWindow(Binder.getCallingPid(), mWindowPtr); } @@ -102,11 +107,15 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { throw new CursorWindowAllocationException("Cursor window could not be " + "created from binder."); } + mCloseGuard.open("close"); } @Override protected void finalize() throws Throwable { try { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } dispose(); } finally { super.finalize(); @@ -114,6 +123,9 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { } private void dispose() { + if (mCloseGuard != null) { + mCloseGuard.close(); + } if (mWindowPtr != 0) { recordClosingOfWindow(mWindowPtr); nativeDispose(mWindowPtr); @@ -677,6 +689,10 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeStrongBinder(nativeGetBinder(mWindowPtr)); dest.writeInt(mStartPos); + + if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) { + releaseReference(); + } } @Override diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java index 81fe824..9d7e152 100644 --- a/core/java/android/database/sqlite/SQLiteCursor.java +++ b/core/java/android/database/sqlite/SQLiteCursor.java @@ -89,8 +89,6 @@ public class SQLiteCursor extends AbstractWindowedCursor { * @param query the {@link SQLiteQuery} object associated with this cursor object. */ public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) { - // The AbstractCursor constructor needs to do some setup. - super(); if (query == null) { throw new IllegalArgumentException("query object cannot be null"); } @@ -157,12 +155,7 @@ public class SQLiteCursor extends AbstractWindowedCursor { } private void fillWindow(int startPos) { - if (mWindow == null) { - // If there isn't a window set already it will only be accessed locally - mWindow = new CursorWindow(true /* the window is local only */); - } else { - mWindow.clear(); - } + clearOrCreateLocalWindow(); mWindow.setStartPosition(startPos); int count = getQuery().fillWindow(mWindow); if (startPos == 0) { // fillWindow returns count(*) only for startPos = 0 @@ -214,16 +207,9 @@ public class SQLiteCursor extends AbstractWindowedCursor { return mColumns; } - private void deactivateCommon() { - if (false) Log.v(TAG, "<<< Releasing cursor " + this); - closeWindow(); - if (false) Log.v("DatabaseWindow", "closing window in release()"); - } - @Override public void deactivate() { super.deactivate(); - deactivateCommon(); mDriver.cursorDeactivated(); } @@ -231,7 +217,6 @@ public class SQLiteCursor extends AbstractWindowedCursor { public void close() { super.close(); synchronized (this) { - deactivateCommon(); mQuery.close(); mDriver.cursorClosed(); } |