1 /*
2  * Copyright (C) 2013 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 
17 package com.android.photos.data;
18 
19 import android.content.ContentProvider;
20 import android.content.ContentProviderOperation;
21 import android.content.ContentProviderResult;
22 import android.content.ContentResolver;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.content.OperationApplicationException;
26 import android.database.sqlite.SQLiteDatabase;
27 import android.database.sqlite.SQLiteOpenHelper;
28 import android.net.Uri;
29 
30 import java.util.ArrayList;
31 import java.util.HashSet;
32 import java.util.Set;
33 
34 /**
35  * General purpose {@link ContentProvider} base class that uses SQLiteDatabase
36  * for storage.
37  */
38 public abstract class SQLiteContentProvider extends ContentProvider {
39 
40     @SuppressWarnings("unused")
41     private static final String TAG = "SQLiteContentProvider";
42 
43     private SQLiteOpenHelper mOpenHelper;
44     private Set<Uri> mChangedUris;
45 
46     private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>();
47     private static final int SLEEP_AFTER_YIELD_DELAY = 4000;
48 
49     /**
50      * Maximum number of operations allowed in a batch between yield points.
51      */
52     private static final int MAX_OPERATIONS_PER_YIELD_POINT = 500;
53 
54     @Override
onCreate()55     public boolean onCreate() {
56         Context context = getContext();
57         mOpenHelper = getDatabaseHelper(context);
58         mChangedUris = new HashSet<Uri>();
59         return true;
60     }
61 
62     @Override
shutdown()63     public void shutdown() {
64         getDatabaseHelper().close();
65     }
66 
67     /**
68      * Returns a {@link SQLiteOpenHelper} that can open the database.
69      */
getDatabaseHelper(Context context)70     public abstract SQLiteOpenHelper getDatabaseHelper(Context context);
71 
72     /**
73      * The equivalent of the {@link #insert} method, but invoked within a
74      * transaction.
75      */
insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter)76     public abstract Uri insertInTransaction(Uri uri, ContentValues values,
77             boolean callerIsSyncAdapter);
78 
79     /**
80      * The equivalent of the {@link #update} method, but invoked within a
81      * transaction.
82      */
updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs, boolean callerIsSyncAdapter)83     public abstract int updateInTransaction(Uri uri, ContentValues values, String selection,
84             String[] selectionArgs, boolean callerIsSyncAdapter);
85 
86     /**
87      * The equivalent of the {@link #delete} method, but invoked within a
88      * transaction.
89      */
deleteInTransaction(Uri uri, String selection, String[] selectionArgs, boolean callerIsSyncAdapter)90     public abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
91             boolean callerIsSyncAdapter);
92 
93     /**
94      * Call this to add a URI to the list of URIs to be notified when the
95      * transaction is committed.
96      */
postNotifyUri(Uri uri)97     protected void postNotifyUri(Uri uri) {
98         synchronized (mChangedUris) {
99             mChangedUris.add(uri);
100         }
101     }
102 
isCallerSyncAdapter(Uri uri)103     public boolean isCallerSyncAdapter(Uri uri) {
104         return false;
105     }
106 
getDatabaseHelper()107     public SQLiteOpenHelper getDatabaseHelper() {
108         return mOpenHelper;
109     }
110 
applyingBatch()111     private boolean applyingBatch() {
112         return mApplyingBatch.get() != null && mApplyingBatch.get();
113     }
114 
115     @Override
insert(Uri uri, ContentValues values)116     public Uri insert(Uri uri, ContentValues values) {
117         Uri result = null;
118         boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
119         boolean applyingBatch = applyingBatch();
120         if (!applyingBatch) {
121             SQLiteDatabase db = mOpenHelper.getWritableDatabase();
122             db.beginTransaction();
123             try {
124                 result = insertInTransaction(uri, values, callerIsSyncAdapter);
125                 db.setTransactionSuccessful();
126             } finally {
127                 db.endTransaction();
128             }
129 
130             onEndTransaction(callerIsSyncAdapter);
131         } else {
132             result = insertInTransaction(uri, values, callerIsSyncAdapter);
133         }
134         return result;
135     }
136 
137     @Override
bulkInsert(Uri uri, ContentValues[] values)138     public int bulkInsert(Uri uri, ContentValues[] values) {
139         int numValues = values.length;
140         boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
141         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
142         db.beginTransaction();
143         try {
144             for (int i = 0; i < numValues; i++) {
145                 @SuppressWarnings("unused")
146                 Uri result = insertInTransaction(uri, values[i], callerIsSyncAdapter);
147                 db.yieldIfContendedSafely();
148             }
149             db.setTransactionSuccessful();
150         } finally {
151             db.endTransaction();
152         }
153 
154         onEndTransaction(callerIsSyncAdapter);
155         return numValues;
156     }
157 
158     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)159     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
160         int count = 0;
161         boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
162         boolean applyingBatch = applyingBatch();
163         if (!applyingBatch) {
164             SQLiteDatabase db = mOpenHelper.getWritableDatabase();
165             db.beginTransaction();
166             try {
167                 count = updateInTransaction(uri, values, selection, selectionArgs,
168                         callerIsSyncAdapter);
169                 db.setTransactionSuccessful();
170             } finally {
171                 db.endTransaction();
172             }
173 
174             onEndTransaction(callerIsSyncAdapter);
175         } else {
176             count = updateInTransaction(uri, values, selection, selectionArgs, callerIsSyncAdapter);
177         }
178 
179         return count;
180     }
181 
182     @Override
delete(Uri uri, String selection, String[] selectionArgs)183     public int delete(Uri uri, String selection, String[] selectionArgs) {
184         int count = 0;
185         boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
186         boolean applyingBatch = applyingBatch();
187         if (!applyingBatch) {
188             SQLiteDatabase db = mOpenHelper.getWritableDatabase();
189             db.beginTransaction();
190             try {
191                 count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter);
192                 db.setTransactionSuccessful();
193             } finally {
194                 db.endTransaction();
195             }
196 
197             onEndTransaction(callerIsSyncAdapter);
198         } else {
199             count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter);
200         }
201         return count;
202     }
203 
204     @Override
applyBatch(ArrayList<ContentProviderOperation> operations)205     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
206             throws OperationApplicationException {
207         int ypCount = 0;
208         int opCount = 0;
209         boolean callerIsSyncAdapter = false;
210         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
211         db.beginTransaction();
212         try {
213             mApplyingBatch.set(true);
214             final int numOperations = operations.size();
215             final ContentProviderResult[] results = new ContentProviderResult[numOperations];
216             for (int i = 0; i < numOperations; i++) {
217                 if (++opCount >= MAX_OPERATIONS_PER_YIELD_POINT) {
218                     throw new OperationApplicationException(
219                             "Too many content provider operations between yield points. "
220                                     + "The maximum number of operations per yield point is "
221                                     + MAX_OPERATIONS_PER_YIELD_POINT, ypCount);
222                 }
223                 final ContentProviderOperation operation = operations.get(i);
224                 if (!callerIsSyncAdapter && isCallerSyncAdapter(operation.getUri())) {
225                     callerIsSyncAdapter = true;
226                 }
227                 if (i > 0 && operation.isYieldAllowed()) {
228                     opCount = 0;
229                     if (db.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY)) {
230                         ypCount++;
231                     }
232                 }
233                 results[i] = operation.apply(this, results, i);
234             }
235             db.setTransactionSuccessful();
236             return results;
237         } finally {
238             mApplyingBatch.set(false);
239             db.endTransaction();
240             onEndTransaction(callerIsSyncAdapter);
241         }
242     }
243 
onEndTransaction(boolean callerIsSyncAdapter)244     protected Set<Uri> onEndTransaction(boolean callerIsSyncAdapter) {
245         Set<Uri> changed;
246         synchronized (mChangedUris) {
247             changed = new HashSet<Uri>(mChangedUris);
248             mChangedUris.clear();
249         }
250         ContentResolver resolver = getContext().getContentResolver();
251         for (Uri uri : changed) {
252             boolean syncToNetwork = !callerIsSyncAdapter && syncToNetwork(uri);
253             notifyChange(resolver, uri, syncToNetwork);
254         }
255         return changed;
256     }
257 
notifyChange(ContentResolver resolver, Uri uri, boolean syncToNetwork)258     protected void notifyChange(ContentResolver resolver, Uri uri, boolean syncToNetwork) {
259         resolver.notifyChange(uri, null, syncToNetwork);
260     }
261 
syncToNetwork(Uri uri)262     protected boolean syncToNetwork(Uri uri) {
263         return false;
264     }
265 }