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 android.content.ContentProvider; 20 import android.content.ContentProviderOperation; 21 import android.content.ContentProviderResult; 22 import android.content.ContentValues; 23 import android.content.Context; 24 import android.content.OperationApplicationException; 25 import android.database.sqlite.SQLiteOpenHelper; 26 import android.database.sqlite.SQLiteTransactionListener; 27 import android.net.Uri; 28 import android.util.Log; 29 30 import java.util.ArrayList; 31 32 /** 33 * A common base class for the contacts and profile providers. This handles much of the same 34 * logic that SQLiteContentProvider does (i.e. starting transactions on the appropriate database), 35 * but exposes awareness of batch operations to the subclass so that cross-database operations 36 * can be supported. 37 */ 38 public abstract class AbstractContactsProvider extends ContentProvider 39 implements SQLiteTransactionListener { 40 41 protected static final String TAG = "ContactsProvider"; 42 43 protected static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE); 44 45 /** 46 * Duration in ms to sleep after successfully yielding the lock during a batch operation. 47 */ 48 protected static final int SLEEP_AFTER_YIELD_DELAY = 4000; 49 50 /** 51 * Maximum number of operations allowed in a batch between yield points. 52 */ 53 private static final int MAX_OPERATIONS_PER_YIELD_POINT = 500; 54 55 /** 56 * Number of inserts performed in bulk to allow before yielding the transaction. 57 */ 58 private static final int BULK_INSERTS_PER_YIELD_POINT = 50; 59 60 /** 61 * The contacts transaction that is active in this thread. 62 */ 63 private ThreadLocal<ContactsTransaction> mTransactionHolder; 64 65 /** 66 * The DB helper to use for this content provider. 67 */ 68 private SQLiteOpenHelper mDbHelper; 69 70 /** 71 * The database helper to serialize all transactions on. If non-null, any new transaction 72 * created by this provider will automatically retrieve a writable database from this helper 73 * and initiate a transaction on that database. This should be used to ensure that operations 74 * across multiple databases are all blocked on a single DB lock (to prevent deadlock cases). 75 */ 76 private SQLiteOpenHelper mSerializeOnDbHelper; 77 78 /** 79 * The tag corresponding to the database used for serializing transactions. 80 */ 81 private String mSerializeDbTag; 82 83 @Override onCreate()84 public boolean onCreate() { 85 Context context = getContext(); 86 mDbHelper = getDatabaseHelper(context); 87 mTransactionHolder = getTransactionHolder(); 88 return true; 89 } 90 getDatabaseHelper()91 public SQLiteOpenHelper getDatabaseHelper() { 92 return mDbHelper; 93 } 94 95 /** 96 * Specifies a database helper (and corresponding tag) to serialize all transactions on. 97 * @param serializeOnDbHelper The database helper to use for serializing transactions. 98 * @param tag The tag for this database. 99 */ setDbHelperToSerializeOn(SQLiteOpenHelper serializeOnDbHelper, String tag)100 public void setDbHelperToSerializeOn(SQLiteOpenHelper serializeOnDbHelper, String tag) { 101 mSerializeOnDbHelper = serializeOnDbHelper; 102 mSerializeDbTag = tag; 103 } 104 getCurrentTransaction()105 public ContactsTransaction getCurrentTransaction() { 106 return mTransactionHolder.get(); 107 } 108 109 @Override insert(Uri uri, ContentValues values)110 public Uri insert(Uri uri, ContentValues values) { 111 ContactsTransaction transaction = startTransaction(false); 112 try { 113 Uri result = insertInTransaction(uri, values); 114 if (result != null) { 115 transaction.markDirty(); 116 } 117 transaction.markSuccessful(false); 118 return result; 119 } finally { 120 endTransaction(false); 121 } 122 } 123 124 @Override delete(Uri uri, String selection, String[] selectionArgs)125 public int delete(Uri uri, String selection, String[] selectionArgs) { 126 ContactsTransaction transaction = startTransaction(false); 127 try { 128 int deleted = deleteInTransaction(uri, selection, selectionArgs); 129 if (deleted > 0) { 130 transaction.markDirty(); 131 } 132 transaction.markSuccessful(false); 133 return deleted; 134 } finally { 135 endTransaction(false); 136 } 137 } 138 139 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)140 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 141 ContactsTransaction transaction = startTransaction(false); 142 try { 143 int updated = updateInTransaction(uri, values, selection, selectionArgs); 144 if (updated > 0) { 145 transaction.markDirty(); 146 } 147 transaction.markSuccessful(false); 148 return updated; 149 } finally { 150 endTransaction(false); 151 } 152 } 153 154 @Override bulkInsert(Uri uri, ContentValues[] values)155 public int bulkInsert(Uri uri, ContentValues[] values) { 156 ContactsTransaction transaction = startTransaction(true); 157 int numValues = values.length; 158 int opCount = 0; 159 try { 160 for (int i = 0; i < numValues; i++) { 161 insert(uri, values[i]); 162 if (++opCount >= BULK_INSERTS_PER_YIELD_POINT) { 163 opCount = 0; 164 try { 165 yield(transaction); 166 } catch (RuntimeException re) { 167 transaction.markYieldFailed(); 168 throw re; 169 } 170 } 171 } 172 transaction.markSuccessful(true); 173 } finally { 174 endTransaction(true); 175 } 176 return numValues; 177 } 178 179 @Override applyBatch(ArrayList<ContentProviderOperation> operations)180 public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) 181 throws OperationApplicationException { 182 if (VERBOSE_LOGGING) { 183 Log.v(TAG, "applyBatch: " + operations.size() + " ops"); 184 } 185 int ypCount = 0; 186 int opCount = 0; 187 ContactsTransaction transaction = startTransaction(true); 188 try { 189 final int numOperations = operations.size(); 190 final ContentProviderResult[] results = new ContentProviderResult[numOperations]; 191 for (int i = 0; i < numOperations; i++) { 192 if (++opCount >= MAX_OPERATIONS_PER_YIELD_POINT) { 193 throw new OperationApplicationException( 194 "Too many content provider operations between yield points. " 195 + "The maximum number of operations per yield point is " 196 + MAX_OPERATIONS_PER_YIELD_POINT, ypCount); 197 } 198 final ContentProviderOperation operation = operations.get(i); 199 if (i > 0 && operation.isYieldAllowed()) { 200 if (VERBOSE_LOGGING) { 201 Log.v(TAG, "applyBatch: " + opCount + " ops finished; about to yield..."); 202 } 203 opCount = 0; 204 try { 205 if (yield(transaction)) { 206 ypCount++; 207 } 208 } catch (RuntimeException re) { 209 transaction.markYieldFailed(); 210 throw re; 211 } 212 } 213 214 results[i] = operation.apply(this, results, i); 215 } 216 transaction.markSuccessful(true); 217 return results; 218 } finally { 219 endTransaction(true); 220 } 221 } 222 223 /** 224 * If we are not yet already in a transaction, this starts one (on the DB to serialize on, if 225 * present) and sets the thread-local transaction variable for tracking. If we are already in 226 * a transaction, this returns that transaction, and the batch parameter is ignored. 227 * @param callerIsBatch Whether the caller is operating in batch mode. 228 */ startTransaction(boolean callerIsBatch)229 private ContactsTransaction startTransaction(boolean callerIsBatch) { 230 ContactsTransaction transaction = mTransactionHolder.get(); 231 if (transaction == null) { 232 transaction = new ContactsTransaction(callerIsBatch); 233 if (mSerializeOnDbHelper != null) { 234 transaction.startTransactionForDb(mSerializeOnDbHelper.getWritableDatabase(), 235 mSerializeDbTag, this); 236 } 237 mTransactionHolder.set(transaction); 238 } 239 return transaction; 240 } 241 242 /** 243 * Ends the current transaction and clears out the member variable. This does not set the 244 * transaction as being successful. 245 * @param callerIsBatch Whether the caller is operating in batch mode. 246 */ endTransaction(boolean callerIsBatch)247 private void endTransaction(boolean callerIsBatch) { 248 ContactsTransaction transaction = mTransactionHolder.get(); 249 if (transaction != null && (!transaction.isBatch() || callerIsBatch)) { 250 try { 251 if (transaction.isDirty()) { 252 notifyChange(); 253 } 254 transaction.finish(callerIsBatch); 255 } finally { 256 // No matter what, make sure we clear out the thread-local transaction reference. 257 mTransactionHolder.set(null); 258 } 259 } 260 } 261 262 /** 263 * Gets the database helper for this contacts provider. This is called once, during onCreate(). 264 */ getDatabaseHelper(Context context)265 protected abstract SQLiteOpenHelper getDatabaseHelper(Context context); 266 267 /** 268 * Gets the thread-local transaction holder to use for keeping track of the transaction. This 269 * is called once, in onCreate(). If multiple classes are inheriting from this class that need 270 * to be kept in sync on the same transaction, they must all return the same thread-local. 271 */ getTransactionHolder()272 protected abstract ThreadLocal<ContactsTransaction> getTransactionHolder(); 273 insertInTransaction(Uri uri, ContentValues values)274 protected abstract Uri insertInTransaction(Uri uri, ContentValues values); 275 deleteInTransaction(Uri uri, String selection, String[] selectionArgs)276 protected abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs); 277 updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs)278 protected abstract int updateInTransaction(Uri uri, ContentValues values, String selection, 279 String[] selectionArgs); 280 yield(ContactsTransaction transaction)281 protected abstract boolean yield(ContactsTransaction transaction); 282 notifyChange()283 protected abstract void notifyChange(); 284 } 285