Home | History | Annotate | Download | only in provider
      1 /*
      2  * Copyright (C) 2015 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.tv.dvr.provider;
     18 
     19 import android.content.ContentValues;
     20 import android.content.Context;
     21 import android.database.Cursor;
     22 import android.database.sqlite.SQLiteDatabase;
     23 import android.database.sqlite.SQLiteOpenHelper;
     24 import android.database.sqlite.SQLiteQueryBuilder;
     25 import android.database.sqlite.SQLiteStatement;
     26 import android.provider.BaseColumns;
     27 import android.text.TextUtils;
     28 import android.util.Log;
     29 import com.android.tv.dvr.data.ScheduledRecording;
     30 import com.android.tv.dvr.data.SeriesRecording;
     31 import com.android.tv.dvr.provider.DvrContract.Schedules;
     32 import com.android.tv.dvr.provider.DvrContract.SeriesRecordings;
     33 
     34 /** A data class for one recorded contents. */
     35 public class DvrDatabaseHelper extends SQLiteOpenHelper {
     36     private static final String TAG = "DvrDatabaseHelper";
     37     private static final boolean DEBUG = false;
     38 
     39     private static final int DATABASE_VERSION = 18;
     40     private static final String DB_NAME = "dvr.db";
     41 
     42     private static final String SQL_CREATE_SCHEDULES =
     43             "CREATE TABLE "
     44                     + Schedules.TABLE_NAME
     45                     + "("
     46                     + Schedules._ID
     47                     + " INTEGER PRIMARY KEY AUTOINCREMENT,"
     48                     + Schedules.COLUMN_PRIORITY
     49                     + " INTEGER DEFAULT "
     50                     + ScheduledRecording.DEFAULT_PRIORITY
     51                     + ","
     52                     + Schedules.COLUMN_TYPE
     53                     + " TEXT NOT NULL,"
     54                     + Schedules.COLUMN_INPUT_ID
     55                     + " TEXT NOT NULL,"
     56                     + Schedules.COLUMN_CHANNEL_ID
     57                     + " INTEGER NOT NULL,"
     58                     + Schedules.COLUMN_PROGRAM_ID
     59                     + " INTEGER,"
     60                     + Schedules.COLUMN_PROGRAM_TITLE
     61                     + " TEXT,"
     62                     + Schedules.COLUMN_START_TIME_UTC_MILLIS
     63                     + " INTEGER NOT NULL,"
     64                     + Schedules.COLUMN_END_TIME_UTC_MILLIS
     65                     + " INTEGER NOT NULL,"
     66                     + Schedules.COLUMN_SEASON_NUMBER
     67                     + " TEXT,"
     68                     + Schedules.COLUMN_EPISODE_NUMBER
     69                     + " TEXT,"
     70                     + Schedules.COLUMN_EPISODE_TITLE
     71                     + " TEXT,"
     72                     + Schedules.COLUMN_PROGRAM_DESCRIPTION
     73                     + " TEXT,"
     74                     + Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION
     75                     + " TEXT,"
     76                     + Schedules.COLUMN_PROGRAM_POST_ART_URI
     77                     + " TEXT,"
     78                     + Schedules.COLUMN_PROGRAM_THUMBNAIL_URI
     79                     + " TEXT,"
     80                     + Schedules.COLUMN_STATE
     81                     + " TEXT NOT NULL,"
     82                     + Schedules.COLUMN_SERIES_RECORDING_ID
     83                     + " INTEGER,"
     84                     + "FOREIGN KEY("
     85                     + Schedules.COLUMN_SERIES_RECORDING_ID
     86                     + ") "
     87                     + "REFERENCES "
     88                     + SeriesRecordings.TABLE_NAME
     89                     + "("
     90                     + SeriesRecordings._ID
     91                     + ") "
     92                     + "ON UPDATE CASCADE ON DELETE SET NULL);";
     93 
     94     private static final String SQL_DROP_SCHEDULES = "DROP TABLE IF EXISTS " + Schedules.TABLE_NAME;
     95 
     96     private static final String SQL_CREATE_SERIES_RECORDINGS =
     97             "CREATE TABLE "
     98                     + SeriesRecordings.TABLE_NAME
     99                     + "("
    100                     + SeriesRecordings._ID
    101                     + " INTEGER PRIMARY KEY AUTOINCREMENT,"
    102                     + SeriesRecordings.COLUMN_PRIORITY
    103                     + " INTEGER DEFAULT "
    104                     + SeriesRecording.DEFAULT_PRIORITY
    105                     + ","
    106                     + SeriesRecordings.COLUMN_TITLE
    107                     + " TEXT NOT NULL,"
    108                     + SeriesRecordings.COLUMN_SHORT_DESCRIPTION
    109                     + " TEXT,"
    110                     + SeriesRecordings.COLUMN_LONG_DESCRIPTION
    111                     + " TEXT,"
    112                     + SeriesRecordings.COLUMN_INPUT_ID
    113                     + " TEXT NOT NULL,"
    114                     + SeriesRecordings.COLUMN_CHANNEL_ID
    115                     + " INTEGER NOT NULL,"
    116                     + SeriesRecordings.COLUMN_SERIES_ID
    117                     + " TEXT NOT NULL,"
    118                     + SeriesRecordings.COLUMN_START_FROM_SEASON
    119                     + " INTEGER DEFAULT "
    120                     + SeriesRecordings.THE_BEGINNING
    121                     + ","
    122                     + SeriesRecordings.COLUMN_START_FROM_EPISODE
    123                     + " INTEGER DEFAULT "
    124                     + SeriesRecordings.THE_BEGINNING
    125                     + ","
    126                     + SeriesRecordings.COLUMN_CHANNEL_OPTION
    127                     + " TEXT DEFAULT "
    128                     + SeriesRecordings.OPTION_CHANNEL_ONE
    129                     + ","
    130                     + SeriesRecordings.COLUMN_CANONICAL_GENRE
    131                     + " TEXT,"
    132                     + SeriesRecordings.COLUMN_POSTER_URI
    133                     + " TEXT,"
    134                     + SeriesRecordings.COLUMN_PHOTO_URI
    135                     + " TEXT,"
    136                     + SeriesRecordings.COLUMN_STATE
    137                     + " TEXT)";
    138 
    139     private static final String SQL_DROP_SERIES_RECORDINGS =
    140             "DROP TABLE IF EXISTS " + SeriesRecordings.TABLE_NAME;
    141 
    142     private static final int SQL_DATA_TYPE_LONG = 0;
    143     private static final int SQL_DATA_TYPE_INT = 1;
    144     private static final int SQL_DATA_TYPE_STRING = 2;
    145 
    146     private static final ColumnInfo[] COLUMNS_SCHEDULES =
    147             new ColumnInfo[] {
    148                 new ColumnInfo(Schedules._ID, SQL_DATA_TYPE_LONG),
    149                 new ColumnInfo(Schedules.COLUMN_PRIORITY, SQL_DATA_TYPE_LONG),
    150                 new ColumnInfo(Schedules.COLUMN_TYPE, SQL_DATA_TYPE_STRING),
    151                 new ColumnInfo(Schedules.COLUMN_INPUT_ID, SQL_DATA_TYPE_STRING),
    152                 new ColumnInfo(Schedules.COLUMN_CHANNEL_ID, SQL_DATA_TYPE_LONG),
    153                 new ColumnInfo(Schedules.COLUMN_PROGRAM_ID, SQL_DATA_TYPE_LONG),
    154                 new ColumnInfo(Schedules.COLUMN_PROGRAM_TITLE, SQL_DATA_TYPE_STRING),
    155                 new ColumnInfo(Schedules.COLUMN_START_TIME_UTC_MILLIS, SQL_DATA_TYPE_LONG),
    156                 new ColumnInfo(Schedules.COLUMN_END_TIME_UTC_MILLIS, SQL_DATA_TYPE_LONG),
    157                 new ColumnInfo(Schedules.COLUMN_SEASON_NUMBER, SQL_DATA_TYPE_STRING),
    158                 new ColumnInfo(Schedules.COLUMN_EPISODE_NUMBER, SQL_DATA_TYPE_STRING),
    159                 new ColumnInfo(Schedules.COLUMN_EPISODE_TITLE, SQL_DATA_TYPE_STRING),
    160                 new ColumnInfo(Schedules.COLUMN_PROGRAM_DESCRIPTION, SQL_DATA_TYPE_STRING),
    161                 new ColumnInfo(Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION, SQL_DATA_TYPE_STRING),
    162                 new ColumnInfo(Schedules.COLUMN_PROGRAM_POST_ART_URI, SQL_DATA_TYPE_STRING),
    163                 new ColumnInfo(Schedules.COLUMN_PROGRAM_THUMBNAIL_URI, SQL_DATA_TYPE_STRING),
    164                 new ColumnInfo(Schedules.COLUMN_STATE, SQL_DATA_TYPE_STRING),
    165                 new ColumnInfo(Schedules.COLUMN_FAILED_REASON, SQL_DATA_TYPE_STRING),
    166                 new ColumnInfo(Schedules.COLUMN_SERIES_RECORDING_ID, SQL_DATA_TYPE_LONG)
    167             };
    168 
    169     private static final String SQL_INSERT_SCHEDULES =
    170             buildInsertSql(Schedules.TABLE_NAME, COLUMNS_SCHEDULES);
    171     private static final String SQL_UPDATE_SCHEDULES =
    172             buildUpdateSql(Schedules.TABLE_NAME, COLUMNS_SCHEDULES);
    173     private static final String SQL_DELETE_SCHEDULES = buildDeleteSql(Schedules.TABLE_NAME);
    174 
    175     private static final ColumnInfo[] COLUMNS_SERIES_RECORDINGS =
    176             new ColumnInfo[] {
    177                 new ColumnInfo(SeriesRecordings._ID, SQL_DATA_TYPE_LONG),
    178                 new ColumnInfo(SeriesRecordings.COLUMN_PRIORITY, SQL_DATA_TYPE_LONG),
    179                 new ColumnInfo(SeriesRecordings.COLUMN_INPUT_ID, SQL_DATA_TYPE_STRING),
    180                 new ColumnInfo(SeriesRecordings.COLUMN_CHANNEL_ID, SQL_DATA_TYPE_LONG),
    181                 new ColumnInfo(SeriesRecordings.COLUMN_SERIES_ID, SQL_DATA_TYPE_STRING),
    182                 new ColumnInfo(SeriesRecordings.COLUMN_TITLE, SQL_DATA_TYPE_STRING),
    183                 new ColumnInfo(SeriesRecordings.COLUMN_SHORT_DESCRIPTION, SQL_DATA_TYPE_STRING),
    184                 new ColumnInfo(SeriesRecordings.COLUMN_LONG_DESCRIPTION, SQL_DATA_TYPE_STRING),
    185                 new ColumnInfo(SeriesRecordings.COLUMN_START_FROM_SEASON, SQL_DATA_TYPE_INT),
    186                 new ColumnInfo(SeriesRecordings.COLUMN_START_FROM_EPISODE, SQL_DATA_TYPE_INT),
    187                 new ColumnInfo(SeriesRecordings.COLUMN_CHANNEL_OPTION, SQL_DATA_TYPE_STRING),
    188                 new ColumnInfo(SeriesRecordings.COLUMN_CANONICAL_GENRE, SQL_DATA_TYPE_STRING),
    189                 new ColumnInfo(SeriesRecordings.COLUMN_POSTER_URI, SQL_DATA_TYPE_STRING),
    190                 new ColumnInfo(SeriesRecordings.COLUMN_PHOTO_URI, SQL_DATA_TYPE_STRING),
    191                 new ColumnInfo(SeriesRecordings.COLUMN_STATE, SQL_DATA_TYPE_STRING)
    192             };
    193 
    194     private static final String SQL_INSERT_SERIES_RECORDINGS =
    195             buildInsertSql(SeriesRecordings.TABLE_NAME, COLUMNS_SERIES_RECORDINGS);
    196     private static final String SQL_UPDATE_SERIES_RECORDINGS =
    197             buildUpdateSql(SeriesRecordings.TABLE_NAME, COLUMNS_SERIES_RECORDINGS);
    198     private static final String SQL_DELETE_SERIES_RECORDINGS =
    199             buildDeleteSql(SeriesRecordings.TABLE_NAME);
    200 
    201     private static String buildInsertSql(String tableName, ColumnInfo[] columns) {
    202         StringBuilder sb = new StringBuilder();
    203         sb.append("INSERT INTO ").append(tableName).append(" (");
    204         boolean appendComma = false;
    205         for (ColumnInfo columnInfo : columns) {
    206             if (appendComma) {
    207                 sb.append(",");
    208             }
    209             appendComma = true;
    210             sb.append(columnInfo.name);
    211         }
    212         sb.append(") VALUES (?");
    213         for (int i = 1; i < columns.length; ++i) {
    214             sb.append(",?");
    215         }
    216         sb.append(")");
    217         return sb.toString();
    218     }
    219 
    220     private static String buildUpdateSql(String tableName, ColumnInfo[] columns) {
    221         StringBuilder sb = new StringBuilder();
    222         sb.append("UPDATE ").append(tableName).append(" SET ");
    223         boolean appendComma = false;
    224         for (ColumnInfo columnInfo : columns) {
    225             if (appendComma) {
    226                 sb.append(",");
    227             }
    228             appendComma = true;
    229             sb.append(columnInfo.name).append("=?");
    230         }
    231         sb.append(" WHERE ").append(BaseColumns._ID).append("=?");
    232         return sb.toString();
    233     }
    234 
    235     private static String buildDeleteSql(String tableName) {
    236         return "DELETE FROM " + tableName + " WHERE " + BaseColumns._ID + "=?";
    237     }
    238 
    239     public DvrDatabaseHelper(Context context) {
    240         super(context.getApplicationContext(), DB_NAME, null, DATABASE_VERSION);
    241     }
    242 
    243     @Override
    244     public void onConfigure(SQLiteDatabase db) {
    245         db.setForeignKeyConstraintsEnabled(true);
    246     }
    247 
    248     @Override
    249     public void onCreate(SQLiteDatabase db) {
    250         if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_CREATE_SCHEDULES);
    251         db.execSQL(SQL_CREATE_SCHEDULES);
    252         if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_CREATE_SERIES_RECORDINGS);
    253         db.execSQL(SQL_CREATE_SERIES_RECORDINGS);
    254     }
    255 
    256     @Override
    257     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    258         if (oldVersion < 17) {
    259             if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_DROP_SCHEDULES);
    260             db.execSQL(SQL_DROP_SCHEDULES);
    261             if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_DROP_SERIES_RECORDINGS);
    262             db.execSQL(SQL_DROP_SERIES_RECORDINGS);
    263             onCreate(db);
    264         }
    265         if (oldVersion < 18) {
    266             db.execSQL("ALTER TABLE " + Schedules.TABLE_NAME + " ADD COLUMN "
    267                     + Schedules.COLUMN_FAILED_REASON + " TEXT DEFAULT null;");
    268         }
    269     }
    270 
    271     /** Handles the query request and returns a {@link Cursor}. */
    272     public Cursor query(String tableName, String[] projections) {
    273         SQLiteDatabase db = getReadableDatabase();
    274         SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
    275         builder.setTables(tableName);
    276         return builder.query(db, projections, null, null, null, null, null);
    277     }
    278 
    279     /** Inserts schedules. */
    280     public void insertSchedules(ScheduledRecording... scheduledRecordings) {
    281         SQLiteDatabase db = getWritableDatabase();
    282         SQLiteStatement statement = db.compileStatement(SQL_INSERT_SCHEDULES);
    283         db.beginTransaction();
    284         try {
    285             for (ScheduledRecording r : scheduledRecordings) {
    286                 statement.clearBindings();
    287                 ContentValues values = ScheduledRecording.toContentValues(r);
    288                 bindColumns(statement, COLUMNS_SCHEDULES, values);
    289                 statement.execute();
    290             }
    291             db.setTransactionSuccessful();
    292         } finally {
    293             db.endTransaction();
    294         }
    295     }
    296 
    297     /** Update schedules. */
    298     public void updateSchedules(ScheduledRecording... scheduledRecordings) {
    299         SQLiteDatabase db = getWritableDatabase();
    300         SQLiteStatement statement = db.compileStatement(SQL_UPDATE_SCHEDULES);
    301         db.beginTransaction();
    302         try {
    303             for (ScheduledRecording r : scheduledRecordings) {
    304                 statement.clearBindings();
    305                 ContentValues values = ScheduledRecording.toContentValues(r);
    306                 bindColumns(statement, COLUMNS_SCHEDULES, values);
    307                 statement.bindLong(COLUMNS_SCHEDULES.length + 1, r.getId());
    308                 statement.execute();
    309             }
    310             db.setTransactionSuccessful();
    311         } finally {
    312             db.endTransaction();
    313         }
    314     }
    315 
    316     /** Delete schedules. */
    317     public void deleteSchedules(ScheduledRecording... scheduledRecordings) {
    318         SQLiteDatabase db = getWritableDatabase();
    319         SQLiteStatement statement = db.compileStatement(SQL_DELETE_SCHEDULES);
    320         db.beginTransaction();
    321         try {
    322             for (ScheduledRecording r : scheduledRecordings) {
    323                 statement.clearBindings();
    324                 statement.bindLong(1, r.getId());
    325                 statement.execute();
    326             }
    327             db.setTransactionSuccessful();
    328         } finally {
    329             db.endTransaction();
    330         }
    331     }
    332 
    333     /** Inserts series recordings. */
    334     public void insertSeriesRecordings(SeriesRecording... seriesRecordings) {
    335         SQLiteDatabase db = getWritableDatabase();
    336         SQLiteStatement statement = db.compileStatement(SQL_INSERT_SERIES_RECORDINGS);
    337         db.beginTransaction();
    338         try {
    339             for (SeriesRecording r : seriesRecordings) {
    340                 statement.clearBindings();
    341                 ContentValues values = SeriesRecording.toContentValues(r);
    342                 bindColumns(statement, COLUMNS_SERIES_RECORDINGS, values);
    343                 statement.execute();
    344             }
    345             db.setTransactionSuccessful();
    346         } finally {
    347             db.endTransaction();
    348         }
    349     }
    350 
    351     /** Update series recordings. */
    352     public void updateSeriesRecordings(SeriesRecording... seriesRecordings) {
    353         SQLiteDatabase db = getWritableDatabase();
    354         SQLiteStatement statement = db.compileStatement(SQL_UPDATE_SERIES_RECORDINGS);
    355         db.beginTransaction();
    356         try {
    357             for (SeriesRecording r : seriesRecordings) {
    358                 statement.clearBindings();
    359                 ContentValues values = SeriesRecording.toContentValues(r);
    360                 bindColumns(statement, COLUMNS_SERIES_RECORDINGS, values);
    361                 statement.bindLong(COLUMNS_SERIES_RECORDINGS.length + 1, r.getId());
    362                 statement.execute();
    363             }
    364             db.setTransactionSuccessful();
    365         } finally {
    366             db.endTransaction();
    367         }
    368     }
    369 
    370     /** Delete series recordings. */
    371     public void deleteSeriesRecordings(SeriesRecording... seriesRecordings) {
    372         SQLiteDatabase db = getWritableDatabase();
    373         SQLiteStatement statement = db.compileStatement(SQL_DELETE_SERIES_RECORDINGS);
    374         db.beginTransaction();
    375         try {
    376             for (SeriesRecording r : seriesRecordings) {
    377                 statement.clearBindings();
    378                 statement.bindLong(1, r.getId());
    379                 statement.execute();
    380             }
    381             db.setTransactionSuccessful();
    382         } finally {
    383             db.endTransaction();
    384         }
    385     }
    386 
    387     private void bindColumns(
    388             SQLiteStatement statement, ColumnInfo[] columns, ContentValues values) {
    389         for (int i = 0; i < columns.length; ++i) {
    390             ColumnInfo columnInfo = columns[i];
    391             Object value = values.get(columnInfo.name);
    392             switch (columnInfo.type) {
    393                 case SQL_DATA_TYPE_LONG:
    394                     if (value == null) {
    395                         statement.bindNull(i + 1);
    396                     } else {
    397                         statement.bindLong(i + 1, (Long) value);
    398                     }
    399                     break;
    400                 case SQL_DATA_TYPE_INT:
    401                     if (value == null) {
    402                         statement.bindNull(i + 1);
    403                     } else {
    404                         statement.bindLong(i + 1, (Integer) value);
    405                     }
    406                     break;
    407                 case SQL_DATA_TYPE_STRING:
    408                     {
    409                         if (TextUtils.isEmpty((String) value)) {
    410                             statement.bindNull(i + 1);
    411                         } else {
    412                             statement.bindString(i + 1, (String) value);
    413                         }
    414                         break;
    415                     }
    416             }
    417         }
    418     }
    419 
    420     private static class ColumnInfo {
    421         final String name;
    422         final int type;
    423 
    424         ColumnInfo(String name, int type) {
    425             this.name = name;
    426             this.type = type;
    427         }
    428     }
    429 }
    430