1 /* 2 * Copyright (C) 2020 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.deskclock.provider 18 19 import android.content.ContentValues 20 import android.content.Context 21 import android.database.Cursor 22 import android.database.SQLException 23 import android.database.sqlite.SQLiteDatabase 24 import android.database.sqlite.SQLiteOpenHelper 25 import android.net.Uri 26 import android.provider.BaseColumns 27 import android.text.TextUtils 28 29 import com.android.deskclock.LogUtils 30 import com.android.deskclock.data.Weekdays 31 import com.android.deskclock.provider.ClockContract.AlarmSettingColumns 32 import com.android.deskclock.provider.ClockContract.AlarmsColumns 33 import com.android.deskclock.provider.ClockContract.InstancesColumns 34 35 import java.util.Calendar 36 37 /** 38 * Helper class for opening the database from multiple providers. Also provides 39 * some common functionality. 40 */ 41 class ClockDatabaseHelper(context: Context) 42 : SQLiteOpenHelper(context, DATABASE_NAME, null, VERSION_8) { 43 onCreatenull44 override fun onCreate(db: SQLiteDatabase) { 45 createAlarmsTable(db) 46 createInstanceTable(db) 47 48 // insert default alarms 49 LogUtils.i("Inserting default alarms") 50 val cs: String = ", " // comma and space 51 val insertMe: String = "INSERT INTO " + ALARMS_TABLE_NAME + " (" + 52 AlarmsColumns.HOUR + cs + 53 AlarmsColumns.MINUTES + cs + 54 AlarmsColumns.DAYS_OF_WEEK + cs + 55 AlarmsColumns.ENABLED + cs + 56 AlarmSettingColumns.VIBRATE + cs + 57 AlarmSettingColumns.LABEL + cs + 58 AlarmSettingColumns.RINGTONE + cs + 59 AlarmsColumns.DELETE_AFTER_USE + ") VALUES " 60 db.execSQL(insertMe + DEFAULT_ALARM_1) 61 db.execSQL(insertMe + DEFAULT_ALARM_2) 62 } 63 onUpgradenull64 override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, currentVersion: Int) { 65 LogUtils.v("Upgrading alarms database from version %d to %d", 66 oldVersion, currentVersion) 67 68 if (oldVersion <= VERSION_7) { 69 // This was not used in VERSION_7 or prior, so we can just drop it. 70 db.execSQL("DROP TABLE IF EXISTS " + SELECTED_CITIES_TABLE_NAME + ";") 71 } 72 73 if (oldVersion <= VERSION_6) { 74 // This was not used in VERSION_6 or prior, so we can just drop it. 75 db.execSQL("DROP TABLE IF EXISTS " + INSTANCES_TABLE_NAME + ";") 76 77 // Create new alarms table and copy over the data 78 createAlarmsTable(db) 79 createInstanceTable(db) 80 81 LogUtils.i("Copying old alarms to new table") 82 val OLD_TABLE_COLUMNS: Array<String> = arrayOf( 83 "_id", 84 "hour", 85 "minutes", 86 "daysofweek", 87 "enabled", 88 "vibrate", 89 "message", 90 "alert" 91 ) 92 val cursor: Cursor? = 93 db.query(OLD_ALARMS_TABLE_NAME, OLD_TABLE_COLUMNS, null, null, null, null, null) 94 val currentTime: Calendar = Calendar.getInstance() 95 while (cursor != null && cursor.moveToNext()) { 96 val alarm = Alarm() 97 alarm.id = cursor.getLong(0) 98 alarm.hour = cursor.getInt(1) 99 alarm.minutes = cursor.getInt(2) 100 alarm.daysOfWeek = Weekdays.fromBits(cursor.getInt(3)) 101 alarm.enabled = cursor.getInt(4) == 1 102 alarm.vibrate = cursor.getInt(5) == 1 103 alarm.label = cursor.getString(6) 104 105 val alertString: String = cursor.getString(7) 106 if ("silent" == alertString) { 107 alarm.alert = AlarmSettingColumns.NO_RINGTONE_URI 108 } else { 109 alarm.alert = if (TextUtils.isEmpty(alertString)) { 110 null 111 } else { 112 Uri.parse(alertString) 113 } 114 } 115 116 // Save new version of alarm and create alarm instance for it 117 db.insert(ALARMS_TABLE_NAME, null, Alarm.createContentValues(alarm)) 118 if (alarm.enabled) { 119 val newInstance: AlarmInstance = alarm.createInstanceAfter(currentTime) 120 db.insert(INSTANCES_TABLE_NAME, null, 121 AlarmInstance.createContentValues(newInstance)) 122 } 123 } 124 125 LogUtils.i("Dropping old alarm table") 126 db.execSQL("DROP TABLE IF EXISTS " + OLD_ALARMS_TABLE_NAME + ";") 127 } 128 } 129 fixAlarmInsertnull130 fun fixAlarmInsert(values: ContentValues): Long { 131 // Why are we doing this? Is this not a programming bug if we try to 132 // insert an already used id? 133 val db: SQLiteDatabase = getWritableDatabase() 134 db.beginTransaction() 135 val rowId: Long 136 try { 137 // Check if we are trying to re-use an existing id. 138 val value = values.get(BaseColumns._ID) 139 if (value != null) { 140 val id: Long = value as Long 141 if (id > -1) { 142 val columns: Array<String> = arrayOf(BaseColumns._ID) 143 val selection: String = BaseColumns._ID + " = ?" 144 val selectionArgs: Array<String> = arrayOf(id.toString()) 145 val cursor: Cursor = 146 db.query(ALARMS_TABLE_NAME, columns, 147 selection, selectionArgs, null, null, null) 148 if (cursor.moveToFirst()) { 149 // Record exists. Remove the id so sqlite can generate a new one. 150 values.putNull(BaseColumns._ID) 151 } 152 } 153 } 154 155 rowId = db.insert(ALARMS_TABLE_NAME, AlarmSettingColumns.RINGTONE, values) 156 db.setTransactionSuccessful() 157 } finally { 158 db.endTransaction() 159 } 160 161 if (rowId < 0) { 162 throw SQLException("Failed to insert row") 163 } 164 LogUtils.v("Added alarm rowId = " + rowId) 165 166 return rowId 167 } 168 169 companion object { 170 /** 171 * Original Clock Database. 172 **/ 173 private const val VERSION_5: Int = 5 174 175 /** 176 * Added alarm_instances table 177 * Added selected_cities table 178 * Added DELETE_AFTER_USE column to alarms table 179 */ 180 private const val VERSION_6: Int = 6 181 182 /** 183 * Added alarm settings to instance table. 184 */ 185 private const val VERSION_7: Int = 7 186 187 /** 188 * Removed selected_cities table. 189 */ 190 private const val VERSION_8: Int = 8 191 192 // This creates a default alarm at 8:30 for every Mon,Tue,Wed,Thu,Fri 193 private const val DEFAULT_ALARM_1: String = "(8, 30, 31, 0, 1, '', NULL, 0);" 194 195 // This creates a default alarm at 9:30 for every Sat,Sun 196 private const val DEFAULT_ALARM_2: String = "(9, 00, 96, 0, 1, '', NULL, 0);" 197 198 // Database and table names 199 const val DATABASE_NAME: String = "alarms.db" 200 const val OLD_ALARMS_TABLE_NAME: String = "alarms" 201 const val ALARMS_TABLE_NAME: String = "alarm_templates" 202 const val INSTANCES_TABLE_NAME: String = "alarm_instances" 203 private const val SELECTED_CITIES_TABLE_NAME: String = "selected_cities" 204 createAlarmsTablenull205 private fun createAlarmsTable(db: SQLiteDatabase) { 206 db.execSQL("CREATE TABLE " + ALARMS_TABLE_NAME + " (" + 207 BaseColumns._ID + " INTEGER PRIMARY KEY," + 208 AlarmsColumns.HOUR + " INTEGER NOT NULL, " + 209 AlarmsColumns.MINUTES + " INTEGER NOT NULL, " + 210 AlarmsColumns.DAYS_OF_WEEK + " INTEGER NOT NULL, " + 211 AlarmsColumns.ENABLED + " INTEGER NOT NULL, " + 212 AlarmSettingColumns.VIBRATE + " INTEGER NOT NULL, " + 213 AlarmSettingColumns.LABEL + " TEXT NOT NULL, " + 214 AlarmSettingColumns.RINGTONE + " TEXT, " + 215 AlarmsColumns.DELETE_AFTER_USE + " INTEGER NOT NULL DEFAULT 0);") 216 LogUtils.i("Alarms Table created") 217 } 218 createInstanceTablenull219 private fun createInstanceTable(db: SQLiteDatabase) { 220 db.execSQL("CREATE TABLE " + INSTANCES_TABLE_NAME + " (" + 221 BaseColumns._ID + " INTEGER PRIMARY KEY," + 222 InstancesColumns.YEAR + " INTEGER NOT NULL, " + 223 InstancesColumns.MONTH + " INTEGER NOT NULL, " + 224 InstancesColumns.DAY + " INTEGER NOT NULL, " + 225 InstancesColumns.HOUR + " INTEGER NOT NULL, " + 226 InstancesColumns.MINUTES + " INTEGER NOT NULL, " + 227 AlarmSettingColumns.VIBRATE + " INTEGER NOT NULL, " + 228 AlarmSettingColumns.LABEL + " TEXT NOT NULL, " + 229 AlarmSettingColumns.RINGTONE + " TEXT, " + 230 InstancesColumns.ALARM_STATE + " INTEGER NOT NULL, " + 231 InstancesColumns.ALARM_ID + " INTEGER REFERENCES " + 232 ALARMS_TABLE_NAME + "(" + BaseColumns._ID + ") " + 233 "ON UPDATE CASCADE ON DELETE CASCADE" + 234 ");") 235 LogUtils.i("Instance table created") 236 } 237 } 238 } 239