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