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