/* * 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.os.Bundle; import android.os.RemoteException; import android.util.Log; /** * 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 = new SelfContentObserver(this); private IBulkCursor mBulkCursor; private String[] mColumns; private boolean mWantsAllOnMoveCalls; private int mCount; /** * Initializes the adaptor. * Must be called before first use. */ public void initialize(BulkCursorDescriptor d) { mBulkCursor = d.cursor; mColumns = d.columnNames; mWantsAllOnMoveCalls = d.wantsAllOnMoveCalls; mCount = d.count; if (d.window != null) { setWindow(d.window); } } /** * Gets a SelfDataChangeOberserver that can be sent to a remote * process to receive change notifications over IPC. * * @return A SelfContentObserver hooked up to this Cursor */ 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 || newPosition < mWindow.getStartPosition() || newPosition >= mWindow.getStartPosition() + mWindow.getNumRows()) { setWindow(mBulkCursor.getWindow(newPosition)); } else if (mWantsAllOnMoveCalls) { mBulkCursor.onMove(newPosition); } } catch (RemoteException ex) { // We tried to get a window and failed Log.e(TAG, "Unable to get window because the remote process is dead"); return false; } // Couldn't obtain a window, something is wrong if (mWindow == null) { return false; } return true; } @Override public void deactivate() { // This will call onInvalidated(), so make sure to do it before calling release, // which is what actually makes the data set invalid. super.deactivate(); if (mBulkCursor != null) { try { mBulkCursor.deactivate(); } catch (RemoteException ex) { Log.w(TAG, "Remote process exception when deactivating"); } } } @Override public void close() { super.close(); if (mBulkCursor != null) { try { mBulkCursor.close(); } catch (RemoteException ex) { Log.w(TAG, "Remote process exception when closing"); } finally { mBulkCursor = null; } } } @Override public boolean requery() { throwIfCursorIsClosed(); try { mCount = mBulkCursor.requery(getObserver()); 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; } } catch (Exception ex) { Log.e(TAG, "Unable to requery because the remote process exception " + ex.getMessage()); deactivate(); return false; } } @Override public String[] getColumnNames() { throwIfCursorIsClosed(); return mColumns; } @Override public Bundle getExtras() { throwIfCursorIsClosed(); try { return mBulkCursor.getExtras(); } catch (RemoteException e) { // This should never happen because the system kills processes that are using remote // cursors when the provider process is killed. throw new RuntimeException(e); } } @Override public Bundle respond(Bundle extras) { throwIfCursorIsClosed(); try { return mBulkCursor.respond(extras); } catch (RemoteException e) { // the system kills processes that are using remote cursors when the provider process // is killed, but this can still happen if this is being called from the system process, // so, better to log and return an empty bundle. Log.w(TAG, "respond() threw RemoteException, returning an empty bundle.", e); return Bundle.EMPTY; } } }