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