blob: 1f022db94ab674a5cebbe1cf14904fda063af173 [file] [log] [blame]
Michael Chanbed02752010-04-27 10:56:53 -07001/*
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
17package com.android.calendar;
18
19import com.android.calendar.AsyncQueryServiceHelper.OperationInfo;
20
21import android.content.ContentProviderOperation;
22import android.content.ContentProviderResult;
23import android.content.ContentResolver;
24import android.content.ContentValues;
25import android.content.Context;
26import android.database.Cursor;
27import android.net.Uri;
28import android.os.Handler;
29import android.os.Message;
30import android.util.Log;
31
32import java.util.ArrayList;
Erik37960c02010-06-22 13:40:18 -070033import java.util.concurrent.atomic.AtomicInteger;
Michael Chanbed02752010-04-27 10:56:53 -070034
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 */
46public class AsyncQueryService extends Handler {
47 private static final String TAG = "AsyncQuery";
Michael Chan0847dbf2011-01-14 13:26:48 -080048 static final boolean localLOGV = false;
Michael Chanbed02752010-04-27 10:56:53 -070049
Erik37960c02010-06-22 13:40:18 -070050 // Used for generating unique tokens for calls to this service
51 private static AtomicInteger mUniqueToken = new AtomicInteger(0);
52
Michael Chanbed02752010-04-27 10:56:53 -070053 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 /**
Erik37960c02010-06-22 13:40:18 -0700117 * returns a practically unique token for db operations
118 */
119 public final int getNextToken() {
120 return mUniqueToken.getAndIncrement();
121 }
122
123 /**
Michael Chanbed02752010-04-27 10:56:53 -0700124 * 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 */
Erik37960c02010-06-22 13:40:18 -0700201 public void startInsert(int token, Object cookie, Uri uri, ContentValues initialValues,
Michael Chanbed02752010-04-27 10:56:53 -0700202 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 */
Erik37960c02010-06-22 13:40:18 -0700237 public void startUpdate(int token, Object cookie, Uri uri, ContentValues values,
Michael Chanbed02752010-04-27 10:56:53 -0700238 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 */
Erik37960c02010-06-22 13:40:18 -0700274 public void startDelete(int token, Object cookie, Uri uri, String selection,
Michael Chanbed02752010-04-27 10:56:53 -0700275 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 */
Erik37960c02010-06-22 13:40:18 -0700305 public void startBatch(int token, Object cookie, String authority,
Michael Chanbed02752010-04-27 10:56:53 -0700306 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}