BulkCursorToCursorAdaptor.java revision 9ffdfa0c238fce3b85741d7f6828fd484cd8f195
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