summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/java/android/content/AsyncTaskLoader.java20
-rw-r--r--core/java/android/content/CancelationSignal.java164
-rw-r--r--core/java/android/content/ContentProvider.java93
-rw-r--r--core/java/android/content/ContentProviderClient.java15
-rw-r--r--core/java/android/content/ContentProviderNative.java41
-rw-r--r--core/java/android/content/ContentResolver.java52
-rw-r--r--core/java/android/content/CursorLoader.java39
-rw-r--r--core/java/android/content/ICancelationSignal.aidl24
-rw-r--r--core/java/android/content/IContentProvider.java5
-rw-r--r--core/java/android/content/OperationCanceledException.java32
-rw-r--r--core/java/android/database/sqlite/SQLiteConnection.java168
-rw-r--r--core/java/android/database/sqlite/SQLiteConnectionPool.java86
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java146
-rw-r--r--core/java/android/database/sqlite/SQLiteDirectCursorDriver.java8
-rw-r--r--core/java/android/database/sqlite/SQLiteProgram.java7
-rw-r--r--core/java/android/database/sqlite/SQLiteQuery.java16
-rw-r--r--core/java/android/database/sqlite/SQLiteQueryBuilder.java62
-rw-r--r--core/java/android/database/sqlite/SQLiteSession.java183
-rw-r--r--core/java/android/database/sqlite/SQLiteStatement.java14
-rw-r--r--core/java/android/provider/Settings.java2
-rw-r--r--core/jni/android_database_SQLiteCommon.cpp63
-rw-r--r--core/jni/android_database_SQLiteConnection.cpp32
22 files changed, 1090 insertions, 182 deletions
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 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
if (DEBUG) Slog.v(TAG, "cancelLoad: cancelled=" + cancelled);
if (cancelled) {
mCancellingTask = mTask;
+ onCancelLoadInBackground();
}
mTask = null;
return cancelled;
@@ -256,6 +257,25 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
}
/**
+ * 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.Binder;
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 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
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 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
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 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
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 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
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 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
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 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
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 @@ public class ContentProviderClient {
/** 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.BulkCursorNative;
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 @@ import java.util.ArrayList;
* {@hide}
*/
abstract public class ContentProviderNative extends Binder implements IContentProvider {
- private static final String TAG = "ContentProvider";
-
public ContentProviderNative()
{
attachInterface(this, descriptor);
@@ -108,8 +105,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
}
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 @@ final class ContentProviderProxy implements IContentProvider
}
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 @@ final class ContentProviderProxy implements IContentProvider
}
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 @@ final class ContentProviderProxy implements IContentProvider
}
}
+ 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.accounts.Account;
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 abstract class ContentResolver {
*/
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 @@ package android.content;
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 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
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();
+ }
+ 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();
+ }
}
- return cursor;
}
/**
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 @@ import java.util.ArrayList;
*/
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 interface IContentProvider extends IInterface {
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 @@ public interface IContentProvider extends IInterface {
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 @@ package android.database.sqlite;
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 @@ import java.util.regex.Pattern;
*
* @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 @@ public final class SQLiteConnection {
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 @@ public final class SQLiteConnection {
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 @@ public final class SQLiteConnection {
*
* @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 @@ public final class SQLiteConnection {
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 @@ public final class SQLiteConnection {
*
* @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 @@ public final class SQLiteConnection {
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 @@ public final class SQLiteConnection {
*
* @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 @@ public final class SQLiteConnection {
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 @@ public final class SQLiteConnection {
*
* @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 @@ public final class SQLiteConnection {
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 @@ public final class SQLiteConnection {
*
* @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 @@ public final class SQLiteConnection {
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 @@ public final class SQLiteConnection {
*
* @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 @@ public final class SQLiteConnection {
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 @@ public final class SQLiteConnection {
* 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 @@ public final class SQLiteConnection {
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 @@ public final class SQLiteConnection {
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 @@ public final class SQLiteConnection {
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 @@ public final class SQLiteConnection {
// 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 @@ package android.database.sqlite;
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 @@ public final class SQLiteConnectionPool implements Closeable {
* @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 @@ public final class SQLiteConnectionPool implements Closeable {
}
// 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 @@ public final class SQLiteConnectionPool implements Closeable {
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 @@ public final class SQLiteConnectionPool implements Closeable {
} 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 @@ public final class SQLiteConnectionPool implements Closeable {
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 @@ public final class SQLiteConnectionPool implements Closeable {
synchronized (mLock) {
throwIfClosedLocked();
- SQLiteConnection connection = waiter.mAssignedConnection;
- if (connection != null) {
- recycleConnectionWaiterLocked(waiter);
- return connection;
- }
-
- RuntimeException ex = waiter.mException;
- if (ex != null) {
+ final SQLiteConnection connection = waiter.mAssignedConnection;
+ final RuntimeException ex = waiter.mException;
+ if (connection != null || ex != null) {
+ if (cancelationSignal != null) {
+ cancelationSignal.setOnCancelListener(null);
+ }
recycleConnectionWaiterLocked(waiter);
+ if (connection != null) {
+ return connection;
+ }
throw ex; // rethrow!
}
@@ -585,6 +611,40 @@ public final class SQLiteConnectionPool implements Closeable {
}
// 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 @@ public final class SQLiteConnectionPool implements Closeable {
waiter.mSql = null;
waiter.mAssignedConnection = null;
waiter.mException = null;
+ waiter.mNonce += 1;
mConnectionWaiterPool = waiter;
}
@@ -904,5 +965,6 @@ public final class SQLiteConnectionPool implements Closeable {
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 @@ public class SQLiteDatabase extends SQLiteClosable {
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 @@ public class SQLiteDatabase extends SQLiteClosable {
* are committed and rolled back.
*/
public void endTransaction() {
- getThreadSession().endTransaction();
+ getThreadSession().endTransaction(null);
}
/**
@@ -597,7 +599,7 @@ public class SQLiteDatabase extends SQLiteClosable {
}
private boolean yieldIfContendedHelper(boolean throwIfUnsafe, long sleepAfterYieldDelay) {
- return getThreadSession().yieldTransaction(sleepAfterYieldDelay, throwIfUnsafe);
+ return getThreadSession().yieldTransaction(sleepAfterYieldDelay, throwIfUnsafe, null);
}
/**
@@ -935,7 +937,48 @@ public class SQLiteDatabase extends SQLiteClosable {
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 @@ public class SQLiteDatabase extends SQLiteClosable {
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 @@ public class SQLiteDatabase extends SQLiteClosable {
* {@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 class SQLiteDatabase extends SQLiteClosable {
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 @@ public class SQLiteDatabase extends SQLiteClosable {
*/
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 @@ public class SQLiteDatabase extends SQLiteClosable {
* @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 @@ public class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
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 @@ public abstract class SQLiteProgram extends SQLiteClosable {
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 @@ public abstract class SQLiteProgram extends SQLiteClosable {
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 @@ import android.util.Log;
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 @@ public final class SQLiteQuery extends SQLiteProgram {
* 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 @@ public final class SQLiteQuery extends SQLiteProgram {
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 @@ public class SQLiteQueryBuilder
/**
* 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 @@ public class SQLiteQueryBuilder
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 class SQLiteQueryBuilder
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 @@ public class SQLiteQueryBuilder
// 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 @@ public class SQLiteQueryBuilder
}
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 @@ import android.os.ParcelFileDescriptor;
* 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 @@ public final class SQLiteSession {
* @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 @@ public final class SQLiteSession {
// 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 @@ public final class SQLiteSession {
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 @@ public final class SQLiteSession {
* 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 @@ public final class SQLiteSession {
} 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 @@ public final class SQLiteSession {
* @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 @@ public final class SQLiteSession {
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 @@ public final class SQLiteSession {
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 @@ public final class SQLiteSession {
}
}
- beginTransactionUnchecked(transactionMode, listener, connectionFlags); // might throw
+ beginTransactionUnchecked(transactionMode, listener, connectionFlags,
+ cancelationSignal); // might throw
return true;
}
@@ -535,17 +566,24 @@ public final class SQLiteSession {
* @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 @@ public final class SQLiteSession {
* @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 @@ public final class SQLiteSession {
* @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 @@ public final class SQLiteSession {
* @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 @@ public final class SQLiteSession {
* @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 @@ public final class SQLiteSession {
* @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 @@ public final class SQLiteSession {
* @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 @@ public final class SQLiteSession {
* 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 @@ public final class SQLiteSession {
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 @@ public final class SQLiteSession {
* @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..b1092d7 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -28,7 +28,7 @@ import android.os.ParcelFileDescriptor;
*/
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 final class SQLiteStatement extends SQLiteProgram {
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 @@ public final class SQLiteStatement extends SQLiteProgram {
acquireReference();
try {
return getSession().executeForChangedRowCount(
- getSql(), getBindArgs(), getConnectionFlags());
+ getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
@@ -84,7 +84,7 @@ public final class SQLiteStatement extends SQLiteProgram {
acquireReference();
try {
return getSession().executeForLastInsertedRowId(
- getSql(), getBindArgs(), getConnectionFlags());
+ getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
@@ -105,7 +105,7 @@ public final class SQLiteStatement extends SQLiteProgram {
acquireReference();
try {
return getSession().executeForLong(
- getSql(), getBindArgs(), getConnectionFlags());
+ getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
@@ -126,7 +126,7 @@ public final class SQLiteStatement extends SQLiteProgram {
acquireReference();
try {
return getSession().executeForString(
- getSql(), getBindArgs(), getConnectionFlags());
+ getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
@@ -147,7 +147,7 @@ public final class SQLiteStatement extends SQLiteProgram {
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 @@ public final class Settings {
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 @@ void throw_sqlite3_exception(JNIEnv* env, int errcode,
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 @@ struct SQLiteConnection {
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 @@ static void sqliteProfileCallback(void *data, const char *sql, sqlite3_uint64 tm
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 @@ static jint nativeGetDbLookaside(JNIEnv* env, jobject clazz, jint connectionPtr)
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 @@ static JNINativeMethod sMethods[] =
(void*)nativeExecuteForCursorWindow },
{ "nativeGetDbLookaside", "(I)I",
(void*)nativeGetDbLookaside },
+ { "nativeCancel", "(I)V",
+ (void*)nativeCancel },
+ { "nativeResetCancel", "(IZ)V",
+ (void*)nativeResetCancel },
};
#define FIND_CLASS(var, className) \