Home | History | Annotate | Download | only in contacts
      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