Implement a cancelation mechanism for queries.

Added new API to enable cancelation of SQLite and content provider
queries by means of a CancelationSignal object.  The application
creates a CancelationSignal object and passes it as an argument
to the query.  The cancelation signal can then be used to cancel
the query while it is executing.

If the cancelation signal is raised before the query is executed,
then it is immediately terminated.

Change-Id: If2c76e9a7e56ea5e98768b6d4f225f0a1ca61c61
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index 0b54396..944ca6b 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -173,6 +173,7 @@
                 if (DEBUG) Slog.v(TAG, "cancelLoad: cancelled=" + cancelled);
                 if (cancelled) {
                     mCancellingTask = mTask;
+                    onCancelLoadInBackground();
                 }
                 mTask = null;
                 return cancelled;
@@ -256,6 +257,25 @@
     }
 
     /**
+     * Override this method to try to abort the computation currently taking
+     * place on a background thread.
+     *
+     * Note that when this method is called, it is possible that {@link #loadInBackground}
+     * has not started yet or has already completed.
+     */
+    protected void onCancelLoadInBackground() {
+    }
+
+    /**
+     * Returns true if the current execution of {@link #loadInBackground()} is being canceled.
+     *
+     * @return True if the current execution of {@link #loadInBackground()} is being canceled.
+     */
+    protected boolean isLoadInBackgroundCanceled() {
+        return mCancellingTask != null;
+    }
+
+    /**
      * Locks the current thread until the loader completes the current load
      * operation. Returns immediately if there is no load operation running.
      * Should not be called from the UI thread: calling it from the UI
diff --git a/core/java/android/content/CancelationSignal.java b/core/java/android/content/CancelationSignal.java
new file mode 100644
index 0000000..58cf59d
--- /dev/null
+++ b/core/java/android/content/CancelationSignal.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2012 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 android.content;
+
+import android.os.RemoteException;
+
+/**
+ * Provides the ability to cancel an operation in progress.
+ */
+public final class CancelationSignal {
+    private boolean mIsCanceled;
+    private OnCancelListener mOnCancelListener;
+    private ICancelationSignal mRemote;
+
+    /**
+     * Creates a cancelation signal, initially not canceled.
+     */
+    public CancelationSignal() {
+    }
+
+    /**
+     * Returns true if the operation has been canceled.
+     *
+     * @return True if the operation has been canceled.
+     */
+    public boolean isCanceled() {
+        synchronized (this) {
+            return mIsCanceled;
+        }
+    }
+
+    /**
+     * Throws {@link OperationCanceledException} if the operation has been canceled.
+     *
+     * @throws OperationCanceledException if the operation has been canceled.
+     */
+    public void throwIfCanceled() {
+        if (isCanceled()) {
+            throw new OperationCanceledException();
+        }
+    }
+
+    /**
+     * Cancels the operation and signals the cancelation listener.
+     * If the operation has not yet started, then it will be canceled as soon as it does.
+     */
+    public void cancel() {
+        synchronized (this) {
+            if (!mIsCanceled) {
+                mIsCanceled = true;
+                if (mOnCancelListener != null) {
+                    mOnCancelListener.onCancel();
+                }
+                if (mRemote != null) {
+                    try {
+                        mRemote.cancel();
+                    } catch (RemoteException ex) {
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Sets the cancelation listener to be called when canceled.
+     * If {@link CancelationSignal#cancel} has already been called, then the provided
+     * listener is invoked immediately.
+     *
+     * The listener is called while holding the cancelation signal's lock which is
+     * also held while registering or unregistering the listener.  Because of the lock,
+     * it is not possible for the listener to run after it has been unregistered.
+     * This design choice makes it easier for clients of {@link CancelationSignal} to
+     * prevent race conditions related to listener registration and unregistration.
+     *
+     * @param listener The cancelation listener, or null to remove the current listener.
+     */
+    public void setOnCancelListener(OnCancelListener listener) {
+        synchronized (this) {
+            mOnCancelListener = listener;
+            if (mIsCanceled && listener != null) {
+                listener.onCancel();
+            }
+        }
+    }
+
+    /**
+     * Sets the remote transport.
+     *
+     * @param remote The remote transport, or null to remove.
+     *
+     * @hide
+     */
+    public void setRemote(ICancelationSignal remote) {
+        synchronized (this) {
+            mRemote = remote;
+            if (mIsCanceled && remote != null) {
+                try {
+                    remote.cancel();
+                } catch (RemoteException ex) {
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates a transport that can be returned back to the caller of
+     * a Binder function and subsequently used to dispatch a cancelation signal.
+     *
+     * @return The new cancelation signal transport.
+     *
+     * @hide
+     */
+    public static ICancelationSignal createTransport() {
+        return new Transport();
+    }
+
+    /**
+     * Given a locally created transport, returns its associated cancelation signal.
+     *
+     * @param transport The locally created transport, or null if none.
+     * @return The associated cancelation signal, or null if none.
+     *
+     * @hide
+     */
+    public static CancelationSignal fromTransport(ICancelationSignal transport) {
+        if (transport instanceof Transport) {
+            return ((Transport)transport).mCancelationSignal;
+        }
+        return null;
+    }
+
+    /**
+     * Listens for cancelation.
+     */
+    public interface OnCancelListener {
+        /**
+         * Called when {@link CancelationSignal#cancel} is invoked.
+         */
+        void onCancel();
+    }
+
+    private static final class Transport extends ICancelationSignal.Stub {
+        final CancelationSignal mCancelationSignal = new CancelationSignal();
+
+        @Override
+        public void cancel() throws RemoteException {
+            mCancelationSignal.cancel();
+        }
+    }
+}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 116ca48..adbeb6a 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -29,6 +29,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.os.RemoteException;
 import android.util.Log;
 
 import java.io.File;
@@ -174,28 +175,33 @@
             return getContentProvider().getClass().getName();
         }
 
+        @Override
         public Cursor query(Uri uri, String[] projection,
-                String selection, String[] selectionArgs, String sortOrder) {
+                String selection, String[] selectionArgs, String sortOrder,
+                ICancelationSignal cancelationSignal) {
             enforceReadPermission(uri);
-            return ContentProvider.this.query(uri, projection, selection,
-                    selectionArgs, sortOrder);
+            return ContentProvider.this.query(uri, projection, selection, selectionArgs, sortOrder,
+                    CancelationSignal.fromTransport(cancelationSignal));
         }
 
+        @Override
         public String getType(Uri uri) {
             return ContentProvider.this.getType(uri);
         }
 
-
+        @Override
         public Uri insert(Uri uri, ContentValues initialValues) {
             enforceWritePermission(uri);
             return ContentProvider.this.insert(uri, initialValues);
         }
 
+        @Override
         public int bulkInsert(Uri uri, ContentValues[] initialValues) {
             enforceWritePermission(uri);
             return ContentProvider.this.bulkInsert(uri, initialValues);
         }
 
+        @Override
         public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
                 throws OperationApplicationException {
             for (ContentProviderOperation operation : operations) {
@@ -210,17 +216,20 @@
             return ContentProvider.this.applyBatch(operations);
         }
 
+        @Override
         public int delete(Uri uri, String selection, String[] selectionArgs) {
             enforceWritePermission(uri);
             return ContentProvider.this.delete(uri, selection, selectionArgs);
         }
 
+        @Override
         public int update(Uri uri, ContentValues values, String selection,
                 String[] selectionArgs) {
             enforceWritePermission(uri);
             return ContentProvider.this.update(uri, values, selection, selectionArgs);
         }
 
+        @Override
         public ParcelFileDescriptor openFile(Uri uri, String mode)
                 throws FileNotFoundException {
             if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri);
@@ -228,6 +237,7 @@
             return ContentProvider.this.openFile(uri, mode);
         }
 
+        @Override
         public AssetFileDescriptor openAssetFile(Uri uri, String mode)
                 throws FileNotFoundException {
             if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri);
@@ -235,6 +245,7 @@
             return ContentProvider.this.openAssetFile(uri, mode);
         }
 
+        @Override
         public Bundle call(String method, String arg, Bundle extras) {
             return ContentProvider.this.call(method, arg, extras);
         }
@@ -251,6 +262,11 @@
             return ContentProvider.this.openTypedAssetFile(uri, mimeType, opts);
         }
 
+        @Override
+        public ICancelationSignal createCancelationSignal() throws RemoteException {
+            return CancelationSignal.createTransport();
+        }
+
         private void enforceReadPermission(Uri uri) {
             final int uid = Binder.getCallingUid();
             if (uid == mMyUid) {
@@ -541,6 +557,75 @@
             String selection, String[] selectionArgs, String sortOrder);
 
     /**
+     * Implement this to handle query requests from clients with support for cancelation.
+     * This method can be called from multiple threads, as described in
+     * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+     * and Threads</a>.
+     * <p>
+     * Example client call:<p>
+     * <pre>// Request a specific record.
+     * Cursor managedCursor = managedQuery(
+                ContentUris.withAppendedId(Contacts.People.CONTENT_URI, 2),
+                projection,    // Which columns to return.
+                null,          // WHERE clause.
+                null,          // WHERE clause value substitution
+                People.NAME + " ASC");   // Sort order.</pre>
+     * Example implementation:<p>
+     * <pre>// SQLiteQueryBuilder is a helper class that creates the
+        // proper SQL syntax for us.
+        SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder();
+
+        // Set the table we're querying.
+        qBuilder.setTables(DATABASE_TABLE_NAME);
+
+        // If the query ends in a specific record number, we're
+        // being asked for a specific record, so set the
+        // WHERE clause in our query.
+        if((URI_MATCHER.match(uri)) == SPECIFIC_MESSAGE){
+            qBuilder.appendWhere("_id=" + uri.getPathLeafId());
+        }
+
+        // Make the query.
+        Cursor c = qBuilder.query(mDb,
+                projection,
+                selection,
+                selectionArgs,
+                groupBy,
+                having,
+                sortOrder);
+        c.setNotificationUri(getContext().getContentResolver(), uri);
+        return c;</pre>
+     * <p>
+     * If you implement this method then you must also implement the version of
+     * {@link #query(Uri, String[], String, String[], String)} that does not take a cancelation
+     * provider to ensure correct operation on older versions of the Android Framework in
+     * which the cancelation signal overload was not available.
+     *
+     * @param uri The URI to query. This will be the full URI sent by the client;
+     *      if the client is requesting a specific record, the URI will end in a record number
+     *      that the implementation should parse and add to a WHERE or HAVING clause, specifying
+     *      that _id value.
+     * @param projection The list of columns to put into the cursor. If
+     *      null all columns are included.
+     * @param selection A selection criteria to apply when filtering rows.
+     *      If null then all rows are included.
+     * @param selectionArgs You may include ?s in selection, which will be replaced by
+     *      the values from selectionArgs, in order that they appear in the selection.
+     *      The values will be bound as Strings.
+     * @param sortOrder How the rows in the cursor should be sorted.
+     *      If null then the provider is free to define the sort order.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
+     * If the operation is canceled, then {@link OperationCanceledException} will be thrown
+     * when the query is executed.
+     * @return a Cursor or null.
+     */
+    public Cursor query(Uri uri, String[] projection,
+            String selection, String[] selectionArgs, String sortOrder,
+            CancelationSignal cancelationSignal) {
+        return query(uri, projection, selection, selectionArgs, sortOrder);
+    }
+
+    /**
      * Implement this to handle requests for the MIME type of the data at the
      * given URI.  The returned MIME type should start with
      * <code>vnd.android.cursor.item</code> for a single record,
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 0540109..9a1fa65 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -47,7 +47,20 @@
     /** See {@link ContentProvider#query ContentProvider.query} */
     public Cursor query(Uri url, String[] projection, String selection,
             String[] selectionArgs, String sortOrder) throws RemoteException {
-        return mContentProvider.query(url, projection, selection,  selectionArgs, sortOrder);
+        return query(url, projection, selection,  selectionArgs, sortOrder, null);
+    }
+
+    /** See {@link ContentProvider#query ContentProvider.query} */
+    public Cursor query(Uri url, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder, CancelationSignal cancelationSignal)
+                    throws RemoteException {
+        ICancelationSignal remoteCancelationSignal = null;
+        if (cancelationSignal != null) {
+            remoteCancelationSignal = mContentProvider.createCancelationSignal();
+            cancelationSignal.setRemote(remoteCancelationSignal);
+        }
+        return mContentProvider.query(url, projection, selection,  selectionArgs, sortOrder,
+                remoteCancelationSignal);
     }
 
     /** See {@link ContentProvider#getType ContentProvider.getType} */
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index b089bf2..e0e277a 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -21,7 +21,6 @@
 import android.database.BulkCursorToCursorAdaptor;
 import android.database.Cursor;
 import android.database.CursorToBulkCursorAdaptor;
-import android.database.CursorWindow;
 import android.database.DatabaseUtils;
 import android.database.IBulkCursor;
 import android.database.IContentObserver;
@@ -41,8 +40,6 @@
  * {@hide}
  */
 abstract public class ContentProviderNative extends Binder implements IContentProvider {
-    private static final String TAG = "ContentProvider";
-
     public ContentProviderNative()
     {
         attachInterface(this, descriptor);
@@ -108,8 +105,11 @@
                     String sortOrder = data.readString();
                     IContentObserver observer = IContentObserver.Stub.asInterface(
                             data.readStrongBinder());
+                    ICancelationSignal cancelationSignal = ICancelationSignal.Stub.asInterface(
+                            data.readStrongBinder());
 
-                    Cursor cursor = query(url, projection, selection, selectionArgs, sortOrder);
+                    Cursor cursor = query(url, projection, selection, selectionArgs, sortOrder,
+                            cancelationSignal);
                     if (cursor != null) {
                         CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor(
                                 cursor, observer, getProviderName());
@@ -295,6 +295,16 @@
                     }
                     return true;
                 }
+
+                case CREATE_CANCELATION_SIGNAL_TRANSACTION:
+                {
+                    data.enforceInterface(IContentProvider.descriptor);
+
+                    ICancelationSignal cancelationSignal = createCancelationSignal();
+                    reply.writeNoException();
+                    reply.writeStrongBinder(cancelationSignal.asBinder());
+                    return true;
+                }
             }
         } catch (Exception e) {
             DatabaseUtils.writeExceptionToParcel(reply, e);
@@ -324,7 +334,8 @@
     }
 
     public Cursor query(Uri url, String[] projection, String selection,
-            String[] selectionArgs, String sortOrder) throws RemoteException {
+            String[] selectionArgs, String sortOrder, ICancelationSignal cancelationSignal)
+                    throws RemoteException {
         BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -352,6 +363,7 @@
             }
             data.writeString(sortOrder);
             data.writeStrongBinder(adaptor.getObserver().asBinder());
+            data.writeStrongBinder(cancelationSignal != null ? cancelationSignal.asBinder() : null);
 
             mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);
 
@@ -620,5 +632,24 @@
         }
     }
 
+    public ICancelationSignal createCancelationSignal() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        try {
+            data.writeInterfaceToken(IContentProvider.descriptor);
+
+            mRemote.transact(IContentProvider.CREATE_CANCELATION_SIGNAL_TRANSACTION,
+                    data, reply, 0);
+
+            DatabaseUtils.readExceptionFromParcel(reply);
+            ICancelationSignal cancelationSignal = ICancelationSignal.Stub.asInterface(
+                    reply.readStrongBinder());
+            return cancelationSignal;
+        } finally {
+            data.recycle();
+            reply.recycle();
+        }
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 0debb84..e79475a 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -22,6 +22,7 @@
 import android.app.ActivityManagerNative;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
+import android.content.ContentProvider.Transport;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
@@ -302,13 +303,62 @@
      */
     public final Cursor query(Uri uri, String[] projection,
             String selection, String[] selectionArgs, String sortOrder) {
+        return query(uri, projection, selection, selectionArgs, sortOrder, null);
+    }
+
+    /**
+     * <p>
+     * Query the given URI, returning a {@link Cursor} over the result set.
+     * </p>
+     * <p>
+     * For best performance, the caller should follow these guidelines:
+     * <ul>
+     * <li>Provide an explicit projection, to prevent
+     * reading data from storage that aren't going to be used.</li>
+     * <li>Use question mark parameter markers such as 'phone=?' instead of
+     * explicit values in the {@code selection} parameter, so that queries
+     * that differ only by those values will be recognized as the same
+     * for caching purposes.</li>
+     * </ul>
+     * </p>
+     *
+     * @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 inefficient.
+     * @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 sortOrder 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.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
+     * If the operation is canceled, then {@link OperationCanceledException} will be thrown
+     * when the query is executed.
+     * @return A Cursor object, which is positioned before the first entry, or null
+     * @see Cursor
+     */
+    public final Cursor query(final Uri uri, String[] projection,
+            String selection, String[] selectionArgs, String sortOrder,
+            CancelationSignal cancelationSignal) {
         IContentProvider provider = acquireProvider(uri);
         if (provider == null) {
             return null;
         }
         try {
             long startTime = SystemClock.uptimeMillis();
-            Cursor qCursor = provider.query(uri, projection, selection, selectionArgs, sortOrder);
+
+            ICancelationSignal remoteCancelationSignal = null;
+            if (cancelationSignal != null) {
+                cancelationSignal.throwIfCanceled();
+                remoteCancelationSignal = provider.createCancelationSignal();
+                cancelationSignal.setRemote(remoteCancelationSignal);
+            }
+            Cursor qCursor = provider.query(uri, projection,
+                    selection, selectionArgs, sortOrder, remoteCancelationSignal);
             if (qCursor == null) {
                 releaseProvider(provider);
                 return null;
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 7af535b..6e4aca8 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -19,7 +19,6 @@
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.AsyncTask;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -49,18 +48,42 @@
     String mSortOrder;
 
     Cursor mCursor;
+    CancelationSignal mCancelationSignal;
 
     /* Runs on a worker thread */
     @Override
     public Cursor loadInBackground() {
-        Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
-                mSelectionArgs, mSortOrder);
-        if (cursor != null) {
-            // Ensure the cursor window is filled
-            cursor.getCount();
-            registerContentObserver(cursor, mObserver);
+        synchronized (this) {
+            if (isLoadInBackgroundCanceled()) {
+                throw new OperationCanceledException();
+            }
+            mCancelationSignal = new CancelationSignal();
         }
-        return cursor;
+        try {
+            Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
+                    mSelectionArgs, mSortOrder, mCancelationSignal);
+            if (cursor != null) {
+                // Ensure the cursor window is filled
+                cursor.getCount();
+                registerContentObserver(cursor, mObserver);
+            }
+            return cursor;
+        } finally {
+            synchronized (this) {
+                mCancelationSignal = null;
+            }
+        }
+    }
+
+    @Override
+    protected void onCancelLoadInBackground() {
+        super.onCancelLoadInBackground();
+
+        synchronized (this) {
+            if (mCancelationSignal != null) {
+                mCancelationSignal.cancel();
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/content/ICancelationSignal.aidl b/core/java/android/content/ICancelationSignal.aidl
new file mode 100644
index 0000000..3f5a24d
--- /dev/null
+++ b/core/java/android/content/ICancelationSignal.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2012 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 android.content;
+
+/**
+ * @hide
+ */
+interface ICancelationSignal {
+    oneway void cancel();
+}
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 2a67ff8..f52157f 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -34,7 +34,8 @@
  */
 public interface IContentProvider extends IInterface {
     public Cursor query(Uri url, String[] projection, String selection,
-            String[] selectionArgs, String sortOrder) throws RemoteException;
+            String[] selectionArgs, String sortOrder, ICancelationSignal cancelationSignal)
+                    throws RemoteException;
     public String getType(Uri url) throws RemoteException;
     public Uri insert(Uri url, ContentValues initialValues)
             throws RemoteException;
@@ -50,6 +51,7 @@
     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
             throws RemoteException, OperationApplicationException;
     public Bundle call(String method, String arg, Bundle extras) throws RemoteException;
+    public ICancelationSignal createCancelationSignal() throws RemoteException;
 
     // Data interchange.
     public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
@@ -71,4 +73,5 @@
     static final int CALL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 20;
     static final int GET_STREAM_TYPES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 21;
     static final int OPEN_TYPED_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 22;
+    static final int CREATE_CANCELATION_SIGNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 23;
 }
diff --git a/core/java/android/content/OperationCanceledException.java b/core/java/android/content/OperationCanceledException.java
new file mode 100644
index 0000000..24afcfa
--- /dev/null
+++ b/core/java/android/content/OperationCanceledException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 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 android.content;
+
+/**
+ * An exception type that is thrown when an operation in progress is canceled.
+ *
+ * @see CancelationSignal
+ */
+public class OperationCanceledException extends RuntimeException {
+    public OperationCanceledException() {
+        this(null);
+    }
+
+    public OperationCanceledException(String message) {
+        super(message != null ? message : "The operation has been canceled.");
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 72f62fd..710bd53 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -19,6 +19,8 @@
 import dalvik.system.BlockGuard;
 import dalvik.system.CloseGuard;
 
+import android.content.CancelationSignal;
+import android.content.OperationCanceledException;
 import android.database.Cursor;
 import android.database.CursorWindow;
 import android.database.DatabaseUtils;
@@ -82,7 +84,7 @@
  *
  * @hide
  */
-public final class SQLiteConnection {
+public final class SQLiteConnection implements CancelationSignal.OnCancelListener {
     private static final String TAG = "SQLiteConnection";
     private static final boolean DEBUG = false;
 
@@ -108,6 +110,12 @@
 
     private boolean mOnlyAllowReadOnlyOperations;
 
+    // The number of times attachCancelationSignal has been called.
+    // Because SQLite statement execution can be re-entrant, we keep track of how many
+    // times we have attempted to attach a cancelation signal to the connection so that
+    // we can ensure that we detach the signal at the right time.
+    private int mCancelationSignalAttachCount;
+
     private static native int nativeOpen(String path, int openFlags, String label,
             boolean enableTrace, boolean enableProfile);
     private static native void nativeClose(int connectionPtr);
@@ -145,6 +153,8 @@
             int connectionPtr, int statementPtr, int windowPtr,
             int startPos, int requiredPos, boolean countAllRows);
     private static native int nativeGetDbLookaside(int connectionPtr);
+    private static native void nativeCancel(int connectionPtr);
+    private static native void nativeResetCancel(int connectionPtr, boolean cancelable);
 
     private SQLiteConnection(SQLiteConnectionPool pool,
             SQLiteDatabaseConfiguration configuration,
@@ -345,11 +355,14 @@
      *
      * @param sql The SQL statement to execute.
      * @param bindArgs The arguments to bind, or null if none.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      *
      * @throws SQLiteException if an error occurs, such as a syntax error
      * or invalid number of bind arguments.
+     * @throws OperationCanceledException if the operation was canceled.
      */
-    public void execute(String sql, Object[] bindArgs) {
+    public void execute(String sql, Object[] bindArgs,
+            CancelationSignal cancelationSignal) {
         if (sql == null) {
             throw new IllegalArgumentException("sql must not be null.");
         }
@@ -361,7 +374,12 @@
                 throwIfStatementForbidden(statement);
                 bindArguments(statement, bindArgs);
                 applyBlockGuardPolicy(statement);
-                nativeExecute(mConnectionPtr, statement.mStatementPtr);
+                attachCancelationSignal(cancelationSignal);
+                try {
+                    nativeExecute(mConnectionPtr, statement.mStatementPtr);
+                } finally {
+                    detachCancelationSignal(cancelationSignal);
+                }
             } finally {
                 releasePreparedStatement(statement);
             }
@@ -378,13 +396,16 @@
      *
      * @param sql The SQL statement to execute.
      * @param bindArgs The arguments to bind, or null if none.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      * @return The value of the first column in the first row of the result set
      * as a <code>long</code>, or zero if none.
      *
      * @throws SQLiteException if an error occurs, such as a syntax error
      * or invalid number of bind arguments.
+     * @throws OperationCanceledException if the operation was canceled.
      */
-    public long executeForLong(String sql, Object[] bindArgs) {
+    public long executeForLong(String sql, Object[] bindArgs,
+            CancelationSignal cancelationSignal) {
         if (sql == null) {
             throw new IllegalArgumentException("sql must not be null.");
         }
@@ -396,7 +417,12 @@
                 throwIfStatementForbidden(statement);
                 bindArguments(statement, bindArgs);
                 applyBlockGuardPolicy(statement);
-                return nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr);
+                attachCancelationSignal(cancelationSignal);
+                try {
+                    return nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr);
+                } finally {
+                    detachCancelationSignal(cancelationSignal);
+                }
             } finally {
                 releasePreparedStatement(statement);
             }
@@ -413,13 +439,16 @@
      *
      * @param sql The SQL statement to execute.
      * @param bindArgs The arguments to bind, or null if none.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      * @return The value of the first column in the first row of the result set
      * as a <code>String</code>, or null if none.
      *
      * @throws SQLiteException if an error occurs, such as a syntax error
      * or invalid number of bind arguments.
+     * @throws OperationCanceledException if the operation was canceled.
      */
-    public String executeForString(String sql, Object[] bindArgs) {
+    public String executeForString(String sql, Object[] bindArgs,
+            CancelationSignal cancelationSignal) {
         if (sql == null) {
             throw new IllegalArgumentException("sql must not be null.");
         }
@@ -431,7 +460,12 @@
                 throwIfStatementForbidden(statement);
                 bindArguments(statement, bindArgs);
                 applyBlockGuardPolicy(statement);
-                return nativeExecuteForString(mConnectionPtr, statement.mStatementPtr);
+                attachCancelationSignal(cancelationSignal);
+                try {
+                    return nativeExecuteForString(mConnectionPtr, statement.mStatementPtr);
+                } finally {
+                    detachCancelationSignal(cancelationSignal);
+                }
             } finally {
                 releasePreparedStatement(statement);
             }
@@ -449,14 +483,17 @@
      *
      * @param sql The SQL statement to execute.
      * @param bindArgs The arguments to bind, or null if none.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      * @return The file descriptor for a shared memory region that contains
      * the value of the first column in the first row of the result set as a BLOB,
      * or null if none.
      *
      * @throws SQLiteException if an error occurs, such as a syntax error
      * or invalid number of bind arguments.
+     * @throws OperationCanceledException if the operation was canceled.
      */
-    public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs) {
+    public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs,
+            CancelationSignal cancelationSignal) {
         if (sql == null) {
             throw new IllegalArgumentException("sql must not be null.");
         }
@@ -469,9 +506,14 @@
                 throwIfStatementForbidden(statement);
                 bindArguments(statement, bindArgs);
                 applyBlockGuardPolicy(statement);
-                int fd = nativeExecuteForBlobFileDescriptor(
-                        mConnectionPtr, statement.mStatementPtr);
-                return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null;
+                attachCancelationSignal(cancelationSignal);
+                try {
+                    int fd = nativeExecuteForBlobFileDescriptor(
+                            mConnectionPtr, statement.mStatementPtr);
+                    return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null;
+                } finally {
+                    detachCancelationSignal(cancelationSignal);
+                }
             } finally {
                 releasePreparedStatement(statement);
             }
@@ -489,12 +531,15 @@
      *
      * @param sql The SQL statement to execute.
      * @param bindArgs The arguments to bind, or null if none.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      * @return The number of rows that were changed.
      *
      * @throws SQLiteException if an error occurs, such as a syntax error
      * or invalid number of bind arguments.
+     * @throws OperationCanceledException if the operation was canceled.
      */
-    public int executeForChangedRowCount(String sql, Object[] bindArgs) {
+    public int executeForChangedRowCount(String sql, Object[] bindArgs,
+            CancelationSignal cancelationSignal) {
         if (sql == null) {
             throw new IllegalArgumentException("sql must not be null.");
         }
@@ -507,8 +552,13 @@
                 throwIfStatementForbidden(statement);
                 bindArguments(statement, bindArgs);
                 applyBlockGuardPolicy(statement);
-                return nativeExecuteForChangedRowCount(
-                        mConnectionPtr, statement.mStatementPtr);
+                attachCancelationSignal(cancelationSignal);
+                try {
+                    return nativeExecuteForChangedRowCount(
+                            mConnectionPtr, statement.mStatementPtr);
+                } finally {
+                    detachCancelationSignal(cancelationSignal);
+                }
             } finally {
                 releasePreparedStatement(statement);
             }
@@ -526,12 +576,15 @@
      *
      * @param sql The SQL statement to execute.
      * @param bindArgs The arguments to bind, or null if none.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      * @return The row id of the last row that was inserted, or 0 if none.
      *
      * @throws SQLiteException if an error occurs, such as a syntax error
      * or invalid number of bind arguments.
+     * @throws OperationCanceledException if the operation was canceled.
      */
-    public long executeForLastInsertedRowId(String sql, Object[] bindArgs) {
+    public long executeForLastInsertedRowId(String sql, Object[] bindArgs,
+            CancelationSignal cancelationSignal) {
         if (sql == null) {
             throw new IllegalArgumentException("sql must not be null.");
         }
@@ -544,8 +597,13 @@
                 throwIfStatementForbidden(statement);
                 bindArguments(statement, bindArgs);
                 applyBlockGuardPolicy(statement);
-                return nativeExecuteForLastInsertedRowId(
-                        mConnectionPtr, statement.mStatementPtr);
+                attachCancelationSignal(cancelationSignal);
+                try {
+                    return nativeExecuteForLastInsertedRowId(
+                            mConnectionPtr, statement.mStatementPtr);
+                } finally {
+                    detachCancelationSignal(cancelationSignal);
+                }
             } finally {
                 releasePreparedStatement(statement);
             }
@@ -571,14 +629,17 @@
      * so that it does.  Must be greater than or equal to <code>startPos</code>.
      * @param countAllRows True to count all rows that the query would return
      * regagless of whether they fit in the window.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      * @return The number of rows that were counted during query execution.  Might
      * not be all rows in the result set unless <code>countAllRows</code> is true.
      *
      * @throws SQLiteException if an error occurs, such as a syntax error
      * or invalid number of bind arguments.
+     * @throws OperationCanceledException if the operation was canceled.
      */
     public int executeForCursorWindow(String sql, Object[] bindArgs,
-            CursorWindow window, int startPos, int requiredPos, boolean countAllRows) {
+            CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
+            CancelationSignal cancelationSignal) {
         if (sql == null) {
             throw new IllegalArgumentException("sql must not be null.");
         }
@@ -597,14 +658,19 @@
                 throwIfStatementForbidden(statement);
                 bindArguments(statement, bindArgs);
                 applyBlockGuardPolicy(statement);
-                final long result = nativeExecuteForCursorWindow(
-                        mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
-                        startPos, requiredPos, countAllRows);
-                actualPos = (int)(result >> 32);
-                countedRows = (int)result;
-                filledRows = window.getNumRows();
-                window.setStartPosition(actualPos);
-                return countedRows;
+                attachCancelationSignal(cancelationSignal);
+                try {
+                    final long result = nativeExecuteForCursorWindow(
+                            mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
+                            startPos, requiredPos, countAllRows);
+                    actualPos = (int)(result >> 32);
+                    countedRows = (int)result;
+                    filledRows = window.getNumRows();
+                    window.setStartPosition(actualPos);
+                    return countedRows;
+                } finally {
+                    detachCancelationSignal(cancelationSignal);
+                }
             } finally {
                 releasePreparedStatement(statement);
             }
@@ -685,6 +751,46 @@
         recyclePreparedStatement(statement);
     }
 
+    private void attachCancelationSignal(CancelationSignal cancelationSignal) {
+        if (cancelationSignal != null) {
+            cancelationSignal.throwIfCanceled();
+
+            mCancelationSignalAttachCount += 1;
+            if (mCancelationSignalAttachCount == 1) {
+                // Reset cancelation flag before executing the statement.
+                nativeResetCancel(mConnectionPtr, true /*cancelable*/);
+
+                // After this point, onCancel() may be called concurrently.
+                cancelationSignal.setOnCancelListener(this);
+            }
+        }
+    }
+
+    private void detachCancelationSignal(CancelationSignal cancelationSignal) {
+        if (cancelationSignal != null) {
+            assert mCancelationSignalAttachCount > 0;
+
+            mCancelationSignalAttachCount -= 1;
+            if (mCancelationSignalAttachCount == 0) {
+                // After this point, onCancel() cannot be called concurrently.
+                cancelationSignal.setOnCancelListener(null);
+
+                // Reset cancelation flag after executing the statement.
+                nativeResetCancel(mConnectionPtr, false /*cancelable*/);
+            }
+        }
+    }
+
+    // CancelationSignal.OnCancelationListener callback.
+    // This method may be called on a different thread than the executing statement.
+    // However, it will only be called between calls to attachCancelationSignal and
+    // detachCancelationSignal, while a statement is executing.  We can safely assume
+    // that the SQLite connection is still alive.
+    @Override
+    public void onCancel() {
+        nativeCancel(mConnectionPtr);
+    }
+
     private void bindArguments(PreparedStatement statement, Object[] bindArgs) {
         final int count = bindArgs != null ? bindArgs.length : 0;
         if (count != statement.mNumParameters) {
@@ -822,8 +928,8 @@
         long pageCount = 0;
         long pageSize = 0;
         try {
-            pageCount = executeForLong("PRAGMA page_count;", null);
-            pageSize = executeForLong("PRAGMA page_size;", null);
+            pageCount = executeForLong("PRAGMA page_count;", null, null);
+            pageSize = executeForLong("PRAGMA page_size;", null, null);
         } catch (SQLiteException ex) {
             // Ignore.
         }
@@ -834,15 +940,15 @@
         // the main database which we have already described.
         CursorWindow window = new CursorWindow("collectDbStats");
         try {
-            executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false);
+            executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null);
             for (int i = 1; i < window.getNumRows(); i++) {
                 String name = window.getString(i, 1);
                 String path = window.getString(i, 2);
                 pageCount = 0;
                 pageSize = 0;
                 try {
-                    pageCount = executeForLong("PRAGMA " + name + ".page_count;", null);
-                    pageSize = executeForLong("PRAGMA " + name + ".page_size;", null);
+                    pageCount = executeForLong("PRAGMA " + name + ".page_count;", null, null);
+                    pageSize = executeForLong("PRAGMA " + name + ".page_size;", null, null);
                 } catch (SQLiteException ex) {
                     // Ignore.
                 }
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 5469213..d335738 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -18,6 +18,8 @@
 
 import dalvik.system.CloseGuard;
 
+import android.content.CancelationSignal;
+import android.content.OperationCanceledException;
 import android.database.sqlite.SQLiteDebug.DbStats;
 import android.os.SystemClock;
 import android.util.Log;
@@ -282,13 +284,16 @@
      * @param sql If not null, try to find a connection that already has
      * the specified SQL statement in its prepared statement cache.
      * @param connectionFlags The connection request flags.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      * @return The connection that was acquired, never null.
      *
      * @throws IllegalStateException if the pool has been closed.
      * @throws SQLiteException if a database error occurs.
+     * @throws OperationCanceledException if the operation was canceled.
      */
-    public SQLiteConnection acquireConnection(String sql, int connectionFlags) {
-        return waitForConnection(sql, connectionFlags);
+    public SQLiteConnection acquireConnection(String sql, int connectionFlags,
+            CancelationSignal cancelationSignal) {
+        return waitForConnection(sql, connectionFlags, cancelationSignal);
     }
 
     /**
@@ -497,7 +502,8 @@
     }
 
     // Might throw.
-    private SQLiteConnection waitForConnection(String sql, int connectionFlags) {
+    private SQLiteConnection waitForConnection(String sql, int connectionFlags,
+            CancelationSignal cancelationSignal) {
         final boolean wantPrimaryConnection =
                 (connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0;
 
@@ -505,6 +511,11 @@
         synchronized (mLock) {
             throwIfClosedLocked();
 
+            // Abort if canceled.
+            if (cancelationSignal != null) {
+                cancelationSignal.throwIfCanceled();
+            }
+
             // Try to acquire a connection.
             SQLiteConnection connection = null;
             if (!wantPrimaryConnection) {
@@ -538,6 +549,18 @@
             } else {
                 mConnectionWaiterQueue = waiter;
             }
+
+            if (cancelationSignal != null) {
+                final int nonce = waiter.mNonce;
+                cancelationSignal.setOnCancelListener(new CancelationSignal.OnCancelListener() {
+                    @Override
+                    public void onCancel() {
+                        synchronized (mLock) {
+                            cancelConnectionWaiterLocked(waiter, nonce);
+                        }
+                    }
+                });
+            }
         }
 
         // Park the thread until a connection is assigned or the pool is closed.
@@ -547,7 +570,9 @@
         for (;;) {
             // Detect and recover from connection leaks.
             if (mConnectionLeaked.compareAndSet(true, false)) {
-                wakeConnectionWaitersLocked();
+                synchronized (mLock) {
+                    wakeConnectionWaitersLocked();
+                }
             }
 
             // Wait to be unparked (may already have happened), a timeout, or interruption.
@@ -560,15 +585,16 @@
             synchronized (mLock) {
                 throwIfClosedLocked();
 
-                SQLiteConnection connection = waiter.mAssignedConnection;
-                if (connection != null) {
+                final SQLiteConnection connection = waiter.mAssignedConnection;
+                final RuntimeException ex = waiter.mException;
+                if (connection != null || ex != null) {
+                    if (cancelationSignal != null) {
+                        cancelationSignal.setOnCancelListener(null);
+                    }
                     recycleConnectionWaiterLocked(waiter);
-                    return connection;
-                }
-
-                RuntimeException ex = waiter.mException;
-                if (ex != null) {
-                    recycleConnectionWaiterLocked(waiter);
+                    if (connection != null) {
+                        return connection;
+                    }
                     throw ex; // rethrow!
                 }
 
@@ -585,6 +611,40 @@
     }
 
     // Can't throw.
+    private void cancelConnectionWaiterLocked(ConnectionWaiter waiter, int nonce) {
+        if (waiter.mNonce != nonce) {
+            // Waiter already removed and recycled.
+            return;
+        }
+
+        if (waiter.mAssignedConnection != null || waiter.mException != null) {
+            // Waiter is done waiting but has not woken up yet.
+            return;
+        }
+
+        // Waiter must still be waiting.  Dequeue it.
+        ConnectionWaiter predecessor = null;
+        ConnectionWaiter current = mConnectionWaiterQueue;
+        while (current != waiter) {
+            assert current != null;
+            predecessor = current;
+            current = current.mNext;
+        }
+        if (predecessor != null) {
+            predecessor.mNext = waiter.mNext;
+        } else {
+            mConnectionWaiterQueue = waiter.mNext;
+        }
+
+        // Send the waiter an exception and unpark it.
+        waiter.mException = new OperationCanceledException();
+        LockSupport.unpark(waiter.mThread);
+
+        // Check whether removing this waiter will enable other waiters to make progress.
+        wakeConnectionWaitersLocked();
+    }
+
+    // Can't throw.
     private void logConnectionPoolBusyLocked(long waitMillis, int connectionFlags) {
         final Thread thread = Thread.currentThread();
         StringBuilder msg = new StringBuilder();
@@ -826,6 +886,7 @@
         waiter.mSql = null;
         waiter.mAssignedConnection = null;
         waiter.mException = null;
+        waiter.mNonce += 1;
         mConnectionWaiterPool = waiter;
     }
 
@@ -904,5 +965,6 @@
         public int mConnectionFlags;
         public SQLiteConnection mAssignedConnection;
         public RuntimeException mException;
+        public int mNonce;
     }
 }
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 9cb6480..7db7bfb 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -16,7 +16,9 @@
 
 package android.database.sqlite;
 
+import android.content.CancelationSignal;
 import android.content.ContentValues;
+import android.content.OperationCanceledException;
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.DatabaseErrorHandler;
@@ -492,7 +494,7 @@
             boolean exclusive) {
         getThreadSession().beginTransaction(exclusive ? SQLiteSession.TRANSACTION_MODE_EXCLUSIVE :
                 SQLiteSession.TRANSACTION_MODE_IMMEDIATE, transactionListener,
-                getThreadDefaultConnectionFlags(false /*readOnly*/));
+                getThreadDefaultConnectionFlags(false /*readOnly*/), null);
     }
 
     /**
@@ -500,7 +502,7 @@
      * are committed and rolled back.
      */
     public void endTransaction() {
-        getThreadSession().endTransaction();
+        getThreadSession().endTransaction(null);
     }
 
     /**
@@ -597,7 +599,7 @@
     }
 
     private boolean yieldIfContendedHelper(boolean throwIfUnsafe, long sleepAfterYieldDelay) {
-        return getThreadSession().yieldTransaction(sleepAfterYieldDelay, throwIfUnsafe);
+        return getThreadSession().yieldTransaction(sleepAfterYieldDelay, throwIfUnsafe, null);
     }
 
     /**
@@ -935,7 +937,48 @@
             String selection, String[] selectionArgs, String groupBy,
             String having, String orderBy, String limit) {
         return queryWithFactory(null, distinct, table, columns, selection, selectionArgs,
-                groupBy, having, orderBy, limit);
+                groupBy, having, orderBy, limit, null);
+    }
+
+    /**
+     * Query the given URL, returning a {@link Cursor} over the result set.
+     *
+     * @param distinct true if you want each row to be unique, false otherwise.
+     * @param table The table name to compile the query against.
+     * @param columns 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 table.
+     * @param selectionArgs You may include ?s in selection, which will be
+     *         replaced by the values from selectionArgs, in order that they
+     *         appear in the selection. The values will be bound as Strings.
+     * @param groupBy A filter declaring how to group rows, formatted as an SQL
+     *            GROUP BY clause (excluding the GROUP BY itself). Passing null
+     *            will cause the rows to not be grouped.
+     * @param having A filter declare which row groups to include in the cursor,
+     *            if row grouping is being used, formatted as an SQL HAVING
+     *            clause (excluding the HAVING itself). Passing null will cause
+     *            all row groups to be included, and is required when row
+     *            grouping is not being used.
+     * @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.
+     * @param limit Limits the number of rows returned by the query,
+     *            formatted as LIMIT clause. Passing null denotes no LIMIT clause.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
+     * If the operation is canceled, then {@link OperationCanceledException} will be thrown
+     * when the query is executed.
+     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
+     * {@link Cursor}s are not synchronized, see the documentation for more details.
+     * @see Cursor
+     */
+    public Cursor query(boolean distinct, String table, String[] columns,
+            String selection, String[] selectionArgs, String groupBy,
+            String having, String orderBy, String limit, CancelationSignal cancelationSignal) {
+        return queryWithFactory(null, distinct, table, columns, selection, selectionArgs,
+                groupBy, having, orderBy, limit, cancelationSignal);
     }
 
     /**
@@ -974,12 +1017,55 @@
             boolean distinct, String table, String[] columns,
             String selection, String[] selectionArgs, String groupBy,
             String having, String orderBy, String limit) {
+        return queryWithFactory(cursorFactory, distinct, table, columns, selection,
+                selectionArgs, groupBy, having, orderBy, limit, null);
+    }
+
+    /**
+     * Query the given URL, returning a {@link Cursor} over the result set.
+     *
+     * @param cursorFactory the cursor factory to use, or null for the default factory
+     * @param distinct true if you want each row to be unique, false otherwise.
+     * @param table The table name to compile the query against.
+     * @param columns 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 table.
+     * @param selectionArgs You may include ?s in selection, which will be
+     *         replaced by the values from selectionArgs, in order that they
+     *         appear in the selection. The values will be bound as Strings.
+     * @param groupBy A filter declaring how to group rows, formatted as an SQL
+     *            GROUP BY clause (excluding the GROUP BY itself). Passing null
+     *            will cause the rows to not be grouped.
+     * @param having A filter declare which row groups to include in the cursor,
+     *            if row grouping is being used, formatted as an SQL HAVING
+     *            clause (excluding the HAVING itself). Passing null will cause
+     *            all row groups to be included, and is required when row
+     *            grouping is not being used.
+     * @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.
+     * @param limit Limits the number of rows returned by the query,
+     *            formatted as LIMIT clause. Passing null denotes no LIMIT clause.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
+     * If the operation is canceled, then {@link OperationCanceledException} will be thrown
+     * when the query is executed.
+     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
+     * {@link Cursor}s are not synchronized, see the documentation for more details.
+     * @see Cursor
+     */
+    public Cursor queryWithFactory(CursorFactory cursorFactory,
+            boolean distinct, String table, String[] columns,
+            String selection, String[] selectionArgs, String groupBy,
+            String having, String orderBy, String limit, CancelationSignal cancelationSignal) {
         throwIfNotOpen(); // fail fast
         String sql = SQLiteQueryBuilder.buildQueryString(
                 distinct, table, columns, selection, groupBy, having, orderBy, limit);
 
-        return rawQueryWithFactory(
-                cursorFactory, sql, selectionArgs, findEditTable(table));
+        return rawQueryWithFactory(cursorFactory, sql, selectionArgs,
+                findEditTable(table), cancelationSignal);
     }
 
     /**
@@ -1067,7 +1153,25 @@
      * {@link Cursor}s are not synchronized, see the documentation for more details.
      */
     public Cursor rawQuery(String sql, String[] selectionArgs) {
-        return rawQueryWithFactory(null, sql, selectionArgs, null);
+        return rawQueryWithFactory(null, sql, selectionArgs, null, null);
+    }
+
+    /**
+     * Runs the provided SQL and returns a {@link Cursor} over the result set.
+     *
+     * @param sql the SQL query. The SQL string must not be ; terminated
+     * @param selectionArgs You may include ?s in where clause in the query,
+     *     which will be replaced by the values from selectionArgs. The
+     *     values will be bound as Strings.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
+     * If the operation is canceled, then {@link OperationCanceledException} will be thrown
+     * when the query is executed.
+     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
+     * {@link Cursor}s are not synchronized, see the documentation for more details.
+     */
+    public Cursor rawQuery(String sql, String[] selectionArgs,
+            CancelationSignal cancelationSignal) {
+        return rawQueryWithFactory(null, sql, selectionArgs, null, cancelationSignal);
     }
 
     /**
@@ -1085,9 +1189,31 @@
     public Cursor rawQueryWithFactory(
             CursorFactory cursorFactory, String sql, String[] selectionArgs,
             String editTable) {
+        return rawQueryWithFactory(cursorFactory, sql, selectionArgs, editTable, null);
+    }
+
+    /**
+     * Runs the provided SQL and returns a cursor over the result set.
+     *
+     * @param cursorFactory the cursor factory to use, or null for the default factory
+     * @param sql the SQL query. The SQL string must not be ; terminated
+     * @param selectionArgs You may include ?s in where clause in the query,
+     *     which will be replaced by the values from selectionArgs. The
+     *     values will be bound as Strings.
+     * @param editTable the name of the first table, which is editable
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
+     * If the operation is canceled, then {@link OperationCanceledException} will be thrown
+     * when the query is executed.
+     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
+     * {@link Cursor}s are not synchronized, see the documentation for more details.
+     */
+    public Cursor rawQueryWithFactory(
+            CursorFactory cursorFactory, String sql, String[] selectionArgs,
+            String editTable, CancelationSignal cancelationSignal) {
         throwIfNotOpen(); // fail fast
 
-        SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable);
+        SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable,
+                cancelationSignal);
         return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory,
                 selectionArgs);
     }
@@ -1786,7 +1912,7 @@
      */
     void lockPrimaryConnection() {
         getThreadSession().beginTransaction(SQLiteSession.TRANSACTION_MODE_DEFERRED,
-                null, SQLiteConnectionPool.CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY);
+                null, SQLiteConnectionPool.CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY, null);
     }
 
     /**
@@ -1795,7 +1921,7 @@
      * @see #lockPrimaryConnection()
      */
     void unlockPrimaryConnection() {
-        getThreadSession().endTransaction();
+        getThreadSession().endTransaction(null);
     }
 
     @Override
diff --git a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
index 52fd1d2..c490dc6 100644
--- a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
+++ b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
@@ -16,6 +16,7 @@
 
 package android.database.sqlite;
 
+import android.content.CancelationSignal;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase.CursorFactory;
 
@@ -28,16 +29,19 @@
     private final SQLiteDatabase mDatabase;
     private final String mEditTable; 
     private final String mSql;
+    private final CancelationSignal mCancelationSignal;
     private SQLiteQuery mQuery;
 
-    public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable) {
+    public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable,
+            CancelationSignal cancelationSignal) {
         mDatabase = db;
         mEditTable = editTable;
         mSql = sql;
+        mCancelationSignal = cancelationSignal;
     }
 
     public Cursor query(CursorFactory factory, String[] selectionArgs) {
-        final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql);
+        final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancelationSignal);
         final Cursor cursor;
         try {
             query.bindAllArgsAsStrings(selectionArgs);
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 8194458..f3da2a6 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -16,6 +16,7 @@
 
 package android.database.sqlite;
 
+import android.content.CancelationSignal;
 import android.database.DatabaseUtils;
 
 import java.util.Arrays;
@@ -36,7 +37,8 @@
     private final int mNumParameters;
     private final Object[] mBindArgs;
 
-    SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs) {
+    SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs,
+            CancelationSignal cancelationSignalForPrepare) {
         mDatabase = db;
         mSql = sql.trim();
 
@@ -54,7 +56,8 @@
                 boolean assumeReadOnly = (n == DatabaseUtils.STATEMENT_SELECT);
                 SQLiteStatementInfo info = new SQLiteStatementInfo();
                 db.getThreadSession().prepare(mSql,
-                        db.getThreadDefaultConnectionFlags(assumeReadOnly), info);
+                        db.getThreadDefaultConnectionFlags(assumeReadOnly),
+                        cancelationSignalForPrepare, info);
                 mReadOnly = info.readOnly;
                 mColumnNames = info.columnNames;
                 mNumParameters = info.numParameters;
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index 17aa886..df2e260 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -16,6 +16,8 @@
 
 package android.database.sqlite;
 
+import android.content.CancelationSignal;
+import android.content.OperationCanceledException;
 import android.database.CursorWindow;
 import android.util.Log;
 
@@ -29,8 +31,12 @@
 public final class SQLiteQuery extends SQLiteProgram {
     private static final String TAG = "SQLiteQuery";
 
-    SQLiteQuery(SQLiteDatabase db, String query) {
-        super(db, query, null);
+    private final CancelationSignal mCancelationSignal;
+
+    SQLiteQuery(SQLiteDatabase db, String query, CancelationSignal cancelationSignal) {
+        super(db, query, null, cancelationSignal);
+
+        mCancelationSignal = cancelationSignal;
     }
 
     /**
@@ -44,6 +50,9 @@
      * return regardless of whether they fit in the window.
      * @return Number of rows that were enumerated.  Might not be all rows
      * unless countAllRows is true.
+     *
+     * @throws SQLiteException if an error occurs.
+     * @throws OperationCanceledException if the operation was canceled.
      */
     int fillWindow(CursorWindow window, int startPos, int requiredPos, boolean countAllRows) {
         acquireReference();
@@ -51,7 +60,8 @@
             window.acquireReference();
             try {
                 int numRows = getSession().executeForCursorWindow(getSql(), getBindArgs(),
-                        window, startPos, requiredPos, countAllRows, getConnectionFlags());
+                        window, startPos, requiredPos, countAllRows, getConnectionFlags(),
+                        mCancelationSignal);
                 return numRows;
             } catch (SQLiteDatabaseCorruptException ex) {
                 onCorruption();
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index 1b7b398..89469cb 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -16,6 +16,8 @@
 
 package android.database.sqlite;
 
+import android.content.CancelationSignal;
+import android.content.OperationCanceledException;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.provider.BaseColumns;
@@ -137,8 +139,9 @@
     /**
      * Sets the cursor factory to be used for the query.  You can use
      * one factory for all queries on a database but it is normally
-     * easier to specify the factory when doing this query.  @param
-     * factory the factor to use
+     * easier to specify the factory when doing this query.
+     *
+     * @param factory the factory to use.
      */
     public void setCursorFactory(SQLiteDatabase.CursorFactory factory) {
         mFactory = factory;
@@ -289,7 +292,7 @@
             String selection, String[] selectionArgs, String groupBy,
             String having, String sortOrder) {
         return query(db, projectionIn, selection, selectionArgs, groupBy, having, sortOrder,
-                null /* limit */);
+                null /* limit */, null /* cancelationSignal */);
     }
 
     /**
@@ -327,6 +330,48 @@
     public Cursor query(SQLiteDatabase db, String[] projectionIn,
             String selection, String[] selectionArgs, String groupBy,
             String having, String sortOrder, String limit) {
+        return query(db, projectionIn, selection, selectionArgs,
+                groupBy, having, sortOrder, limit, null);
+    }
+
+    /**
+     * Perform a query by combining all current settings and the
+     * information passed into this method.
+     *
+     * @param db the database to query on
+     * @param projectionIn 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 URL.
+     * @param selectionArgs You may include ?s in selection, which
+     *   will be replaced by the values from selectionArgs, in order
+     *   that they appear in the selection. The values will be bound
+     *   as Strings.
+     * @param groupBy A filter declaring how to group rows, formatted
+     *   as an SQL GROUP BY clause (excluding the GROUP BY
+     *   itself). Passing null will cause the rows to not be grouped.
+     * @param having A filter declare which row groups to include in
+     *   the cursor, if row grouping is being used, formatted as an
+     *   SQL HAVING clause (excluding the HAVING itself).  Passing
+     *   null will cause all row groups to be included, and is
+     *   required when row grouping is not being used.
+     * @param sortOrder 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.
+     * @param limit Limits the number of rows returned by the query,
+     *   formatted as LIMIT clause. Passing null denotes no LIMIT clause.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
+     * If the operation is canceled, then {@link OperationCanceledException} will be thrown
+     * when the query is executed.
+     * @return a cursor over the result set
+     * @see android.content.ContentResolver#query(android.net.Uri, String[],
+     *      String, String[], String)
+     */
+    public Cursor query(SQLiteDatabase db, String[] projectionIn,
+            String selection, String[] selectionArgs, String groupBy,
+            String having, String sortOrder, String limit, CancelationSignal cancelationSignal) {
         if (mTables == null) {
             return null;
         }
@@ -341,7 +386,8 @@
             // in both the wrapped and original forms.
             String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy,
                     having, sortOrder, limit);
-            validateQuerySql(db, sqlForValidation); // will throw if query is invalid
+            validateQuerySql(db, sqlForValidation,
+                    cancelationSignal); // will throw if query is invalid
         }
 
         String sql = buildQuery(
@@ -353,16 +399,18 @@
         }
         return db.rawQueryWithFactory(
                 mFactory, sql, selectionArgs,
-                SQLiteDatabase.findEditTable(mTables)); // will throw if query is invalid
+                SQLiteDatabase.findEditTable(mTables),
+                cancelationSignal); // will throw if query is invalid
     }
 
     /**
      * Verifies that a SQL SELECT statement is valid by compiling it.
      * If the SQL statement is not valid, this method will throw a {@link SQLiteException}.
      */
-    private void validateQuerySql(SQLiteDatabase db, String sql) {
+    private void validateQuerySql(SQLiteDatabase db, String sql,
+            CancelationSignal cancelationSignal) {
         db.getThreadSession().prepare(sql,
-                db.getThreadDefaultConnectionFlags(true /*readOnly*/), null);
+                db.getThreadDefaultConnectionFlags(true /*readOnly*/), cancelationSignal, null);
     }
 
     /**
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index a933051..b5a3e31 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -16,6 +16,8 @@
 
 package android.database.sqlite;
 
+import android.content.CancelationSignal;
+import android.content.OperationCanceledException;
 import android.database.CursorWindow;
 import android.database.DatabaseUtils;
 import android.os.ParcelFileDescriptor;
@@ -156,8 +158,6 @@
  * triggers may call custom SQLite functions that perform additional queries.
  * </p>
  *
- * TODO: Support timeouts on all possibly blocking operations.
- *
  * @hide
  */
 public final class SQLiteSession {
@@ -280,24 +280,34 @@
      * @param transactionListener The transaction listener, or null if none.
      * @param connectionFlags The connection flags to use if a connection must be
      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      *
      * @throws IllegalStateException if {@link #setTransactionSuccessful} has already been
      * called for the current transaction.
+     * @throws SQLiteException if an error occurs.
+     * @throws OperationCanceledException if the operation was canceled.
      *
      * @see #setTransactionSuccessful
      * @see #yieldTransaction
      * @see #endTransaction
      */
     public void beginTransaction(int transactionMode,
-            SQLiteTransactionListener transactionListener, int connectionFlags) {
+            SQLiteTransactionListener transactionListener, int connectionFlags,
+            CancelationSignal cancelationSignal) {
         throwIfTransactionMarkedSuccessful();
-        beginTransactionUnchecked(transactionMode, transactionListener, connectionFlags);
+        beginTransactionUnchecked(transactionMode, transactionListener, connectionFlags,
+                cancelationSignal);
     }
 
     private void beginTransactionUnchecked(int transactionMode,
-            SQLiteTransactionListener transactionListener, int connectionFlags) {
+            SQLiteTransactionListener transactionListener, int connectionFlags,
+            CancelationSignal cancelationSignal) {
+        if (cancelationSignal != null) {
+            cancelationSignal.throwIfCanceled();
+        }
+
         if (mTransactionStack == null) {
-            acquireConnection(null, connectionFlags); // might throw
+            acquireConnection(null, connectionFlags, cancelationSignal); // might throw
         }
         try {
             // Set up the transaction such that we can back out safely
@@ -306,13 +316,15 @@
                 // Execute SQL might throw a runtime exception.
                 switch (transactionMode) {
                     case TRANSACTION_MODE_IMMEDIATE:
-                        mConnection.execute("BEGIN IMMEDIATE;", null); // might throw
+                        mConnection.execute("BEGIN IMMEDIATE;", null,
+                                cancelationSignal); // might throw
                         break;
                     case TRANSACTION_MODE_EXCLUSIVE:
-                        mConnection.execute("BEGIN EXCLUSIVE;", null); // might throw
+                        mConnection.execute("BEGIN EXCLUSIVE;", null,
+                                cancelationSignal); // might throw
                         break;
                     default:
-                        mConnection.execute("BEGIN;", null); // might throw
+                        mConnection.execute("BEGIN;", null, cancelationSignal); // might throw
                         break;
                 }
             }
@@ -323,7 +335,7 @@
                     transactionListener.onBegin(); // might throw
                 } catch (RuntimeException ex) {
                     if (mTransactionStack == null) {
-                        mConnection.execute("ROLLBACK;", null); // might throw
+                        mConnection.execute("ROLLBACK;", null, cancelationSignal); // might throw
                     }
                     throw ex;
                 }
@@ -372,20 +384,28 @@
      * This method must be called exactly once for each call to {@link #beginTransaction}.
      * </p>
      *
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
+     *
      * @throws IllegalStateException if there is no current transaction.
+     * @throws SQLiteException if an error occurs.
+     * @throws OperationCanceledException if the operation was canceled.
      *
      * @see #beginTransaction
      * @see #setTransactionSuccessful
      * @see #yieldTransaction
      */
-    public void endTransaction() {
+    public void endTransaction(CancelationSignal cancelationSignal) {
         throwIfNoTransaction();
         assert mConnection != null;
 
-        endTransactionUnchecked();
+        endTransactionUnchecked(cancelationSignal);
     }
 
-    private void endTransactionUnchecked() {
+    private void endTransactionUnchecked(CancelationSignal cancelationSignal) {
+        if (cancelationSignal != null) {
+            cancelationSignal.throwIfCanceled();
+        }
+
         final Transaction top = mTransactionStack;
         boolean successful = top.mMarkedSuccessful && !top.mChildFailed;
 
@@ -414,9 +434,9 @@
         } else {
             try {
                 if (successful) {
-                    mConnection.execute("COMMIT;", null); // might throw
+                    mConnection.execute("COMMIT;", null, cancelationSignal); // might throw
                 } else {
-                    mConnection.execute("ROLLBACK;", null); // might throw
+                    mConnection.execute("ROLLBACK;", null, cancelationSignal); // might throw
                 }
             } finally {
                 releaseConnection(); // might throw
@@ -467,16 +487,20 @@
      * @param throwIfUnsafe If true, then instead of returning false when no
      * transaction is in progress, a nested transaction is in progress, or when
      * the transaction has already been marked successful, throws {@link IllegalStateException}.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      * @return True if the transaction was actually yielded.
      *
      * @throws IllegalStateException if <code>throwIfNested</code> is true and
      * there is no current transaction, there is a nested transaction in progress or
      * if {@link #setTransactionSuccessful} has already been called for the current transaction.
+     * @throws SQLiteException if an error occurs.
+     * @throws OperationCanceledException if the operation was canceled.
      *
      * @see #beginTransaction
      * @see #endTransaction
      */
-    public boolean yieldTransaction(long sleepAfterYieldDelayMillis, boolean throwIfUnsafe) {
+    public boolean yieldTransaction(long sleepAfterYieldDelayMillis, boolean throwIfUnsafe,
+            CancelationSignal cancelationSignal) {
         if (throwIfUnsafe) {
             throwIfNoTransaction();
             throwIfTransactionMarkedSuccessful();
@@ -493,10 +517,16 @@
             return false;
         }
 
-        return yieldTransactionUnchecked(sleepAfterYieldDelayMillis); // might throw
+        return yieldTransactionUnchecked(sleepAfterYieldDelayMillis,
+                cancelationSignal); // might throw
     }
 
-    private boolean yieldTransactionUnchecked(long sleepAfterYieldDelayMillis) {
+    private boolean yieldTransactionUnchecked(long sleepAfterYieldDelayMillis,
+            CancelationSignal cancelationSignal) {
+        if (cancelationSignal != null) {
+            cancelationSignal.throwIfCanceled();
+        }
+
         if (!mConnectionPool.shouldYieldConnection(mConnection, mConnectionFlags)) {
             return false;
         }
@@ -504,7 +534,7 @@
         final int transactionMode = mTransactionStack.mMode;
         final SQLiteTransactionListener listener = mTransactionStack.mListener;
         final int connectionFlags = mConnectionFlags;
-        endTransactionUnchecked(); // might throw
+        endTransactionUnchecked(cancelationSignal); // might throw
 
         if (sleepAfterYieldDelayMillis > 0) {
             try {
@@ -514,7 +544,8 @@
             }
         }
 
-        beginTransactionUnchecked(transactionMode, listener, connectionFlags); // might throw
+        beginTransactionUnchecked(transactionMode, listener, connectionFlags,
+                cancelationSignal); // might throw
         return true;
     }
 
@@ -535,17 +566,24 @@
      * @param sql The SQL statement to prepare.
      * @param connectionFlags The connection flags to use if a connection must be
      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate
      * with information about the statement, or null if none.
      *
      * @throws SQLiteException if an error occurs, such as a syntax error.
+     * @throws OperationCanceledException if the operation was canceled.
      */
-    public void prepare(String sql, int connectionFlags, SQLiteStatementInfo outStatementInfo) {
+    public void prepare(String sql, int connectionFlags, CancelationSignal cancelationSignal,
+            SQLiteStatementInfo outStatementInfo) {
         if (sql == null) {
             throw new IllegalArgumentException("sql must not be null.");
         }
 
-        acquireConnection(sql, connectionFlags); // might throw
+        if (cancelationSignal != null) {
+            cancelationSignal.throwIfCanceled();
+        }
+
+        acquireConnection(sql, connectionFlags, cancelationSignal); // might throw
         try {
             mConnection.prepare(sql, outStatementInfo); // might throw
         } finally {
@@ -560,22 +598,25 @@
      * @param bindArgs The arguments to bind, or null if none.
      * @param connectionFlags The connection flags to use if a connection must be
      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      *
      * @throws SQLiteException if an error occurs, such as a syntax error
      * or invalid number of bind arguments.
+     * @throws OperationCanceledException if the operation was canceled.
      */
-    public void execute(String sql, Object[] bindArgs, int connectionFlags) {
+    public void execute(String sql, Object[] bindArgs, int connectionFlags,
+            CancelationSignal cancelationSignal) {
         if (sql == null) {
             throw new IllegalArgumentException("sql must not be null.");
         }
 
-        if (executeSpecial(sql, bindArgs, connectionFlags)) {
+        if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) {
             return;
         }
 
-        acquireConnection(sql, connectionFlags); // might throw
+        acquireConnection(sql, connectionFlags, cancelationSignal); // might throw
         try {
-            mConnection.execute(sql, bindArgs); // might throw
+            mConnection.execute(sql, bindArgs, cancelationSignal); // might throw
         } finally {
             releaseConnection(); // might throw
         }
@@ -588,24 +629,27 @@
      * @param bindArgs The arguments to bind, or null if none.
      * @param connectionFlags The connection flags to use if a connection must be
      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      * @return The value of the first column in the first row of the result set
      * as a <code>long</code>, or zero if none.
      *
      * @throws SQLiteException if an error occurs, such as a syntax error
      * or invalid number of bind arguments.
+     * @throws OperationCanceledException if the operation was canceled.
      */
-    public long executeForLong(String sql, Object[] bindArgs, int connectionFlags) {
+    public long executeForLong(String sql, Object[] bindArgs, int connectionFlags,
+            CancelationSignal cancelationSignal) {
         if (sql == null) {
             throw new IllegalArgumentException("sql must not be null.");
         }
 
-        if (executeSpecial(sql, bindArgs, connectionFlags)) {
+        if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) {
             return 0;
         }
 
-        acquireConnection(sql, connectionFlags); // might throw
+        acquireConnection(sql, connectionFlags, cancelationSignal); // might throw
         try {
-            return mConnection.executeForLong(sql, bindArgs); // might throw
+            return mConnection.executeForLong(sql, bindArgs, cancelationSignal); // might throw
         } finally {
             releaseConnection(); // might throw
         }
@@ -618,24 +662,27 @@
      * @param bindArgs The arguments to bind, or null if none.
      * @param connectionFlags The connection flags to use if a connection must be
      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      * @return The value of the first column in the first row of the result set
      * as a <code>String</code>, or null if none.
      *
      * @throws SQLiteException if an error occurs, such as a syntax error
      * or invalid number of bind arguments.
+     * @throws OperationCanceledException if the operation was canceled.
      */
-    public String executeForString(String sql, Object[] bindArgs, int connectionFlags) {
+    public String executeForString(String sql, Object[] bindArgs, int connectionFlags,
+            CancelationSignal cancelationSignal) {
         if (sql == null) {
             throw new IllegalArgumentException("sql must not be null.");
         }
 
-        if (executeSpecial(sql, bindArgs, connectionFlags)) {
+        if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) {
             return null;
         }
 
-        acquireConnection(sql, connectionFlags); // might throw
+        acquireConnection(sql, connectionFlags, cancelationSignal); // might throw
         try {
-            return mConnection.executeForString(sql, bindArgs); // might throw
+            return mConnection.executeForString(sql, bindArgs, cancelationSignal); // might throw
         } finally {
             releaseConnection(); // might throw
         }
@@ -649,26 +696,29 @@
      * @param bindArgs The arguments to bind, or null if none.
      * @param connectionFlags The connection flags to use if a connection must be
      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      * @return The file descriptor for a shared memory region that contains
      * the value of the first column in the first row of the result set as a BLOB,
      * or null if none.
      *
      * @throws SQLiteException if an error occurs, such as a syntax error
      * or invalid number of bind arguments.
+     * @throws OperationCanceledException if the operation was canceled.
      */
     public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs,
-            int connectionFlags) {
+            int connectionFlags, CancelationSignal cancelationSignal) {
         if (sql == null) {
             throw new IllegalArgumentException("sql must not be null.");
         }
 
-        if (executeSpecial(sql, bindArgs, connectionFlags)) {
+        if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) {
             return null;
         }
 
-        acquireConnection(sql, connectionFlags); // might throw
+        acquireConnection(sql, connectionFlags, cancelationSignal); // might throw
         try {
-            return mConnection.executeForBlobFileDescriptor(sql, bindArgs); // might throw
+            return mConnection.executeForBlobFileDescriptor(sql, bindArgs,
+                    cancelationSignal); // might throw
         } finally {
             releaseConnection(); // might throw
         }
@@ -682,23 +732,27 @@
      * @param bindArgs The arguments to bind, or null if none.
      * @param connectionFlags The connection flags to use if a connection must be
      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      * @return The number of rows that were changed.
      *
      * @throws SQLiteException if an error occurs, such as a syntax error
      * or invalid number of bind arguments.
+     * @throws OperationCanceledException if the operation was canceled.
      */
-    public int executeForChangedRowCount(String sql, Object[] bindArgs, int connectionFlags) {
+    public int executeForChangedRowCount(String sql, Object[] bindArgs, int connectionFlags,
+            CancelationSignal cancelationSignal) {
         if (sql == null) {
             throw new IllegalArgumentException("sql must not be null.");
         }
 
-        if (executeSpecial(sql, bindArgs, connectionFlags)) {
+        if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) {
             return 0;
         }
 
-        acquireConnection(sql, connectionFlags); // might throw
+        acquireConnection(sql, connectionFlags, cancelationSignal); // might throw
         try {
-            return mConnection.executeForChangedRowCount(sql, bindArgs); // might throw
+            return mConnection.executeForChangedRowCount(sql, bindArgs,
+                    cancelationSignal); // might throw
         } finally {
             releaseConnection(); // might throw
         }
@@ -712,23 +766,27 @@
      * @param bindArgs The arguments to bind, or null if none.
      * @param connectionFlags The connection flags to use if a connection must be
      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      * @return The row id of the last row that was inserted, or 0 if none.
      *
      * @throws SQLiteException if an error occurs, such as a syntax error
      * or invalid number of bind arguments.
+     * @throws OperationCanceledException if the operation was canceled.
      */
-    public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags) {
+    public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags,
+            CancelationSignal cancelationSignal) {
         if (sql == null) {
             throw new IllegalArgumentException("sql must not be null.");
         }
 
-        if (executeSpecial(sql, bindArgs, connectionFlags)) {
+        if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) {
             return 0;
         }
 
-        acquireConnection(sql, connectionFlags); // might throw
+        acquireConnection(sql, connectionFlags, cancelationSignal); // might throw
         try {
-            return mConnection.executeForLastInsertedRowId(sql, bindArgs); // might throw
+            return mConnection.executeForLastInsertedRowId(sql, bindArgs,
+                    cancelationSignal); // might throw
         } finally {
             releaseConnection(); // might throw
         }
@@ -750,15 +808,17 @@
      * regagless of whether they fit in the window.
      * @param connectionFlags The connection flags to use if a connection must be
      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      * @return The number of rows that were counted during query execution.  Might
      * not be all rows in the result set unless <code>countAllRows</code> is true.
      *
      * @throws SQLiteException if an error occurs, such as a syntax error
      * or invalid number of bind arguments.
+     * @throws OperationCanceledException if the operation was canceled.
      */
     public int executeForCursorWindow(String sql, Object[] bindArgs,
             CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
-            int connectionFlags) {
+            int connectionFlags, CancelationSignal cancelationSignal) {
         if (sql == null) {
             throw new IllegalArgumentException("sql must not be null.");
         }
@@ -766,15 +826,16 @@
             throw new IllegalArgumentException("window must not be null.");
         }
 
-        if (executeSpecial(sql, bindArgs, connectionFlags)) {
+        if (executeSpecial(sql, bindArgs, connectionFlags, cancelationSignal)) {
             window.clear();
             return 0;
         }
 
-        acquireConnection(sql, connectionFlags); // might throw
+        acquireConnection(sql, connectionFlags, cancelationSignal); // might throw
         try {
             return mConnection.executeForCursorWindow(sql, bindArgs,
-                    window, startPos, requiredPos, countAllRows); // might throw
+                    window, startPos, requiredPos, countAllRows,
+                    cancelationSignal); // might throw
         } finally {
             releaseConnection(); // might throw
         }
@@ -793,35 +854,45 @@
      * @param bindArgs The arguments to bind, or null if none.
      * @param connectionFlags The connection flags to use if a connection must be
      * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
      * @return True if the statement was of a special form that was handled here,
      * false otherwise.
      *
      * @throws SQLiteException if an error occurs, such as a syntax error
      * or invalid number of bind arguments.
+     * @throws OperationCanceledException if the operation was canceled.
      */
-    private boolean executeSpecial(String sql, Object[] bindArgs, int connectionFlags) {
+    private boolean executeSpecial(String sql, Object[] bindArgs, int connectionFlags,
+            CancelationSignal cancelationSignal) {
+        if (cancelationSignal != null) {
+            cancelationSignal.throwIfCanceled();
+        }
+
         final int type = DatabaseUtils.getSqlStatementType(sql);
         switch (type) {
             case DatabaseUtils.STATEMENT_BEGIN:
-                beginTransaction(TRANSACTION_MODE_EXCLUSIVE, null, connectionFlags);
+                beginTransaction(TRANSACTION_MODE_EXCLUSIVE, null, connectionFlags,
+                        cancelationSignal);
                 return true;
 
             case DatabaseUtils.STATEMENT_COMMIT:
                 setTransactionSuccessful();
-                endTransaction();
+                endTransaction(cancelationSignal);
                 return true;
 
             case DatabaseUtils.STATEMENT_ABORT:
-                endTransaction();
+                endTransaction(cancelationSignal);
                 return true;
         }
         return false;
     }
 
-    private void acquireConnection(String sql, int connectionFlags) {
+    private void acquireConnection(String sql, int connectionFlags,
+            CancelationSignal cancelationSignal) {
         if (mConnection == null) {
             assert mConnectionUseCount == 0;
-            mConnection = mConnectionPool.acquireConnection(sql, connectionFlags); // might throw
+            mConnection = mConnectionPool.acquireConnection(sql, connectionFlags,
+                    cancelationSignal); // might throw
             mConnectionFlags = connectionFlags;
         }
         mConnectionUseCount += 1;
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index 4e20da0..b1092d76 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -28,7 +28,7 @@
  */
 public final class SQLiteStatement extends SQLiteProgram {
     SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) {
-        super(db, sql, bindArgs);
+        super(db, sql, bindArgs, null);
     }
 
     /**
@@ -41,7 +41,7 @@
     public void execute() {
         acquireReference();
         try {
-            getSession().execute(getSql(), getBindArgs(), getConnectionFlags());
+            getSession().execute(getSql(), getBindArgs(), getConnectionFlags(), null);
         } catch (SQLiteDatabaseCorruptException ex) {
             onCorruption();
             throw ex;
@@ -62,7 +62,7 @@
         acquireReference();
         try {
             return getSession().executeForChangedRowCount(
-                    getSql(), getBindArgs(), getConnectionFlags());
+                    getSql(), getBindArgs(), getConnectionFlags(), null);
         } catch (SQLiteDatabaseCorruptException ex) {
             onCorruption();
             throw ex;
@@ -84,7 +84,7 @@
         acquireReference();
         try {
             return getSession().executeForLastInsertedRowId(
-                    getSql(), getBindArgs(), getConnectionFlags());
+                    getSql(), getBindArgs(), getConnectionFlags(), null);
         } catch (SQLiteDatabaseCorruptException ex) {
             onCorruption();
             throw ex;
@@ -105,7 +105,7 @@
         acquireReference();
         try {
             return getSession().executeForLong(
-                    getSql(), getBindArgs(), getConnectionFlags());
+                    getSql(), getBindArgs(), getConnectionFlags(), null);
         } catch (SQLiteDatabaseCorruptException ex) {
             onCorruption();
             throw ex;
@@ -126,7 +126,7 @@
         acquireReference();
         try {
             return getSession().executeForString(
-                    getSql(), getBindArgs(), getConnectionFlags());
+                    getSql(), getBindArgs(), getConnectionFlags(), null);
         } catch (SQLiteDatabaseCorruptException ex) {
             onCorruption();
             throw ex;
@@ -147,7 +147,7 @@
         acquireReference();
         try {
             return getSession().executeForBlobFileDescriptor(
-                    getSql(), getBindArgs(), getConnectionFlags());
+                    getSql(), getBindArgs(), getConnectionFlags(), null);
         } catch (SQLiteDatabaseCorruptException ex) {
             onCorruption();
             throw ex;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ba9046c..9d96c0d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -747,7 +747,7 @@
             Cursor c = null;
             try {
                 c = cp.query(mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
-                             new String[]{name}, null);
+                             new String[]{name}, null, null);
                 if (c == null) {
                     Log.w(TAG, "Can't get key " + name + " from " + mUri);
                     return null;
diff --git a/core/jni/android_database_SQLiteCommon.cpp b/core/jni/android_database_SQLiteCommon.cpp
index a94b9d2..3484467 100644
--- a/core/jni/android_database_SQLiteCommon.cpp
+++ b/core/jni/android_database_SQLiteCommon.cpp
@@ -68,50 +68,53 @@
             exceptionClass = "android/database/sqlite/SQLiteDatabaseCorruptException";
             break;
         case SQLITE_CONSTRAINT:
-           exceptionClass = "android/database/sqlite/SQLiteConstraintException";
-           break;
+            exceptionClass = "android/database/sqlite/SQLiteConstraintException";
+            break;
         case SQLITE_ABORT:
-           exceptionClass = "android/database/sqlite/SQLiteAbortException";
-           break;
+            exceptionClass = "android/database/sqlite/SQLiteAbortException";
+            break;
         case SQLITE_DONE:
-           exceptionClass = "android/database/sqlite/SQLiteDoneException";
-           break;
+            exceptionClass = "android/database/sqlite/SQLiteDoneException";
+            break;
         case SQLITE_FULL:
-           exceptionClass = "android/database/sqlite/SQLiteFullException";
-           break;
+            exceptionClass = "android/database/sqlite/SQLiteFullException";
+            break;
         case SQLITE_MISUSE:
-           exceptionClass = "android/database/sqlite/SQLiteMisuseException";
-           break;
+            exceptionClass = "android/database/sqlite/SQLiteMisuseException";
+            break;
         case SQLITE_PERM:
-           exceptionClass = "android/database/sqlite/SQLiteAccessPermException";
-           break;
+            exceptionClass = "android/database/sqlite/SQLiteAccessPermException";
+            break;
         case SQLITE_BUSY:
-           exceptionClass = "android/database/sqlite/SQLiteDatabaseLockedException";
-           break;
+            exceptionClass = "android/database/sqlite/SQLiteDatabaseLockedException";
+            break;
         case SQLITE_LOCKED:
-           exceptionClass = "android/database/sqlite/SQLiteTableLockedException";
-           break;
+            exceptionClass = "android/database/sqlite/SQLiteTableLockedException";
+            break;
         case SQLITE_READONLY:
-           exceptionClass = "android/database/sqlite/SQLiteReadOnlyDatabaseException";
-           break;
+            exceptionClass = "android/database/sqlite/SQLiteReadOnlyDatabaseException";
+            break;
         case SQLITE_CANTOPEN:
-           exceptionClass = "android/database/sqlite/SQLiteCantOpenDatabaseException";
-           break;
+            exceptionClass = "android/database/sqlite/SQLiteCantOpenDatabaseException";
+            break;
         case SQLITE_TOOBIG:
-           exceptionClass = "android/database/sqlite/SQLiteBlobTooBigException";
-           break;
+            exceptionClass = "android/database/sqlite/SQLiteBlobTooBigException";
+            break;
         case SQLITE_RANGE:
-           exceptionClass = "android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException";
-           break;
+            exceptionClass = "android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException";
+            break;
         case SQLITE_NOMEM:
-           exceptionClass = "android/database/sqlite/SQLiteOutOfMemoryException";
-           break;
+            exceptionClass = "android/database/sqlite/SQLiteOutOfMemoryException";
+            break;
         case SQLITE_MISMATCH:
-           exceptionClass = "android/database/sqlite/SQLiteDatatypeMismatchException";
-           break;
+            exceptionClass = "android/database/sqlite/SQLiteDatatypeMismatchException";
+            break;
+        case SQLITE_INTERRUPT:
+            exceptionClass = "android/content/OperationCanceledException";
+            break;
         default:
-           exceptionClass = "android/database/sqlite/SQLiteException";
-           break;
+            exceptionClass = "android/database/sqlite/SQLiteException";
+            break;
     }
 
     if (sqlite3Message != NULL && message != NULL) {
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index d0d53f6..e061ac3 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -67,8 +67,10 @@
     const String8 path;
     const String8 label;
 
+    volatile bool canceled;
+
     SQLiteConnection(sqlite3* db, int openFlags, const String8& path, const String8& label) :
-        db(db), openFlags(openFlags), path(path), label(label) { }
+        db(db), openFlags(openFlags), path(path), label(label), canceled(false) { }
 };
 
 // Called each time a statement begins execution, when tracing is enabled.
@@ -85,6 +87,12 @@
             connection->label.string(), sql, tm * 0.000001f);
 }
 
+// Called after each SQLite VM instruction when cancelation is enabled.
+static int sqliteProgressHandlerCallback(void* data) {
+    SQLiteConnection* connection = static_cast<SQLiteConnection*>(data);
+    return connection->canceled;
+}
+
 
 static jint nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFlags,
         jstring labelStr, jboolean enableTrace, jboolean enableProfile) {
@@ -871,6 +879,24 @@
     return cur;
 }
 
+static void nativeCancel(JNIEnv* env, jobject clazz, jint connectionPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    connection->canceled = true;
+}
+
+static void nativeResetCancel(JNIEnv* env, jobject clazz, jint connectionPtr,
+        jboolean cancelable) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    connection->canceled = false;
+
+    if (cancelable) {
+        sqlite3_progress_handler(connection->db, 4, sqliteProgressHandlerCallback,
+                connection);
+    } else {
+        sqlite3_progress_handler(connection->db, 0, NULL, NULL);
+    }
+}
+
 
 static JNINativeMethod sMethods[] =
 {
@@ -923,6 +949,10 @@
             (void*)nativeExecuteForCursorWindow },
     { "nativeGetDbLookaside", "(I)I",
             (void*)nativeGetDbLookaside },
+    { "nativeCancel", "(I)V",
+            (void*)nativeCancel },
+    { "nativeResetCancel", "(IZ)V",
+            (void*)nativeResetCancel },
 };
 
 #define FIND_CLASS(var, className) \