Home | History | Annotate | Download | only in content
      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 {
     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.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 
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    325     protected void onDeleteComplete(int token, Object cookie, int result) {
    326         // Empty
    327     }
    328 
    329     @Override
    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