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.AsyncQueryService.Operation; 20 21 import android.app.IntentService; 22 import android.content.ContentProviderOperation; 23 import android.content.ContentResolver; 24 import android.content.ContentValues; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.OperationApplicationException; 28 import android.database.Cursor; 29 import android.net.Uri; 30 import android.os.Handler; 31 import android.os.Message; 32 import android.os.RemoteException; 33 import android.os.SystemClock; 34 import android.util.Log; 35 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.Iterator; 39 import java.util.PriorityQueue; 40 import java.util.concurrent.Delayed; 41 import java.util.concurrent.TimeUnit; 42 43 public class AsyncQueryServiceHelper extends IntentService { 44 private static final String TAG = "AsyncQuery"; 45 46 private static final PriorityQueue<OperationInfo> sWorkQueue = 47 new PriorityQueue<OperationInfo>(); 48 49 protected Class<AsyncQueryService> mService = AsyncQueryService.class; 50 51 protected static class OperationInfo implements Delayed{ 52 public int token; // Used for cancel 53 public int op; 54 public ContentResolver resolver; 55 public Uri uri; 56 public String authority; 57 public Handler handler; 58 public String[] projection; 59 public String selection; 60 public String[] selectionArgs; 61 public String orderBy; 62 public Object result; 63 public Object cookie; 64 public ContentValues values; 65 public ArrayList<ContentProviderOperation> cpo; 66 67 /** 68 * delayMillis is relative time e.g. 10,000 milliseconds 69 */ 70 public long delayMillis; 71 72 /** 73 * scheduleTimeMillis is the time scheduled for this to be processed. 74 * e.g. SystemClock.elapsedRealtime() + 10,000 milliseconds Based on 75 * {@link android.os.SystemClock#elapsedRealtime } 76 */ 77 private long mScheduledTimeMillis = 0; 78 79 // @VisibleForTesting 80 void calculateScheduledTime() { 81 mScheduledTimeMillis = SystemClock.elapsedRealtime() + delayMillis; 82 } 83 84 // @Override // Uncomment with Java6 85 public long getDelay(TimeUnit unit) { 86 return unit.convert(mScheduledTimeMillis - SystemClock.elapsedRealtime(), 87 TimeUnit.MILLISECONDS); 88 } 89 90 // @Override // Uncomment with Java6 91 public int compareTo(Delayed another) { 92 OperationInfo anotherArgs = (OperationInfo) another; 93 if (this.mScheduledTimeMillis == anotherArgs.mScheduledTimeMillis) { 94 return 0; 95 } else if (this.mScheduledTimeMillis < anotherArgs.mScheduledTimeMillis) { 96 return -1; 97 } else { 98 return 1; 99 } 100 } 101 102 @Override 103 public String toString() { 104 StringBuilder builder = new StringBuilder(); 105 builder.append("OperationInfo [\n\t token= "); 106 builder.append(token); 107 builder.append(",\n\t op= "); 108 builder.append(Operation.opToChar(op)); 109 builder.append(",\n\t uri= "); 110 builder.append(uri); 111 builder.append(",\n\t authority= "); 112 builder.append(authority); 113 builder.append(",\n\t delayMillis= "); 114 builder.append(delayMillis); 115 builder.append(",\n\t mScheduledTimeMillis= "); 116 builder.append(mScheduledTimeMillis); 117 builder.append(",\n\t resolver= "); 118 builder.append(resolver); 119 builder.append(",\n\t handler= "); 120 builder.append(handler); 121 builder.append(",\n\t projection= "); 122 builder.append(Arrays.toString(projection)); 123 builder.append(",\n\t selection= "); 124 builder.append(selection); 125 builder.append(",\n\t selectionArgs= "); 126 builder.append(Arrays.toString(selectionArgs)); 127 builder.append(",\n\t orderBy= "); 128 builder.append(orderBy); 129 builder.append(",\n\t result= "); 130 builder.append(result); 131 builder.append(",\n\t cookie= "); 132 builder.append(cookie); 133 builder.append(",\n\t values= "); 134 builder.append(values); 135 builder.append(",\n\t cpo= "); 136 builder.append(cpo); 137 builder.append("\n]"); 138 return builder.toString(); 139 } 140 141 /** 142 * Compares an user-visible operation to this private OperationInfo 143 * object 144 * 145 * @param o operation to be compared 146 * @return true if logically equivalent 147 */ 148 public boolean equivalent(Operation o) { 149 return o.token == this.token && o.op == this.op; 150 } 151 } 152 153 /** 154 * Queues the operation for execution 155 * 156 * @param context 157 * @param args OperationInfo object describing the operation 158 */ 159 static public void queueOperation(Context context, OperationInfo args) { 160 // Set the schedule time for execution based on the desired delay. 161 args.calculateScheduledTime(); 162 163 synchronized (sWorkQueue) { 164 sWorkQueue.add(args); 165 sWorkQueue.notify(); 166 } 167 168 context.startService(new Intent(context, AsyncQueryServiceHelper.class)); 169 } 170 171 /** 172 * Gets the last delayed operation. It is typically used for canceling. 173 * 174 * @return Operation object which contains of the last cancelable operation 175 */ 176 static public Operation getLastCancelableOperation() { 177 long lastScheduleTime = Long.MIN_VALUE; 178 Operation op = null; 179 180 synchronized (sWorkQueue) { 181 // Unknown order even for a PriorityQueue 182 Iterator<OperationInfo> it = sWorkQueue.iterator(); 183 while (it.hasNext()) { 184 OperationInfo info = it.next(); 185 if (info.delayMillis > 0 && lastScheduleTime < info.mScheduledTimeMillis) { 186 if (op == null) { 187 op = new Operation(); 188 } 189 190 op.token = info.token; 191 op.op = info.op; 192 op.scheduledExecutionTime = info.mScheduledTimeMillis; 193 194 lastScheduleTime = info.mScheduledTimeMillis; 195 } 196 } 197 } 198 199 if (AsyncQueryService.localLOGV) { 200 Log.d(TAG, "getLastCancelableOperation -> Operation:" + Operation.opToChar(op.op) 201 + " token:" + op.token); 202 } 203 return op; 204 } 205 206 /** 207 * Attempts to cancel operation that has not already started. Note that 208 * there is no guarantee that the operation will be canceled. They still may 209 * result in a call to on[Query/Insert/Update/Delete/Batch]Complete after 210 * this call has completed. 211 * 212 * @param token The token representing the operation to be canceled. If 213 * multiple operations have the same token they will all be 214 * canceled. 215 */ 216 static public int cancelOperation(int token) { 217 int canceled = 0; 218 synchronized (sWorkQueue) { 219 Iterator<OperationInfo> it = sWorkQueue.iterator(); 220 while (it.hasNext()) { 221 if (it.next().token == token) { 222 it.remove(); 223 ++canceled; 224 } 225 } 226 } 227 228 if (AsyncQueryService.localLOGV) { 229 Log.d(TAG, "cancelOperation(" + token + ") -> " + canceled); 230 } 231 return canceled; 232 } 233 234 public AsyncQueryServiceHelper(String name) { 235 super(name); 236 } 237 238 public AsyncQueryServiceHelper() { 239 super("AsyncQueryServiceHelper"); 240 } 241 242 @Override 243 protected void onHandleIntent(Intent intent) { 244 OperationInfo args; 245 246 if (AsyncQueryService.localLOGV) { 247 Log.d(TAG, "onHandleIntent: queue size=" + sWorkQueue.size()); 248 } 249 synchronized (sWorkQueue) { 250 while (true) { 251 /* 252 * This method can be called with no work because of 253 * cancellations 254 */ 255 if (sWorkQueue.size() == 0) { 256 return; 257 } else if (sWorkQueue.size() == 1) { 258 OperationInfo first = sWorkQueue.peek(); 259 long waitTime = first.mScheduledTimeMillis - SystemClock.elapsedRealtime(); 260 if (waitTime > 0) { 261 try { 262 sWorkQueue.wait(waitTime); 263 } catch (InterruptedException e) { 264 } 265 } 266 } 267 268 args = sWorkQueue.poll(); 269 if (args != null) { 270 // Got work to do. Break out of waiting loop 271 break; 272 } 273 } 274 } 275 276 if (AsyncQueryService.localLOGV) { 277 Log.d(TAG, "onHandleIntent: " + args); 278 } 279 280 ContentResolver resolver = args.resolver; 281 if (resolver != null) { 282 283 switch (args.op) { 284 case Operation.EVENT_ARG_QUERY: 285 Cursor cursor; 286 try { 287 cursor = resolver.query(args.uri, args.projection, args.selection, 288 args.selectionArgs, args.orderBy); 289 /* 290 * Calling getCount() causes the cursor window to be 291 * filled, which will make the first access on the main 292 * thread a lot faster 293 */ 294 if (cursor != null) { 295 cursor.getCount(); 296 } 297 } catch (Exception e) { 298 Log.w(TAG, e.toString()); 299 cursor = null; 300 } 301 302 args.result = cursor; 303 break; 304 305 case Operation.EVENT_ARG_INSERT: 306 args.result = resolver.insert(args.uri, args.values); 307 break; 308 309 case Operation.EVENT_ARG_UPDATE: 310 args.result = resolver.update(args.uri, args.values, args.selection, 311 args.selectionArgs); 312 break; 313 314 case Operation.EVENT_ARG_DELETE: 315 try { 316 args.result = resolver.delete(args.uri, args.selection, args.selectionArgs); 317 } catch (IllegalArgumentException e) { 318 Log.w(TAG, "Delete failed."); 319 Log.w(TAG, e.toString()); 320 args.result = 0; 321 } 322 323 break; 324 325 case Operation.EVENT_ARG_BATCH: 326 try { 327 args.result = resolver.applyBatch(args.authority, args.cpo); 328 } catch (RemoteException e) { 329 Log.e(TAG, e.toString()); 330 args.result = null; 331 } catch (OperationApplicationException e) { 332 Log.e(TAG, e.toString()); 333 args.result = null; 334 } 335 break; 336 } 337 338 /* 339 * passing the original token value back to the caller on top of the 340 * event values in arg1. 341 */ 342 Message reply = args.handler.obtainMessage(args.token); 343 reply.obj = args; 344 reply.arg1 = args.op; 345 346 if (AsyncQueryService.localLOGV) { 347 Log.d(TAG, "onHandleIntent: op=" + Operation.opToChar(args.op) + ", token=" 348 + reply.what); 349 } 350 351 reply.sendToTarget(); 352 } 353 } 354 355 @Override 356 public void onStart(Intent intent, int startId) { 357 if (AsyncQueryService.localLOGV) { 358 Log.d(TAG, "onStart startId=" + startId); 359 } 360 super.onStart(intent, startId); 361 } 362 363 @Override 364 public void onCreate() { 365 if (AsyncQueryService.localLOGV) { 366 Log.d(TAG, "onCreate"); 367 } 368 super.onCreate(); 369 } 370 371 @Override 372 public void onDestroy() { 373 if (AsyncQueryService.localLOGV) { 374 Log.d(TAG, "onDestroy"); 375 } 376 super.onDestroy(); 377 } 378 } 379