Home | History | Annotate | Download | only in presence
      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      */
     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      */
     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      */
     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      */
     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      */
    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      */
    126     protected void onDatabaseOpened(SQLiteDatabase db) {
    127     }
    128 
    129     private class DatabaseHelper extends SQLiteOpenHelper {
    130         private File mDatabaseFile = null;
    131 
    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
    139         public void onCreate(SQLiteDatabase db) {
    140             bootstrapDatabase(db);
    141         }
    142 
    143         @Override
    144         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    145             upgradeDatabase(db, oldVersion, newVersion);
    146         }
    147 
    148         @Override
    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
    156         public void onOpen(SQLiteDatabase db) {
    157             onDatabaseOpened(db);
    158         }
    159 
    160         @Override
    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
    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      */
    207     protected abstract int deleteInternal(final SQLiteDatabase db, Uri uri, String selection,
    208             String[] selectionArgs);
    209 
    210     @Override
    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      */
    256     protected abstract Uri insertInternal(final SQLiteDatabase db, Uri uri, ContentValues values);
    257 
    258     @Override
    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
    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      */
    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      */
    319     protected abstract Cursor queryInternal(final SQLiteDatabase db, Uri uri, String[] projection,
    320             String selection, String[] selectionArgs, String sortOrder);
    321 
    322     @Override
    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 
    338     protected abstract int updateInternal(final SQLiteDatabase db, Uri uri, ContentValues values,
    339             String selection, String[] selectionArgs);
    340 
    341     @Override
    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
    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 
    421     private void sendStorageFullIntent(Context context) {
    422         Intent fullStorageIntent = new Intent(ACTION_DEVICE_STORAGE_FULL);
    423         context.sendBroadcast(fullStorageIntent);
    424     }
    425 
    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