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