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 
opToChar(int op)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
toString()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 
AsyncQueryService(Context context)112     public AsyncQueryService(Context context) {
113         mContext = context;
114     }
115 
116     /**
117      * returns a practically unique token for db operations
118      */
getNextToken()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      */
getLastCancelableOperation()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      */
cancelOperation(int token)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      */
startQuery(int token, Object cookie, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)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      */
startInsert(int token, Object cookie, Uri uri, ContentValues initialValues, long delayMillis)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      */
startUpdate(int token, Object cookie, Uri uri, ContentValues values, String selection, String[] selectionArgs, long delayMillis)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      */
startDelete(int token, Object cookie, Uri uri, String selection, String[] selectionArgs, long delayMillis)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      */
startBatch(int token, Object cookie, String authority, ArrayList<ContentProviderOperation> cpo, long delayMillis)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      */
onQueryComplete(int token, Object cookie, Cursor cursor)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      */
onInsertComplete(int token, Object cookie, Uri uri)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      */
onUpdateComplete(int token, Object cookie, int result)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      */
onDeleteComplete(int token, Object cookie, int result)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      */
onBatchComplete(int token, Object cookie, ContentProviderResult[] results)391     protected void onBatchComplete(int token, Object cookie, ContentProviderResult[] results) {
392         if (localLOGV) {
393             Log.d(TAG, "########## default onBatchComplete");
394         }
395     }
396 
397     @Override
handleMessage(Message msg)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
setTestHandler(Handler handler)434     protected void setTestHandler(Handler handler) {
435         mHandler = handler;
436     }
437 }
438