Michael Chan | bed0275 | 2010-04-27 10:56:53 -0700 | [diff] [blame] | 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; |
Erik | 37960c0 | 2010-06-22 13:40:18 -0700 | [diff] [blame] | 33 | import java.util.concurrent.atomic.AtomicInteger; |
Michael Chan | bed0275 | 2010-04-27 10:56:53 -0700 | [diff] [blame] | 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"; |
Michael Chan | 0847dbf | 2011-01-14 13:26:48 -0800 | [diff] [blame] | 48 | static final boolean localLOGV = false; |
Michael Chan | bed0275 | 2010-04-27 10:56:53 -0700 | [diff] [blame] | 49 | |
Erik | 37960c0 | 2010-06-22 13:40:18 -0700 | [diff] [blame] | 50 | // Used for generating unique tokens for calls to this service |
| 51 | private static AtomicInteger mUniqueToken = new AtomicInteger(0); |
| 52 | |
Michael Chan | bed0275 | 2010-04-27 10:56:53 -0700 | [diff] [blame] | 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 | /** |
Erik | 37960c0 | 2010-06-22 13:40:18 -0700 | [diff] [blame] | 117 | * returns a practically unique token for db operations |
| 118 | */ |
| 119 | public final int getNextToken() { |
| 120 | return mUniqueToken.getAndIncrement(); |
| 121 | } |
| 122 | |
| 123 | /** |
Michael Chan | bed0275 | 2010-04-27 10:56:53 -0700 | [diff] [blame] | 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 | */ |
Erik | 37960c0 | 2010-06-22 13:40:18 -0700 | [diff] [blame] | 201 | public void startInsert(int token, Object cookie, Uri uri, ContentValues initialValues, |
Michael Chan | bed0275 | 2010-04-27 10:56:53 -0700 | [diff] [blame] | 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 | */ |
Erik | 37960c0 | 2010-06-22 13:40:18 -0700 | [diff] [blame] | 237 | public void startUpdate(int token, Object cookie, Uri uri, ContentValues values, |
Michael Chan | bed0275 | 2010-04-27 10:56:53 -0700 | [diff] [blame] | 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 | */ |
Erik | 37960c0 | 2010-06-22 13:40:18 -0700 | [diff] [blame] | 274 | public void startDelete(int token, Object cookie, Uri uri, String selection, |
Michael Chan | bed0275 | 2010-04-27 10:56:53 -0700 | [diff] [blame] | 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 | */ |
Erik | 37960c0 | 2010-06-22 13:40:18 -0700 | [diff] [blame] | 305 | public void startBatch(int token, Object cookie, String authority, |
Michael Chan | bed0275 | 2010-04-27 10:56:53 -0700 | [diff] [blame] | 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 | } |