| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.calendar; |
| |
| import com.android.calendar.AsyncQueryServiceHelper.OperationInfo; |
| |
| import android.content.ContentProviderOperation; |
| import android.content.ContentProviderResult; |
| import android.content.ContentResolver; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.util.Log; |
| |
| import java.util.ArrayList; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * A helper class that executes {@link ContentResolver} calls in a background |
| * {@link android.app.Service}. This minimizes the chance of the call getting |
| * lost because the caller ({@link android.app.Activity}) is killed. It is |
| * designed for easy migration from {@link android.content.AsyncQueryHandler} |
| * which calls the {@link ContentResolver} in a background thread. This supports |
| * query/insert/update/delete and also batch mode i.e. |
| * {@link ContentProviderOperation}. It also supports delay execution and cancel |
| * which allows for time-limited undo. Note that there's one queue per |
| * application which serializes all the calls. |
| */ |
| public class AsyncQueryService extends Handler { |
| private static final String TAG = "AsyncQuery"; |
| static final boolean localLOGV = false; |
| |
| // Used for generating unique tokens for calls to this service |
| private static AtomicInteger mUniqueToken = new AtomicInteger(0); |
| |
| private Context mContext; |
| private Handler mHandler = this; // can be overridden for testing |
| |
| /** |
| * Data class which holds into info of the queued operation |
| */ |
| public static class Operation { |
| static final int EVENT_ARG_QUERY = 1; |
| static final int EVENT_ARG_INSERT = 2; |
| static final int EVENT_ARG_UPDATE = 3; |
| static final int EVENT_ARG_DELETE = 4; |
| static final int EVENT_ARG_BATCH = 5; |
| |
| /** |
| * unique identify for cancellation purpose |
| */ |
| public int token; |
| |
| /** |
| * One of the EVENT_ARG_ constants in the class describing the operation |
| */ |
| public int op; |
| |
| /** |
| * {@link SystemClock.elapsedRealtime()} based |
| */ |
| public long scheduledExecutionTime; |
| |
| protected static char opToChar(int op) { |
| switch (op) { |
| case Operation.EVENT_ARG_QUERY: |
| return 'Q'; |
| case Operation.EVENT_ARG_INSERT: |
| return 'I'; |
| case Operation.EVENT_ARG_UPDATE: |
| return 'U'; |
| case Operation.EVENT_ARG_DELETE: |
| return 'D'; |
| case Operation.EVENT_ARG_BATCH: |
| return 'B'; |
| default: |
| return '?'; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("Operation [op="); |
| builder.append(op); |
| builder.append(", token="); |
| builder.append(token); |
| builder.append(", scheduledExecutionTime="); |
| builder.append(scheduledExecutionTime); |
| builder.append("]"); |
| return builder.toString(); |
| } |
| } |
| |
| public AsyncQueryService(Context context) { |
| mContext = context; |
| } |
| |
| /** |
| * returns a practically unique token for db operations |
| */ |
| public final int getNextToken() { |
| return mUniqueToken.getAndIncrement(); |
| } |
| |
| /** |
| * Gets the last delayed operation. It is typically used for canceling. |
| * |
| * @return Operation object which contains of the last cancelable operation |
| */ |
| public final Operation getLastCancelableOperation() { |
| return AsyncQueryServiceHelper.getLastCancelableOperation(); |
| } |
| |
| /** |
| * Attempts to cancel operation that has not already started. Note that |
| * there is no guarantee that the operation will be canceled. They still may |
| * result in a call to on[Query/Insert/Update/Delete/Batch]Complete after |
| * this call has completed. |
| * |
| * @param token The token representing the operation to be canceled. If |
| * multiple operations have the same token they will all be |
| * canceled. |
| */ |
| public final int cancelOperation(int token) { |
| return AsyncQueryServiceHelper.cancelOperation(token); |
| } |
| |
| /** |
| * This method begins an asynchronous query. When the query is done |
| * {@link #onQueryComplete} is called. |
| * |
| * @param token A token passed into {@link #onQueryComplete} to identify the |
| * query. |
| * @param cookie An object that gets passed into {@link #onQueryComplete} |
| * @param uri The URI, using the content:// scheme, for the content to |
| * retrieve. |
| * @param projection A list of which columns to return. Passing null will |
| * return all columns, which is discouraged to prevent reading |
| * data from storage that isn't going to be used. |
| * @param selection A filter declaring which rows to return, formatted as an |
| * SQL WHERE clause (excluding the WHERE itself). Passing null |
| * will return all rows for the given URI. |
| * @param selectionArgs You may include ?s in selection, which will be |
| * replaced by the values from selectionArgs, in the order that |
| * they appear in the selection. The values will be bound as |
| * Strings. |
| * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause |
| * (excluding the ORDER BY itself). Passing null will use the |
| * default sort order, which may be unordered. |
| */ |
| public void startQuery(int token, Object cookie, Uri uri, String[] projection, |
| String selection, String[] selectionArgs, String orderBy) { |
| OperationInfo info = new OperationInfo(); |
| info.op = Operation.EVENT_ARG_QUERY; |
| info.resolver = mContext.getContentResolver(); |
| |
| info.handler = mHandler; |
| info.token = token; |
| info.cookie = cookie; |
| info.uri = uri; |
| info.projection = projection; |
| info.selection = selection; |
| info.selectionArgs = selectionArgs; |
| info.orderBy = orderBy; |
| |
| AsyncQueryServiceHelper.queueOperation(mContext, info); |
| } |
| |
| /** |
| * This method begins an asynchronous insert. When the insert operation is |
| * done {@link #onInsertComplete} is called. |
| * |
| * @param token A token passed into {@link #onInsertComplete} to identify |
| * the insert operation. |
| * @param cookie An object that gets passed into {@link #onInsertComplete} |
| * @param uri the Uri passed to the insert operation. |
| * @param initialValues the ContentValues parameter passed to the insert |
| * operation. |
| * @param delayMillis delay in executing the operation. This operation will |
| * execute before the delayed time when another operation is |
| * added. Useful for implementing single level undo. |
| */ |
| public void startInsert(int token, Object cookie, Uri uri, ContentValues initialValues, |
| long delayMillis) { |
| OperationInfo info = new OperationInfo(); |
| info.op = Operation.EVENT_ARG_INSERT; |
| info.resolver = mContext.getContentResolver(); |
| info.handler = mHandler; |
| |
| info.token = token; |
| info.cookie = cookie; |
| info.uri = uri; |
| info.values = initialValues; |
| info.delayMillis = delayMillis; |
| |
| AsyncQueryServiceHelper.queueOperation(mContext, info); |
| } |
| |
| /** |
| * This method begins an asynchronous update. When the update operation is |
| * done {@link #onUpdateComplete} is called. |
| * |
| * @param token A token passed into {@link #onUpdateComplete} to identify |
| * the update operation. |
| * @param cookie An object that gets passed into {@link #onUpdateComplete} |
| * @param uri the Uri passed to the update operation. |
| * @param values the ContentValues parameter passed to the update operation. |
| * @param selection A filter declaring which rows to update, formatted as an |
| * SQL WHERE clause (excluding the WHERE itself). Passing null |
| * will update all rows for the given URI. |
| * @param selectionArgs You may include ?s in selection, which will be |
| * replaced by the values from selectionArgs, in the order that |
| * they appear in the selection. The values will be bound as |
| * Strings. |
| * @param delayMillis delay in executing the operation. This operation will |
| * execute before the delayed time when another operation is |
| * added. Useful for implementing single level undo. |
| */ |
| public void startUpdate(int token, Object cookie, Uri uri, ContentValues values, |
| String selection, String[] selectionArgs, long delayMillis) { |
| OperationInfo info = new OperationInfo(); |
| info.op = Operation.EVENT_ARG_UPDATE; |
| info.resolver = mContext.getContentResolver(); |
| info.handler = mHandler; |
| |
| info.token = token; |
| info.cookie = cookie; |
| info.uri = uri; |
| info.values = values; |
| info.selection = selection; |
| info.selectionArgs = selectionArgs; |
| info.delayMillis = delayMillis; |
| |
| AsyncQueryServiceHelper.queueOperation(mContext, info); |
| } |
| |
| /** |
| * This method begins an asynchronous delete. When the delete operation is |
| * done {@link #onDeleteComplete} is called. |
| * |
| * @param token A token passed into {@link #onDeleteComplete} to identify |
| * the delete operation. |
| * @param cookie An object that gets passed into {@link #onDeleteComplete} |
| * @param uri the Uri passed to the delete operation. |
| * @param selection A filter declaring which rows to delete, formatted as an |
| * SQL WHERE clause (excluding the WHERE itself). Passing null |
| * will delete all rows for the given URI. |
| * @param selectionArgs You may include ?s in selection, which will be |
| * replaced by the values from selectionArgs, in the order that |
| * they appear in the selection. The values will be bound as |
| * Strings. |
| * @param delayMillis delay in executing the operation. This operation will |
| * execute before the delayed time when another operation is |
| * added. Useful for implementing single level undo. |
| */ |
| public void startDelete(int token, Object cookie, Uri uri, String selection, |
| String[] selectionArgs, long delayMillis) { |
| OperationInfo info = new OperationInfo(); |
| info.op = Operation.EVENT_ARG_DELETE; |
| info.resolver = mContext.getContentResolver(); |
| info.handler = mHandler; |
| |
| info.token = token; |
| info.cookie = cookie; |
| info.uri = uri; |
| info.selection = selection; |
| info.selectionArgs = selectionArgs; |
| info.delayMillis = delayMillis; |
| |
| AsyncQueryServiceHelper.queueOperation(mContext, info); |
| } |
| |
| /** |
| * This method begins an asynchronous {@link ContentProviderOperation}. When |
| * the operation is done {@link #onBatchComplete} is called. |
| * |
| * @param token A token passed into {@link #onDeleteComplete} to identify |
| * the delete operation. |
| * @param cookie An object that gets passed into {@link #onDeleteComplete} |
| * @param authority the authority used for the |
| * {@link ContentProviderOperation}. |
| * @param cpo the {@link ContentProviderOperation} to be executed. |
| * @param delayMillis delay in executing the operation. This operation will |
| * execute before the delayed time when another operation is |
| * added. Useful for implementing single level undo. |
| */ |
| public void startBatch(int token, Object cookie, String authority, |
| ArrayList<ContentProviderOperation> cpo, long delayMillis) { |
| OperationInfo info = new OperationInfo(); |
| info.op = Operation.EVENT_ARG_BATCH; |
| info.resolver = mContext.getContentResolver(); |
| info.handler = mHandler; |
| |
| info.token = token; |
| info.cookie = cookie; |
| info.authority = authority; |
| info.cpo = cpo; |
| info.delayMillis = delayMillis; |
| |
| AsyncQueryServiceHelper.queueOperation(mContext, info); |
| } |
| |
| /** |
| * Called when an asynchronous query is completed. |
| * |
| * @param token the token to identify the query, passed in from |
| * {@link #startQuery}. |
| * @param cookie the cookie object passed in from {@link #startQuery}. |
| * @param cursor The cursor holding the results from the query. |
| */ |
| protected void onQueryComplete(int token, Object cookie, Cursor cursor) { |
| if (localLOGV) { |
| Log.d(TAG, "########## default onQueryComplete"); |
| } |
| } |
| |
| /** |
| * Called when an asynchronous insert is completed. |
| * |
| * @param token the token to identify the query, passed in from |
| * {@link #startInsert}. |
| * @param cookie the cookie object that's passed in from |
| * {@link #startInsert}. |
| * @param uri the uri returned from the insert operation. |
| */ |
| protected void onInsertComplete(int token, Object cookie, Uri uri) { |
| if (localLOGV) { |
| Log.d(TAG, "########## default onInsertComplete"); |
| } |
| } |
| |
| /** |
| * Called when an asynchronous update is completed. |
| * |
| * @param token the token to identify the query, passed in from |
| * {@link #startUpdate}. |
| * @param cookie the cookie object that's passed in from |
| * {@link #startUpdate}. |
| * @param result the result returned from the update operation |
| */ |
| protected void onUpdateComplete(int token, Object cookie, int result) { |
| if (localLOGV) { |
| Log.d(TAG, "########## default onUpdateComplete"); |
| } |
| } |
| |
| /** |
| * Called when an asynchronous delete is completed. |
| * |
| * @param token the token to identify the query, passed in from |
| * {@link #startDelete}. |
| * @param cookie the cookie object that's passed in from |
| * {@link #startDelete}. |
| * @param result the result returned from the delete operation |
| */ |
| protected void onDeleteComplete(int token, Object cookie, int result) { |
| if (localLOGV) { |
| Log.d(TAG, "########## default onDeleteComplete"); |
| } |
| } |
| |
| /** |
| * Called when an asynchronous {@link ContentProviderOperation} is |
| * completed. |
| * |
| * @param token the token to identify the query, passed in from |
| * {@link #startDelete}. |
| * @param cookie the cookie object that's passed in from |
| * {@link #startDelete}. |
| * @param results the result returned from executing the |
| * {@link ContentProviderOperation} |
| */ |
| protected void onBatchComplete(int token, Object cookie, ContentProviderResult[] results) { |
| if (localLOGV) { |
| Log.d(TAG, "########## default onBatchComplete"); |
| } |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| OperationInfo info = (OperationInfo) msg.obj; |
| |
| int token = msg.what; |
| int op = msg.arg1; |
| |
| if (localLOGV) { |
| Log.d(TAG, "AsyncQueryService.handleMessage: token=" + token + ", op=" + op |
| + ", result=" + info.result); |
| } |
| |
| // pass token back to caller on each callback. |
| switch (op) { |
| case Operation.EVENT_ARG_QUERY: |
| onQueryComplete(token, info.cookie, (Cursor) info.result); |
| break; |
| |
| case Operation.EVENT_ARG_INSERT: |
| onInsertComplete(token, info.cookie, (Uri) info.result); |
| break; |
| |
| case Operation.EVENT_ARG_UPDATE: |
| onUpdateComplete(token, info.cookie, (Integer) info.result); |
| break; |
| |
| case Operation.EVENT_ARG_DELETE: |
| onDeleteComplete(token, info.cookie, (Integer) info.result); |
| break; |
| |
| case Operation.EVENT_ARG_BATCH: |
| onBatchComplete(token, info.cookie, (ContentProviderResult[]) info.result); |
| break; |
| } |
| } |
| |
| // @VisibleForTesting |
| protected void setTestHandler(Handler handler) { |
| mHandler = handler; |
| } |
| } |