AsyncQueryHandler.java revision 5bba632d877c2878384ff21566c8eb6a1a22f37b
1/* 2 * Copyright (C) 2007 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 android.content; 18 19import android.database.Cursor; 20import android.net.Uri; 21import android.os.Handler; 22import android.os.HandlerThread; 23import android.os.Looper; 24import android.os.Message; 25import android.util.Log; 26 27import java.lang.ref.WeakReference; 28 29/** 30 * A helper class to help make handling asynchronous {@link ContentResolver} 31 * queries easier. 32 */ 33public abstract class AsyncQueryHandler extends Handler { 34 private static final String TAG = "AsyncQuery"; 35 private static final boolean localLOGV = false; 36 37 private static final int EVENT_ARG_QUERY = 1; 38 private static final int EVENT_ARG_INSERT = 2; 39 private static final int EVENT_ARG_UPDATE = 3; 40 private static final int EVENT_ARG_DELETE = 4; 41 private static final int EVENT_ARG_QUERY_ENTITIES = 5; 42 43 /* package */ final WeakReference<ContentResolver> mResolver; 44 45 private static Looper sLooper = null; 46 47 private Handler mWorkerThreadHandler; 48 49 protected static final class WorkerArgs { 50 public Uri uri; 51 public Handler handler; 52 public String[] projection; 53 public String selection; 54 public String[] selectionArgs; 55 public String orderBy; 56 public Object result; 57 public Object cookie; 58 public ContentValues values; 59 } 60 61 protected class WorkerHandler extends Handler { 62 public WorkerHandler(Looper looper) { 63 super(looper); 64 } 65 66 @Override 67 public void handleMessage(Message msg) { 68 final ContentResolver resolver = mResolver.get(); 69 if (resolver == null) return; 70 71 WorkerArgs args = (WorkerArgs) msg.obj; 72 73 int token = msg.what; 74 int event = msg.arg1; 75 76 switch (event) { 77 case EVENT_ARG_QUERY: 78 Cursor cursor; 79 try { 80 cursor = resolver.query(args.uri, args.projection, 81 args.selection, args.selectionArgs, 82 args.orderBy); 83 // Calling getCount() causes the cursor window to be filled, 84 // which will make the first access on the main thread a lot faster. 85 if (cursor != null) { 86 cursor.getCount(); 87 } 88 } catch (Exception e) { 89 Log.w(TAG, e.toString()); 90 cursor = null; 91 } 92 93 args.result = cursor; 94 break; 95 96 case EVENT_ARG_QUERY_ENTITIES: 97 EntityIterator iterator = null; 98 try { 99 iterator = resolver.queryEntities(args.uri, args.selection, 100 args.selectionArgs, args.orderBy); 101 } catch (Exception e) { 102 Log.w(TAG, e.toString()); 103 } 104 105 args.result = iterator; 106 break; 107 108 case EVENT_ARG_INSERT: 109 args.result = resolver.insert(args.uri, args.values); 110 break; 111 112 case EVENT_ARG_UPDATE: 113 args.result = resolver.update(args.uri, args.values, args.selection, 114 args.selectionArgs); 115 break; 116 117 case EVENT_ARG_DELETE: 118 args.result = resolver.delete(args.uri, args.selection, args.selectionArgs); 119 break; 120 } 121 122 // passing the original token value back to the caller 123 // on top of the event values in arg1. 124 Message reply = args.handler.obtainMessage(token); 125 reply.obj = args; 126 reply.arg1 = msg.arg1; 127 128 if (localLOGV) { 129 Log.d(TAG, "WorkerHandler.handleMsg: msg.arg1=" + msg.arg1 130 + ", reply.what=" + reply.what); 131 } 132 133 reply.sendToTarget(); 134 } 135 } 136 137 public AsyncQueryHandler(ContentResolver cr) { 138 super(); 139 mResolver = new WeakReference<ContentResolver>(cr); 140 synchronized (AsyncQueryHandler.class) { 141 if (sLooper == null) { 142 HandlerThread thread = new HandlerThread("AsyncQueryWorker"); 143 thread.start(); 144 145 sLooper = thread.getLooper(); 146 } 147 } 148 mWorkerThreadHandler = createHandler(sLooper); 149 } 150 151 protected Handler createHandler(Looper looper) { 152 return new WorkerHandler(looper); 153 } 154 155 /** 156 * This method begins an asynchronous query. When the query is done 157 * {@link #onQueryComplete} is called. 158 * 159 * @param token A token passed into {@link #onQueryComplete} to identify 160 * the query. 161 * @param cookie An object that gets passed into {@link #onQueryComplete} 162 * @param uri The URI, using the content:// scheme, for the content to 163 * retrieve. 164 * @param projection A list of which columns to return. Passing null will 165 * return all columns, which is discouraged to prevent reading data 166 * from storage that isn't going to be used. 167 * @param selection A filter declaring which rows to return, formatted as an 168 * SQL WHERE clause (excluding the WHERE itself). Passing null will 169 * return all rows for the given URI. 170 * @param selectionArgs You may include ?s in selection, which will be 171 * replaced by the values from selectionArgs, in the order that they 172 * appear in the selection. The values will be bound as Strings. 173 * @param orderBy How to order the rows, formatted as an SQL ORDER BY 174 * clause (excluding the ORDER BY itself). Passing null will use the 175 * default sort order, which may be unordered. 176 */ 177 public void startQuery(int token, Object cookie, Uri uri, 178 String[] projection, String selection, String[] selectionArgs, 179 String orderBy) { 180 // Use the token as what so cancelOperations works properly 181 Message msg = mWorkerThreadHandler.obtainMessage(token); 182 msg.arg1 = EVENT_ARG_QUERY; 183 184 WorkerArgs args = new WorkerArgs(); 185 args.handler = this; 186 args.uri = uri; 187 args.projection = projection; 188 args.selection = selection; 189 args.selectionArgs = selectionArgs; 190 args.orderBy = orderBy; 191 args.cookie = cookie; 192 msg.obj = args; 193 194 mWorkerThreadHandler.sendMessage(msg); 195 } 196 197 /** 198 * This method begins an asynchronous query for an {@link EntityIterator}. 199 * When the query is done {@link #onQueryEntitiesComplete} is called. 200 * 201 * @param token A token passed into {@link #onQueryComplete} to identify the 202 * query. 203 * @param cookie An object that gets passed into {@link #onQueryComplete} 204 * @param uri The URI, using the content:// scheme, for the content to 205 * retrieve. 206 * @param selection A filter declaring which rows to return, formatted as an 207 * SQL WHERE clause (excluding the WHERE itself). Passing null 208 * will return all rows for the given URI. 209 * @param selectionArgs You may include ?s in selection, which will be 210 * replaced by the values from selectionArgs, in the order that 211 * they appear in the selection. The values will be bound as 212 * Strings. 213 * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause 214 * (excluding the ORDER BY itself). Passing null will use the 215 * default sort order, which may be unordered. 216 */ 217 public void startQueryEntities(int token, Object cookie, Uri uri, String selection, 218 String[] selectionArgs, String orderBy) { 219 // Use the token as what so cancelOperations works properly 220 Message msg = mWorkerThreadHandler.obtainMessage(token); 221 msg.arg1 = EVENT_ARG_QUERY_ENTITIES; 222 223 WorkerArgs args = new WorkerArgs(); 224 args.handler = this; 225 args.uri = uri; 226 args.selection = selection; 227 args.selectionArgs = selectionArgs; 228 args.orderBy = orderBy; 229 args.cookie = cookie; 230 msg.obj = args; 231 232 mWorkerThreadHandler.sendMessage(msg); 233 } 234 235 /** 236 * Attempts to cancel operation that has not already started. Note that 237 * there is no guarantee that the operation will be canceled. They still may 238 * result in a call to on[Query/Insert/Update/Delete]Complete after this 239 * call has completed. 240 * 241 * @param token The token representing the operation to be canceled. 242 * If multiple operations have the same token they will all be canceled. 243 */ 244 public final void cancelOperation(int token) { 245 mWorkerThreadHandler.removeMessages(token); 246 } 247 248 /** 249 * This method begins an asynchronous insert. When the insert operation is 250 * done {@link #onInsertComplete} is called. 251 * 252 * @param token A token passed into {@link #onInsertComplete} to identify 253 * the insert operation. 254 * @param cookie An object that gets passed into {@link #onInsertComplete} 255 * @param uri the Uri passed to the insert operation. 256 * @param initialValues the ContentValues parameter passed to the insert operation. 257 */ 258 public final void startInsert(int token, Object cookie, Uri uri, 259 ContentValues initialValues) { 260 // Use the token as what so cancelOperations works properly 261 Message msg = mWorkerThreadHandler.obtainMessage(token); 262 msg.arg1 = EVENT_ARG_INSERT; 263 264 WorkerArgs args = new WorkerArgs(); 265 args.handler = this; 266 args.uri = uri; 267 args.cookie = cookie; 268 args.values = initialValues; 269 msg.obj = args; 270 271 mWorkerThreadHandler.sendMessage(msg); 272 } 273 274 /** 275 * This method begins an asynchronous update. When the update operation is 276 * done {@link #onUpdateComplete} is called. 277 * 278 * @param token A token passed into {@link #onUpdateComplete} to identify 279 * the update operation. 280 * @param cookie An object that gets passed into {@link #onUpdateComplete} 281 * @param uri the Uri passed to the update operation. 282 * @param values the ContentValues parameter passed to the update operation. 283 */ 284 public final void startUpdate(int token, Object cookie, Uri uri, 285 ContentValues values, String selection, String[] selectionArgs) { 286 // Use the token as what so cancelOperations works properly 287 Message msg = mWorkerThreadHandler.obtainMessage(token); 288 msg.arg1 = EVENT_ARG_UPDATE; 289 290 WorkerArgs args = new WorkerArgs(); 291 args.handler = this; 292 args.uri = uri; 293 args.cookie = cookie; 294 args.values = values; 295 args.selection = selection; 296 args.selectionArgs = selectionArgs; 297 msg.obj = args; 298 299 mWorkerThreadHandler.sendMessage(msg); 300 } 301 302 /** 303 * This method begins an asynchronous delete. When the delete operation is 304 * done {@link #onDeleteComplete} is called. 305 * 306 * @param token A token passed into {@link #onDeleteComplete} to identify 307 * the delete operation. 308 * @param cookie An object that gets passed into {@link #onDeleteComplete} 309 * @param uri the Uri passed to the delete operation. 310 * @param selection the where clause. 311 */ 312 public final void startDelete(int token, Object cookie, Uri uri, 313 String selection, String[] selectionArgs) { 314 // Use the token as what so cancelOperations works properly 315 Message msg = mWorkerThreadHandler.obtainMessage(token); 316 msg.arg1 = EVENT_ARG_DELETE; 317 318 WorkerArgs args = new WorkerArgs(); 319 args.handler = this; 320 args.uri = uri; 321 args.cookie = cookie; 322 args.selection = selection; 323 args.selectionArgs = selectionArgs; 324 msg.obj = args; 325 326 mWorkerThreadHandler.sendMessage(msg); 327 } 328 329 /** 330 * Called when an asynchronous query is completed. 331 * 332 * @param token the token to identify the query, passed in from 333 * {@link #startQuery}. 334 * @param cookie the cookie object passed in from {@link #startQuery}. 335 * @param cursor The cursor holding the results from the query. 336 */ 337 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 338 // Empty 339 } 340 341 /** 342 * Called when an asynchronous query is completed. 343 * 344 * @param token The token to identify the query. 345 * @param cookie The cookie object. 346 * @param iterator The iterator holding the query results. 347 * @hide 348 */ 349 protected void onQueryEntitiesComplete(int token, Object cookie, EntityIterator iterator) { 350 // Empty 351 } 352 353 /** 354 * Called when an asynchronous insert is completed. 355 * 356 * @param token the token to identify the query, passed in from 357 * {@link #startInsert}. 358 * @param cookie the cookie object that's passed in from 359 * {@link #startInsert}. 360 * @param uri the uri returned from the insert operation. 361 */ 362 protected void onInsertComplete(int token, Object cookie, Uri uri) { 363 // Empty 364 } 365 366 /** 367 * Called when an asynchronous update is completed. 368 * 369 * @param token the token to identify the query, passed in from 370 * {@link #startUpdate}. 371 * @param cookie the cookie object that's passed in from 372 * {@link #startUpdate}. 373 * @param result the result returned from the update operation 374 */ 375 protected void onUpdateComplete(int token, Object cookie, int result) { 376 // Empty 377 } 378 379 /** 380 * Called when an asynchronous delete is completed. 381 * 382 * @param token the token to identify the query, passed in from 383 * {@link #startDelete}. 384 * @param cookie the cookie object that's passed in from 385 * {@link #startDelete}. 386 * @param result the result returned from the delete operation 387 */ 388 protected void onDeleteComplete(int token, Object cookie, int result) { 389 // Empty 390 } 391 392 @Override 393 public void handleMessage(Message msg) { 394 WorkerArgs args = (WorkerArgs) msg.obj; 395 396 if (localLOGV) { 397 Log.d(TAG, "AsyncQueryHandler.handleMessage: msg.what=" + msg.what 398 + ", msg.arg1=" + msg.arg1); 399 } 400 401 int token = msg.what; 402 int event = msg.arg1; 403 404 // pass token back to caller on each callback. 405 switch (event) { 406 case EVENT_ARG_QUERY: 407 onQueryComplete(token, args.cookie, (Cursor) args.result); 408 break; 409 410 case EVENT_ARG_QUERY_ENTITIES: 411 onQueryEntitiesComplete(token, args.cookie, (EntityIterator)args.result); 412 break; 413 414 case EVENT_ARG_INSERT: 415 onInsertComplete(token, args.cookie, (Uri) args.result); 416 break; 417 418 case EVENT_ARG_UPDATE: 419 onUpdateComplete(token, args.cookie, (Integer) args.result); 420 break; 421 422 case EVENT_ARG_DELETE: 423 onDeleteComplete(token, args.cookie, (Integer) args.result); 424 break; 425 } 426 } 427} 428