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.providers.calendar; 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; 29import android.os.Binder; 30import android.os.Process; 31import android.provider.CalendarContract; 32 33import java.util.ArrayList; 34 35/** 36 * General purpose {@link ContentProvider} base class that uses SQLiteDatabase for storage. 37 */ 38public abstract class SQLiteContentProvider extends ContentProvider 39 implements SQLiteTransactionListener { 40 41 private static final String TAG = "SQLiteContentProvider"; 42 43 private SQLiteOpenHelper mOpenHelper; 44 private volatile boolean mNotifyChange; 45 protected SQLiteDatabase mDb; 46 47 private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>(); 48 private static final int SLEEP_AFTER_YIELD_DELAY = 4000; 49 50 private Boolean mIsCallerSyncAdapter; 51 52 @Override 53 public boolean onCreate() { 54 Context context = getContext(); 55 mOpenHelper = getDatabaseHelper(context); 56 return true; 57 } 58 59 protected abstract SQLiteOpenHelper getDatabaseHelper(Context context); 60 61 /** 62 * The equivalent of the {@link #insert} method, but invoked within a transaction. 63 */ 64 protected abstract Uri insertInTransaction(Uri uri, ContentValues values, 65 boolean callerIsSyncAdapter); 66 67 /** 68 * The equivalent of the {@link #update} method, but invoked within a transaction. 69 */ 70 protected abstract int updateInTransaction(Uri uri, ContentValues values, String selection, 71 String[] selectionArgs, boolean callerIsSyncAdapter); 72 73 /** 74 * The equivalent of the {@link #delete} method, but invoked within a transaction. 75 */ 76 protected abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs, 77 boolean callerIsSyncAdapter); 78 79 protected abstract void notifyChange(boolean syncToNetwork); 80 81 protected SQLiteOpenHelper getDatabaseHelper() { 82 return mOpenHelper; 83 } 84 85 private boolean applyingBatch() { 86 return mApplyingBatch.get() != null && mApplyingBatch.get(); 87 } 88 89 @Override 90 public Uri insert(Uri uri, ContentValues values) { 91 Uri result = null; 92 boolean applyingBatch = applyingBatch(); 93 boolean isCallerSyncAdapter = getIsCallerSyncAdapter(uri); 94 if (!applyingBatch) { 95 mDb = mOpenHelper.getWritableDatabase(); 96 mDb.beginTransactionWithListener(this); 97 final long identity = clearCallingIdentityInternal(); 98 try { 99 result = insertInTransaction(uri, values, isCallerSyncAdapter); 100 if (result != null) { 101 mNotifyChange = true; 102 } 103 mDb.setTransactionSuccessful(); 104 } finally { 105 restoreCallingIdentityInternal(identity); 106 mDb.endTransaction(); 107 } 108 109 onEndTransaction(!isCallerSyncAdapter && shouldSyncFor(uri)); 110 } else { 111 result = insertInTransaction(uri, values, isCallerSyncAdapter); 112 if (result != null) { 113 mNotifyChange = true; 114 } 115 } 116 return result; 117 } 118 119 @Override 120 public int bulkInsert(Uri uri, ContentValues[] values) { 121 int numValues = values.length; 122 boolean isCallerSyncAdapter = getIsCallerSyncAdapter(uri); 123 mDb = mOpenHelper.getWritableDatabase(); 124 mDb.beginTransactionWithListener(this); 125 final long identity = clearCallingIdentityInternal(); 126 try { 127 for (int i = 0; i < numValues; i++) { 128 Uri result = insertInTransaction(uri, values[i], isCallerSyncAdapter); 129 if (result != null) { 130 mNotifyChange = true; 131 } 132 mDb.yieldIfContendedSafely(); 133 } 134 mDb.setTransactionSuccessful(); 135 } finally { 136 restoreCallingIdentityInternal(identity); 137 mDb.endTransaction(); 138 } 139 140 onEndTransaction(!isCallerSyncAdapter); 141 return numValues; 142 } 143 144 @Override 145 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 146 int count = 0; 147 boolean applyingBatch = applyingBatch(); 148 boolean isCallerSyncAdapter = getIsCallerSyncAdapter(uri); 149 if (!applyingBatch) { 150 mDb = mOpenHelper.getWritableDatabase(); 151 mDb.beginTransactionWithListener(this); 152 final long identity = clearCallingIdentityInternal(); 153 try { 154 count = updateInTransaction(uri, values, selection, selectionArgs, 155 isCallerSyncAdapter); 156 if (count > 0) { 157 mNotifyChange = true; 158 } 159 mDb.setTransactionSuccessful(); 160 } finally { 161 restoreCallingIdentityInternal(identity); 162 mDb.endTransaction(); 163 } 164 165 onEndTransaction(!isCallerSyncAdapter && shouldSyncFor(uri)); 166 } else { 167 count = updateInTransaction(uri, values, selection, selectionArgs, 168 isCallerSyncAdapter); 169 if (count > 0) { 170 mNotifyChange = true; 171 } 172 } 173 174 return count; 175 } 176 177 @Override 178 public int delete(Uri uri, String selection, String[] selectionArgs) { 179 int count = 0; 180 boolean applyingBatch = applyingBatch(); 181 boolean isCallerSyncAdapter = getIsCallerSyncAdapter(uri); 182 if (!applyingBatch) { 183 mDb = mOpenHelper.getWritableDatabase(); 184 mDb.beginTransactionWithListener(this); 185 final long identity = clearCallingIdentityInternal(); 186 try { 187 count = deleteInTransaction(uri, selection, selectionArgs, isCallerSyncAdapter); 188 if (count > 0) { 189 mNotifyChange = true; 190 } 191 mDb.setTransactionSuccessful(); 192 } finally { 193 restoreCallingIdentityInternal(identity); 194 mDb.endTransaction(); 195 } 196 197 onEndTransaction(!isCallerSyncAdapter && shouldSyncFor(uri)); 198 } else { 199 count = deleteInTransaction(uri, selection, selectionArgs, isCallerSyncAdapter); 200 if (count > 0) { 201 mNotifyChange = true; 202 } 203 } 204 return count; 205 } 206 207 protected boolean getIsCallerSyncAdapter(Uri uri) { 208 boolean isCurrentSyncAdapter = QueryParameterUtils.readBooleanQueryParameter(uri, 209 CalendarContract.CALLER_IS_SYNCADAPTER, false); 210 if (mIsCallerSyncAdapter == null || mIsCallerSyncAdapter) { 211 mIsCallerSyncAdapter = isCurrentSyncAdapter; 212 } 213 return isCurrentSyncAdapter; 214 } 215 216 @Override 217 public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) 218 throws OperationApplicationException { 219 final int numOperations = operations.size(); 220 if (numOperations == 0) { 221 return new ContentProviderResult[0]; 222 } 223 mDb = mOpenHelper.getWritableDatabase(); 224 mDb.beginTransactionWithListener(this); 225 final boolean isCallerSyncAdapter = getIsCallerSyncAdapter(operations.get(0).getUri()); 226 final long identity = clearCallingIdentityInternal(); 227 try { 228 mApplyingBatch.set(true); 229 final ContentProviderResult[] results = new ContentProviderResult[numOperations]; 230 for (int i = 0; i < numOperations; i++) { 231 final ContentProviderOperation operation = operations.get(i); 232 if (i > 0 && operation.isYieldAllowed()) { 233 mDb.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY); 234 } 235 results[i] = operation.apply(this, results, i); 236 } 237 mDb.setTransactionSuccessful(); 238 return results; 239 } finally { 240 mApplyingBatch.set(false); 241 mDb.endTransaction(); 242 onEndTransaction(!isCallerSyncAdapter); 243 restoreCallingIdentityInternal(identity); 244 } 245 } 246 247 public void onBegin() { 248 mIsCallerSyncAdapter = null; 249 onBeginTransaction(); 250 } 251 252 public void onCommit() { 253 beforeTransactionCommit(); 254 } 255 256 public void onRollback() { 257 // not used 258 } 259 260 protected void onBeginTransaction() { 261 } 262 263 protected void beforeTransactionCommit() { 264 } 265 266 protected void onEndTransaction(boolean syncToNetwork) { 267 if (mNotifyChange) { 268 mNotifyChange = false; 269 // We sync to network if the caller was not the sync adapter 270 notifyChange(syncToNetwork); 271 } 272 } 273 274 /** 275 * Some URI's are maintained locally so we should not request a sync for them 276 */ 277 protected abstract boolean shouldSyncFor(Uri uri); 278 279 /** The package to most recently query(), not including further internally recursive calls. */ 280 private final ThreadLocal<String> mCallingPackage = new ThreadLocal<String>(); 281 282 /** 283 * The calling Uid when a calling package is cached, so we know when the stack of any 284 * recursive calls to clearCallingIdentity and restoreCallingIdentity is complete. 285 */ 286 private final ThreadLocal<Integer> mOriginalCallingUid = new ThreadLocal<Integer>(); 287 288 289 protected String getCachedCallingPackage() { 290 return mCallingPackage.get(); 291 } 292 293 /** 294 * Call {@link android.os.Binder#clearCallingIdentity()}, while caching the calling package 295 * name, so that it can be saved if this is part of an event mutation. 296 */ 297 protected long clearCallingIdentityInternal() { 298 // Only set the calling package if the calling UID is not our own. 299 int uid = Process.myUid(); 300 int callingUid = Binder.getCallingUid(); 301 if (uid != callingUid) { 302 try { 303 mOriginalCallingUid.set(callingUid); 304 String callingPackage = getCallingPackage(); 305 mCallingPackage.set(callingPackage); 306 } catch (SecurityException e) { 307 // If this exception is thrown, clearCallingIdentity has already been called, and 308 // calling package is already available. 309 } 310 } 311 312 return Binder.clearCallingIdentity(); 313 } 314 315 /** 316 * Call {@link Binder#restoreCallingIdentity(long)}. 317 * </p> 318 * If this is the last restore on the stack of calls to 319 * {@link android.os.Binder#clearCallingIdentity()}, then the cached calling package will also 320 * be cleared. 321 * @param identity 322 */ 323 protected void restoreCallingIdentityInternal(long identity) { 324 Binder.restoreCallingIdentity(identity); 325 326 int callingUid = Binder.getCallingUid(); 327 if (mOriginalCallingUid.get() != null && mOriginalCallingUid.get() == callingUid) { 328 mCallingPackage.set(null); 329 mOriginalCallingUid.set(null); 330 } 331 } 332} 333