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.provider.CalendarContract;
     31 
     32 import java.util.ArrayList;
     33 
     34 /**
     35  * General purpose {@link ContentProvider} base class that uses SQLiteDatabase for storage.
     36  */
     37 public abstract class SQLiteContentProvider extends ContentProvider
     38         implements SQLiteTransactionListener {
     39 
     40     private static final String TAG = "SQLiteContentProvider";
     41 
     42     private SQLiteOpenHelper mOpenHelper;
     43     private volatile boolean mNotifyChange;
     44     protected SQLiteDatabase mDb;
     45 
     46     private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>();
     47     private static final int SLEEP_AFTER_YIELD_DELAY = 4000;
     48 
     49     private Boolean mIsCallerSyncAdapter;
     50 
     51     @Override
     52     public boolean onCreate() {
     53         Context context = getContext();
     54         mOpenHelper = getDatabaseHelper(context);
     55         return true;
     56     }
     57 
     58     protected abstract SQLiteOpenHelper getDatabaseHelper(Context context);
     59 
     60     /**
     61      * The equivalent of the {@link #insert} method, but invoked within a transaction.
     62      */
     63     protected abstract Uri insertInTransaction(Uri uri, ContentValues values,
     64             boolean callerIsSyncAdapter);
     65 
     66     /**
     67      * The equivalent of the {@link #update} method, but invoked within a transaction.
     68      */
     69     protected abstract int updateInTransaction(Uri uri, ContentValues values, String selection,
     70             String[] selectionArgs, boolean callerIsSyncAdapter);
     71 
     72     /**
     73      * The equivalent of the {@link #delete} method, but invoked within a transaction.
     74      */
     75     protected abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
     76             boolean callerIsSyncAdapter);
     77 
     78     protected abstract void notifyChange(boolean syncToNetwork);
     79 
     80     protected SQLiteOpenHelper getDatabaseHelper() {
     81         return mOpenHelper;
     82     }
     83 
     84     private boolean applyingBatch() {
     85         return mApplyingBatch.get() != null && mApplyingBatch.get();
     86     }
     87 
     88     @Override
     89     public Uri insert(Uri uri, ContentValues values) {
     90         Uri result = null;
     91         boolean applyingBatch = applyingBatch();
     92         boolean isCallerSyncAdapter = getIsCallerSyncAdapter(uri);
     93         if (!applyingBatch) {
     94             mDb = mOpenHelper.getWritableDatabase();
     95             mDb.beginTransactionWithListener(this);
     96             final long identity = Binder.clearCallingIdentity();
     97             try {
     98                 result = insertInTransaction(uri, values, isCallerSyncAdapter);
     99                 if (result != null) {
    100                     mNotifyChange = true;
    101                 }
    102                 mDb.setTransactionSuccessful();
    103             } finally {
    104                 Binder.restoreCallingIdentity(identity);
    105                 mDb.endTransaction();
    106             }
    107 
    108             onEndTransaction(!isCallerSyncAdapter && shouldSyncFor(uri));
    109         } else {
    110             result = insertInTransaction(uri, values, isCallerSyncAdapter);
    111             if (result != null) {
    112                 mNotifyChange = true;
    113             }
    114         }
    115         return result;
    116     }
    117 
    118     @Override
    119     public int bulkInsert(Uri uri, ContentValues[] values) {
    120         int numValues = values.length;
    121         boolean isCallerSyncAdapter = getIsCallerSyncAdapter(uri);
    122         mDb = mOpenHelper.getWritableDatabase();
    123         mDb.beginTransactionWithListener(this);
    124         final long identity = Binder.clearCallingIdentity();
    125         try {
    126             for (int i = 0; i < numValues; i++) {
    127                 Uri result = insertInTransaction(uri, values[i], isCallerSyncAdapter);
    128                 if (result != null) {
    129                     mNotifyChange = true;
    130                 }
    131                 mDb.yieldIfContendedSafely();
    132             }
    133             mDb.setTransactionSuccessful();
    134         } finally {
    135             Binder.restoreCallingIdentity(identity);
    136             mDb.endTransaction();
    137         }
    138 
    139         onEndTransaction(!isCallerSyncAdapter);
    140         return numValues;
    141     }
    142 
    143     @Override
    144     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    145         int count = 0;
    146         boolean applyingBatch = applyingBatch();
    147         boolean isCallerSyncAdapter = getIsCallerSyncAdapter(uri);
    148         if (!applyingBatch) {
    149             mDb = mOpenHelper.getWritableDatabase();
    150             mDb.beginTransactionWithListener(this);
    151             final long identity = Binder.clearCallingIdentity();
    152             try {
    153                 count = updateInTransaction(uri, values, selection, selectionArgs,
    154                             isCallerSyncAdapter);
    155                 if (count > 0) {
    156                     mNotifyChange = true;
    157                 }
    158                 mDb.setTransactionSuccessful();
    159             } finally {
    160                 Binder.restoreCallingIdentity(identity);
    161                 mDb.endTransaction();
    162             }
    163 
    164             onEndTransaction(!isCallerSyncAdapter && shouldSyncFor(uri));
    165         } else {
    166             count = updateInTransaction(uri, values, selection, selectionArgs,
    167                         isCallerSyncAdapter);
    168             if (count > 0) {
    169                 mNotifyChange = true;
    170             }
    171         }
    172 
    173         return count;
    174     }
    175 
    176     @Override
    177     public int delete(Uri uri, String selection, String[] selectionArgs) {
    178         int count = 0;
    179         boolean applyingBatch = applyingBatch();
    180         boolean isCallerSyncAdapter = getIsCallerSyncAdapter(uri);
    181         if (!applyingBatch) {
    182             mDb = mOpenHelper.getWritableDatabase();
    183             mDb.beginTransactionWithListener(this);
    184             final long identity = Binder.clearCallingIdentity();
    185             try {
    186                 count = deleteInTransaction(uri, selection, selectionArgs, isCallerSyncAdapter);
    187                 if (count > 0) {
    188                     mNotifyChange = true;
    189                 }
    190                 mDb.setTransactionSuccessful();
    191             } finally {
    192                 Binder.restoreCallingIdentity(identity);
    193                 mDb.endTransaction();
    194             }
    195 
    196             onEndTransaction(!isCallerSyncAdapter && shouldSyncFor(uri));
    197         } else {
    198             count = deleteInTransaction(uri, selection, selectionArgs, isCallerSyncAdapter);
    199             if (count > 0) {
    200                 mNotifyChange = true;
    201             }
    202         }
    203         return count;
    204     }
    205 
    206     protected boolean getIsCallerSyncAdapter(Uri uri) {
    207         boolean isCurrentSyncAdapter = QueryParameterUtils.readBooleanQueryParameter(uri,
    208                 CalendarContract.CALLER_IS_SYNCADAPTER, false);
    209         if (mIsCallerSyncAdapter == null || mIsCallerSyncAdapter) {
    210             mIsCallerSyncAdapter = isCurrentSyncAdapter;
    211         }
    212         return isCurrentSyncAdapter;
    213     }
    214 
    215     @Override
    216     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
    217             throws OperationApplicationException {
    218         final int numOperations = operations.size();
    219         if (numOperations == 0) {
    220             return new ContentProviderResult[0];
    221         }
    222         mDb = mOpenHelper.getWritableDatabase();
    223         mDb.beginTransactionWithListener(this);
    224         final boolean isCallerSyncAdapter = getIsCallerSyncAdapter(operations.get(0).getUri());
    225         final long identity = Binder.clearCallingIdentity();
    226         try {
    227             mApplyingBatch.set(true);
    228             final ContentProviderResult[] results = new ContentProviderResult[numOperations];
    229             for (int i = 0; i < numOperations; i++) {
    230                 final ContentProviderOperation operation = operations.get(i);
    231                 if (i > 0 && operation.isYieldAllowed()) {
    232                     mDb.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY);
    233                 }
    234                 results[i] = operation.apply(this, results, i);
    235             }
    236             mDb.setTransactionSuccessful();
    237             return results;
    238         } finally {
    239             mApplyingBatch.set(false);
    240             mDb.endTransaction();
    241             onEndTransaction(!isCallerSyncAdapter);
    242             Binder.restoreCallingIdentity(identity);
    243         }
    244     }
    245 
    246     public void onBegin() {
    247         mIsCallerSyncAdapter = null;
    248         onBeginTransaction();
    249     }
    250 
    251     public void onCommit() {
    252         beforeTransactionCommit();
    253     }
    254 
    255     public void onRollback() {
    256         // not used
    257     }
    258 
    259     protected void onBeginTransaction() {
    260     }
    261 
    262     protected void beforeTransactionCommit() {
    263     }
    264 
    265     protected void onEndTransaction(boolean syncToNetwork) {
    266         if (mNotifyChange) {
    267             mNotifyChange = false;
    268             // We sync to network if the caller was not the sync adapter
    269             notifyChange(syncToNetwork);
    270         }
    271     }
    272 
    273     /**
    274      * Some URI's are maintained locally so we should not request a sync for them
    275      */
    276     protected abstract boolean shouldSyncFor(Uri uri);
    277 }
    278