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 17package com.android.common.content; 18 19import android.content.ContentProvider; 20import android.content.ContentProviderOperation; 21import android.content.ContentProviderResult; 22import android.content.ContentValues; 23import android.content.Context; 24import android.content.OperationApplicationException; 25import android.database.sqlite.SQLiteDatabase; 26import android.database.sqlite.SQLiteOpenHelper; 27import android.database.sqlite.SQLiteTransactionListener; 28import android.net.Uri; 29 30import java.util.ArrayList; 31 32/** 33 * General purpose {@link ContentProvider} base class that uses SQLiteDatabase for storage. 34 */ 35public 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