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