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
calculateScheduledTime()80         void calculateScheduledTime() {
81             mScheduledTimeMillis = SystemClock.elapsedRealtime() + delayMillis;
82         }
83 
84         // @Override // Uncomment with Java6
getDelay(TimeUnit unit)85         public long getDelay(TimeUnit unit) {
86             return unit.convert(mScheduledTimeMillis - SystemClock.elapsedRealtime(),
87                     TimeUnit.MILLISECONDS);
88         }
89 
90         // @Override // Uncomment with Java6
compareTo(Delayed another)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
toString()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          */
equivalent(Operation o)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      */
queueOperation(Context context, OperationInfo args)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      */
getLastCancelableOperation()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      */
cancelOperation(int token)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 
AsyncQueryServiceHelper(String name)234     public AsyncQueryServiceHelper(String name) {
235         super(name);
236     }
237 
AsyncQueryServiceHelper()238     public AsyncQueryServiceHelper() {
239         super("AsyncQueryServiceHelper");
240     }
241 
242     @Override
onHandleIntent(Intent intent)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
onStart(Intent intent, int startId)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
onCreate()364     public void onCreate() {
365         if (AsyncQueryService.localLOGV) {
366             Log.d(TAG, "onCreate");
367         }
368         super.onCreate();
369     }
370 
371     @Override
onDestroy()372     public void onDestroy() {
373         if (AsyncQueryService.localLOGV) {
374             Log.d(TAG, "onDestroy");
375         }
376         super.onDestroy();
377     }
378 }
379