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