1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.providers.contacts; 18 19 import com.google.android.collect.Lists; 20 import com.google.android.collect.Maps; 21 22 import android.database.sqlite.SQLiteDatabase; 23 import android.database.sqlite.SQLiteTransactionListener; 24 25 import java.util.List; 26 import java.util.Map; 27 28 /** 29 * A transaction for interacting with a Contacts provider. This is used to pass state around 30 * throughout the operations comprising the transaction, including which databases the overall 31 * transaction is involved in, and whether the operation being performed is a batch operation. 32 */ 33 public class ContactsTransaction { 34 35 /** 36 * Whether this transaction is encompassing a batch of operations. If we're in batch mode, 37 * transactional operations from non-batch callers are ignored. 38 */ 39 private final boolean mBatch; 40 41 /** 42 * The list of databases that have been enlisted in this transaction. 43 */ 44 private List<SQLiteDatabase> mDatabasesForTransaction; 45 46 /** 47 * The mapping of tags to databases involved in this transaction. 48 */ 49 private Map<String, SQLiteDatabase> mDatabaseTagMap; 50 51 /** 52 * Whether any actual changes have been made successfully in this transaction. 53 */ 54 private boolean mIsDirty; 55 56 /** 57 * Whether a yield operation failed with an exception. If this occurred, we may not have a 58 * lock on one of the databases that we started the transaction with (the yield code cleans 59 * that up itself), so we should do an extra check before ending transactions. 60 */ 61 private boolean mYieldFailed; 62 63 /** 64 * Creates a new transaction object, optionally marked as a batch transaction. 65 * @param batch Whether the transaction is in batch mode. 66 */ 67 public ContactsTransaction(boolean batch) { 68 mBatch = batch; 69 mDatabasesForTransaction = Lists.newArrayList(); 70 mDatabaseTagMap = Maps.newHashMap(); 71 mIsDirty = false; 72 } 73 74 public boolean isBatch() { 75 return mBatch; 76 } 77 78 public boolean isDirty() { 79 return mIsDirty; 80 } 81 82 public void markDirty() { 83 mIsDirty = true; 84 } 85 86 public void markYieldFailed() { 87 mYieldFailed = true; 88 } 89 90 /** 91 * If the given database has not already been enlisted in this transaction, adds it to our 92 * list of affected databases and starts a transaction on it. If we already have the given 93 * database in this transaction, this is a no-op. 94 * @param db The database to start a transaction on, if necessary. 95 * @param tag A constant that can be used to retrieve the DB instance in this transaction. 96 * @param listener A transaction listener to attach to this transaction. May be null. 97 */ 98 public void startTransactionForDb(SQLiteDatabase db, String tag, 99 SQLiteTransactionListener listener) { 100 if (!hasDbInTransaction(tag)) { 101 mDatabasesForTransaction.add(db); 102 mDatabaseTagMap.put(tag, db); 103 if (listener != null) { 104 db.beginTransactionWithListener(listener); 105 } else { 106 db.beginTransaction(); 107 } 108 } 109 } 110 111 /** 112 * Returns whether DB corresponding to the given tag is currently enlisted in this transaction. 113 */ 114 public boolean hasDbInTransaction(String tag) { 115 return mDatabaseTagMap.containsKey(tag); 116 } 117 118 /** 119 * Retrieves the database enlisted in the transaction corresponding to the given tag. 120 * @param tag The tag of the database to look up. 121 * @return The database corresponding to the tag, or null if no database with that tag has been 122 * enlisted in this transaction. 123 */ 124 public SQLiteDatabase getDbForTag(String tag) { 125 return mDatabaseTagMap.get(tag); 126 } 127 128 /** 129 * Removes the database corresponding to the given tag from this transaction. It is now the 130 * caller's responsibility to do whatever needs to happen with this database - it is no longer 131 * a part of this transaction. 132 * @param tag The tag of the database to remove. 133 * @return The database corresponding to the tag, or null if no database with that tag has been 134 * enlisted in this transaction. 135 */ 136 public SQLiteDatabase removeDbForTag(String tag) { 137 SQLiteDatabase db = mDatabaseTagMap.get(tag); 138 mDatabaseTagMap.remove(tag); 139 mDatabasesForTransaction.remove(db); 140 return db; 141 } 142 143 /** 144 * Marks all active DB transactions as successful. 145 * @param callerIsBatch Whether this is being performed in the context of a batch operation. 146 * If it is not, and the transaction is marked as batch, this call is a no-op. 147 */ 148 public void markSuccessful(boolean callerIsBatch) { 149 if (!mBatch || callerIsBatch) { 150 for (SQLiteDatabase db : mDatabasesForTransaction) { 151 db.setTransactionSuccessful(); 152 } 153 } 154 } 155 156 /** 157 * Completes the transaction, ending the DB transactions for all associated databases. 158 * @param callerIsBatch Whether this is being performed in the context of a batch operation. 159 * If it is not, and the transaction is marked as batch, this call is a no-op. 160 */ 161 public void finish(boolean callerIsBatch) { 162 if (!mBatch || callerIsBatch) { 163 for (SQLiteDatabase db : mDatabasesForTransaction) { 164 // If an exception was thrown while yielding, it's possible that we no longer have 165 // a lock on this database, so we need to check before attempting to end its 166 // transaction. Otherwise, we should always expect to be in a transaction (and will 167 // throw an exception if this is not the case). 168 if (mYieldFailed && !db.isDbLockedByCurrentThread()) { 169 // We no longer hold the lock, so don't do anything with this database. 170 continue; 171 } 172 db.endTransaction(); 173 } 174 mDatabasesForTransaction.clear(); 175 mDatabaseTagMap.clear(); 176 mIsDirty = false; 177 } 178 } 179 } 180