1 /* 2 * Copyright 2019 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.bluetooth.btservice.storage; 18 19 import android.content.Context; 20 import android.database.Cursor; 21 import android.database.SQLException; 22 23 import androidx.room.Database; 24 import androidx.room.Room; 25 import androidx.room.RoomDatabase; 26 import androidx.room.migration.Migration; 27 import androidx.sqlite.db.SupportSQLiteDatabase; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 31 import java.util.List; 32 33 /** 34 * MetadataDatabase is a Room database stores Bluetooth persistence data 35 */ 36 @Database(entities = {Metadata.class}, version = 106) 37 public abstract class MetadataDatabase extends RoomDatabase { 38 /** 39 * The metadata database file name 40 */ 41 public static final String DATABASE_NAME = "bluetooth_db"; 42 43 static int sCurrentConnectionNumber = 0; 44 mMetadataDao()45 protected abstract MetadataDao mMetadataDao(); 46 47 /** 48 * Create a {@link MetadataDatabase} database with migrations 49 * 50 * @param context the Context to create database 51 * @return the created {@link MetadataDatabase} 52 */ createDatabase(Context context)53 public static MetadataDatabase createDatabase(Context context) { 54 return Room.databaseBuilder(context, 55 MetadataDatabase.class, DATABASE_NAME) 56 .addMigrations(MIGRATION_100_101) 57 .addMigrations(MIGRATION_101_102) 58 .addMigrations(MIGRATION_102_103) 59 .addMigrations(MIGRATION_103_104) 60 .addMigrations(MIGRATION_104_105) 61 .addMigrations(MIGRATION_105_106) 62 .allowMainThreadQueries() 63 .build(); 64 } 65 66 /** 67 * Create a {@link MetadataDatabase} database without migration, database 68 * would be reset if any load failure happens 69 * 70 * @param context the Context to create database 71 * @return the created {@link MetadataDatabase} 72 */ createDatabaseWithoutMigration(Context context)73 public static MetadataDatabase createDatabaseWithoutMigration(Context context) { 74 return Room.databaseBuilder(context, 75 MetadataDatabase.class, DATABASE_NAME) 76 .fallbackToDestructiveMigration() 77 .allowMainThreadQueries() 78 .build(); 79 } 80 81 /** 82 * Insert a {@link Metadata} to metadata table 83 * 84 * @param metadata the data wish to put into storage 85 */ insert(Metadata... metadata)86 public void insert(Metadata... metadata) { 87 mMetadataDao().insert(metadata); 88 } 89 90 /** 91 * Load all data from metadata table as a {@link List} of {@link Metadata} 92 * 93 * @return a {@link List} of {@link Metadata} 94 */ load()95 public List<Metadata> load() { 96 return mMetadataDao().load(); 97 } 98 99 /** 100 * Delete one of the {@link Metadata} contained in the metadata table 101 * 102 * @param address the address of Metadata to delete 103 */ delete(String address)104 public void delete(String address) { 105 mMetadataDao().delete(address); 106 } 107 108 /** 109 * Clear metadata table. 110 */ deleteAll()111 public void deleteAll() { 112 mMetadataDao().deleteAll(); 113 } 114 115 @VisibleForTesting 116 static final Migration MIGRATION_100_101 = new Migration(100, 101) { 117 @Override 118 public void migrate(SupportSQLiteDatabase database) { 119 database.execSQL("ALTER TABLE metadata ADD COLUMN `pbap_client_priority` INTEGER"); 120 } 121 }; 122 123 @VisibleForTesting 124 static final Migration MIGRATION_101_102 = new Migration(101, 102) { 125 @Override 126 public void migrate(SupportSQLiteDatabase database) { 127 database.execSQL("CREATE TABLE IF NOT EXISTS `metadata_tmp` (" 128 + "`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, " 129 + "`a2dpSupportsOptionalCodecs` INTEGER NOT NULL, " 130 + "`a2dpOptionalCodecsEnabled` INTEGER NOT NULL, " 131 + "`a2dp_priority` INTEGER, `a2dp_sink_priority` INTEGER, " 132 + "`hfp_priority` INTEGER, `hfp_client_priority` INTEGER, " 133 + "`hid_host_priority` INTEGER, `pan_priority` INTEGER, " 134 + "`pbap_priority` INTEGER, `pbap_client_priority` INTEGER, " 135 + "`map_priority` INTEGER, `sap_priority` INTEGER, " 136 + "`hearing_aid_priority` INTEGER, `map_client_priority` INTEGER, " 137 + "`manufacturer_name` BLOB, `model_name` BLOB, `software_version` BLOB, " 138 + "`hardware_version` BLOB, `companion_app` BLOB, `main_icon` BLOB, " 139 + "`is_untethered_headset` BLOB, `untethered_left_icon` BLOB, " 140 + "`untethered_right_icon` BLOB, `untethered_case_icon` BLOB, " 141 + "`untethered_left_battery` BLOB, `untethered_right_battery` BLOB, " 142 + "`untethered_case_battery` BLOB, `untethered_left_charging` BLOB, " 143 + "`untethered_right_charging` BLOB, `untethered_case_charging` BLOB, " 144 + "`enhanced_settings_ui_uri` BLOB, PRIMARY KEY(`address`))"); 145 146 database.execSQL("INSERT INTO metadata_tmp (" 147 + "address, migrated, a2dpSupportsOptionalCodecs, a2dpOptionalCodecsEnabled, " 148 + "a2dp_priority, a2dp_sink_priority, hfp_priority, hfp_client_priority, " 149 + "hid_host_priority, pan_priority, pbap_priority, pbap_client_priority, " 150 + "map_priority, sap_priority, hearing_aid_priority, map_client_priority, " 151 + "manufacturer_name, model_name, software_version, hardware_version, " 152 + "companion_app, main_icon, is_untethered_headset, untethered_left_icon, " 153 + "untethered_right_icon, untethered_case_icon, untethered_left_battery, " 154 + "untethered_right_battery, untethered_case_battery, " 155 + "untethered_left_charging, untethered_right_charging, " 156 + "untethered_case_charging, enhanced_settings_ui_uri) " 157 + "SELECT " 158 + "address, migrated, a2dpSupportsOptionalCodecs, a2dpOptionalCodecsEnabled, " 159 + "a2dp_priority, a2dp_sink_priority, hfp_priority, hfp_client_priority, " 160 + "hid_host_priority, pan_priority, pbap_priority, pbap_client_priority, " 161 + "map_priority, sap_priority, hearing_aid_priority, map_client_priority, " 162 + "CAST (manufacturer_name AS BLOB), " 163 + "CAST (model_name AS BLOB), " 164 + "CAST (software_version AS BLOB), " 165 + "CAST (hardware_version AS BLOB), " 166 + "CAST (companion_app AS BLOB), " 167 + "CAST (main_icon AS BLOB), " 168 + "CAST (is_unthethered_headset AS BLOB), " 169 + "CAST (unthethered_left_icon AS BLOB), " 170 + "CAST (unthethered_right_icon AS BLOB), " 171 + "CAST (unthethered_case_icon AS BLOB), " 172 + "CAST (unthethered_left_battery AS BLOB), " 173 + "CAST (unthethered_right_battery AS BLOB), " 174 + "CAST (unthethered_case_battery AS BLOB), " 175 + "CAST (unthethered_left_charging AS BLOB), " 176 + "CAST (unthethered_right_charging AS BLOB), " 177 + "CAST (unthethered_case_charging AS BLOB), " 178 + "CAST (enhanced_settings_ui_uri AS BLOB)" 179 + "FROM metadata"); 180 181 database.execSQL("DROP TABLE `metadata`"); 182 database.execSQL("ALTER TABLE `metadata_tmp` RENAME TO `metadata`"); 183 } 184 }; 185 186 @VisibleForTesting 187 static final Migration MIGRATION_102_103 = new Migration(102, 103) { 188 @Override 189 public void migrate(SupportSQLiteDatabase database) { 190 try { 191 database.execSQL("CREATE TABLE IF NOT EXISTS `metadata_tmp` (" 192 + "`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, " 193 + "`a2dpSupportsOptionalCodecs` INTEGER NOT NULL, " 194 + "`a2dpOptionalCodecsEnabled` INTEGER NOT NULL, " 195 + "`a2dp_connection_policy` INTEGER, " 196 + "`a2dp_sink_connection_policy` INTEGER, `hfp_connection_policy` INTEGER, " 197 + "`hfp_client_connection_policy` INTEGER, " 198 + "`hid_host_connection_policy` INTEGER, `pan_connection_policy` INTEGER, " 199 + "`pbap_connection_policy` INTEGER, " 200 + "`pbap_client_connection_policy` INTEGER, " 201 + "`map_connection_policy` INTEGER, `sap_connection_policy` INTEGER, " 202 + "`hearing_aid_connection_policy` INTEGER, " 203 + "`map_client_connection_policy` INTEGER, `manufacturer_name` BLOB, " 204 + "`model_name` BLOB, `software_version` BLOB, `hardware_version` BLOB, " 205 + "`companion_app` BLOB, `main_icon` BLOB, `is_untethered_headset` BLOB, " 206 + "`untethered_left_icon` BLOB, `untethered_right_icon` BLOB, " 207 + "`untethered_case_icon` BLOB, `untethered_left_battery` BLOB, " 208 + "`untethered_right_battery` BLOB, `untethered_case_battery` BLOB, " 209 + "`untethered_left_charging` BLOB, `untethered_right_charging` BLOB, " 210 + "`untethered_case_charging` BLOB, `enhanced_settings_ui_uri` BLOB, " 211 + "PRIMARY KEY(`address`))"); 212 213 database.execSQL("INSERT INTO metadata_tmp (" 214 + "address, migrated, a2dpSupportsOptionalCodecs, " 215 + "a2dpOptionalCodecsEnabled, a2dp_connection_policy, " 216 + "a2dp_sink_connection_policy, hfp_connection_policy," 217 + "hfp_client_connection_policy, hid_host_connection_policy," 218 + "pan_connection_policy, pbap_connection_policy," 219 + "pbap_client_connection_policy, map_connection_policy, " 220 + "sap_connection_policy, hearing_aid_connection_policy, " 221 + "map_client_connection_policy, manufacturer_name, model_name, " 222 + "software_version, hardware_version, companion_app, main_icon, " 223 + "is_untethered_headset, untethered_left_icon, untethered_right_icon, " 224 + "untethered_case_icon, untethered_left_battery, " 225 + "untethered_right_battery, untethered_case_battery, " 226 + "untethered_left_charging, untethered_right_charging, " 227 + "untethered_case_charging, enhanced_settings_ui_uri) " 228 + "SELECT " 229 + "address, migrated, a2dpSupportsOptionalCodecs, " 230 + "a2dpOptionalCodecsEnabled, a2dp_priority, a2dp_sink_priority, " 231 + "hfp_priority, hfp_client_priority, hid_host_priority, pan_priority, " 232 + "pbap_priority, pbap_client_priority, map_priority, sap_priority, " 233 + "hearing_aid_priority, map_client_priority, " 234 + "CAST (manufacturer_name AS BLOB), " 235 + "CAST (model_name AS BLOB), " 236 + "CAST (software_version AS BLOB), " 237 + "CAST (hardware_version AS BLOB), " 238 + "CAST (companion_app AS BLOB), " 239 + "CAST (main_icon AS BLOB), " 240 + "CAST (is_untethered_headset AS BLOB), " 241 + "CAST (untethered_left_icon AS BLOB), " 242 + "CAST (untethered_right_icon AS BLOB), " 243 + "CAST (untethered_case_icon AS BLOB), " 244 + "CAST (untethered_left_battery AS BLOB), " 245 + "CAST (untethered_right_battery AS BLOB), " 246 + "CAST (untethered_case_battery AS BLOB), " 247 + "CAST (untethered_left_charging AS BLOB), " 248 + "CAST (untethered_right_charging AS BLOB), " 249 + "CAST (untethered_case_charging AS BLOB), " 250 + "CAST (enhanced_settings_ui_uri AS BLOB)" 251 + "FROM metadata"); 252 253 database.execSQL("UPDATE metadata_tmp SET a2dp_connection_policy = 100 " 254 + "WHERE a2dp_connection_policy = 1000"); 255 database.execSQL("UPDATE metadata_tmp SET a2dp_sink_connection_policy = 100 " 256 + "WHERE a2dp_sink_connection_policy = 1000"); 257 database.execSQL("UPDATE metadata_tmp SET hfp_connection_policy = 100 " 258 + "WHERE hfp_connection_policy = 1000"); 259 database.execSQL("UPDATE metadata_tmp SET hfp_client_connection_policy = 100 " 260 + "WHERE hfp_client_connection_policy = 1000"); 261 database.execSQL("UPDATE metadata_tmp SET hid_host_connection_policy = 100 " 262 + "WHERE hid_host_connection_policy = 1000"); 263 database.execSQL("UPDATE metadata_tmp SET pan_connection_policy = 100 " 264 + "WHERE pan_connection_policy = 1000"); 265 database.execSQL("UPDATE metadata_tmp SET pbap_connection_policy = 100 " 266 + "WHERE pbap_connection_policy = 1000"); 267 database.execSQL("UPDATE metadata_tmp SET pbap_client_connection_policy = 100 " 268 + "WHERE pbap_client_connection_policy = 1000"); 269 database.execSQL("UPDATE metadata_tmp SET map_connection_policy = 100 " 270 + "WHERE map_connection_policy = 1000"); 271 database.execSQL("UPDATE metadata_tmp SET sap_connection_policy = 100 " 272 + "WHERE sap_connection_policy = 1000"); 273 database.execSQL("UPDATE metadata_tmp SET hearing_aid_connection_policy = 100 " 274 + "WHERE hearing_aid_connection_policy = 1000"); 275 database.execSQL("UPDATE metadata_tmp SET map_client_connection_policy = 100 " 276 + "WHERE map_client_connection_policy = 1000"); 277 278 database.execSQL("DROP TABLE `metadata`"); 279 database.execSQL("ALTER TABLE `metadata_tmp` RENAME TO `metadata`"); 280 } catch (SQLException ex) { 281 // Check if user has new schema, but is just missing the version update 282 Cursor cursor = database.query("SELECT * FROM metadata"); 283 if (cursor == null || cursor.getColumnIndex("a2dp_connection_policy") == -1) { 284 throw ex; 285 } 286 } 287 } 288 }; 289 290 @VisibleForTesting 291 static final Migration MIGRATION_103_104 = new Migration(103, 104) { 292 @Override 293 public void migrate(SupportSQLiteDatabase database) { 294 try { 295 database.execSQL("ALTER TABLE metadata ADD COLUMN `last_active_time` " 296 + "INTEGER NOT NULL DEFAULT -1"); 297 database.execSQL("ALTER TABLE metadata ADD COLUMN `is_active_a2dp_device` " 298 + "INTEGER NOT NULL DEFAULT 0"); 299 } catch (SQLException ex) { 300 // Check if user has new schema, but is just missing the version update 301 Cursor cursor = database.query("SELECT * FROM metadata"); 302 if (cursor == null || cursor.getColumnIndex("last_active_time") == -1) { 303 throw ex; 304 } 305 } 306 } 307 }; 308 309 @VisibleForTesting 310 static final Migration MIGRATION_104_105 = new Migration(104, 105) { 311 @Override 312 public void migrate(SupportSQLiteDatabase database) { 313 try { 314 database.execSQL("ALTER TABLE metadata ADD COLUMN `device_type` BLOB"); 315 database.execSQL("ALTER TABLE metadata ADD COLUMN `main_battery` BLOB"); 316 database.execSQL("ALTER TABLE metadata ADD COLUMN `main_charging` BLOB"); 317 database.execSQL("ALTER TABLE metadata ADD COLUMN " 318 + "`main_low_battery_threshold` BLOB"); 319 database.execSQL("ALTER TABLE metadata ADD COLUMN " 320 + "`untethered_left_low_battery_threshold` BLOB"); 321 database.execSQL("ALTER TABLE metadata ADD COLUMN " 322 + "`untethered_right_low_battery_threshold` BLOB"); 323 database.execSQL("ALTER TABLE metadata ADD COLUMN " 324 + "`untethered_case_low_battery_threshold` BLOB"); 325 } catch (SQLException ex) { 326 // Check if user has new schema, but is just missing the version update 327 Cursor cursor = database.query("SELECT * FROM metadata"); 328 if (cursor == null || cursor.getColumnIndex("device_type") == -1) { 329 throw ex; 330 } 331 } 332 } 333 }; 334 335 @VisibleForTesting 336 static final Migration MIGRATION_105_106 = new Migration(105, 106) { 337 @Override 338 public void migrate(SupportSQLiteDatabase database) { 339 try { 340 database.execSQL("ALTER TABLE metadata ADD COLUMN `le_audio_connection_policy` " 341 + "INTEGER DEFAULT 100"); 342 } catch (SQLException ex) { 343 // Check if user has new schema, but is just missing the version update 344 Cursor cursor = database.query("SELECT * FROM metadata"); 345 if (cursor == null || cursor.getColumnIndex("le_audio_connection_policy") == -1) { 346 throw ex; 347 } 348 } 349 } 350 }; 351 } 352