| /* |
| * 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.database.sqlite.SQLiteDatabase; |
| import android.database.sqlite.SQLiteTransactionListener; |
| import android.util.Log; |
| |
| import com.google.android.collect.Lists; |
| import com.google.android.collect.Maps; |
| |
| 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. |
| * |
| * Note we insert elements to the head of the list, so that we endTransaction() in the reverse |
| * order. |
| */ |
| private final List<SQLiteDatabase> mDatabasesForTransaction; |
| |
| /** |
| * The mapping of tags to databases involved in this transaction. |
| */ |
| private final Map<String, SQLiteDatabase> mDatabaseTagMap; |
| |
| /** |
| * Whether any actual changes have been made successfully in this transaction. |
| */ |
| private boolean mIsDirty; |
| |
| /** |
| * Whether a yield operation failed with an exception. If this occurred, we may not have a |
| * lock on one of the databases that we started the transaction with (the yield code cleans |
| * that up itself), so we should do an extra check before ending transactions. |
| */ |
| private boolean mYieldFailed; |
| |
| /** |
| * 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; |
| } |
| |
| public void markYieldFailed() { |
| mYieldFailed = 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 (AbstractContactsProvider.ENABLE_TRANSACTION_LOG) { |
| Log.i(AbstractContactsProvider.TAG, "startTransactionForDb: db=" + db.getPath() + |
| " tag=" + tag + " listener=" + listener + |
| " startTransaction=" + !hasDbInTransaction(tag), |
| new RuntimeException("startTransactionForDb")); |
| } |
| if (!hasDbInTransaction(tag)) { |
| // Insert a new db into the head of the list, so that we'll endTransaction() in |
| // the reverse order. |
| mDatabasesForTransaction.add(0, 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(); |
| } |
| } |
| } |
| |
| /** |
| * @return the tag for a database. Only intended to be used for logging. |
| */ |
| private String getTagForDb(SQLiteDatabase db) { |
| for (String tag : mDatabaseTagMap.keySet()) { |
| if (db == mDatabaseTagMap.get(tag)) { |
| return tag; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Completes the transaction, ending the DB transactions for all associated databases. |
| * @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 (AbstractContactsProvider.ENABLE_TRANSACTION_LOG) { |
| Log.i(AbstractContactsProvider.TAG, "ContactsTransaction.finish callerIsBatch=" + |
| callerIsBatch, new RuntimeException("ContactsTransaction.finish")); |
| } |
| if (!mBatch || callerIsBatch) { |
| for (SQLiteDatabase db : mDatabasesForTransaction) { |
| if (AbstractContactsProvider.ENABLE_TRANSACTION_LOG) { |
| Log.i(AbstractContactsProvider.TAG, "ContactsTransaction.finish: " + |
| "endTransaction for " + getTagForDb(db)); |
| } |
| // If an exception was thrown while yielding, it's possible that we no longer have |
| // a lock on this database, so we need to check before attempting to end its |
| // transaction. Otherwise, we should always expect to be in a transaction (and will |
| // throw an exception if this is not the case). |
| if (mYieldFailed && !db.isDbLockedByCurrentThread()) { |
| // We no longer hold the lock, so don't do anything with this database. |
| continue; |
| } |
| db.endTransaction(); |
| } |
| mDatabasesForTransaction.clear(); |
| mDatabaseTagMap.clear(); |
| mIsDirty = false; |
| } |
| } |
| } |