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;
32import android.util.Log;
33
34import java.util.ArrayList;
35
36/**
37 * General purpose {@link ContentProvider} base class that uses SQLiteDatabase for storage.
38 */
39public 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
54    public boolean onCreate() {
55        Context context = getContext();
56        mOpenHelper = getDatabaseHelper(context);
57        return true;
58    }
59
60    protected abstract SQLiteOpenHelper getDatabaseHelper(Context context);
61
62    /**
63     * The equivalent of the {@link #insert} method, but invoked within a transaction.
64     */
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     */
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     */
77    protected abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
78            boolean callerIsSyncAdapter);
79
80    protected abstract void notifyChange(boolean syncToNetwork);
81
82    protected SQLiteOpenHelper getDatabaseHelper() {
83        return mOpenHelper;
84    }
85
86    private boolean applyingBatch() {
87        return mApplyingBatch.get() != null && mApplyingBatch.get();
88    }
89
90    @Override
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
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
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
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
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
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
248    public void onBegin() {
249        mIsCallerSyncAdapter = null;
250        onBeginTransaction();
251    }
252
253    public void onCommit() {
254        beforeTransactionCommit();
255    }
256
257    public void onRollback() {
258        // not used
259    }
260
261    protected void onBeginTransaction() {
262    }
263
264    protected void beforeTransactionCommit() {
265    }
266
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     */
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
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     */
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     */
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    SQLiteDatabase getReadableDatabase() {
334        return mOpenHelper != null ? mOpenHelper.getReadableDatabase() : null;
335    }
336
337    SQLiteDatabase getWritableDatabase() {
338        return mOpenHelper != null ? mOpenHelper.getWritableDatabase() : null;
339    }
340}
341