Home | History | Annotate | Download | only in calendar
      1 /*
      2  * Copyright (C) 2010 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 com.android.calendar;
     18 
     19 import com.android.calendar.AsyncQueryServiceHelper.OperationInfo;
     20 
     21 import android.content.ContentProviderOperation;
     22 import android.content.ContentProviderResult;
     23 import android.content.ContentResolver;
     24 import android.content.ContentValues;
     25 import android.content.Context;
     26 import android.database.Cursor;
     27 import android.net.Uri;
     28 import android.os.Handler;
     29 import android.os.Message;
     30 import android.util.Log;
     31 
     32 import java.util.ArrayList;
     33 import java.util.concurrent.atomic.AtomicInteger;
     34 
     35 /**
     36  * A helper class that executes {@link ContentResolver} calls in a background
     37  * {@link android.app.Service}. This minimizes the chance of the call getting
     38  * lost because the caller ({@link android.app.Activity}) is killed. It is
     39  * designed for easy migration from {@link android.content.AsyncQueryHandler}
     40  * which calls the {@link ContentResolver} in a background thread. This supports
     41  * query/insert/update/delete and also batch mode i.e.
     42  * {@link ContentProviderOperation}. It also supports delay execution and cancel
     43  * which allows for time-limited undo. Note that there's one queue per
     44  * application which serializes all the calls.
     45  */
     46 public class AsyncQueryService extends Handler {
     47     private static final String TAG = "AsyncQuery";
     48     static final boolean localLOGV = false;
     49 
     50     // Used for generating unique tokens for calls to this service
     51     private static AtomicInteger mUniqueToken = new AtomicInteger(0);
     52 
     53     private Context mContext;
     54     private Handler mHandler = this; // can be overridden for testing
     55 
     56     /**
     57      * Data class which holds into info of the queued operation
     58      */
     59     public static class Operation {
     60         static final int EVENT_ARG_QUERY = 1;
     61         static final int EVENT_ARG_INSERT = 2;
     62         static final int EVENT_ARG_UPDATE = 3;
     63         static final int EVENT_ARG_DELETE = 4;
     64         static final int EVENT_ARG_BATCH = 5;
     65 
     66         /**
     67          * unique identify for cancellation purpose
     68          */
     69         public int token;
     70 
     71         /**
     72          * One of the EVENT_ARG_ constants in the class describing the operation
     73          */
     74         public int op;
     75 
     76         /**
     77          * {@link SystemClock.elapsedRealtime()} based
     78          */
     79         public long scheduledExecutionTime;
     80 
     81         protected static char opToChar(int op) {
     82             switch (op) {
     83                 case Operation.EVENT_ARG_QUERY:
     84                     return 'Q';
     85                 case Operation.EVENT_ARG_INSERT:
     86                     return 'I';
     87                 case Operation.EVENT_ARG_UPDATE:
     88                     return 'U';
     89                 case Operation.EVENT_ARG_DELETE:
     90                     return 'D';
     91                 case Operation.EVENT_ARG_BATCH:
     92                     return 'B';
     93                 default:
     94                     return '?';
     95             }
     96         }
     97 
     98         @Override
     99         public String toString() {
    100             StringBuilder builder = new StringBuilder();
    101             builder.append("Operation [op=");
    102             builder.append(op);
    103             builder.append(", token=");
    104             builder.append(token);
    105             builder.append(", scheduledExecutionTime=");
    106             builder.append(scheduledExecutionTime);
    107             builder.append("]");
    108             return builder.toString();
    109         }
    110     }
    111 
    112     public AsyncQueryService(Context context) {
    113         mContext = context;
    114     }
    115 
    116     /**
    117      * returns a practically unique token for db operations
    118      */
    119     public final int getNextToken() {
    120         return mUniqueToken.getAndIncrement();
    121     }
    122 
    123     /**
    124      * Gets the last delayed operation. It is typically used for canceling.
    125      *
    126      * @return Operation object which contains of the last cancelable operation
    127      */
    128     public final Operation getLastCancelableOperation() {
    129         return AsyncQueryServiceHelper.getLastCancelableOperation();
    130     }
    131 
    132     /**
    133      * Attempts to cancel operation that has not already started. Note that
    134      * there is no guarantee that the operation will be canceled. They still may
    135      * result in a call to on[Query/Insert/Update/Delete/Batch]Complete after
    136      * this call has completed.
    137      *
    138      * @param token The token representing the operation to be canceled. If
    139      *            multiple operations have the same token they will all be
    140      *            canceled.
    141      */
    142     public final int cancelOperation(int token) {
    143         return AsyncQueryServiceHelper.cancelOperation(token);
    144     }
    145 
    146     /**
    147      * This method begins an asynchronous query. When the query is done
    148      * {@link #onQueryComplete} is called.
    149      *
    150      * @param token A token passed into {@link #onQueryComplete} to identify the
    151      *            query.
    152      * @param cookie An object that gets passed into {@link #onQueryComplete}
    153      * @param uri The URI, using the content:// scheme, for the content to
    154      *            retrieve.
    155      * @param projection A list of which columns to return. Passing null will
    156      *            return all columns, which is discouraged to prevent reading
    157      *            data from storage that isn't going to be used.
    158      * @param selection A filter declaring which rows to return, formatted as an
    159      *            SQL WHERE clause (excluding the WHERE itself). Passing null
    160      *            will return all rows for the given URI.
    161      * @param selectionArgs You may include ?s in selection, which will be
    162      *            replaced by the values from selectionArgs, in the order that
    163      *            they appear in the selection. The values will be bound as
    164      *            Strings.
    165      * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
    166      *            (excluding the ORDER BY itself). Passing null will use the
    167      *            default sort order, which may be unordered.
    168      */
    169     public void startQuery(int token, Object cookie, Uri uri, String[] projection,
    170             String selection, String[] selectionArgs, String orderBy) {
    171         OperationInfo info = new OperationInfo();
    172         info.op = Operation.EVENT_ARG_QUERY;
    173         info.resolver = mContext.getContentResolver();
    174 
    175         info.handler = mHandler;
    176         info.token = token;
    177         info.cookie = cookie;
    178         info.uri = uri;
    179         info.projection = projection;
    180         info.selection = selection;
    181         info.selectionArgs = selectionArgs;
    182         info.orderBy = orderBy;
    183 
    184         AsyncQueryServiceHelper.queueOperation(mContext, info);
    185     }
    186 
    187     /**
    188      * This method begins an asynchronous insert. When the insert operation is
    189      * done {@link #onInsertComplete} is called.
    190      *
    191      * @param token A token passed into {@link #onInsertComplete} to identify
    192      *            the insert operation.
    193      * @param cookie An object that gets passed into {@link #onInsertComplete}
    194      * @param uri the Uri passed to the insert operation.
    195      * @param initialValues the ContentValues parameter passed to the insert
    196      *            operation.
    197      * @param delayMillis delay in executing the operation. This operation will
    198      *            execute before the delayed time when another operation is
    199      *            added. Useful for implementing single level undo.
    200      */
    201     public void startInsert(int token, Object cookie, Uri uri, ContentValues initialValues,
    202             long delayMillis) {
    203         OperationInfo info = new OperationInfo();
    204         info.op = Operation.EVENT_ARG_INSERT;
    205         info.resolver = mContext.getContentResolver();
    206         info.handler = mHandler;
    207 
    208         info.token = token;
    209         info.cookie = cookie;
    210         info.uri = uri;
    211         info.values = initialValues;
    212         info.delayMillis = delayMillis;
    213 
    214         AsyncQueryServiceHelper.queueOperation(mContext, info);
    215     }
    216 
    217     /**
    218      * This method begins an asynchronous update. When the update operation is
    219      * done {@link #onUpdateComplete} is called.
    220      *
    221      * @param token A token passed into {@link #onUpdateComplete} to identify
    222      *            the update operation.
    223      * @param cookie An object that gets passed into {@link #onUpdateComplete}
    224      * @param uri the Uri passed to the update operation.
    225      * @param values the ContentValues parameter passed to the update operation.
    226      * @param selection A filter declaring which rows to update, formatted as an
    227      *            SQL WHERE clause (excluding the WHERE itself). Passing null
    228      *            will update all rows for the given URI.
    229      * @param selectionArgs You may include ?s in selection, which will be
    230      *            replaced by the values from selectionArgs, in the order that
    231      *            they appear in the selection. The values will be bound as
    232      *            Strings.
    233      * @param delayMillis delay in executing the operation. This operation will
    234      *            execute before the delayed time when another operation is
    235      *            added. Useful for implementing single level undo.
    236      */
    237     public void startUpdate(int token, Object cookie, Uri uri, ContentValues values,
    238             String selection, String[] selectionArgs, long delayMillis) {
    239         OperationInfo info = new OperationInfo();
    240         info.op = Operation.EVENT_ARG_UPDATE;
    241         info.resolver = mContext.getContentResolver();
    242         info.handler = mHandler;
    243 
    244         info.token = token;
    245         info.cookie = cookie;
    246         info.uri = uri;
    247         info.values = values;
    248         info.selection = selection;
    249         info.selectionArgs = selectionArgs;
    250         info.delayMillis = delayMillis;
    251 
    252         AsyncQueryServiceHelper.queueOperation(mContext, info);
    253     }
    254 
    255     /**
    256      * This method begins an asynchronous delete. When the delete operation is
    257      * done {@link #onDeleteComplete} is called.
    258      *
    259      * @param token A token passed into {@link #onDeleteComplete} to identify
    260      *            the delete operation.
    261      * @param cookie An object that gets passed into {@link #onDeleteComplete}
    262      * @param uri the Uri passed to the delete operation.
    263      * @param selection A filter declaring which rows to delete, formatted as an
    264      *            SQL WHERE clause (excluding the WHERE itself). Passing null
    265      *            will delete all rows for the given URI.
    266      * @param selectionArgs You may include ?s in selection, which will be
    267      *            replaced by the values from selectionArgs, in the order that
    268      *            they appear in the selection. The values will be bound as
    269      *            Strings.
    270      * @param delayMillis delay in executing the operation. This operation will
    271      *            execute before the delayed time when another operation is
    272      *            added. Useful for implementing single level undo.
    273      */
    274     public void startDelete(int token, Object cookie, Uri uri, String selection,
    275             String[] selectionArgs, long delayMillis) {
    276         OperationInfo info = new OperationInfo();
    277         info.op = Operation.EVENT_ARG_DELETE;
    278         info.resolver = mContext.getContentResolver();
    279         info.handler = mHandler;
    280 
    281         info.token = token;
    282         info.cookie = cookie;
    283         info.uri = uri;
    284         info.selection = selection;
    285         info.selectionArgs = selectionArgs;
    286         info.delayMillis = delayMillis;
    287 
    288         AsyncQueryServiceHelper.queueOperation(mContext, info);
    289     }
    290 
    291     /**
    292      * This method begins an asynchronous {@link ContentProviderOperation}. When
    293      * the operation is done {@link #onBatchComplete} is called.
    294      *
    295      * @param token A token passed into {@link #onDeleteComplete} to identify
    296      *            the delete operation.
    297      * @param cookie An object that gets passed into {@link #onDeleteComplete}
    298      * @param authority the authority used for the
    299      *            {@link ContentProviderOperation}.
    300      * @param cpo the {@link ContentProviderOperation} to be executed.
    301      * @param delayMillis delay in executing the operation. This operation will
    302      *            execute before the delayed time when another operation is
    303      *            added. Useful for implementing single level undo.
    304      */
    305     public void startBatch(int token, Object cookie, String authority,
    306             ArrayList<ContentProviderOperation> cpo, long delayMillis) {
    307         OperationInfo info = new OperationInfo();
    308         info.op = Operation.EVENT_ARG_BATCH;
    309         info.resolver = mContext.getContentResolver();
    310         info.handler = mHandler;
    311 
    312         info.token = token;
    313         info.cookie = cookie;
    314         info.authority = authority;
    315         info.cpo = cpo;
    316         info.delayMillis = delayMillis;
    317 
    318         AsyncQueryServiceHelper.queueOperation(mContext, info);
    319     }
    320 
    321     /**
    322      * Called when an asynchronous query is completed.
    323      *
    324      * @param token the token to identify the query, passed in from
    325      *            {@link #startQuery}.
    326      * @param cookie the cookie object passed in from {@link #startQuery}.
    327      * @param cursor The cursor holding the results from the query.
    328      */
    329     protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    330         if (localLOGV) {
    331             Log.d(TAG, "########## default onQueryComplete");
    332         }
    333     }
    334 
    335     /**
    336      * Called when an asynchronous insert is completed.
    337      *
    338      * @param token the token to identify the query, passed in from
    339      *            {@link #startInsert}.
    340      * @param cookie the cookie object that's passed in from
    341      *            {@link #startInsert}.
    342      * @param uri the uri returned from the insert operation.
    343      */
    344     protected void onInsertComplete(int token, Object cookie, Uri uri) {
    345         if (localLOGV) {
    346             Log.d(TAG, "########## default onInsertComplete");
    347         }
    348     }
    349 
    350     /**
    351      * Called when an asynchronous update is completed.
    352      *
    353      * @param token the token to identify the query, passed in from
    354      *            {@link #startUpdate}.
    355      * @param cookie the cookie object that's passed in from
    356      *            {@link #startUpdate}.
    357      * @param result the result returned from the update operation
    358      */
    359     protected void onUpdateComplete(int token, Object cookie, int result) {
    360         if (localLOGV) {
    361             Log.d(TAG, "########## default onUpdateComplete");
    362         }
    363     }
    364 
    365     /**
    366      * Called when an asynchronous delete is completed.
    367      *
    368      * @param token the token to identify the query, passed in from
    369      *            {@link #startDelete}.
    370      * @param cookie the cookie object that's passed in from
    371      *            {@link #startDelete}.
    372      * @param result the result returned from the delete operation
    373      */
    374     protected void onDeleteComplete(int token, Object cookie, int result) {
    375         if (localLOGV) {
    376             Log.d(TAG, "########## default onDeleteComplete");
    377         }
    378     }
    379 
    380     /**
    381      * Called when an asynchronous {@link ContentProviderOperation} is
    382      * completed.
    383      *
    384      * @param token the token to identify the query, passed in from
    385      *            {@link #startDelete}.
    386      * @param cookie the cookie object that's passed in from
    387      *            {@link #startDelete}.
    388      * @param results the result returned from executing the
    389      *            {@link ContentProviderOperation}
    390      */
    391     protected void onBatchComplete(int token, Object cookie, ContentProviderResult[] results) {
    392         if (localLOGV) {
    393             Log.d(TAG, "########## default onBatchComplete");
    394         }
    395     }
    396 
    397     @Override
    398     public void handleMessage(Message msg) {
    399         OperationInfo info = (OperationInfo) msg.obj;
    400 
    401         int token = msg.what;
    402         int op = msg.arg1;
    403 
    404         if (localLOGV) {
    405             Log.d(TAG, "AsyncQueryService.handleMessage: token=" + token + ", op=" + op
    406                     + ", result=" + info.result);
    407         }
    408 
    409         // pass token back to caller on each callback.
    410         switch (op) {
    411             case Operation.EVENT_ARG_QUERY:
    412                 onQueryComplete(token, info.cookie, (Cursor) info.result);
    413                 break;
    414 
    415             case Operation.EVENT_ARG_INSERT:
    416                 onInsertComplete(token, info.cookie, (Uri) info.result);
    417                 break;
    418 
    419             case Operation.EVENT_ARG_UPDATE:
    420                 onUpdateComplete(token, info.cookie, (Integer) info.result);
    421                 break;
    422 
    423             case Operation.EVENT_ARG_DELETE:
    424                 onDeleteComplete(token, info.cookie, (Integer) info.result);
    425                 break;
    426 
    427             case Operation.EVENT_ARG_BATCH:
    428                 onBatchComplete(token, info.cookie, (ContentProviderResult[]) info.result);
    429                 break;
    430         }
    431     }
    432 
    433 //    @VisibleForTesting
    434     protected void setTestHandler(Handler handler) {
    435         mHandler = handler;
    436     }
    437 }
    438