Home | History | Annotate | Download | only in calendar
      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                     args.result = resolver.delete(args.uri, args.selection, args.selectionArgs);
    316                     break;
    317 
    318                 case Operation.EVENT_ARG_BATCH:
    319                     try {
    320                         args.result = resolver.applyBatch(args.authority, args.cpo);
    321                     } catch (RemoteException e) {
    322                         Log.e(TAG, e.toString());
    323                         args.result = null;
    324                     } catch (OperationApplicationException e) {
    325                         Log.e(TAG, e.toString());
    326                         args.result = null;
    327                     }
    328                     break;
    329             }
    330 
    331             /*
    332              * passing the original token value back to the caller on top of the
    333              * event values in arg1.
    334              */
    335             Message reply = args.handler.obtainMessage(args.token);
    336             reply.obj = args;
    337             reply.arg1 = args.op;
    338 
    339             if (AsyncQueryService.localLOGV) {
    340                 Log.d(TAG, "onHandleIntent: op=" + Operation.opToChar(args.op) + ", token="
    341                         + reply.what);
    342             }
    343 
    344             reply.sendToTarget();
    345         }
    346     }
    347 
    348     @Override
    349     public void onStart(Intent intent, int startId) {
    350         if (AsyncQueryService.localLOGV) {
    351             Log.d(TAG, "onStart startId=" + startId);
    352         }
    353         super.onStart(intent, startId);
    354     }
    355 
    356     @Override
    357     public void onCreate() {
    358         if (AsyncQueryService.localLOGV) {
    359             Log.d(TAG, "onCreate");
    360         }
    361         super.onCreate();
    362     }
    363 
    364     @Override
    365     public void onDestroy() {
    366         if (AsyncQueryService.localLOGV) {
    367             Log.d(TAG, "onDestroy");
    368         }
    369         super.onDestroy();
    370     }
    371 }
    372