Here is the standard idiom for transactions: + * + *
+ * db.beginTransactionWithListener(listener);
+ * try {
+ * ...
+ * db.setTransactionSuccessful();
+ * } finally {
+ * db.endTransaction();
+ * }
+ *
+ * @param transactionListener listener that should be notified when the transaction begins,
+ * commits, or is rolled back, either explicitly or by a call to
+ * {@link #yieldIfContendedSafely}.
+ */
+ public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) {
lockForced();
boolean ok = false;
try {
@@ -413,8 +443,17 @@ public class SQLiteDatabase extends SQLiteClosable {
// This thread didn't already have the lock, so begin a database
// transaction now.
execSQL("BEGIN EXCLUSIVE;");
+ mTransactionListener = transactionListener;
mTransactionIsSuccessful = true;
mInnerTransactionIsSuccessful = false;
+ if (transactionListener != null) {
+ try {
+ transactionListener.onBegin();
+ } catch (RuntimeException e) {
+ execSQL("ROLLBACK;");
+ throw e;
+ }
+ }
ok = true;
} finally {
if (!ok) {
@@ -442,11 +481,27 @@ public class SQLiteDatabase extends SQLiteClosable {
if (mLock.getHoldCount() != 1) {
return;
}
+ RuntimeException savedException = null;
+ if (mTransactionListener != null) {
+ try {
+ if (mTransactionIsSuccessful) {
+ mTransactionListener.onCommit();
+ } else {
+ mTransactionListener.onRollback();
+ }
+ } catch (RuntimeException e) {
+ savedException = e;
+ mTransactionIsSuccessful = false;
+ }
+ }
if (mTransactionIsSuccessful) {
execSQL("COMMIT;");
} else {
try {
execSQL("ROLLBACK;");
+ if (savedException != null) {
+ throw savedException;
+ }
} catch (SQLException e) {
if (Config.LOGD) {
Log.d(TAG, "exception during rollback, maybe the DB previously "
@@ -455,6 +510,7 @@ public class SQLiteDatabase extends SQLiteClosable {
}
}
} finally {
+ mTransactionListener = null;
unlockForced();
if (Config.LOGV) {
Log.v(TAG, "unlocked " + Thread.currentThread()
@@ -561,6 +617,7 @@ public class SQLiteDatabase extends SQLiteClosable {
return false;
}
setTransactionSuccessful();
+ SQLiteTransactionListener transactionListener = mTransactionListener;
endTransaction();
if (checkFullyYielded) {
if (this.isDbLockedByCurrentThread()) {
@@ -586,7 +643,7 @@ public class SQLiteDatabase extends SQLiteClosable {
}
}
}
- beginTransaction();
+ beginTransactionWithListener(transactionListener);
return true;
}
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index d04afb01c3e34..84d8879374733 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -17,6 +17,7 @@
package android.database.sqlite;
import android.util.Config;
+import android.util.Log;
/**
* Provides debugging info about all SQLite databases running in the current process.
@@ -27,23 +28,27 @@ public final class SQLiteDebug {
/**
* Controls the printing of SQL statements as they are executed.
*/
- public static final boolean DEBUG_SQL_STATEMENTS = Config.LOGV;
+ public static final boolean DEBUG_SQL_STATEMENTS =
+ Log.isLoggable("SQLiteStatements", Log.VERBOSE);
/**
* Controls the stack trace reporting of active cursors being
* finalized.
*/
- public static final boolean DEBUG_ACTIVE_CURSOR_FINALIZATION = Config.LOGV;
+ public static final boolean DEBUG_ACTIVE_CURSOR_FINALIZATION =
+ Log.isLoggable("SQLiteCursorClosing", Log.VERBOSE);
/**
* Controls the tracking of time spent holding the database lock.
*/
- public static final boolean DEBUG_LOCK_TIME_TRACKING = false;
+ public static final boolean DEBUG_LOCK_TIME_TRACKING =
+ Log.isLoggable("SQLiteLockTime", Log.VERBOSE);
/**
* Controls the printing of stack traces when tracking the time spent holding the database lock.
*/
- public static final boolean DEBUG_LOCK_TIME_TRACKING_STACK_TRACE = false;
+ public static final boolean DEBUG_LOCK_TIME_TRACKING_STACK_TRACE =
+ Log.isLoggable("SQLiteLockStackTrace", Log.VERBOSE);
/**
* Contains statistics about the active pagers in the current process.
diff --git a/core/java/android/database/sqlite/SQLiteTransactionListener.java b/core/java/android/database/sqlite/SQLiteTransactionListener.java
new file mode 100644
index 0000000000000..e97ece8f7facc
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteTransactionListener.java
@@ -0,0 +1,21 @@
+package android.database.sqlite;
+
+/**
+ * A listener for transaction events.
+ */
+public interface SQLiteTransactionListener {
+ /**
+ * Called immediately after the transaction begins.
+ */
+ void onBegin();
+
+ /**
+ * Called immediately before commiting the transaction.
+ */
+ void onCommit();
+
+ /**
+ * Called if the transaction is about to be rolled back.
+ */
+ void onRollback();
+}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index a318f08a0a078..31d43ee8252a7 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -29,6 +29,7 @@ import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.RemoteException;
import android.text.TextUtils;
+import android.util.Pair;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
@@ -71,6 +72,14 @@ public final class ContactsContract {
return SyncStateContract.Helpers.get(provider, CONTENT_URI, account);
}
+ /**
+ * @see android.provider.SyncStateContract.Helpers#get
+ */
+ public static Pair