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