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