• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
set(IBulkCursor bulkCursor)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      */
set(IBulkCursor bulkCursor, int count, int idIndex)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      */
findRowIdColumnIndex(String[] columnNames)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      */
getObserver()85     public synchronized IContentObserver getObserver() {
86         if (mObserverBridge == null) {
87             mObserverBridge = new SelfContentObserver(this);
88         }
89         return mObserverBridge.getContentObserver();
90     }
91 
92     @Override
getCount()93     public int getCount() {
94         return mCount;
95     }
96 
97     @Override
onMove(int oldPosition, int newPosition)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
deactivate()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
close()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
requery()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
deleteRow()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
getColumnNames()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
commitUpdates(Map<? extends Long, ? extends Map<String,Object>> additionalValues)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
getExtras()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
respond(Bundle extras)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