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
onCreate()105     public boolean onCreate() {
106         Context context = getContext();
107         mDbHelper = getDatabaseHelper(context);
108         mTransactionHolder = getTransactionHolder();
109         return true;
110     }
111 
getDatabaseHelper()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      */
setDbHelperToSerializeOn(SQLiteOpenHelper serializeOnDbHelper, String tag, SQLiteTransactionListener listener)121     public void setDbHelperToSerializeOn(SQLiteOpenHelper serializeOnDbHelper, String tag,
122             SQLiteTransactionListener listener) {
123         mSerializeOnDbHelper = serializeOnDbHelper;
124         mSerializeDbTag = tag;
125         mSerializedDbTransactionListener = listener;
126     }
127 
getCurrentTransaction()128     public ContactsTransaction getCurrentTransaction() {
129         return mTransactionHolder.get();
130     }
131 
132     @Override
insert(Uri uri, ContentValues values)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
delete(Uri uri, String selection, String[] selectionArgs)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
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)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
bulkInsert(Uri uri, ContentValues[] values)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
applyBatch(ArrayList<ContentProviderOperation> operations)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      */
startTransaction(boolean callerIsBatch)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      */
endTransaction(boolean callerIsBatch)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      */
getDatabaseHelper(Context context)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      */
getTransactionHolder()303     protected abstract ThreadLocal<ContactsTransaction> getTransactionHolder();
304 
insertInTransaction(Uri uri, ContentValues values)305     protected abstract Uri insertInTransaction(Uri uri, ContentValues values);
306 
deleteInTransaction(Uri uri, String selection, String[] selectionArgs)307     protected abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs);
308 
updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs)309     protected abstract int updateInTransaction(Uri uri, ContentValues values, String selection,
310             String[] selectionArgs);
311 
yield(ContactsTransaction transaction)312     protected abstract boolean yield(ContactsTransaction transaction);
313 
notifyChange()314     protected abstract void notifyChange();
315 }
316