/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.providers.calendar; import android.content.ContentProvider; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentValues; import android.content.Context; import android.content.OperationApplicationException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteTransactionListener; import android.net.Uri; import android.os.Binder; import android.os.Process; import android.provider.CalendarContract; import android.util.Log; import java.util.ArrayList; /** * General purpose {@link ContentProvider} base class that uses SQLiteDatabase for storage. */ public abstract class SQLiteContentProvider extends ContentProvider implements SQLiteTransactionListener { private static final String TAG = "SQLiteContentProvider"; private SQLiteOpenHelper mOpenHelper; private volatile boolean mNotifyChange; protected SQLiteDatabase mDb; private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>(); private static final int SLEEP_AFTER_YIELD_DELAY = 4000; private Boolean mIsCallerSyncAdapter; @Override public boolean onCreate() { Context context = getContext(); mOpenHelper = getDatabaseHelper(context); return true; } protected abstract SQLiteOpenHelper getDatabaseHelper(Context context); /** * The equivalent of the {@link #insert} method, but invoked within a transaction. */ protected abstract Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter); /** * The equivalent of the {@link #update} method, but invoked within a transaction. */ protected abstract int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs, boolean callerIsSyncAdapter); /** * The equivalent of the {@link #delete} method, but invoked within a transaction. */ protected abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs, boolean callerIsSyncAdapter); protected abstract void notifyChange(boolean syncToNetwork); protected SQLiteOpenHelper getDatabaseHelper() { return mOpenHelper; } private boolean applyingBatch() { return mApplyingBatch.get() != null && mApplyingBatch.get(); } @Override public Uri insert(Uri uri, ContentValues values) { Uri result = null; boolean applyingBatch = applyingBatch(); boolean isCallerSyncAdapter = getIsCallerSyncAdapter(uri); if (!applyingBatch) { mDb = mOpenHelper.getWritableDatabase(); mDb.beginTransactionWithListener(this); final long identity = clearCallingIdentityInternal(); try { result = insertInTransaction(uri, values, isCallerSyncAdapter); if (result != null) { mNotifyChange = true; } mDb.setTransactionSuccessful(); } finally { restoreCallingIdentityInternal(identity); mDb.endTransaction(); } onEndTransaction(!isCallerSyncAdapter && shouldSyncFor(uri)); } else { result = insertInTransaction(uri, values, isCallerSyncAdapter); if (result != null) { mNotifyChange = true; } } return result; } @Override public int bulkInsert(Uri uri, ContentValues[] values) { int numValues = values.length; boolean isCallerSyncAdapter = getIsCallerSyncAdapter(uri); mDb = mOpenHelper.getWritableDatabase(); mDb.beginTransactionWithListener(this); final long identity = clearCallingIdentityInternal(); try { for (int i = 0; i < numValues; i++) { Uri result = insertInTransaction(uri, values[i], isCallerSyncAdapter); if (result != null) { mNotifyChange = true; } mDb.yieldIfContendedSafely(); } mDb.setTransactionSuccessful(); } finally { restoreCallingIdentityInternal(identity); mDb.endTransaction(); } onEndTransaction(!isCallerSyncAdapter); return numValues; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int count = 0; boolean applyingBatch = applyingBatch(); boolean isCallerSyncAdapter = getIsCallerSyncAdapter(uri); if (!applyingBatch) { mDb = mOpenHelper.getWritableDatabase(); mDb.beginTransactionWithListener(this); final long identity = clearCallingIdentityInternal(); try { count = updateInTransaction(uri, values, selection, selectionArgs, isCallerSyncAdapter); if (count > 0) { mNotifyChange = true; } mDb.setTransactionSuccessful(); } finally { restoreCallingIdentityInternal(identity); mDb.endTransaction(); } onEndTransaction(!isCallerSyncAdapter && shouldSyncFor(uri)); } else { count = updateInTransaction(uri, values, selection, selectionArgs, isCallerSyncAdapter); if (count > 0) { mNotifyChange = true; } } return count; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int count = 0; boolean applyingBatch = applyingBatch(); boolean isCallerSyncAdapter = getIsCallerSyncAdapter(uri); if (!applyingBatch) { mDb = mOpenHelper.getWritableDatabase(); mDb.beginTransactionWithListener(this); final long identity = clearCallingIdentityInternal(); try { count = deleteInTransaction(uri, selection, selectionArgs, isCallerSyncAdapter); if (count > 0) { mNotifyChange = true; } mDb.setTransactionSuccessful(); } finally { restoreCallingIdentityInternal(identity); mDb.endTransaction(); } onEndTransaction(!isCallerSyncAdapter && shouldSyncFor(uri)); } else { count = deleteInTransaction(uri, selection, selectionArgs, isCallerSyncAdapter); if (count > 0) { mNotifyChange = true; } } return count; } protected boolean getIsCallerSyncAdapter(Uri uri) { boolean isCurrentSyncAdapter = QueryParameterUtils.readBooleanQueryParameter(uri, CalendarContract.CALLER_IS_SYNCADAPTER, false); if (mIsCallerSyncAdapter == null || mIsCallerSyncAdapter) { mIsCallerSyncAdapter = isCurrentSyncAdapter; } return isCurrentSyncAdapter; } @Override public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws OperationApplicationException { final int numOperations = operations.size(); if (numOperations == 0) { return new ContentProviderResult[0]; } mDb = mOpenHelper.getWritableDatabase(); mDb.beginTransactionWithListener(this); final boolean isCallerSyncAdapter = getIsCallerSyncAdapter(operations.get(0).getUri()); final long identity = clearCallingIdentityInternal(); try { mApplyingBatch.set(true); final ContentProviderResult[] results = new ContentProviderResult[numOperations]; for (int i = 0; i < numOperations; i++) { final ContentProviderOperation operation = operations.get(i); if (i > 0 && operation.isYieldAllowed()) { mDb.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY); } results[i] = operation.apply(this, results, i); } mDb.setTransactionSuccessful(); return results; } finally { mApplyingBatch.set(false); mDb.endTransaction(); onEndTransaction(!isCallerSyncAdapter); restoreCallingIdentityInternal(identity); } } public void onBegin() { mIsCallerSyncAdapter = null; onBeginTransaction(); } public void onCommit() { beforeTransactionCommit(); } public void onRollback() { // not used } protected void onBeginTransaction() { } protected void beforeTransactionCommit() { } protected void onEndTransaction(boolean syncToNetwork) { if (mNotifyChange) { mNotifyChange = false; // We sync to network if the caller was not the sync adapter notifyChange(syncToNetwork); } } /** * Some URI's are maintained locally so we should not request a sync for them */ protected abstract boolean shouldSyncFor(Uri uri); /** The package to most recently query(), not including further internally recursive calls. */ private final ThreadLocal<String> mCallingPackage = new ThreadLocal<String>(); /** * The calling Uid when a calling package is cached, so we know when the stack of any * recursive calls to clearCallingIdentity and restoreCallingIdentity is complete. */ private final ThreadLocal<Integer> mOriginalCallingUid = new ThreadLocal<Integer>(); protected String getCachedCallingPackage() { return mCallingPackage.get(); } /** * Call {@link android.os.Binder#clearCallingIdentity()}, while caching the calling package * name, so that it can be saved if this is part of an event mutation. */ protected long clearCallingIdentityInternal() { // Only set the calling package if the calling UID is not our own. int uid = Process.myUid(); int callingUid = Binder.getCallingUid(); if (uid != callingUid) { try { mOriginalCallingUid.set(callingUid); String callingPackage = getCallingPackage(); mCallingPackage.set(callingPackage); } catch (SecurityException e) { Log.e(TAG, "Error getting the calling package.", e); } } return Binder.clearCallingIdentity(); } /** * Call {@link Binder#restoreCallingIdentity(long)}. * </p> * If this is the last restore on the stack of calls to * {@link android.os.Binder#clearCallingIdentity()}, then the cached calling package will also * be cleared. * @param identity */ protected void restoreCallingIdentityInternal(long identity) { Binder.restoreCallingIdentity(identity); int callingUid = Binder.getCallingUid(); if (mOriginalCallingUid.get() != null && mOriginalCallingUid.get() == callingUid) { mCallingPackage.set(null); mOriginalCallingUid.set(null); } } }