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.RemoteException; 20import android.os.Bundle; 21import android.util.Log; 22 23import 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 */ 31public 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