summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/providers/contacts/AbstractContactsProvider.java239
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java211
-rw-r--r--src/com/android/providers/contacts/ContactsTransaction.java161
-rw-r--r--src/com/android/providers/contacts/ProfileProvider.java71
4 files changed, 481 insertions, 201 deletions
diff --git a/src/com/android/providers/contacts/AbstractContactsProvider.java b/src/com/android/providers/contacts/AbstractContactsProvider.java
new file mode 100644
index 0000000..6b0ec87
--- /dev/null
+++ b/src/com/android/providers/contacts/AbstractContactsProvider.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2011 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 com.android.providers.contacts;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteTransactionListener;
+import android.net.Uri;
+
+import java.util.ArrayList;
+
+/**
+ * A common base class for the contacts and profile providers. This handles much of the same
+ * logic that SQLiteContentProvider does (i.e. starting transactions on the appropriate database),
+ * but exposes awareness of batch operations to the subclass so that cross-database operations
+ * can be supported.
+ */
+public abstract class AbstractContactsProvider extends ContentProvider
+ implements SQLiteTransactionListener {
+
+ /**
+ * Duration in ms to sleep after successfully yielding the lock during a batch operation.
+ */
+ protected static final int SLEEP_AFTER_YIELD_DELAY = 4000;
+
+ /**
+ * Maximum number of operations allowed in a batch between yield points.
+ */
+ private static final int MAX_OPERATIONS_PER_YIELD_POINT = 500;
+
+ /**
+ * The contacts transaction that is active in this thread.
+ */
+ private ThreadLocal<ContactsTransaction> mTransaction = new ThreadLocal<ContactsTransaction>();
+
+ /**
+ * The DB helper to use for this content provider.
+ */
+ private SQLiteOpenHelper mDbHelper;
+
+ /**
+ * The database helper to serialize all transactions on. If non-null, any new transaction
+ * created by this provider will automatically retrieve a writable database from this helper
+ * and initiate a transaction on that database. This should be used to ensure that operations
+ * across multiple databases are all blocked on a single DB lock (to prevent deadlock cases).
+ */
+ private SQLiteOpenHelper mSerializeOnDbHelper;
+
+ /**
+ * The tag corresponding to the database used for serializing transactions.
+ */
+ private String mSerializeDbTag;
+
+ @Override
+ public boolean onCreate() {
+ Context context = getContext();
+ mDbHelper = getDatabaseHelper(context);
+ return true;
+ }
+
+ protected abstract SQLiteOpenHelper getDatabaseHelper(Context context);
+
+ public SQLiteOpenHelper getDatabaseHelper() {
+ return mDbHelper;
+ }
+
+ /**
+ * Specifies a database helper (and corresponding tag) to serialize all transactions on.
+ * @param serializeOnDbHelper The database helper to use for serializing transactions.
+ * @param tag The tag for this database.
+ */
+ public void setDbHelperToSerializeOn(SQLiteOpenHelper serializeOnDbHelper, String tag) {
+ mSerializeOnDbHelper = serializeOnDbHelper;
+ mSerializeDbTag = tag;
+ }
+
+ public ContactsTransaction getCurrentTransaction() {
+ return mTransaction.get();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ ContactsTransaction transaction = startTransaction(false);
+ try {
+ Uri result = insertInTransaction(uri, values);
+ if (result != null) {
+ transaction.markDirty();
+ }
+ transaction.markSuccessful(false);
+ return result;
+ } finally {
+ endTransaction(false);
+ }
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ ContactsTransaction transaction = startTransaction(false);
+ try {
+ int deleted = deleteInTransaction(uri, selection, selectionArgs);
+ if (deleted > 0) {
+ transaction.markDirty();
+ }
+ transaction.markSuccessful(false);
+ return deleted;
+ } finally {
+ endTransaction(false);
+ }
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ ContactsTransaction transaction = startTransaction(false);
+ try {
+ int updated = updateInTransaction(uri, values, selection, selectionArgs);
+ if (updated > 0) {
+ transaction.markDirty();
+ }
+ transaction.markSuccessful(false);
+ return updated;
+ } finally {
+ endTransaction(false);
+ }
+ }
+
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] values) {
+ ContactsTransaction transaction = startTransaction(true);
+ int numValues = values.length;
+ try {
+ for (int i = 0; i < numValues; i++) {
+ insert(uri, values[i]);
+ yield(transaction);
+ }
+ transaction.markSuccessful(true);
+ } finally {
+ endTransaction(true);
+ }
+ return numValues;
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
+ throws OperationApplicationException {
+ int ypCount = 0;
+ int opCount = 0;
+ ContactsTransaction transaction = startTransaction(true);
+ try {
+ final int numOperations = operations.size();
+ final ContentProviderResult[] results = new ContentProviderResult[numOperations];
+ for (int i = 0; i < numOperations; i++) {
+ if (++opCount >= MAX_OPERATIONS_PER_YIELD_POINT) {
+ throw new OperationApplicationException(
+ "Too many content provider operations between yield points. "
+ + "The maximum number of operations per yield point is "
+ + MAX_OPERATIONS_PER_YIELD_POINT, ypCount);
+ }
+ final ContentProviderOperation operation = operations.get(i);
+ if (i > 0 && operation.isYieldAllowed()) {
+ opCount = 0;
+ if (yield(transaction)) {
+ ypCount++;
+ }
+ }
+
+ results[i] = operation.apply(this, results, i);
+ }
+ transaction.markSuccessful(true);
+ return results;
+ } finally {
+ endTransaction(true);
+ }
+ }
+
+ /**
+ * If we are not yet already in a transaction, this starts one (on the DB to serialize on, if
+ * present) and sets the thread-local transaction variable for tracking. If we are already in
+ * a transaction, this returns that transaction, and the batch parameter is ignored.
+ * @param callerIsBatch Whether the caller is operating in batch mode.
+ */
+ private ContactsTransaction startTransaction(boolean callerIsBatch) {
+ ContactsTransaction transaction = mTransaction.get();
+ if (transaction == null) {
+ transaction = new ContactsTransaction(callerIsBatch);
+ if (mSerializeOnDbHelper != null) {
+ transaction.startTransactionForDb(mSerializeOnDbHelper.getWritableDatabase(),
+ mSerializeDbTag, this);
+ }
+ mTransaction.set(transaction);
+ }
+ return transaction;
+ }
+
+ /**
+ * Ends the current transaction and clears out the member variable. This does not set the
+ * transaction as being successful.
+ * @param callerIsBatch Whether the caller is operating in batch mode.
+ */
+ private void endTransaction(boolean callerIsBatch) {
+ ContactsTransaction transaction = mTransaction.get();
+ if (transaction != null && (!transaction.isBatch() || callerIsBatch)) {
+ if (transaction.isDirty()) {
+ notifyChange();
+ }
+ transaction.finish(callerIsBatch);
+ mTransaction.set(null);
+ }
+ }
+
+ protected abstract Uri insertInTransaction(Uri uri, ContentValues values);
+
+ protected abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs);
+
+ protected abstract int updateInTransaction(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs);
+
+ protected abstract boolean yield(ContactsTransaction transaction);
+
+ protected abstract void notifyChange();
+}
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 938af0d..f0aa549 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -16,11 +16,9 @@
package com.android.providers.contacts;
-import com.android.common.content.SQLiteContentProvider;
import com.android.common.content.SyncStateContentProviderHelper;
import com.android.providers.contacts.ContactAggregator.AggregationSuggestionParameter;
import com.android.providers.contacts.ContactLookupKey.LookupKeySegment;
-import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.AggregatedPresenceColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Clauses;
@@ -88,7 +86,6 @@ import android.database.MatrixCursor.RowBuilder;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDoneException;
-import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -175,7 +172,8 @@ import java.util.concurrent.CountDownLatch;
* Contacts content provider. The contract between this provider and applications
* is defined in {@link ContactsContract}.
*/
-public class ContactsProvider2 extends SQLiteContentProvider implements OnAccountsUpdateListener {
+public class ContactsProvider2 extends AbstractContactsProvider
+ implements OnAccountsUpdateListener {
private static final String TAG = "ContactsProvider";
@@ -1263,6 +1261,12 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
private ContactDirectoryManager mContactDirectoryManager;
+ // The database tag to use for representing the contacts DB in contacts transactions.
+ /* package */ static final String CONTACTS_DB_TAG = "contacts";
+
+ // The database tag to use for representing the profile DB in contacts transactions.
+ /* package */ static final String PROFILE_DB_TAG = "profile";
+
/**
* The active (thread-local) database. This will be switched between a contacts-specific
* database and a profile-specific database, depending on what the current operation is
@@ -1373,8 +1377,12 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
mMaxThumbnailPhotoDim = resources.getInteger(
R.integer.config_max_thumbnail_photo_dim);
- mContactsHelper = (ContactsDatabaseHelper) getDatabaseHelper();
+ mContactsHelper = getDatabaseHelper(getContext());
mDbHelper.set(mContactsHelper);
+
+ // Set up the DB helper for keeping transactions serialized.
+ setDbHelperToSerializeOn(mContactsHelper, CONTACTS_DB_TAG);
+
mContactDirectoryManager = new ContactDirectoryManager(this);
mGlobalSearchSupport = new GlobalSearchSupport(this);
@@ -1398,7 +1406,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
profileInfo.readPermission = "android.permission.READ_PROFILE";
profileInfo.writePermission = "android.permission.WRITE_PROFILE";
mProfileProvider.attachInfo(getContext(), profileInfo);
- mProfileHelper = (ProfileDatabaseHelper) mProfileProvider.getDatabaseHelper();
+ mProfileHelper = mProfileProvider.getDatabaseHelper(getContext());
scheduleBackgroundTask(BACKGROUND_TASK_INITIALIZE);
scheduleBackgroundTask(BACKGROUND_TASK_IMPORT_LEGACY_CONTACTS);
@@ -1780,7 +1788,6 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
}
/* Visible for testing */
- @Override
protected ContactsDatabaseHelper getDatabaseHelper(final Context context) {
return ContactsDatabaseHelper.getInstance(context);
}
@@ -2036,27 +2043,8 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
public Uri insert(Uri uri, ContentValues values) {
waitForAccess(mWriteAccessLatch);
if (mapsToProfileDbWithInsertedValues(uri, values)) {
- if (applyingBatch()) {
- switchToProfileMode();
- return mProfileProvider.insert(uri, values);
- } else {
- // Start a contacts DB transaction to maintain provider synchronization.
- SQLiteDatabase contactsDb = mContactsHelper.getWritableDatabase();
- contactsDb.beginTransactionWithListener(this);
- Uri result = null;
- try {
- // Now switch to profile mode and proceed with the insert using its provider.
- switchToProfileMode();
- result = mProfileProvider.insert(uri, values);
-
- contactsDb.setTransactionSuccessful();
- } finally {
- // Finish the contacts transaction, allowing other provider operations to
- // proceed.
- contactsDb.endTransaction();
- }
- return result;
- }
+ switchToProfileMode();
+ return mProfileProvider.insert(uri, values);
} else {
switchToContactMode();
return super.insert(uri, values);
@@ -2082,27 +2070,8 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
}
waitForAccess(mWriteAccessLatch);
if (mapsToProfileDb(uri)) {
- if (applyingBatch()) {
- switchToProfileMode();
- return mProfileProvider.update(uri, values, selection, selectionArgs);
- } else {
- // Start a contacts DB transaction to maintain provider synchronization.
- SQLiteDatabase contactsDb = mContactsHelper.getWritableDatabase();
- contactsDb.beginTransactionWithListener(this);
- int result = 0;
- try {
- // Now switch to profile mode and proceed with the update using its provider.
- switchToProfileMode();
- result = mProfileProvider.update(uri, values, selection, selectionArgs);
-
- contactsDb.setTransactionSuccessful();
- } finally {
- // Finish the contacts transaction, allowing other provider operations to
- // proceed.
- contactsDb.endTransaction();
- }
- return result;
- }
+ switchToProfileMode();
+ return mProfileProvider.update(uri, values, selection, selectionArgs);
} else {
switchToContactMode();
return super.update(uri, values, selection, selectionArgs);
@@ -2113,27 +2082,8 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
public int delete(Uri uri, String selection, String[] selectionArgs) {
waitForAccess(mWriteAccessLatch);
if (mapsToProfileDb(uri)) {
- if (applyingBatch()) {
- switchToProfileMode();
- return mProfileProvider.delete(uri, selection, selectionArgs);
- } else {
- // Start a contacts DB transaction to maintain provider synchronization.
- SQLiteDatabase contactsDb = mContactsHelper.getWritableDatabase();
- contactsDb.beginTransactionWithListener(this);
- int result = 0;
- try {
- // Now switch to profile mode and proceed with the delete using its provider.
- switchToProfileMode();
- result = mProfileProvider.delete(uri, selection, selectionArgs);
-
- contactsDb.setTransactionSuccessful();
- } finally {
- // Finish the contacts transaction, allowing other provider operations to
- // proceed.
- contactsDb.endTransaction();
- }
- return result;
- }
+ switchToProfileMode();
+ return mProfileProvider.delete(uri, selection, selectionArgs);
} else {
switchToContactMode();
return super.delete(uri, selection, selectionArgs);
@@ -2149,91 +2099,41 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
}
@Override
+ protected boolean yield(ContactsTransaction transaction) {
+ // If there's a profile transaction in progress, and we're yielding, we need to
+ // end it. Unlike the Contacts DB yield (which re-starts a transaction at its
+ // conclusion), we can just go back into a state in which we have no active
+ // profile transaction, and let it be re-created as needed. We can't hold onto
+ // the transaction without risking a deadlock.
+ SQLiteDatabase profileDb = transaction.removeDbForTag(PROFILE_DB_TAG);
+ if (profileDb != null) {
+ profileDb.setTransactionSuccessful();
+ profileDb.endTransaction();
+ }
+
+ // Now proceed with the Contacts DB yield.
+ SQLiteDatabase contactsDb = transaction.getDbForTag(CONTACTS_DB_TAG);
+ return contactsDb != null && contactsDb.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY);
+ }
+
+ @Override
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
waitForAccess(mWriteAccessLatch);
- ContentProviderResult[] results = null;
- try {
- mApplyingBatch.set(true);
- results = super.applyBatch(operations);
- } finally {
- mApplyingBatch.set(false);
- if (mProfileDbForBatch.get() != null) {
- // A profile operation was involved, so clean up its transaction.
- boolean profileErrors = mProfileErrorsInBatch.get() != null
- && mProfileErrorsInBatch.get();
- if (!profileErrors) {
- mProfileDbForBatch.get().setTransactionSuccessful();
- }
- mProfileDbForBatch.get().endTransaction();
- mProfileDbForBatch.set(null);
- mProfileErrorsInBatch.set(false);
- }
- }
- return results;
+ return super.applyBatch(operations);
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
waitForAccess(mWriteAccessLatch);
-
- // Note: This duplicates much of the logic in the superclass, but handles toggling
- // into profile mode if necessary.
- int numValues = values.length;
- boolean notifyChange = false;
- SQLiteDatabase profileDb = null;
-
- // Always get a contacts DB and start a transaction on it, to maintain provider
- // synchronization.
- mDb = mContactsHelper.getWritableDatabase();
- mDb.beginTransactionWithListener(this);
- try {
- for (int i = 0; i < numValues; i++) {
- Uri result;
- if (mapsToProfileDbWithInsertedValues(uri, values[i])) {
- switchToProfileMode();
-
- // Initialize the profile DB and start a profile transaction if we haven't
- // already done so.
- if (profileDb == null) {
- profileDb = mProfileHelper.getWritableDatabase();
- profileDb.beginTransactionWithListener(this);
- }
- result = mProfileProvider.insertInTransaction(uri, values[i]);
- } else {
- switchToContactMode();
- result = insertInTransaction(uri, values[i]);
- }
- if (result != null) {
- notifyChange = true;
- }
- boolean savedNotifyChange = notifyChange;
- mActiveDb.get().yieldIfContendedSafely();
- notifyChange = savedNotifyChange;
- }
- mDb.setTransactionSuccessful();
- if (profileDb != null) {
- profileDb.setTransactionSuccessful();
- }
- } finally {
- mDb.endTransaction();
- if (profileDb != null) {
- profileDb.endTransaction();
- }
- }
-
- if (notifyChange) {
- notifyChange();
- }
- return numValues;
+ return super.bulkInsert(uri, values);
}
@Override
- protected void onBeginTransaction() {
+ public void onBegin() {
if (VERBOSE_LOGGING) {
Log.v(TAG, "onBeginTransaction");
}
- super.onBeginTransaction();
if (inProfileMode()) {
mProfileAggregator.clearPendingAggregations();
mProfileTransactionContext.clear();
@@ -2243,14 +2143,11 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
}
}
-
@Override
- protected void beforeTransactionCommit() {
-
+ public void onCommit() {
if (VERBOSE_LOGGING) {
Log.v(TAG, "beforeTransactionCommit");
}
- super.beforeTransactionCommit();
flushTransactionalChanges();
mAggregator.get().aggregateInTransaction(mTransactionContext.get(), mActiveDb.get());
if (mVisibleTouched) {
@@ -2266,6 +2163,11 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
}
}
+ @Override
+ public void onRollback() {
+ // Not used.
+ }
+
private void updateSearchIndexInTransaction() {
Set<Long> staleContacts = mTransactionContext.get().getStaleSearchIndexContactIds();
Set<Long> staleRawContacts = mTransactionContext.get().getStaleSearchIndexRawContactIds();
@@ -2377,7 +2279,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
// Default active DB to the contacts DB if none has been set.
if (mActiveDb.get() == null) {
- mActiveDb.set(mDb);
+ mActiveDb.set(mContactsHelper.getWritableDatabase());
}
final boolean callerIsSyncAdapter =
@@ -3427,7 +3329,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
// Default active DB to the contacts DB if none has been set.
if (mActiveDb.get() == null) {
- mActiveDb.set(mDb);
+ mActiveDb.set(mContactsHelper.getWritableDatabase());
}
flushTransactionalChanges();
@@ -3807,7 +3709,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
// Default active DB to the contacts DB if none has been set.
if (mActiveDb.get() == null) {
- mActiveDb.set(mDb);
+ mActiveDb.set(mContactsHelper.getWritableDatabase());
}
int count = 0;
@@ -4933,7 +4835,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
// Default active DB to the contacts DB if none has been set.
if (mActiveDb.get() == null) {
- mActiveDb.set(mDb);
+ mActiveDb.set(mContactsHelper.getReadableDatabase());
}
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
@@ -6802,11 +6704,6 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
return mProfileProvider.openAssetFile(uri, mode);
} else {
switchToContactMode();
- if (mode.equals("r")) {
- mDb = mDbHelper.get().getReadableDatabase();
- } else {
- mDb = mDbHelper.get().getWritableDatabase();
- }
return openAssetFileLocal(uri, mode);
}
}
@@ -6816,7 +6713,11 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
// Default active DB to the contacts DB if none has been set.
if (mActiveDb.get() == null) {
- mActiveDb.set(mDb);
+ if (mode.equals("r")) {
+ mActiveDb.set(mContactsHelper.getReadableDatabase());
+ } else {
+ mActiveDb.set(mContactsHelper.getWritableDatabase());
+ }
}
int match = sUriMatcher.match(uri);
diff --git a/src/com/android/providers/contacts/ContactsTransaction.java b/src/com/android/providers/contacts/ContactsTransaction.java
new file mode 100644
index 0000000..1b4284d
--- /dev/null
+++ b/src/com/android/providers/contacts/ContactsTransaction.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2011 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 com.android.providers.contacts;
+
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteTransactionListener;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A transaction for interacting with a Contacts provider. This is used to pass state around
+ * throughout the operations comprising the transaction, including which databases the overall
+ * transaction is involved in, and whether the operation being performed is a batch operation.
+ */
+public class ContactsTransaction {
+
+ /**
+ * Whether this transaction is encompassing a batch of operations. If we're in batch mode,
+ * transactional operations from non-batch callers are ignored.
+ */
+ private final boolean mBatch;
+
+ /**
+ * The list of databases that have been enlisted in this transaction.
+ */
+ private List<SQLiteDatabase> mDatabasesForTransaction;
+
+ /**
+ * The mapping of tags to databases involved in this transaction.
+ */
+ private Map<String, SQLiteDatabase> mDatabaseTagMap;
+
+ /**
+ * Whether any actual changes have been made successfully in this transaction.
+ */
+ private boolean mIsDirty;
+
+ /**
+ * Creates a new transaction object, optionally marked as a batch transaction.
+ * @param batch Whether the transaction is in batch mode.
+ */
+ public ContactsTransaction(boolean batch) {
+ mBatch = batch;
+ mDatabasesForTransaction = Lists.newArrayList();
+ mDatabaseTagMap = Maps.newHashMap();
+ mIsDirty = false;
+ }
+
+ public boolean isBatch() {
+ return mBatch;
+ }
+
+ public boolean isDirty() {
+ return mIsDirty;
+ }
+
+ public void markDirty() {
+ mIsDirty = true;
+ }
+
+ /**
+ * If the given database has not already been enlisted in this transaction, adds it to our
+ * list of affected databases and starts a transaction on it. If we already have the given
+ * database in this transaction, this is a no-op.
+ * @param db The database to start a transaction on, if necessary.
+ * @param tag A constant that can be used to retrieve the DB instance in this transaction.
+ * @param listener A transaction listener to attach to this transaction. May be null.
+ */
+ public void startTransactionForDb(SQLiteDatabase db, String tag,
+ SQLiteTransactionListener listener) {
+ if (!hasDbInTransaction(tag)) {
+ mDatabasesForTransaction.add(db);
+ mDatabaseTagMap.put(tag, db);
+ if (listener != null) {
+ db.beginTransactionWithListener(listener);
+ } else {
+ db.beginTransaction();
+ }
+ }
+ }
+
+ /**
+ * Returns whether DB corresponding to the given tag is currently enlisted in this transaction.
+ */
+ public boolean hasDbInTransaction(String tag) {
+ return mDatabaseTagMap.containsKey(tag);
+ }
+
+ /**
+ * Retrieves the database enlisted in the transaction corresponding to the given tag.
+ * @param tag The tag of the database to look up.
+ * @return The database corresponding to the tag, or null if no database with that tag has been
+ * enlisted in this transaction.
+ */
+ public SQLiteDatabase getDbForTag(String tag) {
+ return mDatabaseTagMap.get(tag);
+ }
+
+ /**
+ * Removes the database corresponding to the given tag from this transaction. It is now the
+ * caller's responsibility to do whatever needs to happen with this database - it is no longer
+ * a part of this transaction.
+ * @param tag The tag of the database to remove.
+ * @return The database corresponding to the tag, or null if no database with that tag has been
+ * enlisted in this transaction.
+ */
+ public SQLiteDatabase removeDbForTag(String tag) {
+ SQLiteDatabase db = mDatabaseTagMap.get(tag);
+ mDatabaseTagMap.remove(tag);
+ mDatabasesForTransaction.remove(db);
+ return db;
+ }
+
+ /**
+ * Marks all active DB transactions as successful.
+ * @param callerIsBatch Whether this is being performed in the context of a batch operation.
+ * If it is not, and the transaction is marked as batch, this call is a no-op.
+ */
+ public void markSuccessful(boolean callerIsBatch) {
+ if (!mBatch || callerIsBatch) {
+ for (SQLiteDatabase db : mDatabasesForTransaction) {
+ db.setTransactionSuccessful();
+ }
+ }
+ }
+
+ /**
+ * Completes the transaction, marking the transactions for all databases as successful (or not),
+ * and ending them.
+ * @param callerIsBatch Whether this is being performed in the context of a batch operation.
+ * If it is not, and the transaction is marked as batch, this call is a no-op.
+ */
+ public void finish(boolean callerIsBatch) {
+ if (!mBatch || callerIsBatch) {
+ for (SQLiteDatabase db : mDatabasesForTransaction) {
+ db.endTransaction();
+ }
+ mDatabasesForTransaction.clear();
+ mDatabaseTagMap.clear();
+ mIsDirty = false;
+ }
+ }
+}
diff --git a/src/com/android/providers/contacts/ProfileProvider.java b/src/com/android/providers/contacts/ProfileProvider.java
index 2dc10fe..87528e1 100644
--- a/src/com/android/providers/contacts/ProfileProvider.java
+++ b/src/com/android/providers/contacts/ProfileProvider.java
@@ -15,13 +15,11 @@
*/
package com.android.providers.contacts;
-import com.android.common.content.SQLiteContentProvider;
-
-import android.accounts.Account;
import android.content.ContentValues;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import java.io.FileNotFoundException;
@@ -31,7 +29,7 @@ import java.util.Locale;
* Simple content provider to handle directing profile-specific calls against a separate
* database from the rest of contacts.
*/
-public class ProfileProvider extends SQLiteContentProvider {
+public class ProfileProvider extends AbstractContactsProvider {
private static final String READ_PERMISSION = "android.permission.READ_PROFILE";
private static final String WRITE_PERMISSION = "android.permission.WRITE_PROFILE";
@@ -52,7 +50,6 @@ public class ProfileProvider extends SQLiteContentProvider {
mDelegate.getContext().enforceCallingOrSelfPermission(WRITE_PERMISSION, null);
}
- @Override
protected ProfileDatabaseHelper getDatabaseHelper(Context context) {
return ProfileDatabaseHelper.getInstance(context);
}
@@ -61,17 +58,14 @@ public class ProfileProvider extends SQLiteContentProvider {
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
enforceReadPermission();
- if (mDb == null) {
- mDb = getDatabaseHelper().getReadableDatabase();
- }
- mDelegate.substituteDb(mDb);
+ mDelegate.substituteDb(getDatabaseHelper().getReadableDatabase());
return mDelegate.queryLocal(uri, projection, selection, selectionArgs, sortOrder, -1);
}
@Override
protected Uri insertInTransaction(Uri uri, ContentValues values) {
enforceWritePermission();
- mDelegate.substituteDb(mDb);
+ useProfileDbForTransaction();
return mDelegate.insertInTransaction(uri, values);
}
@@ -79,14 +73,14 @@ public class ProfileProvider extends SQLiteContentProvider {
protected int updateInTransaction(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
enforceWritePermission();
- mDelegate.substituteDb(mDb);
+ useProfileDbForTransaction();
return mDelegate.updateInTransaction(uri, values, selection, selectionArgs);
}
@Override
protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
enforceWritePermission();
- mDelegate.substituteDb(mDb);
+ useProfileDbForTransaction();
return mDelegate.deleteInTransaction(uri, selection, selectionArgs);
}
@@ -102,6 +96,13 @@ public class ProfileProvider extends SQLiteContentProvider {
return mDelegate.openAssetFileLocal(uri, mode);
}
+ private void useProfileDbForTransaction() {
+ ContactsTransaction transaction = getCurrentTransaction();
+ SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
+ transaction.startTransactionForDb(db, ContactsProvider2.PROFILE_DB_TAG, this);
+ mDelegate.substituteDb(db);
+ }
+
@Override
protected void notifyChange() {
mDelegate.notifyChange();
@@ -111,48 +112,26 @@ public class ProfileProvider extends SQLiteContentProvider {
mDelegate.notifyChange(syncToNetwork);
}
- protected void scheduleBackgroundTask(int task) {
- mDelegate.scheduleBackgroundTask(task);
- }
-
- protected void scheduleBackgroundTask(int task, Object arg) {
- mDelegate.scheduleBackgroundTask(task, arg);
- }
-
- protected void performBackgroundTask(int task, Object arg) {
- mDelegate.performBackgroundTask(task, arg);
- }
-
- protected void updateLocaleInBackground() {
- mDelegate.updateLocaleInBackground();
- }
-
- protected void updateDirectoriesInBackground(boolean rescan) {
- mDelegate.updateDirectoriesInBackground(rescan);
- }
-
- protected Account getDefaultAccount() {
- return mDelegate.getDefaultAccount();
- }
-
- protected boolean isContactsAccount(Account account) {
- return mDelegate.isContactsAccount(account);
- }
-
protected Locale getLocale() {
return mDelegate.getLocale();
}
- protected boolean isWritableAccountWithDataSet(String accountTypeAndDataSet) {
- return mDelegate.isWritableAccountWithDataSet(accountTypeAndDataSet);
+ public void onBegin() {
+ mDelegate.onBegin();
}
- protected void onBeginTransaction() {
- mDelegate.onBeginTransaction();
+ public void onCommit() {
+ mDelegate.onCommit();
}
- protected void beforeTransactionCommit() {
- mDelegate.beforeTransactionCommit();
+ @Override
+ public void onRollback() {
+ mDelegate.onRollback();
+ }
+
+ @Override
+ protected boolean yield(ContactsTransaction transaction) {
+ return mDelegate.yield(transaction);
}
@Override