/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.database;
import android.net.Uri;
import android.os.*;
/**
* Wraps a BulkCursor around an existing Cursor making it remotable.
* <p>
* If the wrapped cursor returns non-null from {@link CrossProcessCursor#getWindow}
* then it is assumed to own the window. Otherwise, the adaptor provides a
* window to be filled 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 Object mLock = new Object();
private final String mProviderName;
private ContentObserverProxy mObserver;
/**
* The cursor that is being adapted.
* This field is set to null when the cursor is closed.
*/
private CrossProcessCursor mCursor;
/**
* The cursor window that was filled by the cross process cursor in the
* case where the cursor does not support getWindow.
* This field is only ever non-null when the window has actually be filled.
*/
private CursorWindow mFilledWindow;
private static final class ContentObserverProxy extends ContentObserver {
protected IContentObserver mRemote;
public ContentObserverProxy(IContentObserver remoteObserver, DeathRecipient recipient) {
super(null);
mRemote = remoteObserver;
try {
remoteObserver.asBinder().linkToDeath(recipient, 0);
} catch (RemoteException e) {
// Do nothing, the far side is dead
}
}
public boolean unlinkToDeath(DeathRecipient recipient) {
return mRemote.asBinder().unlinkToDeath(recipient, 0);
}
@Override
public boolean deliverSelfNotifications() {
// The far side handles the self notifications.
return false;
}
@Override
public void onChange(boolean selfChange, Uri uri) {
try {
mRemote.onChange(selfChange, uri, android.os.Process.myUid());
} catch (RemoteException ex) {
// Do nothing, the far side is dead
}
}
}
public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer,
String providerName) {
if (cursor instanceof CrossProcessCursor) {
mCursor = (CrossProcessCursor)cursor;
} else {
mCursor = new CrossProcessCursorWrapper(cursor);
}
mProviderName = providerName;
synchronized (mLock) {
createAndRegisterObserverProxyLocked(observer);
}
}
private void closeFilledWindowLocked() {
if (mFilledWindow != null) {
mFilledWindow.close();
mFilledWindow = null;
}
}
private void disposeLocked() {
if (mCursor != null) {
unregisterObserverProxyLocked();
mCursor.close();
mCursor = null;
}
closeFilledWindowLocked();
}
private void throwIfCursorIsClosed() {
if (mCursor == null) {
throw new StaleDataException("Attempted to access a cursor after it has been closed.");
}
}
@Override
public void binderDied() {
synchronized (mLock) {
disposeLocked();
}
}
/**
* Returns an object that contains sufficient metadata to reconstruct
* the cursor remotely. May throw if an error occurs when executing the query
* and obtaining the row count.
*/
public BulkCursorDescriptor getBulkCursorDescriptor() {
synchronized (mLock) {
throwIfCursorIsClosed();
BulkCursorDescriptor d = new BulkCursorDescriptor();
d.cursor = this;
d.columnNames = mCursor.getColumnNames();
d.wantsAllOnMoveCalls = mCursor.getWantsAllOnMoveCalls();
d.count = mCursor.getCount();
d.window = mCursor.getWindow();
if (d.window != null) {
// Acquire a reference to the window because its reference count will be
// decremented when it is returned as part of the binder call reply parcel.
d.window.acquireReference();
}
return d;
}
}
@Override
public CursorWindow getWindow(int position) {
synchronized (mLock) {
throwIfCursorIsClosed();
if (!mCursor.moveToPosition(position)) {
closeFilledWindowLocked();
return null;
}
CursorWindow window = mCursor.getWindow();
if (window != null) {
closeFilledWindowLocked();
} else {
window = mFilledWindow;
if (window == null) {
mFilledWindow = new CursorWindow(mProviderName);
window = mFilledWindow;
} else if (position < window.getStartPosition()
|| position >= window.getStartPosition() + window.getNumRows()) {
window.clear();
}
mCursor.fillWindow(position, window);
}
if (window != null) {
// Acquire a reference to the window because its reference count will be
// decremented when it is returned as part of the binder call reply parcel.
window.acquireReference();
}
return window;
}
}
@Override
public void onMove(int position) {
synchronized (mLock) {
throwIfCursorIsClosed();
mCursor.onMove(mCursor.getPosition(), position);
}
}
@Override
public void deactivate() {
synchronized (mLock) {
if (mCursor != null) {
unregisterObserverProxyLocked();
mCursor.deactivate();
}
closeFilledWindowLocked();
}
}
@Override
public void close() {
synchronized (mLock) {
disposeLocked();
}
}
@Override
public int requery(IContentObserver observer) {
synchronized (mLock) {
throwIfCursorIsClosed();
closeFilledWindowLocked();
try {
if (!mCursor.requery()) {
return -1;
}
} catch (IllegalStateException e) {
IllegalStateException leakProgram = new IllegalStateException(
mProviderName + " Requery misuse db, mCursor isClosed:" +
mCursor.isClosed(), e);
throw leakProgram;
}
unregisterObserverProxyLocked();
createAndRegisterObserverProxyLocked(observer);
return mCursor.getCount();
}
}
/**
* Create a ContentObserver from the observer and register it as an observer on the
* underlying cursor.
* @param observer the IContentObserver that wants to monitor the cursor
* @throws IllegalStateException if an observer is already registered
*/
private void createAndRegisterObserverProxyLocked(IContentObserver observer) {
if (mObserver != null) {
throw new IllegalStateException("an observer is already registered");
}
mObserver = new ContentObserverProxy(observer, this);
mCursor.registerContentObserver(mObserver);
}
/** Unregister the observer if it is already registered. */
private void unregisterObserverProxyLocked() {
if (mObserver != null) {
mCursor.unregisterContentObserver(mObserver);
mObserver.unlinkToDeath(this);
mObserver = null;
}
}
@Override
public Bundle getExtras() {
synchronized (mLock) {
throwIfCursorIsClosed();
return mCursor.getExtras();
}
}
@Override
public Bundle respond(Bundle extras) {
synchronized (mLock) {
throwIfCursorIsClosed();
return mCursor.respond(extras);
}
}
}