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 
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 {
WorkerHandler(Looper looper)61         public WorkerHandler(Looper looper) {
62             super(looper);
63         }
64 
65         @Override
handleMessage(Message msg)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.w(TAG, "Exception thrown during handling EVENT_ARG_QUERY", e);
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             // passing the original token value back to the caller
110             // on top of the event values in arg1.
111             Message reply = args.handler.obtainMessage(token);
112             reply.obj = args;
113             reply.arg1 = msg.arg1;
114 
115             if (localLOGV) {
116                 Log.d(TAG, "WorkerHandler.handleMsg: msg.arg1=" + msg.arg1
117                         + ", reply.what=" + reply.what);
118             }
119 
120             reply.sendToTarget();
121         }
122     }
123 
AsyncQueryHandler(ContentResolver cr)124     public AsyncQueryHandler(ContentResolver cr) {
125         super();
126         mResolver = new WeakReference<ContentResolver>(cr);
127         synchronized (AsyncQueryHandler.class) {
128             if (sLooper == null) {
129                 HandlerThread thread = new HandlerThread("AsyncQueryWorker");
130                 thread.start();
131 
132                 sLooper = thread.getLooper();
133             }
134         }
135         mWorkerThreadHandler = createHandler(sLooper);
136     }
137 
createHandler(Looper looper)138     protected Handler createHandler(Looper looper) {
139         return new WorkerHandler(looper);
140     }
141 
142     /**
143      * This method begins an asynchronous query. When the query is done
144      * {@link #onQueryComplete} is called.
145      *
146      * @param token A token passed into {@link #onQueryComplete} to identify
147      *  the query.
148      * @param cookie An object that gets passed into {@link #onQueryComplete}
149      * @param uri The URI, using the content:// scheme, for the content to
150      *         retrieve.
151      * @param projection A list of which columns to return. Passing null will
152      *         return all columns, which is discouraged to prevent reading data
153      *         from storage that isn't going to be used.
154      * @param selection A filter declaring which rows to return, formatted as an
155      *         SQL WHERE clause (excluding the WHERE itself). Passing null will
156      *         return all rows for the given URI.
157      * @param selectionArgs You may include ?s in selection, which will be
158      *         replaced by the values from selectionArgs, in the order that they
159      *         appear in the selection. The values will be bound as Strings.
160      * @param orderBy How to order the rows, formatted as an SQL ORDER BY
161      *         clause (excluding the ORDER BY itself). Passing null will use the
162      *         default sort order, which may be unordered.
163      */
startQuery(int token, Object cookie, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)164     public void startQuery(int token, Object cookie, Uri uri,
165             String[] projection, String selection, String[] selectionArgs,
166             String orderBy) {
167         // Use the token as what so cancelOperations works properly
168         Message msg = mWorkerThreadHandler.obtainMessage(token);
169         msg.arg1 = EVENT_ARG_QUERY;
170 
171         WorkerArgs args = new WorkerArgs();
172         args.handler = this;
173         args.uri = uri;
174         args.projection = projection;
175         args.selection = selection;
176         args.selectionArgs = selectionArgs;
177         args.orderBy = orderBy;
178         args.cookie = cookie;
179         msg.obj = args;
180 
181         mWorkerThreadHandler.sendMessage(msg);
182     }
183 
184     /**
185      * Attempts to cancel operation that has not already started. Note that
186      * there is no guarantee that the operation will be canceled. They still may
187      * result in a call to on[Query/Insert/Update/Delete]Complete after this
188      * call has completed.
189      *
190      * @param token The token representing the operation to be canceled.
191      *  If multiple operations have the same token they will all be canceled.
192      */
cancelOperation(int token)193     public final void cancelOperation(int token) {
194         mWorkerThreadHandler.removeMessages(token);
195     }
196 
197     /**
198      * This method begins an asynchronous insert. When the insert operation is
199      * done {@link #onInsertComplete} is called.
200      *
201      * @param token A token passed into {@link #onInsertComplete} to identify
202      *  the insert operation.
203      * @param cookie An object that gets passed into {@link #onInsertComplete}
204      * @param uri the Uri passed to the insert operation.
205      * @param initialValues the ContentValues parameter passed to the insert operation.
206      */
startInsert(int token, Object cookie, Uri uri, ContentValues initialValues)207     public final void startInsert(int token, Object cookie, Uri uri,
208             ContentValues initialValues) {
209         // Use the token as what so cancelOperations works properly
210         Message msg = mWorkerThreadHandler.obtainMessage(token);
211         msg.arg1 = EVENT_ARG_INSERT;
212 
213         WorkerArgs args = new WorkerArgs();
214         args.handler = this;
215         args.uri = uri;
216         args.cookie = cookie;
217         args.values = initialValues;
218         msg.obj = args;
219 
220         mWorkerThreadHandler.sendMessage(msg);
221     }
222 
223     /**
224      * This method begins an asynchronous update. When the update operation is
225      * done {@link #onUpdateComplete} is called.
226      *
227      * @param token A token passed into {@link #onUpdateComplete} to identify
228      *  the update operation.
229      * @param cookie An object that gets passed into {@link #onUpdateComplete}
230      * @param uri the Uri passed to the update operation.
231      * @param values the ContentValues parameter passed to the update operation.
232      */
startUpdate(int token, Object cookie, Uri uri, ContentValues values, String selection, String[] selectionArgs)233     public final void startUpdate(int token, Object cookie, Uri uri,
234             ContentValues values, String selection, String[] selectionArgs) {
235         // Use the token as what so cancelOperations works properly
236         Message msg = mWorkerThreadHandler.obtainMessage(token);
237         msg.arg1 = EVENT_ARG_UPDATE;
238 
239         WorkerArgs args = new WorkerArgs();
240         args.handler = this;
241         args.uri = uri;
242         args.cookie = cookie;
243         args.values = values;
244         args.selection = selection;
245         args.selectionArgs = selectionArgs;
246         msg.obj = args;
247 
248         mWorkerThreadHandler.sendMessage(msg);
249     }
250 
251     /**
252      * This method begins an asynchronous delete. When the delete operation is
253      * done {@link #onDeleteComplete} is called.
254      *
255      * @param token A token passed into {@link #onDeleteComplete} to identify
256      *  the delete operation.
257      * @param cookie An object that gets passed into {@link #onDeleteComplete}
258      * @param uri the Uri passed to the delete operation.
259      * @param selection the where clause.
260      */
startDelete(int token, Object cookie, Uri uri, String selection, String[] selectionArgs)261     public final void startDelete(int token, Object cookie, Uri uri,
262             String selection, String[] selectionArgs) {
263         // Use the token as what so cancelOperations works properly
264         Message msg = mWorkerThreadHandler.obtainMessage(token);
265         msg.arg1 = EVENT_ARG_DELETE;
266 
267         WorkerArgs args = new WorkerArgs();
268         args.handler = this;
269         args.uri = uri;
270         args.cookie = cookie;
271         args.selection = selection;
272         args.selectionArgs = selectionArgs;
273         msg.obj = args;
274 
275         mWorkerThreadHandler.sendMessage(msg);
276     }
277 
278     /**
279      * Called when an asynchronous query is completed.
280      *
281      * @param token the token to identify the query, passed in from
282      *            {@link #startQuery}.
283      * @param cookie the cookie object passed in from {@link #startQuery}.
284      * @param cursor The cursor holding the results from the query.
285      */
onQueryComplete(int token, Object cookie, Cursor cursor)286     protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
287         // Empty
288     }
289 
290     /**
291      * Called when an asynchronous insert is completed.
292      *
293      * @param token the token to identify the query, passed in from
294      *        {@link #startInsert}.
295      * @param cookie the cookie object that's passed in from
296      *        {@link #startInsert}.
297      * @param uri the uri returned from the insert operation.
298      */
onInsertComplete(int token, Object cookie, Uri uri)299     protected void onInsertComplete(int token, Object cookie, Uri uri) {
300         // Empty
301     }
302 
303     /**
304      * Called when an asynchronous update is completed.
305      *
306      * @param token the token to identify the query, passed in from
307      *        {@link #startUpdate}.
308      * @param cookie the cookie object that's passed in from
309      *        {@link #startUpdate}.
310      * @param result the result returned from the update operation
311      */
onUpdateComplete(int token, Object cookie, int result)312     protected void onUpdateComplete(int token, Object cookie, int result) {
313         // Empty
314     }
315 
316     /**
317      * Called when an asynchronous delete is completed.
318      *
319      * @param token the token to identify the query, passed in from
320      *        {@link #startDelete}.
321      * @param cookie the cookie object that's passed in from
322      *        {@link #startDelete}.
323      * @param result the result returned from the delete operation
324      */
onDeleteComplete(int token, Object cookie, int result)325     protected void onDeleteComplete(int token, Object cookie, int result) {
326         // Empty
327     }
328 
329     @Override
handleMessage(Message msg)330     public void handleMessage(Message msg) {
331         WorkerArgs args = (WorkerArgs) msg.obj;
332 
333         if (localLOGV) {
334             Log.d(TAG, "AsyncQueryHandler.handleMessage: msg.what=" + msg.what
335                     + ", msg.arg1=" + msg.arg1);
336         }
337 
338         int token = msg.what;
339         int event = msg.arg1;
340 
341         // pass token back to caller on each callback.
342         switch (event) {
343             case EVENT_ARG_QUERY:
344                 onQueryComplete(token, args.cookie, (Cursor) args.result);
345                 break;
346 
347             case EVENT_ARG_INSERT:
348                 onInsertComplete(token, args.cookie, (Uri) args.result);
349                 break;
350 
351             case EVENT_ARG_UPDATE:
352                 onUpdateComplete(token, args.cookie, (Integer) args.result);
353                 break;
354 
355             case EVENT_ARG_DELETE:
356                 onDeleteComplete(token, args.cookie, (Integer) args.result);
357                 break;
358         }
359     }
360 }
361