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;
29
30import java.util.ArrayList;
31
32/**
33 * General purpose {@link ContentProvider} base class that uses SQLiteDatabase for storage.
34 */
35public abstract class SQLiteContentProvider extends ContentProvider
36        implements SQLiteTransactionListener {
37
38    private static final String TAG = "SQLiteContentProvider";
39
40    private SQLiteOpenHelper mOpenHelper;
41    private volatile boolean mNotifyChange;
42    protected SQLiteDatabase mDb;
43
44    private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>();
45    private static final int SLEEP_AFTER_YIELD_DELAY = 4000;
46
47    @Override
48    public boolean onCreate() {
49        Context context = getContext();
50        mOpenHelper = getDatabaseHelper(context);
51        return true;
52    }
53
54    protected abstract SQLiteOpenHelper getDatabaseHelper(Context context);
55
56    /**
57     * The equivalent of the {@link #insert} method, but invoked within a transaction.
58     */
59    protected abstract Uri insertInTransaction(Uri uri, ContentValues values);
60
61    /**
62     * The equivalent of the {@link #update} method, but invoked within a transaction.
63     */
64    protected abstract int updateInTransaction(Uri uri, ContentValues values, String selection,
65            String[] selectionArgs);
66
67    /**
68     * The equivalent of the {@link #delete} method, but invoked within a transaction.
69     */
70    protected abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs);
71
72    protected abstract void notifyChange();
73
74    protected SQLiteOpenHelper getDatabaseHelper() {
75        return mOpenHelper;
76    }
77
78    private boolean applyingBatch() {
79        return mApplyingBatch.get() != null && mApplyingBatch.get();
80    }
81
82    @Override
83    public Uri insert(Uri uri, ContentValues values) {
84        Uri result = null;
85        boolean applyingBatch = applyingBatch();
86        if (!applyingBatch) {
87            mDb = mOpenHelper.getWritableDatabase();
88            mDb.beginTransactionWithListener(this);
89            try {
90                result = insertInTransaction(uri, values);
91                if (result != null) {
92                    mNotifyChange = true;
93                }
94                mDb.setTransactionSuccessful();
95            } finally {
96                mDb.endTransaction();
97            }
98
99            onEndTransaction();
100        } else {
101            result = insertInTransaction(uri, values);
102            if (result != null) {
103                mNotifyChange = true;
104            }
105        }
106        return result;
107    }
108
109    @Override
110    public int bulkInsert(Uri uri, ContentValues[] values) {
111        int numValues = values.length;
112        mDb = mOpenHelper.getWritableDatabase();
113        mDb.beginTransactionWithListener(this);
114        try {
115            for (int i = 0; i < numValues; i++) {
116                Uri result = insertInTransaction(uri, values[i]);
117                if (result != null) {
118                    mNotifyChange = true;
119                }
120                mDb.yieldIfContendedSafely();
121            }
122            mDb.setTransactionSuccessful();
123        } finally {
124            mDb.endTransaction();
125        }
126
127        onEndTransaction();
128        return numValues;
129    }
130
131    @Override
132    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
133        int count = 0;
134        boolean applyingBatch = applyingBatch();
135        if (!applyingBatch) {
136            mDb = mOpenHelper.getWritableDatabase();
137            mDb.beginTransactionWithListener(this);
138            try {
139                count = updateInTransaction(uri, values, selection, selectionArgs);
140                if (count > 0) {
141                    mNotifyChange = true;
142                }
143                mDb.setTransactionSuccessful();
144            } finally {
145                mDb.endTransaction();
146            }
147
148            onEndTransaction();
149        } else {
150            count = updateInTransaction(uri, values, selection, selectionArgs);
151            if (count > 0) {
152                mNotifyChange = true;
153            }
154        }
155
156        return count;
157    }
158
159    @Override
160    public int delete(Uri uri, String selection, String[] selectionArgs) {
161        int count = 0;
162        boolean applyingBatch = applyingBatch();
163        if (!applyingBatch) {
164            mDb = mOpenHelper.getWritableDatabase();
165            mDb.beginTransactionWithListener(this);
166            try {
167                count = deleteInTransaction(uri, selection, selectionArgs);
168                if (count > 0) {
169                    mNotifyChange = true;
170                }
171                mDb.setTransactionSuccessful();
172            } finally {
173                mDb.endTransaction();
174            }
175
176            onEndTransaction();
177        } else {
178            count = deleteInTransaction(uri, selection, selectionArgs);
179            if (count > 0) {
180                mNotifyChange = true;
181            }
182        }
183        return count;
184    }
185
186    @Override
187    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
188            throws OperationApplicationException {
189        mDb = mOpenHelper.getWritableDatabase();
190        mDb.beginTransactionWithListener(this);
191        try {
192            mApplyingBatch.set(true);
193            final int numOperations = operations.size();
194            final ContentProviderResult[] results = new ContentProviderResult[numOperations];
195            for (int i = 0; i < numOperations; i++) {
196                final ContentProviderOperation operation = operations.get(i);
197                if (i > 0 && operation.isYieldAllowed()) {
198                    mDb.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY);
199                }
200                results[i] = operation.apply(this, results, i);
201            }
202            mDb.setTransactionSuccessful();
203            return results;
204        } finally {
205            mApplyingBatch.set(false);
206            mDb.endTransaction();
207            onEndTransaction();
208        }
209    }
210
211    public void onBegin() {
212        onBeginTransaction();
213    }
214
215    public void onCommit() {
216        beforeTransactionCommit();
217    }
218
219    public void onRollback() {
220        // not used
221    }
222
223    protected void onBeginTransaction() {
224    }
225
226    protected void beforeTransactionCommit() {
227    }
228
229    protected void onEndTransaction() {
230        if (mNotifyChange) {
231            mNotifyChange = false;
232            notifyChange();
233        }
234    }
235}
236