| /* |
| * 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.provider.CalendarContract; |
| |
| 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); |
| try { |
| result = insertInTransaction(uri, values, isCallerSyncAdapter); |
| if (result != null) { |
| mNotifyChange = true; |
| } |
| mDb.setTransactionSuccessful(); |
| } finally { |
| mDb.endTransaction(); |
| } |
| |
| onEndTransaction(); |
| } 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); |
| 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 { |
| mDb.endTransaction(); |
| } |
| |
| onEndTransaction(); |
| 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); |
| try { |
| count = updateInTransaction(uri, values, selection, selectionArgs, |
| isCallerSyncAdapter); |
| if (count > 0) { |
| mNotifyChange = true; |
| } |
| mDb.setTransactionSuccessful(); |
| } finally { |
| mDb.endTransaction(); |
| } |
| |
| onEndTransaction(); |
| } 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); |
| try { |
| count = deleteInTransaction(uri, selection, selectionArgs, isCallerSyncAdapter); |
| if (count > 0) { |
| mNotifyChange = true; |
| } |
| mDb.setTransactionSuccessful(); |
| } finally { |
| mDb.endTransaction(); |
| } |
| |
| onEndTransaction(); |
| } 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 { |
| mDb = mOpenHelper.getWritableDatabase(); |
| mDb.beginTransactionWithListener(this); |
| try { |
| mApplyingBatch.set(true); |
| final int numOperations = operations.size(); |
| 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(); |
| } |
| } |
| |
| public void onBegin() { |
| mIsCallerSyncAdapter = null; |
| onBeginTransaction(); |
| } |
| |
| public void onCommit() { |
| beforeTransactionCommit(); |
| } |
| |
| public void onRollback() { |
| // not used |
| } |
| |
| protected void onBeginTransaction() { |
| } |
| |
| protected void beforeTransactionCommit() { |
| } |
| |
| protected void onEndTransaction() { |
| if (mNotifyChange) { |
| mNotifyChange = false; |
| // We sync to network if the caller was not the sync adapter |
| notifyChange(null == mIsCallerSyncAdapter || !mIsCallerSyncAdapter); |
| } |
| } |
| } |