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