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