• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2015, Motorola Mobility LLC
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *     - Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     - Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     - Neither the name of Motorola Mobility nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
18  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MOTOROLA MOBILITY LLC BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
26  * DAMAGE.
27  */
28 
29 package com.android.service.ims.presence;
30 
31 import java.io.File;
32 
33 import android.content.ContentProvider;
34 import android.content.ContentValues;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.database.Cursor;
38 import android.database.sqlite.SQLiteDatabase;
39 import android.database.sqlite.SQLiteException;
40 import android.database.sqlite.SQLiteFullException;
41 import android.database.sqlite.SQLiteOpenHelper;
42 import android.net.Uri;
43 
44 import com.android.ims.internal.Logger;
45 
46 public abstract class DatabaseContentProvider extends ContentProvider {
47     static private Logger logger = Logger.getLogger("DatabaseContentProvider");
48 
49     //Constants
50     public static final String ACTION_DEVICE_STORAGE_FULL = "com.android.vmm.DEVICE_STORAGE_FULL";
51 
52     //Fields
53     protected SQLiteOpenHelper mDbHelper;
54     /*package*/final int mDbVersion;
55     private final String mDbName;
56 
57     /**
58      * Initializes the DatabaseContentProvider
59      * @param dbName the filename of the database
60      * @param dbVersion the current version of the database schema
61      * @param contentUri The base Uri of the syncable content in this provider
62      */
DatabaseContentProvider(String dbName, int dbVersion)63     public DatabaseContentProvider(String dbName, int dbVersion) {
64         super();
65         mDbName = dbName;
66         mDbVersion = dbVersion;
67     }
68 
69     /**
70      * bootstrapDatabase() allows the implementer to set up their database
71      * after it is opened for the first time.  this is a perfect place
72      * to create tables and triggers :)
73      * @param db
74      */
bootstrapDatabase(SQLiteDatabase db)75     protected void bootstrapDatabase(SQLiteDatabase db) {
76     }
77 
78     /**
79      * updgradeDatabase() allows the user to do whatever they like
80      * when the database is upgraded between versions.
81      * @param db - the SQLiteDatabase that will be upgraded
82      * @param oldVersion - the old version number as an int
83      * @param newVersion - the new version number as an int
84      * @return
85      */
upgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion)86     protected abstract boolean upgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion);
87 
88     /**
89      * downgradeDatabase() allows the user to do whatever they like when the
90      * database is downgraded between versions.
91      *
92      * @param db - the SQLiteDatabase that will be downgraded
93      * @param oldVersion - the old version number as an int
94      * @param newVersion - the new version number as an int
95      * @return
96      */
downgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion)97     protected abstract boolean downgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion);
98 
99     /**
100      * Safely wraps an ALTER TABLE table ADD COLUMN columnName columnType
101      * If columnType == null then it's set to INTEGER DEFAULT 0
102      * @param db - db to alter
103      * @param table - table to alter
104      * @param columnDef
105      * @return
106      */
addColumn(SQLiteDatabase db, String table, String columnName, String columnType)107     protected static boolean addColumn(SQLiteDatabase db, String table, String columnName,
108             String columnType) {
109         StringBuilder sb = new StringBuilder();
110         sb.append("ALTER TABLE ").append(table).append(" ADD COLUMN ").append(columnName).append(
111                 ' ').append(columnType == null ? "INTEGER DEFAULT 0" : columnType).append(';');
112         try {
113             db.execSQL(sb.toString());
114         } catch (SQLiteException e) {
115                 logger.debug("Alter table failed : "+ e.getMessage());
116             return false;
117         }
118         return true;
119     }
120 
121     /**
122      * onDatabaseOpened() allows the user to do whatever they might
123      * need to do whenever the database is opened
124      * @param db - SQLiteDatabase that was just opened
125      */
onDatabaseOpened(SQLiteDatabase db)126     protected void onDatabaseOpened(SQLiteDatabase db) {
127     }
128 
129     private class DatabaseHelper extends SQLiteOpenHelper {
130         private File mDatabaseFile = null;
131 
DatabaseHelper(Context context, String name)132         DatabaseHelper(Context context, String name) {
133             // Note: context and name may be null for temp providers
134             super(context, name, null, mDbVersion);
135             mDatabaseFile = context.getDatabasePath(name);
136         }
137 
138         @Override
onCreate(SQLiteDatabase db)139         public void onCreate(SQLiteDatabase db) {
140             bootstrapDatabase(db);
141         }
142 
143         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)144         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
145             upgradeDatabase(db, oldVersion, newVersion);
146         }
147 
148         @Override
onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)149         public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
150             logger.debug("Enter: onDowngrade() - oldVersion = " + oldVersion + " newVersion = "
151                     + newVersion);
152             downgradeDatabase(db, oldVersion, newVersion);
153         }
154 
155         @Override
onOpen(SQLiteDatabase db)156         public void onOpen(SQLiteDatabase db) {
157             onDatabaseOpened(db);
158         }
159 
160         @Override
getWritableDatabase()161         public synchronized SQLiteDatabase getWritableDatabase() {
162             try {
163                 return super.getWritableDatabase();
164             } catch (Exception e) {
165                 logger.error("getWritableDatabase exception " + e);
166             }
167 
168             // try to delete the database file
169             if (null != mDatabaseFile) {
170                 logger.error("deleting mDatabaseFile.");
171                 mDatabaseFile.delete();
172             }
173 
174             // Return a freshly created database.
175             return super.getWritableDatabase();
176         }
177 
178         @Override
getReadableDatabase()179         public synchronized SQLiteDatabase getReadableDatabase() {
180             try {
181                 return super.getReadableDatabase();
182             } catch (Exception e) {
183                 logger.error("getReadableDatabase exception " + e);
184             }
185 
186             // try to delete the database file
187             if (null != mDatabaseFile) {
188                 logger.error("deleting mDatabaseFile.");
189                 mDatabaseFile.delete();
190             }
191 
192             // Return a freshly created database.
193             return super.getReadableDatabase();
194         }
195     }
196 
197     /**
198      * deleteInternal allows getContentResolver().delete() to occur atomically
199      * via transactions and notify the uri automatically upon completion (provided
200      * rows were deleted) - otherwise, it functions exactly as getContentResolver.delete()
201      * would on a regular ContentProvider
202      * @param uri - uri to delete from
203      * @param selection - selection used for the uri
204      * @param selectionArgs - selection args replacing ?'s in the selection
205      * @return returns the number of rows deleted
206      */
deleteInternal(final SQLiteDatabase db, Uri uri, String selection, String[] selectionArgs)207     protected abstract int deleteInternal(final SQLiteDatabase db, Uri uri, String selection,
208             String[] selectionArgs);
209 
210     @Override
delete(Uri uri, String selection, String[] selectionArgs)211     public int delete(Uri uri, String selection, String[] selectionArgs) {
212         int result = 0;
213         SQLiteDatabase db = mDbHelper.getWritableDatabase();
214         if (isClosed(db)) {
215             return result;
216         }
217         try {
218             //acquire reference to prevent from garbage collection
219             db.acquireReference();
220             //beginTransaction can throw a runtime exception
221             //so it needs to be moved into the try
222             db.beginTransaction();
223             result = deleteInternal(db, uri, selection, selectionArgs);
224             db.setTransactionSuccessful();
225         } catch (SQLiteFullException fullEx) {
226             logger.error("" + fullEx);
227             sendStorageFullIntent(getContext());
228         } catch (Exception e) {
229             logger.error("" + e);
230         } finally {
231             try {
232                 db.endTransaction();
233             } catch (SQLiteFullException fullEx) {
234                 logger.error("" + fullEx);
235                 sendStorageFullIntent(getContext());
236             } catch (Exception e) {
237                 logger.error("" + e);
238             }
239             //release reference
240             db.releaseReference();
241         }
242         // don't check return value because it may be 0 if all rows deleted
243         getContext().getContentResolver().notifyChange(uri, null);
244         return result;
245     }
246 
247     /**
248      * insertInternal allows getContentResolver().insert() to occur atomically
249      * via transactions and notify the uri automatically upon completion (provided
250      * rows were added to the db) - otherwise, it functions exactly as getContentResolver().insert()
251      * would on a regular ContentProvider
252      * @param uri - uri on which to insert
253      * @param values - values to insert
254      * @return returns the uri of the row added
255      */
insertInternal(final SQLiteDatabase db, Uri uri, ContentValues values)256     protected abstract Uri insertInternal(final SQLiteDatabase db, Uri uri, ContentValues values);
257 
258     @Override
insert(Uri uri, ContentValues values)259     public Uri insert(Uri uri, ContentValues values) {
260         Uri result = null;
261         SQLiteDatabase db = mDbHelper.getWritableDatabase();
262         if (isClosed(db)) {
263             return result;
264         }
265         try {
266             db.acquireReference();
267             //beginTransaction can throw a runtime exception
268             //so it needs to be moved into the try
269             db.beginTransaction();
270             result = insertInternal(db, uri, values);
271             db.setTransactionSuccessful();
272         } catch (SQLiteFullException fullEx) {
273             logger.warn("" + fullEx);
274             sendStorageFullIntent(getContext());
275         } catch (Exception e) {
276             logger.warn("" + e);
277         } finally {
278             try {
279                 db.endTransaction();
280             } catch (SQLiteFullException fullEx) {
281                 logger.warn("" + fullEx);
282                 sendStorageFullIntent(getContext());
283             } catch (Exception e) {
284                 logger.warn("" + e);
285             }
286             db.releaseReference();
287         }
288         if (result != null) {
289             getContext().getContentResolver().notifyChange(uri, null);
290         }
291         return result;
292     }
293 
294     @Override
onCreate()295     public boolean onCreate() {
296         mDbHelper = new DatabaseHelper(getContext(), mDbName);
297         return onCreateInternal();
298     }
299 
300     /**
301      * Called by onCreate.  Should be overridden by any subclasses
302      * to handle the onCreate lifecycle event.
303      *
304      * @return
305      */
onCreateInternal()306     protected boolean onCreateInternal() {
307         return true;
308     }
309 
310     /**
311      * queryInternal allows getContentResolver().query() to occur
312      * @param uri
313      * @param projection
314      * @param selection
315      * @param selectionArgs
316      * @param sortOrder
317      * @return Cursor holding the contents of the requested query
318      */
queryInternal(final SQLiteDatabase db, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)319     protected abstract Cursor queryInternal(final SQLiteDatabase db, Uri uri, String[] projection,
320             String selection, String[] selectionArgs, String sortOrder);
321 
322     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)323     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
324             String sortOrder) {
325         SQLiteDatabase db = mDbHelper.getReadableDatabase();
326         if (isClosed(db)) {
327             return null;
328         }
329 
330         try {
331             db.acquireReference();
332             return queryInternal(db, uri, projection, selection, selectionArgs, sortOrder);
333         } finally {
334             db.releaseReference();
335         }
336     }
337 
updateInternal(final SQLiteDatabase db, Uri uri, ContentValues values, String selection, String[] selectionArgs)338     protected abstract int updateInternal(final SQLiteDatabase db, Uri uri, ContentValues values,
339             String selection, String[] selectionArgs);
340 
341     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)342     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
343         int result = 0;
344         SQLiteDatabase db = mDbHelper.getWritableDatabase();
345         if (isClosed(db)) {
346             return result;
347         }
348         try {
349             db.acquireReference();
350             //beginTransaction can throw a runtime exception
351             //so it needs to be moved into the try
352             db.beginTransaction();
353             result = updateInternal(db, uri, values, selection, selectionArgs);
354             db.setTransactionSuccessful();
355         } catch (SQLiteFullException fullEx) {
356             logger.error("" + fullEx);
357             sendStorageFullIntent(getContext());
358         } catch (Exception e) {
359             logger.error("" + e);
360         } finally {
361             try {
362                 db.endTransaction();
363             } catch (SQLiteFullException fullEx) {
364                 logger.error("" + fullEx);
365                 sendStorageFullIntent(getContext());
366             } catch (Exception e) {
367                 logger.error("" + e);
368             }
369             db.releaseReference();
370         }
371         if (result > 0) {
372             getContext().getContentResolver().notifyChange(uri, null);
373         }
374         return result;
375     }
376 
377     @Override
bulkInsert(Uri uri, ContentValues[] values)378     public int bulkInsert(Uri uri, ContentValues[] values) {
379         int added = 0;
380         if (values != null) {
381             int numRows = values.length;
382             SQLiteDatabase db = mDbHelper.getWritableDatabase();
383             if (isClosed(db)) {
384                 return added;
385             }
386             try {
387                 db.acquireReference();
388                 //beginTransaction can throw a runtime exception
389                 //so it needs to be moved into the try
390                 db.beginTransaction();
391 
392                 for (int i = 0; i < numRows; i++) {
393                     if (insertInternal(db, uri, values[i]) != null) {
394                         added++;
395                     }
396                 }
397                 db.setTransactionSuccessful();
398                 if (added > 0) {
399                     getContext().getContentResolver().notifyChange(uri, null);
400                 }
401             } catch (SQLiteFullException fullEx) {
402                 logger.error("" + fullEx);
403                 sendStorageFullIntent(getContext());
404             } catch (Exception e) {
405                 logger.error("" + e);
406             } finally {
407                 try {
408                     db.endTransaction();
409                 } catch (SQLiteFullException fullEx) {
410                     logger.error("" + fullEx);
411                     sendStorageFullIntent(getContext());
412                 } catch (Exception e) {
413                     logger.error("" + e);
414                 }
415                 db.releaseReference();
416             }
417         }
418         return added;
419     }
420 
sendStorageFullIntent(Context context)421     private void sendStorageFullIntent(Context context) {
422         Intent fullStorageIntent = new Intent(ACTION_DEVICE_STORAGE_FULL);
423         context.sendBroadcast(fullStorageIntent);
424     }
425 
isClosed(SQLiteDatabase db)426     private boolean isClosed(SQLiteDatabase db) {
427         if (db == null || !db.isOpen()) {
428             logger.warn("Null DB returned from DBHelper for a writable/readable database.");
429             return true;
430         }
431         return false;
432     }
433 
434 }
435