• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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