diff options
author | Android (Google) Code Review <android-gerrit@google.com> | 2009-09-08 17:26:25 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2009-09-08 17:26:25 -0700 |
commit | b0dccf73ddb5a695fe24f1435411feea5876609f (patch) | |
tree | 8ed49ad30b1188bd6a0d9b1d31d4f23dabed7602 /core | |
parent | 046263ce352912990e492eb5cfd833c21109cf9f (diff) | |
parent | c4516a7b62de525e3d6d5e76851bdfaf12c11f05 (diff) | |
download | frameworks_base-b0dccf73ddb5a695fe24f1435411feea5876609f.zip frameworks_base-b0dccf73ddb5a695fe24f1435411feea5876609f.tar.gz frameworks_base-b0dccf73ddb5a695fe24f1435411feea5876609f.tar.bz2 |
Merge change 24012 into eclair
* changes:
add a transaction monitor
Diffstat (limited to 'core')
6 files changed, 156 insertions, 6 deletions
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 57bf3f7..4f31ef0 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -175,6 +175,11 @@ public class SQLiteDatabase extends SQLiteClosable { */ private boolean mTransactionIsSuccessful; + /** + * Valid during the life of a transaction. + */ + private SQLiteTransactionListener mTransactionListener; + /** Synchronize on this when accessing the database */ private final ReentrantLock mLock = new ReentrantLock(true); @@ -394,6 +399,31 @@ public class SQLiteDatabase extends SQLiteClosable { * </pre> */ public void beginTransaction() { + beginTransactionWithListener(null /* transactionStatusCallback */); + } + + /** + * Begins a transaction. Transactions can be nested. When the outer transaction is ended all of + * the work done in that transaction and all of the nested transactions will be committed or + * rolled back. The changes will be rolled back if any transaction is ended without being + * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed. + * + * <p>Here is the standard idiom for transactions: + * + * <pre> + * db.beginTransactionWithListener(listener); + * try { + * ... + * db.setTransactionSuccessful(); + * } finally { + * db.endTransaction(); + * } + * </pre> + * @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 d04afb0..84d8879 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 0000000..e97ece8 --- /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 a318f08..31d43ee 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; @@ -72,6 +73,14 @@ public final class ContactsContract { } /** + * @see android.provider.SyncStateContract.Helpers#get + */ + public static Pair<Uri, byte[]> getWithUri(ContentProviderClient provider, Account account) + throws RemoteException { + return SyncStateContract.Helpers.getWithUri(provider, CONTENT_URI, account); + } + + /** * @see android.provider.SyncStateContract.Helpers#set */ public static void set(ContentProviderClient provider, Account account, byte[] data) diff --git a/core/java/android/provider/SyncStateContract.java b/core/java/android/provider/SyncStateContract.java index 5c93af0..e8177ca 100644 --- a/core/java/android/provider/SyncStateContract.java +++ b/core/java/android/provider/SyncStateContract.java @@ -20,9 +20,11 @@ import android.net.Uri; import android.content.ContentProviderClient; import android.content.ContentValues; import android.content.ContentProviderOperation; +import android.content.ContentUris; import android.accounts.Account; import android.database.Cursor; import android.os.RemoteException; +import android.util.Pair; /** * The ContentProvider contract for associating data with ana data array account. @@ -54,7 +56,7 @@ public class SyncStateContract { } public static final class Helpers { - private static final String[] DATA_PROJECTION = new String[]{Columns.DATA}; + private static final String[] DATA_PROJECTION = new String[]{Columns.DATA, Columns._ID}; private static final String SELECT_BY_ACCOUNT = Columns.ACCOUNT_NAME + "=? AND " + Columns.ACCOUNT_TYPE + "=?"; @@ -101,6 +103,38 @@ public class SyncStateContract { provider.insert(uri, values); } + public static Uri insert(ContentProviderClient provider, Uri uri, + Account account, byte[] data) throws RemoteException { + ContentValues values = new ContentValues(); + values.put(Columns.DATA, data); + values.put(Columns.ACCOUNT_NAME, account.name); + values.put(Columns.ACCOUNT_TYPE, account.type); + return provider.insert(uri, values); + } + + public static void update(ContentProviderClient provider, Uri uri, byte[] data) + throws RemoteException { + ContentValues values = new ContentValues(); + values.put(Columns.DATA, data); + provider.update(uri, values, null, null); + } + + public static Pair<Uri, byte[]> getWithUri(ContentProviderClient provider, Uri uri, + Account account) throws RemoteException { + Cursor c = provider.query(uri, DATA_PROJECTION, SELECT_BY_ACCOUNT, + new String[]{account.name, account.type}, null); + try { + if (c.moveToNext()) { + long rowId = c.getLong(1); + byte[] blob = c.getBlob(c.getColumnIndexOrThrow(Columns.DATA)); + return Pair.create(ContentUris.withAppendedId(uri, rowId), blob); + } + } finally { + c.close(); + } + return null; + } + /** * Creates and returns a ContentProviderOperation that assigns the data array as the * sync state for the given account. @@ -121,5 +155,22 @@ public class SyncStateContract { .withValues(values) .build(); } + + /** + * Creates and returns a ContentProviderOperation that assigns the data array as the + * sync state for the given account. + * @param uri the uri of the specific sync state to set + * @param data the byte[] that contains the sync state + * @return the new ContentProviderOperation that assigns the data array as the + * account's sync state + */ + public static ContentProviderOperation newUpdateOperation(Uri uri, byte[] data) { + ContentValues values = new ContentValues(); + values.put(Columns.DATA, data); + return ContentProviderOperation + .newUpdate(uri) + .withValues(values) + .build(); + } } } diff --git a/core/java/com/android/internal/content/SyncStateContentProviderHelper.java b/core/java/com/android/internal/content/SyncStateContentProviderHelper.java index d2931a4..cd6a9a1 100644 --- a/core/java/com/android/internal/content/SyncStateContentProviderHelper.java +++ b/core/java/com/android/internal/content/SyncStateContentProviderHelper.java @@ -96,6 +96,13 @@ public class SyncStateContentProviderHelper { return db.update(SYNC_STATE_TABLE, values, selection, selectionArgs); } + public void update(SQLiteDatabase db, long rowId, Object data) { + db.execSQL("UPDATE " + SYNC_STATE_TABLE + + " SET " + SyncStateContract.Columns.DATA + "=?" + + " WHERE " + SyncStateContract.Columns._ID + "=" + rowId, + new Object[]{data}); + } + public void onAccountsChanged(SQLiteDatabase db, Account[] accounts) { Cursor c = db.query(SYNC_STATE_TABLE, ACCOUNT_PROJECTION, null, null, null, null, null); try { |