AsyncQueryHandler.java revision 3b7c3c6b551b95c58849e40b53b01d38714bb782
1/*
2 * Copyright (C) 2007 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.content;
18
19import android.database.Cursor;
20import android.net.Uri;
21import android.os.Handler;
22import android.os.HandlerThread;
23import android.os.Looper;
24import android.os.Message;
25import android.util.Log;
26
27import java.lang.ref.WeakReference;
28
29/**
30 * A helper class to help make handling asynchronous {@link ContentResolver}
31 * queries easier.
32 */
33public abstract class AsyncQueryHandler extends Handler {
34    private static final String TAG = "AsyncQuery";
35    private static final boolean localLOGV = false;
36
37    private static final int EVENT_ARG_QUERY = 1;
38    private static final int EVENT_ARG_INSERT = 2;
39    private static final int EVENT_ARG_UPDATE = 3;
40    private static final int EVENT_ARG_DELETE = 4;
41
42    /* package */ final WeakReference<ContentResolver> mResolver;
43
44    private static Looper sLooper = null;
45
46    private Handler mWorkerThreadHandler;
47
48    protected static final class WorkerArgs {
49        public Uri uri;
50        public Handler handler;
51        public String[] projection;
52        public String selection;
53        public String[] selectionArgs;
54        public String orderBy;
55        public Object result;
56        public Object cookie;
57        public ContentValues values;
58    }
59
60    protected class WorkerHandler extends Handler {
61        public WorkerHandler(Looper looper) {
62            super(looper);
63        }
64
65        @Override
66        public void handleMessage(Message msg) {
67            final ContentResolver resolver = mResolver.get();
68            if (resolver == null) return;
69
70            WorkerArgs args = (WorkerArgs) msg.obj;
71
72            int token = msg.what;
73            int event = msg.arg1;
74
75            switch (event) {
76                case EVENT_ARG_QUERY:
77                    Cursor cursor;
78                    try {
79                        cursor = resolver.query(args.uri, args.projection,
80                                args.selection, args.selectionArgs,
81                                args.orderBy);
82                        // Calling getCount() causes the cursor window to be filled,
83                        // which will make the first access on the main thread a lot faster.
84                        if (cursor != null) {
85                            cursor.getCount();
86                        }
87                    } catch (Exception e) {
88                        Log.d(TAG, e.toString());
89                        cursor = null;
90                    }
91
92                    args.result = cursor;
93                    break;
94
95                case EVENT_ARG_INSERT:
96                    args.result = resolver.insert(args.uri, args.values);
97                    break;
98
99                case EVENT_ARG_UPDATE:
100                    args.result = resolver.update(args.uri, args.values, args.selection,
101                            args.selectionArgs);
102                    break;
103
104                case EVENT_ARG_DELETE:
105                    args.result = resolver.delete(args.uri, args.selection, args.selectionArgs);
106                    break;
107
108            }
109
110            // passing the original token value back to the caller
111            // on top of the event values in arg1.
112            Message reply = args.handler.obtainMessage(token);
113            reply.obj = args;
114            reply.arg1 = msg.arg1;
115
116            if (localLOGV) {
117                Log.d(TAG, "WorkerHandler.handleMsg: msg.arg1=" + msg.arg1
118                        + ", reply.what=" + reply.what);
119            }
120
121            reply.sendToTarget();
122        }
123    }
124
125    public AsyncQueryHandler(ContentResolver cr) {
126        super();
127        mResolver = new WeakReference<ContentResolver>(cr);
128        synchronized (AsyncQueryHandler.class) {
129            if (sLooper == null) {
130                HandlerThread thread = new HandlerThread("AsyncQueryWorker");
131                thread.start();
132
133                sLooper = thread.getLooper();
134            }
135        }
136        mWorkerThreadHandler = createHandler(sLooper);
137    }
138
139    protected Handler createHandler(Looper looper) {
140        return new WorkerHandler(looper);
141    }
142
143    /**
144     * This method begins an asynchronous query. When the query is done
145     * {@link #onQueryComplete} is called.
146     *
147     * @param token A token passed into {@link #onQueryComplete} to identify
148     *  the query.
149     * @param cookie An object that gets passed into {@link #onQueryComplete}
150     * @param uri The URI, using the content:// scheme, for the content to
151     *         retrieve.
152     * @param projection A list of which columns to return. Passing null will
153     *         return all columns, which is discouraged to prevent reading data
154     *         from storage that isn't going to be used.
155     * @param selection A filter declaring which rows to return, formatted as an
156     *         SQL WHERE clause (excluding the WHERE itself). Passing null will
157     *         return all rows for the given URI.
158     * @param selectionArgs You may include ?s in selection, which will be
159     *         replaced by the values from selectionArgs, in the order that they
160     *         appear in the selection. The values will be bound as Strings.
161     * @param orderBy How to order the rows, formatted as an SQL ORDER BY
162     *         clause (excluding the ORDER BY itself). Passing null will use the
163     *         default sort order, which may be unordered.
164     */
165    public void startQuery(int token, Object cookie, Uri uri,
166            String[] projection, String selection, String[] selectionArgs,
167            String orderBy) {
168        // Use the token as what so cancelOperations works properly
169        Message msg = mWorkerThreadHandler.obtainMessage(token);
170        msg.arg1 = EVENT_ARG_QUERY;
171
172        WorkerArgs args = new WorkerArgs();
173        args.handler = this;
174        args.uri = uri;
175        args.projection = projection;
176        args.selection = selection;
177        args.selectionArgs = selectionArgs;
178        args.orderBy = orderBy;
179        args.cookie = cookie;
180        msg.obj = args;
181
182        mWorkerThreadHandler.sendMessage(msg);
183    }
184
185    /**
186     * Attempts to cancel operation that has not already started. Note that
187     * there is no guarantee that the operation will be canceled. They still may
188     * result in a call to on[Query/Insert/Update/Delete]Complete after this
189     * call has completed.
190     *
191     * @param token The token representing the operation to be canceled.
192     *  If multiple operations have the same token they will all be canceled.
193     */
194    public final void cancelOperation(int token) {
195        mWorkerThreadHandler.removeMessages(token);
196    }
197
198    /**
199     * This method begins an asynchronous insert. When the insert operation is
200     * done {@link #onInsertComplete} is called.
201     *
202     * @param token A token passed into {@link #onInsertComplete} to identify
203     *  the insert operation.
204     * @param cookie An object that gets passed into {@link #onInsertComplete}
205     * @param uri the Uri passed to the insert operation.
206     * @param initialValues the ContentValues parameter passed to the insert operation.
207     */
208    public final void startInsert(int token, Object cookie, Uri uri,
209            ContentValues initialValues) {
210        // Use the token as what so cancelOperations works properly
211        Message msg = mWorkerThreadHandler.obtainMessage(token);
212        msg.arg1 = EVENT_ARG_INSERT;
213
214        WorkerArgs args = new WorkerArgs();
215        args.handler = this;
216        args.uri = uri;
217        args.cookie = cookie;
218        args.values = initialValues;
219        msg.obj = args;
220
221        mWorkerThreadHandler.sendMessage(msg);
222    }
223
224    /**
225     * This method begins an asynchronous update. When the update operation is
226     * done {@link #onUpdateComplete} is called.
227     *
228     * @param token A token passed into {@link #onUpdateComplete} to identify
229     *  the update operation.
230     * @param cookie An object that gets passed into {@link #onUpdateComplete}
231     * @param uri the Uri passed to the update operation.
232     * @param values the ContentValues parameter passed to the update operation.
233     */
234    public final void startUpdate(int token, Object cookie, Uri uri,
235            ContentValues values, String selection, String[] selectionArgs) {
236        // Use the token as what so cancelOperations works properly
237        Message msg = mWorkerThreadHandler.obtainMessage(token);
238        msg.arg1 = EVENT_ARG_UPDATE;
239
240        WorkerArgs args = new WorkerArgs();
241        args.handler = this;
242        args.uri = uri;
243        args.cookie = cookie;
244        args.values = values;
245        args.selection = selection;
246        args.selectionArgs = selectionArgs;
247        msg.obj = args;
248
249        mWorkerThreadHandler.sendMessage(msg);
250    }
251
252    /**
253     * This method begins an asynchronous delete. When the delete operation is
254     * done {@link #onDeleteComplete} is called.
255     *
256     * @param token A token passed into {@link #onDeleteComplete} to identify
257     *  the delete operation.
258     * @param cookie An object that gets passed into {@link #onDeleteComplete}
259     * @param uri the Uri passed to the delete operation.
260     * @param selection the where clause.
261     */
262    public final void startDelete(int token, Object cookie, Uri uri,
263            String selection, String[] selectionArgs) {
264        // Use the token as what so cancelOperations works properly
265        Message msg = mWorkerThreadHandler.obtainMessage(token);
266        msg.arg1 = EVENT_ARG_DELETE;
267
268        WorkerArgs args = new WorkerArgs();
269        args.handler = this;
270        args.uri = uri;
271        args.cookie = cookie;
272        args.selection = selection;
273        args.selectionArgs = selectionArgs;
274        msg.obj = args;
275
276        mWorkerThreadHandler.sendMessage(msg);
277    }
278
279    /**
280     * Called when an asynchronous query is completed.
281     *
282     * @param token the token to identify the query, passed in from
283     *        {@link #startQuery}.
284     * @param cookie the cookie object that's passed in from {@link #startQuery}.
285     * @param cursor The cursor holding the results from the query.
286     */
287    protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
288        // Empty
289    }
290
291    /**
292     * Called when an asynchronous insert is completed.
293     *
294     * @param token the token to identify the query, passed in from
295     *        {@link #startInsert}.
296     * @param cookie the cookie object that's passed in from
297     *        {@link #startInsert}.
298     * @param uri the uri returned from the insert operation.
299     */
300    protected void onInsertComplete(int token, Object cookie, Uri uri) {
301        // Empty
302    }
303
304    /**
305     * Called when an asynchronous update is completed.
306     *
307     * @param token the token to identify the query, passed in from
308     *        {@link #startUpdate}.
309     * @param cookie the cookie object that's passed in from
310     *        {@link #startUpdate}.
311     * @param result the result returned from the update operation
312     */
313    protected void onUpdateComplete(int token, Object cookie, int result) {
314        // Empty
315    }
316
317    /**
318     * Called when an asynchronous delete is completed.
319     *
320     * @param token the token to identify the query, passed in from
321     *        {@link #startDelete}.
322     * @param cookie the cookie object that's passed in from
323     *        {@link #startDelete}.
324     * @param result the result returned from the delete operation
325     */
326    protected void onDeleteComplete(int token, Object cookie, int result) {
327        // Empty
328    }
329
330    @Override
331    public void handleMessage(Message msg) {
332        WorkerArgs args = (WorkerArgs) msg.obj;
333
334        if (localLOGV) {
335            Log.d(TAG, "AsyncQueryHandler.handleMessage: msg.what=" + msg.what
336                    + ", msg.arg1=" + msg.arg1);
337        }
338
339        int token = msg.what;
340        int event = msg.arg1;
341
342        // pass token back to caller on each callback.
343        switch (event) {
344            case EVENT_ARG_QUERY:
345                onQueryComplete(token, args.cookie, (Cursor) args.result);
346                break;
347
348            case EVENT_ARG_INSERT:
349                onInsertComplete(token, args.cookie, (Uri) args.result);
350                break;
351
352            case EVENT_ARG_UPDATE:
353                onUpdateComplete(token, args.cookie, (Integer) args.result);
354                break;
355
356            case EVENT_ARG_DELETE:
357                onDeleteComplete(token, args.cookie, (Integer) args.result);
358                break;
359        }
360    }
361}
362