blob: fb86c144d218a96cee22035bc175611a3c92480f [file] [log] [blame]
Aurimas Liutikas88c7ff12023-08-10 12:42:26 -07001/*
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.common.content;
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 /**
48 * Maximum number of operations allowed in a batch between yield points.
49 */
50 private static final int MAX_OPERATIONS_PER_YIELD_POINT = 500;
51
52 /**
53 * @return Number of operations that can be applied at once without a yield point.
54 */
55 public int getMaxOperationsPerYield() {
56 return MAX_OPERATIONS_PER_YIELD_POINT;
57 }
58
59 @Override
60 public boolean onCreate() {
61 Context context = getContext();
62 mOpenHelper = getDatabaseHelper(context);
63 return true;
64 }
65
66 protected abstract SQLiteOpenHelper getDatabaseHelper(Context context);
67
68 /**
69 * The equivalent of the {@link #insert} method, but invoked within a transaction.
70 */
71 protected abstract Uri insertInTransaction(Uri uri, ContentValues values);
72
73 /**
74 * The equivalent of the {@link #update} method, but invoked within a transaction.
75 */
76 protected abstract int updateInTransaction(Uri uri, ContentValues values, String selection,
77 String[] selectionArgs);
78
79 /**
80 * The equivalent of the {@link #delete} method, but invoked within a transaction.
81 */
82 protected abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs);
83
84 protected abstract void notifyChange();
85
86 public SQLiteOpenHelper getDatabaseHelper() {
87 return mOpenHelper;
88 }
89
90 private boolean applyingBatch() {
91 return mApplyingBatch.get() != null && mApplyingBatch.get();
92 }
93
94 @Override
95 public Uri insert(Uri uri, ContentValues values) {
96 Uri result = null;
97 boolean applyingBatch = applyingBatch();
98 if (!applyingBatch) {
99 mDb = mOpenHelper.getWritableDatabase();
100 mDb.beginTransactionWithListener(this);
101 try {
102 result = insertInTransaction(uri, values);
103 if (result != null) {
104 mNotifyChange = true;
105 }
106 mDb.setTransactionSuccessful();
107 } finally {
108 mDb.endTransaction();
109 }
110
111 onEndTransaction();
112 } else {
113 result = insertInTransaction(uri, values);
114 if (result != null) {
115 mNotifyChange = true;
116 }
117 }
118 return result;
119 }
120
121 @Override
122 public int bulkInsert(Uri uri, ContentValues[] values) {
123 int numValues = values.length;
124 mDb = mOpenHelper.getWritableDatabase();
125 mDb.beginTransactionWithListener(this);
126 try {
127 for (int i = 0; i < numValues; i++) {
128 Uri result = insertInTransaction(uri, values[i]);
129 if (result != null) {
130 mNotifyChange = true;
131 }
132 boolean savedNotifyChange = mNotifyChange;
133 SQLiteDatabase savedDb = mDb;
134 mDb.yieldIfContendedSafely();
135 mDb = savedDb;
136 mNotifyChange = savedNotifyChange;
137 }
138 mDb.setTransactionSuccessful();
139 } finally {
140 mDb.endTransaction();
141 }
142
143 onEndTransaction();
144 return numValues;
145 }
146
147 @Override
148 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
149 int count = 0;
150 boolean applyingBatch = applyingBatch();
151 if (!applyingBatch) {
152 mDb = mOpenHelper.getWritableDatabase();
153 mDb.beginTransactionWithListener(this);
154 try {
155 count = updateInTransaction(uri, values, selection, selectionArgs);
156 if (count > 0) {
157 mNotifyChange = true;
158 }
159 mDb.setTransactionSuccessful();
160 } finally {
161 mDb.endTransaction();
162 }
163
164 onEndTransaction();
165 } else {
166 count = updateInTransaction(uri, values, selection, selectionArgs);
167 if (count > 0) {
168 mNotifyChange = true;
169 }
170 }
171
172 return count;
173 }
174
175 @Override
176 public int delete(Uri uri, String selection, String[] selectionArgs) {
177 int count = 0;
178 boolean applyingBatch = applyingBatch();
179 if (!applyingBatch) {
180 mDb = mOpenHelper.getWritableDatabase();
181 mDb.beginTransactionWithListener(this);
182 try {
183 count = deleteInTransaction(uri, selection, selectionArgs);
184 if (count > 0) {
185 mNotifyChange = true;
186 }
187 mDb.setTransactionSuccessful();
188 } finally {
189 mDb.endTransaction();
190 }
191
192 onEndTransaction();
193 } else {
194 count = deleteInTransaction(uri, selection, selectionArgs);
195 if (count > 0) {
196 mNotifyChange = true;
197 }
198 }
199 return count;
200 }
201
202 @Override
203 public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
204 throws OperationApplicationException {
205 int ypCount = 0;
206 int opCount = 0;
207 mDb = mOpenHelper.getWritableDatabase();
208 mDb.beginTransactionWithListener(this);
209 try {
210 mApplyingBatch.set(true);
211 final int numOperations = operations.size();
212 final ContentProviderResult[] results = new ContentProviderResult[numOperations];
213 for (int i = 0; i < numOperations; i++) {
214 if (++opCount > getMaxOperationsPerYield()) {
215 throw new OperationApplicationException(
216 "Too many content provider operations between yield points. "
217 + "The maximum number of operations per yield point is "
218 + MAX_OPERATIONS_PER_YIELD_POINT, ypCount);
219 }
220 final ContentProviderOperation operation = operations.get(i);
221 if (i > 0 && operation.isYieldAllowed()) {
222 opCount = 0;
223 boolean savedNotifyChange = mNotifyChange;
224 if (mDb.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY)) {
225 mDb = mOpenHelper.getWritableDatabase();
226 mNotifyChange = savedNotifyChange;
227 ypCount++;
228 }
229 }
230
231 results[i] = operation.apply(this, results, i);
232 }
233 mDb.setTransactionSuccessful();
234 return results;
235 } finally {
236 mApplyingBatch.set(false);
237 mDb.endTransaction();
238 onEndTransaction();
239 }
240 }
241
242 @Override
243 public void onBegin() {
244 onBeginTransaction();
245 }
246
247 @Override
248 public void onCommit() {
249 beforeTransactionCommit();
250 }
251
252 @Override
253 public void onRollback() {
254 // not used
255 }
256
257 protected void onBeginTransaction() {
258 }
259
260 protected void beforeTransactionCommit() {
261 }
262
263 protected void onEndTransaction() {
264 if (mNotifyChange) {
265 mNotifyChange = false;
266 notifyChange();
267 }
268 }
269}