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