BulkCursorToCursorAdaptor.java revision 0cde89f5f025b7826be009ebb9673b970e180e32
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 17package android.database; 18 19import android.os.Bundle; 20import android.os.RemoteException; 21import android.util.Log; 22 23/** 24 * Adapts an {@link IBulkCursor} to a {@link Cursor} for use in the local process. 25 * 26 * {@hide} 27 */ 28public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { 29 private static final String TAG = "BulkCursor"; 30 31 private SelfContentObserver mObserverBridge = new SelfContentObserver(this); 32 private IBulkCursor mBulkCursor; 33 private int mCount; 34 private String[] mColumns; 35 private boolean mWantsAllOnMoveCalls; 36 37 /** 38 * Initializes the adaptor. 39 * Must be called before first use. 40 */ 41 public void initialize(IBulkCursor bulkCursor, int count, int idIndex, 42 boolean wantsAllOnMoveCalls) { 43 mBulkCursor = bulkCursor; 44 mColumns = null; // lazily retrieved 45 mCount = count; 46 mRowIdColumnIndex = idIndex; 47 mWantsAllOnMoveCalls = wantsAllOnMoveCalls; 48 } 49 50 /** 51 * Returns column index of "_id" column, or -1 if not found. 52 */ 53 public static int findRowIdColumnIndex(String[] columnNames) { 54 int length = columnNames.length; 55 for (int i = 0; i < length; i++) { 56 if (columnNames[i].equals("_id")) { 57 return i; 58 } 59 } 60 return -1; 61 } 62 63 /** 64 * Gets a SelfDataChangeOberserver that can be sent to a remote 65 * process to receive change notifications over IPC. 66 * 67 * @return A SelfContentObserver hooked up to this Cursor 68 */ 69 public IContentObserver getObserver() { 70 return mObserverBridge.getContentObserver(); 71 } 72 73 private void throwIfCursorIsClosed() { 74 if (mBulkCursor == null) { 75 throw new StaleDataException("Attempted to access a cursor after it has been closed."); 76 } 77 } 78 79 @Override 80 public int getCount() { 81 throwIfCursorIsClosed(); 82 return mCount; 83 } 84 85 @Override 86 public boolean onMove(int oldPosition, int newPosition) { 87 throwIfCursorIsClosed(); 88 89 try { 90 // Make sure we have the proper window 91 if (mWindow == null 92 || newPosition < mWindow.getStartPosition() 93 || newPosition >= mWindow.getStartPosition() + mWindow.getNumRows()) { 94 setWindow(mBulkCursor.getWindow(newPosition)); 95 } else if (mWantsAllOnMoveCalls) { 96 mBulkCursor.onMove(newPosition); 97 } 98 } catch (RemoteException ex) { 99 // We tried to get a window and failed 100 Log.e(TAG, "Unable to get window because the remote process is dead"); 101 return false; 102 } 103 104 // Couldn't obtain a window, something is wrong 105 if (mWindow == null) { 106 return false; 107 } 108 109 return true; 110 } 111 112 @Override 113 public void deactivate() { 114 // This will call onInvalidated(), so make sure to do it before calling release, 115 // which is what actually makes the data set invalid. 116 super.deactivate(); 117 118 if (mBulkCursor != null) { 119 try { 120 mBulkCursor.deactivate(); 121 } catch (RemoteException ex) { 122 Log.w(TAG, "Remote process exception when deactivating"); 123 } 124 } 125 } 126 127 @Override 128 public void close() { 129 super.close(); 130 131 if (mBulkCursor != null) { 132 try { 133 mBulkCursor.close(); 134 } catch (RemoteException ex) { 135 Log.w(TAG, "Remote process exception when closing"); 136 } finally { 137 mBulkCursor = null; 138 } 139 } 140 } 141 142 @Override 143 public boolean requery() { 144 throwIfCursorIsClosed(); 145 146 try { 147 mCount = mBulkCursor.requery(getObserver()); 148 if (mCount != -1) { 149 mPos = -1; 150 closeWindow(); 151 152 // super.requery() will call onChanged. Do it here instead of relying on the 153 // observer from the far side so that observers can see a correct value for mCount 154 // when responding to onChanged. 155 super.requery(); 156 return true; 157 } else { 158 deactivate(); 159 return false; 160 } 161 } catch (Exception ex) { 162 Log.e(TAG, "Unable to requery because the remote process exception " + ex.getMessage()); 163 deactivate(); 164 return false; 165 } 166 } 167 168 @Override 169 public String[] getColumnNames() { 170 throwIfCursorIsClosed(); 171 172 if (mColumns == null) { 173 try { 174 mColumns = mBulkCursor.getColumnNames(); 175 } catch (RemoteException ex) { 176 Log.e(TAG, "Unable to fetch column names because the remote process is dead"); 177 return null; 178 } 179 } 180 return mColumns; 181 } 182 183 @Override 184 public Bundle getExtras() { 185 throwIfCursorIsClosed(); 186 187 try { 188 return mBulkCursor.getExtras(); 189 } catch (RemoteException e) { 190 // This should never happen because the system kills processes that are using remote 191 // cursors when the provider process is killed. 192 throw new RuntimeException(e); 193 } 194 } 195 196 @Override 197 public Bundle respond(Bundle extras) { 198 throwIfCursorIsClosed(); 199 200 try { 201 return mBulkCursor.respond(extras); 202 } catch (RemoteException e) { 203 // the system kills processes that are using remote cursors when the provider process 204 // is killed, but this can still happen if this is being called from the system process, 205 // so, better to log and return an empty bundle. 206 Log.w(TAG, "respond() threw RemoteException, returning an empty bundle.", e); 207 return Bundle.EMPTY; 208 } 209 } 210} 211