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.Bundle;
20 import android.os.RemoteException;
21 import android.util.Log;
22 
23 /**
24  * Adapts an {@link IBulkCursor} to a {@link Cursor} for use in the local process.
25  *
26  * {@hide}
27  */
28 public 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      */
initialize(BulkCursorDescriptor d)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      */
getObserver()57     public IContentObserver getObserver() {
58         return mObserverBridge.getContentObserver();
59     }
60 
throwIfCursorIsClosed()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
getCount()68     public int getCount() {
69         throwIfCursorIsClosed();
70         return mCount;
71     }
72 
73     @Override
onMove(int oldPosition, int newPosition)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
deactivate()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
close()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
requery()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
getColumnNames()157     public String[] getColumnNames() {
158         throwIfCursorIsClosed();
159 
160         return mColumns;
161     }
162 
163     @Override
getExtras()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
respond(Bundle extras)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