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/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;