Home | History | Annotate | Download | only in calendar
      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
     54     public boolean onCreate() {
     55         Context context = getContext();
     56         mOpenHelper = getDatabaseHelper(context);
     57         return true;
     58     }
     59 
     60     protected abstract SQLiteOpenHelper getDatabaseHelper(Context context);
     61 
     62     /**
     63      * The equivalent of the {@link #insert} method, but invoked within a transaction.
     64      */
     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      */
     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      */
     77     protected abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
     78             boolean callerIsSyncAdapter);
     79 
     80     protected abstract void notifyChange(boolean syncToNetwork);
     81 
     82     protected SQLiteOpenHelper getDatabaseHelper() {
     83         return mOpenHelper;
     84     }
     85 
     86     private boolean applyingBatch() {
     87         return mApplyingBatch.get() != null && mApplyingBatch.get();
     88     }
     89 
     90     @Override
     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
    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
    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
    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 
    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
    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 
    248     public void onBegin() {
    249         mIsCallerSyncAdapter = null;
    250         onBeginTransaction();
    251     }
    252 
    253     public void onCommit() {
    254         beforeTransactionCommit();
    255     }
    256 
    257     public void onRollback() {
    258         // not used
    259     }
    260 
    261     protected void onBeginTransaction() {
    262     }
    263 
    264     protected void beforeTransactionCommit() {
    265     }
    266 
    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      */
    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 
    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      */
    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      */
    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 }
    333