1 /*
2  * Copyright (C) 2009 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.calendar;
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.SQLiteDatabase;
26 import android.database.sqlite.SQLiteOpenHelper;
27 import android.database.sqlite.SQLiteTransactionListener;
28 import android.net.Uri;
29 import android.os.Binder;
30 import android.os.Process;
31 import android.provider.CalendarContract;
32 import android.util.Log;
33 
34 import java.util.ArrayList;
35 
36 /**
37  * General purpose {@link ContentProvider} base class that uses SQLiteDatabase for storage.
38  */
39 public abstract class SQLiteContentProvider extends ContentProvider
40         implements SQLiteTransactionListener {
41 
42     private static final String TAG = "SQLiteContentProvider";
43 
44     private SQLiteOpenHelper mOpenHelper;
45     private volatile boolean mNotifyChange;
46     protected SQLiteDatabase mDb;
47 
48     private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>();
49     private static final int SLEEP_AFTER_YIELD_DELAY = 4000;
50 
51     private Boolean mIsCallerSyncAdapter;
52 
53     @Override
onCreate()54     public boolean onCreate() {
55         Context context = getContext();
56         mOpenHelper = getDatabaseHelper(context);
57         return true;
58     }
59 
getDatabaseHelper(Context context)60     protected abstract SQLiteOpenHelper getDatabaseHelper(Context context);
61 
62     /**
63      * The equivalent of the {@link #insert} method, but invoked within a transaction.
64      */
insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter)65     protected abstract Uri insertInTransaction(Uri uri, ContentValues values,
66             boolean callerIsSyncAdapter);
67 
68     /**
69      * The equivalent of the {@link #update} method, but invoked within a transaction.
70      */
updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs, boolean callerIsSyncAdapter)71     protected abstract int updateInTransaction(Uri uri, ContentValues values, String selection,
72             String[] selectionArgs, boolean callerIsSyncAdapter);
73 
74     /**
75      * The equivalent of the {@link #delete} method, but invoked within a transaction.
76      */
deleteInTransaction(Uri uri, String selection, String[] selectionArgs, boolean callerIsSyncAdapter)77     protected abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
78             boolean callerIsSyncAdapter);
79 
notifyChange(boolean syncToNetwork)80     protected abstract void notifyChange(boolean syncToNetwork);
81 
getDatabaseHelper()82     protected SQLiteOpenHelper getDatabaseHelper() {
83         return mOpenHelper;
84     }
85 
applyingBatch()86     protected boolean applyingBatch() {
87         return mApplyingBatch.get() != null && mApplyingBatch.get();
88     }
89 
90     @Override
insert(Uri uri, ContentValues values)91     public Uri insert(Uri uri, ContentValues values) {
92         Uri result = null;
93         boolean applyingBatch = applyingBatch();
94         boolean isCallerSyncAdapter = getIsCallerSyncAdapter(uri);
95         if (!applyingBatch) {
96             mDb = mOpenHelper.getWritableDatabase();
97             mDb.beginTransactionWithListener(this);
98             final long identity = clearCallingIdentityInternal();
99             try {
100                 result = insertInTransaction(uri, values, isCallerSyncAdapter);
101                 if (result != null) {
102                     mNotifyChange = true;
103                 }
104                 mDb.setTransactionSuccessful();
105             } finally {
106                 restoreCallingIdentityInternal(identity);
107                 mDb.endTransaction();
108             }
109 
110             onEndTransaction(!isCallerSyncAdapter && shouldSyncFor(uri));
111         } else {
112             result = insertInTransaction(uri, values, isCallerSyncAdapter);
113             if (result != null) {
114                 mNotifyChange = true;
115             }
116         }
117         return result;
118     }
119 
120     @Override
bulkInsert(Uri uri, ContentValues[] values)121     public int bulkInsert(Uri uri, ContentValues[] values) {
122         int numValues = values.length;
123         boolean isCallerSyncAdapter = getIsCallerSyncAdapter(uri);
124         mDb = mOpenHelper.getWritableDatabase();
125         mDb.beginTransactionWithListener(this);
126         final long identity = clearCallingIdentityInternal();
127         try {
128             for (int i = 0; i < numValues; i++) {
129                 Uri result = insertInTransaction(uri, values[i], isCallerSyncAdapter);
130                 if (result != null) {
131                     mNotifyChange = true;
132                 }
133                 mDb.yieldIfContendedSafely();
134             }
135             mDb.setTransactionSuccessful();
136         } finally {
137             restoreCallingIdentityInternal(identity);
138             mDb.endTransaction();
139         }
140 
141         onEndTransaction(!isCallerSyncAdapter);
142         return numValues;
143     }
144 
145     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)146     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
147         int count = 0;
148         boolean applyingBatch = applyingBatch();
149         boolean isCallerSyncAdapter = getIsCallerSyncAdapter(uri);
150         if (!applyingBatch) {
151             mDb = mOpenHelper.getWritableDatabase();
152             mDb.beginTransactionWithListener(this);
153             final long identity = clearCallingIdentityInternal();
154             try {
155                 count = updateInTransaction(uri, values, selection, selectionArgs,
156                             isCallerSyncAdapter);
157                 if (count > 0) {
158                     mNotifyChange = true;
159                 }
160                 mDb.setTransactionSuccessful();
161             } finally {
162                 restoreCallingIdentityInternal(identity);
163                 mDb.endTransaction();
164             }
165 
166             onEndTransaction(!isCallerSyncAdapter && shouldSyncFor(uri));
167         } else {
168             count = updateInTransaction(uri, values, selection, selectionArgs,
169                         isCallerSyncAdapter);
170             if (count > 0) {
171                 mNotifyChange = true;
172             }
173         }
174 
175         return count;
176     }
177 
178     @Override
delete(Uri uri, String selection, String[] selectionArgs)179     public int delete(Uri uri, String selection, String[] selectionArgs) {
180         int count = 0;
181         boolean applyingBatch = applyingBatch();
182         boolean isCallerSyncAdapter = getIsCallerSyncAdapter(uri);
183         if (!applyingBatch) {
184             mDb = mOpenHelper.getWritableDatabase();
185             mDb.beginTransactionWithListener(this);
186             final long identity = clearCallingIdentityInternal();
187             try {
188                 count = deleteInTransaction(uri, selection, selectionArgs, isCallerSyncAdapter);
189                 if (count > 0) {
190                     mNotifyChange = true;
191                 }
192                 mDb.setTransactionSuccessful();
193             } finally {
194                 restoreCallingIdentityInternal(identity);
195                 mDb.endTransaction();
196             }
197 
198             onEndTransaction(!isCallerSyncAdapter && shouldSyncFor(uri));
199         } else {
200             count = deleteInTransaction(uri, selection, selectionArgs, isCallerSyncAdapter);
201             if (count > 0) {
202                 mNotifyChange = true;
203             }
204         }
205         return count;
206     }
207 
getIsCallerSyncAdapter(Uri uri)208     protected boolean getIsCallerSyncAdapter(Uri uri) {
209         boolean isCurrentSyncAdapter = QueryParameterUtils.readBooleanQueryParameter(uri,
210                 CalendarContract.CALLER_IS_SYNCADAPTER, false);
211         if (mIsCallerSyncAdapter == null || mIsCallerSyncAdapter) {
212             mIsCallerSyncAdapter = isCurrentSyncAdapter;
213         }
214         return isCurrentSyncAdapter;
215     }
216 
217     @Override
applyBatch(ArrayList<ContentProviderOperation> operations)218     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
219             throws OperationApplicationException {
220         final int numOperations = operations.size();
221         if (numOperations == 0) {
222             return new ContentProviderResult[0];
223         }
224         mDb = mOpenHelper.getWritableDatabase();
225         mDb.beginTransactionWithListener(this);
226         final boolean isCallerSyncAdapter = getIsCallerSyncAdapter(operations.get(0).getUri());
227         final long identity = clearCallingIdentityInternal();
228         try {
229             mApplyingBatch.set(true);
230             final ContentProviderResult[] results = new ContentProviderResult[numOperations];
231             for (int i = 0; i < numOperations; i++) {
232                 final ContentProviderOperation operation = operations.get(i);
233                 if (i > 0 && operation.isYieldAllowed()) {
234                     mDb.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY);
235                 }
236                 results[i] = operation.apply(this, results, i);
237             }
238             mDb.setTransactionSuccessful();
239             return results;
240         } finally {
241             mApplyingBatch.set(false);
242             mDb.endTransaction();
243             onEndTransaction(!isCallerSyncAdapter);
244             restoreCallingIdentityInternal(identity);
245         }
246     }
247 
onBegin()248     public void onBegin() {
249         mIsCallerSyncAdapter = null;
250         onBeginTransaction();
251     }
252 
onCommit()253     public void onCommit() {
254         beforeTransactionCommit();
255     }
256 
onRollback()257     public void onRollback() {
258         // not used
259     }
260 
onBeginTransaction()261     protected void onBeginTransaction() {
262     }
263 
beforeTransactionCommit()264     protected void beforeTransactionCommit() {
265     }
266 
onEndTransaction(boolean syncToNetwork)267     protected void onEndTransaction(boolean syncToNetwork) {
268         if (mNotifyChange) {
269             mNotifyChange = false;
270             // We sync to network if the caller was not the sync adapter
271             notifyChange(syncToNetwork);
272         }
273     }
274 
275     /**
276      * Some URI's are maintained locally so we should not request a sync for them
277      */
shouldSyncFor(Uri uri)278     protected abstract boolean shouldSyncFor(Uri uri);
279 
280     /** The package to most recently query(), not including further internally recursive calls. */
281     private final ThreadLocal<String> mCallingPackage = new ThreadLocal<String>();
282 
283     /**
284      * The calling Uid when a calling package is cached, so we know when the stack of any
285      * recursive calls to clearCallingIdentity and restoreCallingIdentity is complete.
286      */
287     private final ThreadLocal<Integer> mOriginalCallingUid = new ThreadLocal<Integer>();
288 
289 
getCachedCallingPackage()290     protected String getCachedCallingPackage() {
291         return mCallingPackage.get();
292     }
293 
294     /**
295      * Call {@link android.os.Binder#clearCallingIdentity()}, while caching the calling package
296      * name, so that it can be saved if this is part of an event mutation.
297      */
clearCallingIdentityInternal()298     protected long clearCallingIdentityInternal() {
299         // Only set the calling package if the calling UID is not our own.
300         int uid = Process.myUid();
301         int callingUid = Binder.getCallingUid();
302         if (uid != callingUid) {
303             try {
304                 mOriginalCallingUid.set(callingUid);
305                 String callingPackage = getCallingPackage();
306                 mCallingPackage.set(callingPackage);
307             } catch (SecurityException e) {
308                 Log.e(TAG, "Error getting the calling package.", e);
309             }
310         }
311 
312         return Binder.clearCallingIdentity();
313     }
314 
315     /**
316      * Call {@link Binder#restoreCallingIdentity(long)}.
317      * </p>
318      * If this is the last restore on the stack of calls to
319      * {@link android.os.Binder#clearCallingIdentity()}, then the cached calling package will also
320      * be cleared.
321      * @param identity
322      */
restoreCallingIdentityInternal(long identity)323     protected void restoreCallingIdentityInternal(long identity) {
324         Binder.restoreCallingIdentity(identity);
325 
326         int callingUid = Binder.getCallingUid();
327         if (mOriginalCallingUid.get() != null && mOriginalCallingUid.get() == callingUid) {
328             mCallingPackage.set(null);
329             mOriginalCallingUid.set(null);
330         }
331     }
332 
getReadableDatabase()333     SQLiteDatabase getReadableDatabase() {
334         return mOpenHelper != null ? mOpenHelper.getReadableDatabase() : null;
335     }
336 
getWritableDatabase()337     SQLiteDatabase getWritableDatabase() {
338         return mOpenHelper != null ? mOpenHelper.getWritableDatabase() : null;
339     }
340 }
341