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