content: /*
   * 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);
          }
      }
  }
  