diff options
Diffstat (limited to 'src')
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 |