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 onCreate()55 public boolean onCreate() { 56 Context context = getContext(); 57 mOpenHelper = getDatabaseHelper(context); 58 mChangedUris = new HashSet<Uri>(); 59 return true; 60 } 61 62 @Override shutdown()63 public void shutdown() { 64 getDatabaseHelper().close(); 65 } 66 67 /** 68 * Returns a {@link SQLiteOpenHelper} that can open the database. 69 */ getDatabaseHelper(Context context)70 public abstract SQLiteOpenHelper getDatabaseHelper(Context context); 71 72 /** 73 * The equivalent of the {@link #insert} method, but invoked within a 74 * transaction. 75 */ insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter)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 */ updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs, boolean callerIsSyncAdapter)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 */ deleteInTransaction(Uri uri, String selection, String[] selectionArgs, boolean callerIsSyncAdapter)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 */ postNotifyUri(Uri uri)97 protected void postNotifyUri(Uri uri) { 98 synchronized (mChangedUris) { 99 mChangedUris.add(uri); 100 } 101 } 102 isCallerSyncAdapter(Uri uri)103 public boolean isCallerSyncAdapter(Uri uri) { 104 return false; 105 } 106 getDatabaseHelper()107 public SQLiteOpenHelper getDatabaseHelper() { 108 return mOpenHelper; 109 } 110 applyingBatch()111 private boolean applyingBatch() { 112 return mApplyingBatch.get() != null && mApplyingBatch.get(); 113 } 114 115 @Override insert(Uri uri, ContentValues values)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 bulkInsert(Uri uri, ContentValues[] values)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 update(Uri uri, ContentValues values, String selection, String[] selectionArgs)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 delete(Uri uri, String selection, String[] selectionArgs)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 applyBatch(ArrayList<ContentProviderOperation> operations)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 onEndTransaction(boolean callerIsSyncAdapter)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 notifyChange(ContentResolver resolver, Uri uri, boolean syncToNetwork)258 protected void notifyChange(ContentResolver resolver, Uri uri, boolean syncToNetwork) { 259 resolver.notifyChange(uri, null, syncToNetwork); 260 } 261 syncToNetwork(Uri uri)262 protected boolean syncToNetwork(Uri uri) { 263 return false; 264 } 265 }