• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.content;
18 
19 import android.database.Cursor;
20 import android.net.Uri;
21 import android.os.Handler;
22 import android.os.HandlerThread;
23 import android.os.Looper;
24 import android.os.Message;
25 import android.util.Log;
26 
27 import java.lang.ref.WeakReference;
28 
29 /**
30  * A helper class to help make handling asynchronous {@link ContentResolver}
31  * queries easier.
32  */
33 public 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 {
WorkerHandler(Looper looper)62         public WorkerHandler(Looper looper) {
63             super(looper);
64         }
65 
66         @Override
handleMessage(Message msg)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 
AsyncQueryHandler(ContentResolver cr)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 
createHandler(Looper looper)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      */
startQuery(int token, Object cookie, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)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      */
startQueryEntities(int token, Object cookie, Uri uri, String selection, String[] selectionArgs, String orderBy)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      */
cancelOperation(int token)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      */
startInsert(int token, Object cookie, Uri uri, ContentValues initialValues)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      */
startUpdate(int token, Object cookie, Uri uri, ContentValues values, String selection, String[] selectionArgs)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      */
startDelete(int token, Object cookie, Uri uri, String selection, String[] selectionArgs)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      */
onQueryComplete(int token, Object cookie, Cursor cursor)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      */
onQueryEntitiesComplete(int token, Object cookie, EntityIterator iterator)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      */
onInsertComplete(int token, Object cookie, Uri uri)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      */
onUpdateComplete(int token, Object cookie, int result)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      */
onDeleteComplete(int token, Object cookie, int result)389     protected void onDeleteComplete(int token, Object cookie, int result) {
390         // Empty
391     }
392 
393     @Override
handleMessage(Message msg)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