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