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.support.annotation.VisibleForTesting; 28 import android.text.TextUtils; 29 import android.util.Log; 30 31 import com.android.tv.common.dagger.annotations.ApplicationContext; 32 import com.android.tv.common.flags.DvrFlags; 33 import com.android.tv.dvr.data.ScheduledRecording; 34 import com.android.tv.dvr.data.SeriesRecording; 35 import com.android.tv.dvr.provider.DvrContract.Schedules; 36 import com.android.tv.dvr.provider.DvrContract.SeriesRecordings; 37 38 import com.google.common.collect.ObjectArrays; 39 40 import javax.inject.Inject; 41 import javax.inject.Singleton; 42 43 /** A data class for one recorded contents. */ 44 @Singleton 45 public class DvrDatabaseHelper extends SQLiteOpenHelper { 46 private static final String TAG = "DvrDatabaseHelper"; 47 private static final boolean DEBUG = false; 48 49 private static final int DATABASE_VERSION = 18; 50 private static final String DB_NAME = "dvr.db"; 51 private static final String NOT_NULL = " NOT NULL"; 52 private static final String PRIMARY_KEY_AUTOINCREMENT = " PRIMARY KEY AUTOINCREMENT"; 53 54 private static final int SQL_DATA_TYPE_LONG = 0; 55 private static final int SQL_DATA_TYPE_INT = 1; 56 private static final int SQL_DATA_TYPE_STRING = 2; 57 58 private static final ColumnInfo[] COLUMNS_SCHEDULES = 59 new ColumnInfo[] { 60 new ColumnInfo(Schedules._ID, SQL_DATA_TYPE_LONG, PRIMARY_KEY_AUTOINCREMENT), 61 new ColumnInfo( 62 Schedules.COLUMN_PRIORITY, 63 SQL_DATA_TYPE_LONG, 64 defaultConstraint(ScheduledRecording.DEFAULT_PRIORITY)), 65 new ColumnInfo(Schedules.COLUMN_TYPE, SQL_DATA_TYPE_STRING, NOT_NULL), 66 new ColumnInfo(Schedules.COLUMN_INPUT_ID, SQL_DATA_TYPE_STRING, NOT_NULL), 67 new ColumnInfo(Schedules.COLUMN_CHANNEL_ID, SQL_DATA_TYPE_LONG, NOT_NULL), 68 new ColumnInfo(Schedules.COLUMN_PROGRAM_ID, SQL_DATA_TYPE_LONG), 69 new ColumnInfo(Schedules.COLUMN_PROGRAM_TITLE, SQL_DATA_TYPE_STRING), 70 new ColumnInfo( 71 Schedules.COLUMN_START_TIME_UTC_MILLIS, 72 SQL_DATA_TYPE_LONG, 73 NOT_NULL), 74 new ColumnInfo( 75 Schedules.COLUMN_END_TIME_UTC_MILLIS, 76 SQL_DATA_TYPE_LONG, 77 NOT_NULL), 78 new ColumnInfo(Schedules.COLUMN_SEASON_NUMBER, SQL_DATA_TYPE_STRING), 79 new ColumnInfo(Schedules.COLUMN_EPISODE_NUMBER, SQL_DATA_TYPE_STRING), 80 new ColumnInfo(Schedules.COLUMN_EPISODE_TITLE, SQL_DATA_TYPE_STRING), 81 new ColumnInfo(Schedules.COLUMN_PROGRAM_DESCRIPTION, SQL_DATA_TYPE_STRING), 82 new ColumnInfo(Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION, SQL_DATA_TYPE_STRING), 83 new ColumnInfo(Schedules.COLUMN_PROGRAM_POST_ART_URI, SQL_DATA_TYPE_STRING), 84 new ColumnInfo(Schedules.COLUMN_PROGRAM_THUMBNAIL_URI, SQL_DATA_TYPE_STRING), 85 new ColumnInfo(Schedules.COLUMN_STATE, SQL_DATA_TYPE_STRING, NOT_NULL), 86 new ColumnInfo( 87 Schedules.COLUMN_FAILED_REASON, 88 SQL_DATA_TYPE_STRING), 89 new ColumnInfo( 90 Schedules.COLUMN_SERIES_RECORDING_ID, 91 SQL_DATA_TYPE_LONG) 92 }; 93 94 private static final ColumnInfo[] COLUMNS_TIME_OFFSET = 95 new ColumnInfo[] { 96 new ColumnInfo( 97 Schedules.COLUMN_START_OFFSET_MILLIS, 98 SQL_DATA_TYPE_LONG, 99 defaultConstraint(ScheduledRecording.DEFAULT_TIME_OFFSET)), 100 new ColumnInfo( 101 Schedules.COLUMN_END_OFFSET_MILLIS, 102 SQL_DATA_TYPE_LONG, 103 defaultConstraint(ScheduledRecording.DEFAULT_TIME_OFFSET)) 104 }; 105 106 private static final ColumnInfo[] COLUMNS_SCHEDULES_WITH_TIME_OFFSET = 107 ObjectArrays.concat(COLUMNS_SCHEDULES, COLUMNS_TIME_OFFSET, ColumnInfo.class); 108 109 @VisibleForTesting 110 static final String SQL_CREATE_SCHEDULES = 111 buildCreateSchedulesSql(Schedules.TABLE_NAME, COLUMNS_SCHEDULES); 112 private static final String SQL_INSERT_SCHEDULES = 113 buildInsertSql(Schedules.TABLE_NAME, COLUMNS_SCHEDULES); 114 private static final String SQL_UPDATE_SCHEDULES = 115 buildUpdateSql(Schedules.TABLE_NAME, COLUMNS_SCHEDULES); 116 117 private static final String SQL_CREATE_SCHEDULES_WITH_TIME_OFFSET = 118 buildCreateSchedulesSql(Schedules.TABLE_NAME, COLUMNS_SCHEDULES_WITH_TIME_OFFSET); 119 private static final String SQL_INSERT_SCHEDULES_WITH_TIME_OFFSET = 120 buildInsertSql(Schedules.TABLE_NAME, COLUMNS_SCHEDULES_WITH_TIME_OFFSET); 121 private static final String SQL_UPDATE_SCHEDULES_WITH_TIME_OFFSET = 122 buildUpdateSql(Schedules.TABLE_NAME, COLUMNS_SCHEDULES_WITH_TIME_OFFSET); 123 124 private static final String SQL_DELETE_SCHEDULES = buildDeleteSql(Schedules.TABLE_NAME); 125 @VisibleForTesting 126 static final String SQL_DROP_SCHEDULES = buildDropSql(Schedules.TABLE_NAME); 127 128 private static final ColumnInfo[] COLUMNS_SERIES_RECORDINGS = 129 new ColumnInfo[] { 130 new ColumnInfo(SeriesRecordings._ID, SQL_DATA_TYPE_LONG, PRIMARY_KEY_AUTOINCREMENT), 131 new ColumnInfo( 132 SeriesRecordings.COLUMN_PRIORITY, 133 SQL_DATA_TYPE_LONG, 134 defaultConstraint(SeriesRecording.DEFAULT_PRIORITY)), 135 new ColumnInfo(SeriesRecordings.COLUMN_TITLE, SQL_DATA_TYPE_STRING, NOT_NULL), 136 new ColumnInfo(SeriesRecordings.COLUMN_SHORT_DESCRIPTION, SQL_DATA_TYPE_STRING), 137 new ColumnInfo(SeriesRecordings.COLUMN_LONG_DESCRIPTION, SQL_DATA_TYPE_STRING), 138 new ColumnInfo(SeriesRecordings.COLUMN_INPUT_ID, SQL_DATA_TYPE_STRING, NOT_NULL), 139 new ColumnInfo(SeriesRecordings.COLUMN_CHANNEL_ID, SQL_DATA_TYPE_LONG, NOT_NULL), 140 new ColumnInfo(SeriesRecordings.COLUMN_SERIES_ID, SQL_DATA_TYPE_STRING, NOT_NULL), 141 new ColumnInfo( 142 SeriesRecordings.COLUMN_START_FROM_SEASON, 143 SQL_DATA_TYPE_INT, 144 defaultConstraint(SeriesRecordings.THE_BEGINNING)), 145 new ColumnInfo( 146 SeriesRecordings.COLUMN_START_FROM_EPISODE, 147 SQL_DATA_TYPE_INT, 148 defaultConstraint(SeriesRecordings.THE_BEGINNING)), 149 new ColumnInfo( 150 SeriesRecordings.COLUMN_CHANNEL_OPTION, 151 SQL_DATA_TYPE_STRING, 152 defaultConstraint(SeriesRecordings.OPTION_CHANNEL_ONE)), 153 new ColumnInfo(SeriesRecordings.COLUMN_CANONICAL_GENRE, SQL_DATA_TYPE_STRING), 154 new ColumnInfo(SeriesRecordings.COLUMN_POSTER_URI, SQL_DATA_TYPE_STRING), 155 new ColumnInfo(SeriesRecordings.COLUMN_PHOTO_URI, SQL_DATA_TYPE_STRING), 156 new ColumnInfo(SeriesRecordings.COLUMN_STATE, SQL_DATA_TYPE_STRING) 157 }; 158 159 @VisibleForTesting 160 static final String SQL_CREATE_SERIES_RECORDINGS = 161 buildCreateSql(SeriesRecordings.TABLE_NAME, COLUMNS_SERIES_RECORDINGS, null); 162 private static final String SQL_INSERT_SERIES_RECORDINGS = 163 buildInsertSql(SeriesRecordings.TABLE_NAME, COLUMNS_SERIES_RECORDINGS); 164 private static final String SQL_UPDATE_SERIES_RECORDINGS = 165 buildUpdateSql(SeriesRecordings.TABLE_NAME, COLUMNS_SERIES_RECORDINGS); 166 private static final String SQL_DELETE_SERIES_RECORDINGS = 167 buildDeleteSql(SeriesRecordings.TABLE_NAME); 168 @VisibleForTesting 169 static final String SQL_DROP_SERIES_RECORDINGS = 170 buildDropSql(SeriesRecordings.TABLE_NAME); 171 172 private final DvrFlags mDvrFlags; 173 defaultConstraint(int value)174 private static String defaultConstraint(int value) { 175 return defaultConstraint(String.valueOf(value)); 176 } 177 defaultConstraint(long value)178 private static String defaultConstraint(long value) { 179 return defaultConstraint(String.valueOf(value)); 180 } 181 defaultConstraint(String value)182 private static String defaultConstraint(String value) { 183 return " DEFAULT " + value; 184 } 185 foreignKeyConstraint( String column, String referenceTable, String referenceColumn)186 private static String foreignKeyConstraint( 187 String column, 188 String referenceTable, 189 String referenceColumn) { 190 return ",FOREIGN KEY(" + column + ") " 191 + "REFERENCES " + referenceTable + "(" + referenceColumn + ") " 192 + "ON UPDATE CASCADE ON DELETE SET NULL"; 193 } 194 buildCreateSchedulesSql(String tableName, ColumnInfo[] columns)195 private static String buildCreateSchedulesSql(String tableName, ColumnInfo[] columns) { 196 return buildCreateSql( 197 tableName, 198 columns, 199 foreignKeyConstraint( 200 Schedules.COLUMN_SERIES_RECORDING_ID, 201 SeriesRecordings.TABLE_NAME, 202 SeriesRecordings._ID)); 203 } 204 buildCreateSql( String tableName, ColumnInfo[] columns, String foreignKeyConstraint)205 private static String buildCreateSql( 206 String tableName, 207 ColumnInfo[] columns, 208 String foreignKeyConstraint) { 209 StringBuilder sb = new StringBuilder(); 210 sb.append("CREATE TABLE ").append(tableName).append("("); 211 boolean appendComma = false; 212 for (ColumnInfo columnInfo : columns) { 213 if (appendComma) { 214 sb.append(","); 215 } 216 appendComma = true; 217 sb.append(columnInfo.name); 218 switch (columnInfo.type) { 219 case SQL_DATA_TYPE_LONG: 220 case SQL_DATA_TYPE_INT: 221 sb.append(" INTEGER"); 222 break; 223 case SQL_DATA_TYPE_STRING: 224 sb.append(" TEXT"); 225 break; 226 } 227 sb.append(columnInfo.constraint); 228 } 229 if (foreignKeyConstraint != null) { 230 sb.append(foreignKeyConstraint); 231 } 232 sb.append(");"); 233 return sb.toString(); 234 } 235 buildSelectSql(ColumnInfo[] columns)236 private static String buildSelectSql(ColumnInfo[] columns) { 237 StringBuilder sb = new StringBuilder(); 238 sb.append(" SELECT "); 239 boolean appendComma = false; 240 for (ColumnInfo columnInfo : columns) { 241 if (appendComma) { 242 sb.append(","); 243 } 244 appendComma = true; 245 sb.append(columnInfo.name); 246 } 247 return sb.toString(); 248 } 249 buildInsertSql(String tableName, ColumnInfo[] columns)250 private static String buildInsertSql(String tableName, ColumnInfo[] columns) { 251 StringBuilder sb = new StringBuilder(); 252 sb.append("INSERT INTO ").append(tableName).append(" ("); 253 boolean appendComma = false; 254 for (ColumnInfo columnInfo : columns) { 255 if (appendComma) { 256 sb.append(","); 257 } 258 appendComma = true; 259 sb.append(columnInfo.name); 260 } 261 sb.append(") VALUES (?"); 262 for (int i = 1; i < columns.length; ++i) { 263 sb.append(",?"); 264 } 265 sb.append(")"); 266 return sb.toString(); 267 } 268 buildUpdateSql(String tableName, ColumnInfo[] columns)269 private static String buildUpdateSql(String tableName, ColumnInfo[] columns) { 270 StringBuilder sb = new StringBuilder(); 271 sb.append("UPDATE ").append(tableName).append(" SET "); 272 boolean appendComma = false; 273 for (ColumnInfo columnInfo : columns) { 274 if (appendComma) { 275 sb.append(","); 276 } 277 appendComma = true; 278 sb.append(columnInfo.name).append("=?"); 279 } 280 sb.append(" WHERE ").append(BaseColumns._ID).append("=?"); 281 return sb.toString(); 282 } 283 buildDeleteSql(String tableName)284 private static String buildDeleteSql(String tableName) { 285 return "DELETE FROM " + tableName + " WHERE " + BaseColumns._ID + "=?"; 286 } 287 buildDropSql(String tableName)288 private static String buildDropSql(String tableName) { 289 return "DROP TABLE IF EXISTS " + tableName; 290 } 291 292 @Inject DvrDatabaseHelper(@pplicationContext Context context, DvrFlags dvrFlags)293 public DvrDatabaseHelper(@ApplicationContext Context context, DvrFlags dvrFlags) { 294 super(context, 295 DB_NAME, 296 null, 297 (dvrFlags.startEarlyEndLateEnabled() ? DATABASE_VERSION + 1 : DATABASE_VERSION)); 298 mDvrFlags = dvrFlags; 299 } 300 301 @Override onConfigure(SQLiteDatabase db)302 public void onConfigure(SQLiteDatabase db) { 303 db.setForeignKeyConstraintsEnabled(true); 304 } 305 306 @Override onCreate(SQLiteDatabase db)307 public void onCreate(SQLiteDatabase db) { 308 if (mDvrFlags.startEarlyEndLateEnabled()) { 309 if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_CREATE_SCHEDULES_WITH_TIME_OFFSET); 310 db.execSQL(SQL_CREATE_SCHEDULES_WITH_TIME_OFFSET); 311 } else { 312 if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_CREATE_SCHEDULES); 313 db.execSQL(SQL_CREATE_SCHEDULES); 314 } 315 if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_CREATE_SERIES_RECORDINGS); 316 db.execSQL(SQL_CREATE_SERIES_RECORDINGS); 317 } 318 319 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)320 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 321 if (oldVersion < 17) { 322 if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_DROP_SCHEDULES); 323 db.execSQL(SQL_DROP_SCHEDULES); 324 if (DEBUG) Log.d(TAG, "Executing SQL: " + SQL_DROP_SERIES_RECORDINGS); 325 db.execSQL(SQL_DROP_SERIES_RECORDINGS); 326 onCreate(db); 327 return; 328 } 329 if (oldVersion < 18) { 330 db.execSQL( 331 "ALTER TABLE " 332 + Schedules.TABLE_NAME 333 + " ADD COLUMN " 334 + Schedules.COLUMN_FAILED_REASON 335 + " TEXT DEFAULT null;"); 336 } 337 if (mDvrFlags.startEarlyEndLateEnabled() && oldVersion < 19) { 338 db.execSQL("ALTER TABLE " + Schedules.TABLE_NAME + " ADD COLUMN " 339 + Schedules.COLUMN_START_OFFSET_MILLIS + " INTEGER NOT NULL DEFAULT '0';"); 340 db.execSQL("ALTER TABLE " + Schedules.TABLE_NAME + " ADD COLUMN " 341 + Schedules.COLUMN_END_OFFSET_MILLIS + " INTEGER NOT NULL DEFAULT '0';"); 342 } 343 } 344 345 @Override onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)346 public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { 347 if (oldVersion > DATABASE_VERSION) { 348 String schedulesBackup = "schedules_backup"; 349 db.execSQL(buildCreateSchedulesSql(schedulesBackup, COLUMNS_SCHEDULES)); 350 db.execSQL("INSERT INTO " + schedulesBackup + 351 buildSelectSql(COLUMNS_SCHEDULES) + " FROM " + Schedules.TABLE_NAME); 352 db.execSQL(SQL_DROP_SCHEDULES); 353 db.execSQL(SQL_CREATE_SCHEDULES); 354 db.execSQL("INSERT INTO " + Schedules.TABLE_NAME + 355 buildSelectSql(COLUMNS_SCHEDULES) + " FROM " + schedulesBackup); 356 db.execSQL(buildDropSql(schedulesBackup)); 357 } 358 } 359 360 /** Handles the query request and returns a {@link Cursor}. */ query(String tableName, String[] projections)361 public Cursor query(String tableName, String[] projections) { 362 SQLiteDatabase db = getReadableDatabase(); 363 SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); 364 builder.setTables(tableName); 365 return builder.query(db, projections, null, null, null, null, null); 366 } 367 368 /** Inserts schedules. */ insertSchedules(ScheduledRecording... scheduledRecordings)369 public void insertSchedules(ScheduledRecording... scheduledRecordings) { 370 SQLiteDatabase db = getWritableDatabase(); 371 db.beginTransaction(); 372 try { 373 if (mDvrFlags.startEarlyEndLateEnabled()) { 374 SQLiteStatement statement = 375 db.compileStatement(SQL_INSERT_SCHEDULES_WITH_TIME_OFFSET); 376 for (ScheduledRecording r : scheduledRecordings) { 377 statement.clearBindings(); 378 ContentValues values = ScheduledRecording.toContentValuesWithTimeOffset(r); 379 bindColumns(statement, COLUMNS_SCHEDULES_WITH_TIME_OFFSET, values); 380 statement.execute(); 381 } 382 } else { 383 SQLiteStatement statement = db.compileStatement(SQL_INSERT_SCHEDULES); 384 for (ScheduledRecording r : scheduledRecordings) { 385 statement.clearBindings(); 386 ContentValues values = ScheduledRecording.toContentValues(r); 387 bindColumns(statement, COLUMNS_SCHEDULES, values); 388 statement.execute(); 389 } 390 } 391 db.setTransactionSuccessful(); 392 } finally { 393 db.endTransaction(); 394 } 395 } 396 397 /** Update schedules. */ updateSchedules(ScheduledRecording... scheduledRecordings)398 public void updateSchedules(ScheduledRecording... scheduledRecordings) { 399 SQLiteDatabase db = getWritableDatabase(); 400 db.beginTransaction(); 401 try { 402 if (mDvrFlags.startEarlyEndLateEnabled()) { 403 SQLiteStatement statement = 404 db.compileStatement(SQL_UPDATE_SCHEDULES_WITH_TIME_OFFSET); 405 for (ScheduledRecording r : scheduledRecordings) { 406 statement.clearBindings(); 407 ContentValues values = ScheduledRecording.toContentValuesWithTimeOffset(r); 408 bindColumns(statement, COLUMNS_SCHEDULES_WITH_TIME_OFFSET, values); 409 statement.bindLong(COLUMNS_SCHEDULES_WITH_TIME_OFFSET.length + 1, r.getId()); 410 statement.execute(); 411 } 412 } else { 413 SQLiteStatement statement = db.compileStatement(SQL_UPDATE_SCHEDULES); 414 for (ScheduledRecording r : scheduledRecordings) { 415 statement.clearBindings(); 416 ContentValues values = ScheduledRecording.toContentValues(r); 417 bindColumns(statement, COLUMNS_SCHEDULES, values); 418 statement.bindLong(COLUMNS_SCHEDULES.length + 1, r.getId()); 419 statement.execute(); 420 } 421 } 422 db.setTransactionSuccessful(); 423 } finally { 424 db.endTransaction(); 425 } 426 } 427 428 /** Delete schedules. */ deleteSchedules(ScheduledRecording... scheduledRecordings)429 public void deleteSchedules(ScheduledRecording... scheduledRecordings) { 430 SQLiteDatabase db = getWritableDatabase(); 431 SQLiteStatement statement = db.compileStatement(SQL_DELETE_SCHEDULES); 432 db.beginTransaction(); 433 try { 434 for (ScheduledRecording r : scheduledRecordings) { 435 statement.clearBindings(); 436 statement.bindLong(1, r.getId()); 437 statement.execute(); 438 } 439 db.setTransactionSuccessful(); 440 } finally { 441 db.endTransaction(); 442 } 443 } 444 445 /** Inserts series recordings. */ insertSeriesRecordings(SeriesRecording... seriesRecordings)446 public void insertSeriesRecordings(SeriesRecording... seriesRecordings) { 447 SQLiteDatabase db = getWritableDatabase(); 448 SQLiteStatement statement = db.compileStatement(SQL_INSERT_SERIES_RECORDINGS); 449 db.beginTransaction(); 450 try { 451 for (SeriesRecording r : seriesRecordings) { 452 statement.clearBindings(); 453 ContentValues values = SeriesRecording.toContentValues(r); 454 bindColumns(statement, COLUMNS_SERIES_RECORDINGS, values); 455 statement.execute(); 456 } 457 db.setTransactionSuccessful(); 458 } finally { 459 db.endTransaction(); 460 } 461 } 462 463 /** Update series recordings. */ updateSeriesRecordings(SeriesRecording... seriesRecordings)464 public void updateSeriesRecordings(SeriesRecording... seriesRecordings) { 465 SQLiteDatabase db = getWritableDatabase(); 466 SQLiteStatement statement = db.compileStatement(SQL_UPDATE_SERIES_RECORDINGS); 467 db.beginTransaction(); 468 try { 469 for (SeriesRecording r : seriesRecordings) { 470 statement.clearBindings(); 471 ContentValues values = SeriesRecording.toContentValues(r); 472 bindColumns(statement, COLUMNS_SERIES_RECORDINGS, values); 473 statement.bindLong(COLUMNS_SERIES_RECORDINGS.length + 1, r.getId()); 474 statement.execute(); 475 } 476 db.setTransactionSuccessful(); 477 } finally { 478 db.endTransaction(); 479 } 480 } 481 482 /** Delete series recordings. */ deleteSeriesRecordings(SeriesRecording... seriesRecordings)483 public void deleteSeriesRecordings(SeriesRecording... seriesRecordings) { 484 SQLiteDatabase db = getWritableDatabase(); 485 SQLiteStatement statement = db.compileStatement(SQL_DELETE_SERIES_RECORDINGS); 486 db.beginTransaction(); 487 try { 488 for (SeriesRecording r : seriesRecordings) { 489 statement.clearBindings(); 490 statement.bindLong(1, r.getId()); 491 statement.execute(); 492 } 493 db.setTransactionSuccessful(); 494 } finally { 495 db.endTransaction(); 496 } 497 } 498 bindColumns( SQLiteStatement statement, ColumnInfo[] columns, ContentValues values)499 private void bindColumns( 500 SQLiteStatement statement, ColumnInfo[] columns, ContentValues values) { 501 for (int i = 0; i < columns.length; ++i) { 502 ColumnInfo columnInfo = columns[i]; 503 Object value = values.get(columnInfo.name); 504 switch (columnInfo.type) { 505 case SQL_DATA_TYPE_LONG: 506 if (value == null) { 507 statement.bindNull(i + 1); 508 } else { 509 statement.bindLong(i + 1, (Long) value); 510 } 511 break; 512 case SQL_DATA_TYPE_INT: 513 if (value == null) { 514 statement.bindNull(i + 1); 515 } else { 516 statement.bindLong(i + 1, (Integer) value); 517 } 518 break; 519 case SQL_DATA_TYPE_STRING: 520 { 521 if (TextUtils.isEmpty((String) value)) { 522 statement.bindNull(i + 1); 523 } else { 524 statement.bindString(i + 1, (String) value); 525 } 526 break; 527 } 528 } 529 } 530 } 531 532 private static class ColumnInfo { 533 final String name; 534 final int type; 535 final String constraint; 536 ColumnInfo(String name, int type)537 ColumnInfo(String name, int type) { 538 this(name, type, ""); 539 } 540 ColumnInfo(String name, int type, String constraint)541 ColumnInfo(String name, int type, String constraint) { 542 this.name = name; 543 this.type = type; 544 this.constraint = constraint; 545 } 546 } 547 } 548