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.os.RemoteException;
     20 import android.os.Bundle;
     21 import android.util.Log;
     22 
     23 import java.util.Map;
     24 
     25 /**
     26  * Adapts an {@link IBulkCursor} to a {@link Cursor} for use in the local
     27  * process.
     28  *
     29  * {@hide}
     30  */
     31 public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
     32     private static final String TAG = "BulkCursor";
     33 
     34     private SelfContentObserver mObserverBridge;
     35     private IBulkCursor mBulkCursor;
     36     private int mCount;
     37     private String[] mColumns;
     38     private boolean mWantsAllOnMoveCalls;
     39 
     40     public void set(IBulkCursor bulkCursor) {
     41         mBulkCursor = bulkCursor;
     42 
     43         try {
     44             mCount = mBulkCursor.count();
     45             mWantsAllOnMoveCalls = mBulkCursor.getWantsAllOnMoveCalls();
     46 
     47             // Search for the rowID column index and set it for our parent
     48             mColumns = mBulkCursor.getColumnNames();
     49             mRowIdColumnIndex = findRowIdColumnIndex(mColumns);
     50         } catch (RemoteException ex) {
     51             Log.e(TAG, "Setup failed because the remote process is dead");
     52         }
     53     }
     54 
     55     /**
     56      * Version of set() that does fewer Binder calls if the caller
     57      * already knows BulkCursorToCursorAdaptor's properties.
     58      */
     59     public void set(IBulkCursor bulkCursor, int count, int idIndex) {
     60         mBulkCursor = bulkCursor;
     61         mColumns = null;  // lazily retrieved
     62         mCount = count;
     63         mRowIdColumnIndex = idIndex;
     64     }
     65 
     66     /**
     67      * Returns column index of "_id" column, or -1 if not found.
     68      */
     69     public static int findRowIdColumnIndex(String[] columnNames) {
     70         int length = columnNames.length;
     71         for (int i = 0; i < length; i++) {
     72             if (columnNames[i].equals("_id")) {
     73                 return i;
     74             }
     75         }
     76         return -1;
     77     }
     78 
     79     /**
     80      * Gets a SelfDataChangeOberserver that can be sent to a remote
     81      * process to receive change notifications over IPC.
     82      *
     83      * @return A SelfContentObserver hooked up to this Cursor
     84      */
     85     public synchronized IContentObserver getObserver() {
     86         if (mObserverBridge == null) {
     87             mObserverBridge = new SelfContentObserver(this);
     88         }
     89         return mObserverBridge.getContentObserver();
     90     }
     91 
     92     @Override
     93     public int getCount() {
     94         return mCount;
     95     }
     96 
     97     @Override
     98     public boolean onMove(int oldPosition, int newPosition) {
     99         try {
    100             // Make sure we have the proper window
    101             if (mWindow != null) {
    102                 if (newPosition < mWindow.getStartPosition() ||
    103                         newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
    104                     mWindow = mBulkCursor.getWindow(newPosition);
    105                 } else if (mWantsAllOnMoveCalls) {
    106                     mBulkCursor.onMove(newPosition);
    107                 }
    108             } else {
    109                 mWindow = mBulkCursor.getWindow(newPosition);
    110             }
    111         } catch (RemoteException ex) {
    112             // We tried to get a window and failed
    113             Log.e(TAG, "Unable to get window because the remote process is dead");
    114             return false;
    115         }
    116 
    117         // Couldn't obtain a window, something is wrong
    118         if (mWindow == null) {
    119             return false;
    120         }
    121 
    122         return true;
    123     }
    124 
    125     @Override
    126     public void deactivate() {
    127         // This will call onInvalidated(), so make sure to do it before calling release,
    128         // which is what actually makes the data set invalid.
    129         super.deactivate();
    130 
    131         try {
    132             mBulkCursor.deactivate();
    133         } catch (RemoteException ex) {
    134             Log.w(TAG, "Remote process exception when deactivating");
    135         }
    136         mWindow = null;
    137     }
    138 
    139     @Override
    140     public void close() {
    141         super.close();
    142         try {
    143             mBulkCursor.close();
    144         } catch (RemoteException ex) {
    145             Log.w(TAG, "Remote process exception when closing");
    146         }
    147         mWindow = null;
    148     }
    149 
    150     @Override
    151     public boolean requery() {
    152         try {
    153             int oldCount = mCount;
    154             //TODO get the window from a pool somewhere to avoid creating the memory dealer
    155             mCount = mBulkCursor.requery(getObserver(), new CursorWindow(
    156                     false /* the window will be accessed across processes */));
    157             if (mCount != -1) {
    158                 mPos = -1;
    159                 mWindow = null;
    160 
    161                 // super.requery() will call onChanged. Do it here instead of relying on the
    162                 // observer from the far side so that observers can see a correct value for mCount
    163                 // when responding to onChanged.
    164                 super.requery();
    165                 return true;
    166             } else {
    167                 deactivate();
    168                 return false;
    169             }
    170         } catch (Exception ex) {
    171             Log.e(TAG, "Unable to requery because the remote process exception " + ex.getMessage());
    172             deactivate();
    173             return false;
    174         }
    175     }
    176 
    177     /**
    178      * @hide
    179      * @deprecated
    180      */
    181     @Override
    182     public boolean deleteRow() {
    183         try {
    184             boolean result = mBulkCursor.deleteRow(mPos);
    185             if (result != false) {
    186                 // The window contains the old value, discard it
    187                 mWindow = null;
    188 
    189                 // Fix up the position
    190                 mCount = mBulkCursor.count();
    191                 if (mPos < mCount) {
    192                     int oldPos = mPos;
    193                     mPos = -1;
    194                     moveToPosition(oldPos);
    195                 } else {
    196                     mPos = mCount;
    197                 }
    198 
    199                 // Send the change notification
    200                 onChange(true);
    201             }
    202             return result;
    203         } catch (RemoteException ex) {
    204             Log.e(TAG, "Unable to delete row because the remote process is dead");
    205             return false;
    206         }
    207     }
    208 
    209     @Override
    210     public String[] getColumnNames() {
    211         if (mColumns == null) {
    212             try {
    213                 mColumns = mBulkCursor.getColumnNames();
    214             } catch (RemoteException ex) {
    215                 Log.e(TAG, "Unable to fetch column names because the remote process is dead");
    216                 return null;
    217             }
    218         }
    219         return mColumns;
    220     }
    221 
    222     /**
    223      * @hide
    224      * @deprecated
    225      */
    226     @Override
    227     public boolean commitUpdates(Map<? extends Long,
    228             ? extends Map<String,Object>> additionalValues) {
    229         if (!supportsUpdates()) {
    230             Log.e(TAG, "commitUpdates not supported on this cursor, did you include the _id column?");
    231             return false;
    232         }
    233 
    234         synchronized(mUpdatedRows) {
    235             if (additionalValues != null) {
    236                 mUpdatedRows.putAll(additionalValues);
    237             }
    238 
    239             if (mUpdatedRows.size() <= 0) {
    240                 return false;
    241             }
    242 
    243             try {
    244                 boolean result = mBulkCursor.updateRows(mUpdatedRows);
    245 
    246                 if (result == true) {
    247                     mUpdatedRows.clear();
    248 
    249                     // Send the change notification
    250                     onChange(true);
    251                 }
    252                 return result;
    253             } catch (RemoteException ex) {
    254                 Log.e(TAG, "Unable to commit updates because the remote process is dead");
    255                 return false;
    256             }
    257         }
    258     }
    259 
    260     @Override
    261     public Bundle getExtras() {
    262         try {
    263             return mBulkCursor.getExtras();
    264         } catch (RemoteException e) {
    265             // This should never happen because the system kills processes that are using remote
    266             // cursors when the provider process is killed.
    267             throw new RuntimeException(e);
    268         }
    269     }
    270 
    271     @Override
    272     public Bundle respond(Bundle extras) {
    273         try {
    274             return mBulkCursor.respond(extras);
    275         } catch (RemoteException e) {
    276             // the system kills processes that are using remote cursors when the provider process
    277             // is killed, but this can still happen if this is being called from the system process,
    278             // so, better to log and return an empty bundle.
    279             Log.w(TAG, "respond() threw RemoteException, returning an empty bundle.", e);
    280             return Bundle.EMPTY;
    281         }
    282     }
    283 }
    284