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 = 104) 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 .allowMainThreadQueries() 61 .build(); 62 } 63 64 /** 65 * Create a {@link MetadataDatabase} database without migration, database 66 * would be reset if any load failure happens 67 * 68 * @param context the Context to create database 69 * @return the created {@link MetadataDatabase} 70 */ createDatabaseWithoutMigration(Context context)71 public static MetadataDatabase createDatabaseWithoutMigration(Context context) { 72 return Room.databaseBuilder(context, 73 MetadataDatabase.class, DATABASE_NAME) 74 .fallbackToDestructiveMigration() 75 .allowMainThreadQueries() 76 .build(); 77 } 78 79 /** 80 * Insert a {@link Metadata} to metadata table 81 * 82 * @param metadata the data wish to put into storage 83 */ insert(Metadata... metadata)84 public void insert(Metadata... metadata) { 85 mMetadataDao().insert(metadata); 86 } 87 88 /** 89 * Load all data from metadata table as a {@link List} of {@link Metadata} 90 * 91 * @return a {@link List} of {@link Metadata} 92 */ load()93 public List<Metadata> load() { 94 return mMetadataDao().load(); 95 } 96 97 /** 98 * Delete one of the {@link Metadata} contained in the metadata table 99 * 100 * @param address the address of Metadata to delete 101 */ delete(String address)102 public void delete(String address) { 103 mMetadataDao().delete(address); 104 } 105 106 /** 107 * Clear metadata table. 108 */ deleteAll()109 public void deleteAll() { 110 mMetadataDao().deleteAll(); 111 } 112 113 @VisibleForTesting 114 static final Migration MIGRATION_100_101 = new Migration(100, 101) { 115 @Override 116 public void migrate(SupportSQLiteDatabase database) { 117 database.execSQL("ALTER TABLE metadata ADD COLUMN `pbap_client_priority` INTEGER"); 118 } 119 }; 120 121 @VisibleForTesting 122 static final Migration MIGRATION_101_102 = new Migration(101, 102) { 123 @Override 124 public void migrate(SupportSQLiteDatabase database) { 125 database.execSQL("CREATE TABLE IF NOT EXISTS `metadata_tmp` (" 126 + "`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, " 127 + "`a2dpSupportsOptionalCodecs` INTEGER NOT NULL, " 128 + "`a2dpOptionalCodecsEnabled` INTEGER NOT NULL, " 129 + "`a2dp_priority` INTEGER, `a2dp_sink_priority` INTEGER, " 130 + "`hfp_priority` INTEGER, `hfp_client_priority` INTEGER, " 131 + "`hid_host_priority` INTEGER, `pan_priority` INTEGER, " 132 + "`pbap_priority` INTEGER, `pbap_client_priority` INTEGER, " 133 + "`map_priority` INTEGER, `sap_priority` INTEGER, " 134 + "`hearing_aid_priority` INTEGER, `map_client_priority` INTEGER, " 135 + "`manufacturer_name` BLOB, `model_name` BLOB, `software_version` BLOB, " 136 + "`hardware_version` BLOB, `companion_app` BLOB, `main_icon` BLOB, " 137 + "`is_untethered_headset` BLOB, `untethered_left_icon` BLOB, " 138 + "`untethered_right_icon` BLOB, `untethered_case_icon` BLOB, " 139 + "`untethered_left_battery` BLOB, `untethered_right_battery` BLOB, " 140 + "`untethered_case_battery` BLOB, `untethered_left_charging` BLOB, " 141 + "`untethered_right_charging` BLOB, `untethered_case_charging` BLOB, " 142 + "`enhanced_settings_ui_uri` BLOB, PRIMARY KEY(`address`))"); 143 144 database.execSQL("INSERT INTO metadata_tmp (" 145 + "address, migrated, a2dpSupportsOptionalCodecs, a2dpOptionalCodecsEnabled, " 146 + "a2dp_priority, a2dp_sink_priority, hfp_priority, hfp_client_priority, " 147 + "hid_host_priority, pan_priority, pbap_priority, pbap_client_priority, " 148 + "map_priority, sap_priority, hearing_aid_priority, map_client_priority, " 149 + "manufacturer_name, model_name, software_version, hardware_version, " 150 + "companion_app, main_icon, is_untethered_headset, untethered_left_icon, " 151 + "untethered_right_icon, untethered_case_icon, untethered_left_battery, " 152 + "untethered_right_battery, untethered_case_battery, " 153 + "untethered_left_charging, untethered_right_charging, " 154 + "untethered_case_charging, enhanced_settings_ui_uri) " 155 + "SELECT " 156 + "address, migrated, a2dpSupportsOptionalCodecs, a2dpOptionalCodecsEnabled, " 157 + "a2dp_priority, a2dp_sink_priority, hfp_priority, hfp_client_priority, " 158 + "hid_host_priority, pan_priority, pbap_priority, pbap_client_priority, " 159 + "map_priority, sap_priority, hearing_aid_priority, map_client_priority, " 160 + "CAST (manufacturer_name AS BLOB), " 161 + "CAST (model_name AS BLOB), " 162 + "CAST (software_version AS BLOB), " 163 + "CAST (hardware_version AS BLOB), " 164 + "CAST (companion_app AS BLOB), " 165 + "CAST (main_icon AS BLOB), " 166 + "CAST (is_unthethered_headset AS BLOB), " 167 + "CAST (unthethered_left_icon AS BLOB), " 168 + "CAST (unthethered_right_icon AS BLOB), " 169 + "CAST (unthethered_case_icon AS BLOB), " 170 + "CAST (unthethered_left_battery AS BLOB), " 171 + "CAST (unthethered_right_battery AS BLOB), " 172 + "CAST (unthethered_case_battery AS BLOB), " 173 + "CAST (unthethered_left_charging AS BLOB), " 174 + "CAST (unthethered_right_charging AS BLOB), " 175 + "CAST (unthethered_case_charging AS BLOB), " 176 + "CAST (enhanced_settings_ui_uri AS BLOB)" 177 + "FROM metadata"); 178 179 database.execSQL("DROP TABLE `metadata`"); 180 database.execSQL("ALTER TABLE `metadata_tmp` RENAME TO `metadata`"); 181 } 182 }; 183 184 @VisibleForTesting 185 static final Migration MIGRATION_102_103 = new Migration(102, 103) { 186 @Override 187 public void migrate(SupportSQLiteDatabase database) { 188 try { 189 database.execSQL("CREATE TABLE IF NOT EXISTS `metadata_tmp` (" 190 + "`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, " 191 + "`a2dpSupportsOptionalCodecs` INTEGER NOT NULL, " 192 + "`a2dpOptionalCodecsEnabled` INTEGER NOT NULL, " 193 + "`a2dp_connection_policy` INTEGER, " 194 + "`a2dp_sink_connection_policy` INTEGER, `hfp_connection_policy` INTEGER, " 195 + "`hfp_client_connection_policy` INTEGER, " 196 + "`hid_host_connection_policy` INTEGER, `pan_connection_policy` INTEGER, " 197 + "`pbap_connection_policy` INTEGER, " 198 + "`pbap_client_connection_policy` INTEGER, " 199 + "`map_connection_policy` INTEGER, `sap_connection_policy` INTEGER, " 200 + "`hearing_aid_connection_policy` INTEGER, " 201 + "`map_client_connection_policy` INTEGER, `manufacturer_name` BLOB, " 202 + "`model_name` BLOB, `software_version` BLOB, `hardware_version` BLOB, " 203 + "`companion_app` BLOB, `main_icon` BLOB, `is_untethered_headset` BLOB, " 204 + "`untethered_left_icon` BLOB, `untethered_right_icon` BLOB, " 205 + "`untethered_case_icon` BLOB, `untethered_left_battery` BLOB, " 206 + "`untethered_right_battery` BLOB, `untethered_case_battery` BLOB, " 207 + "`untethered_left_charging` BLOB, `untethered_right_charging` BLOB, " 208 + "`untethered_case_charging` BLOB, `enhanced_settings_ui_uri` BLOB, " 209 + "PRIMARY KEY(`address`))"); 210 211 database.execSQL("INSERT INTO metadata_tmp (" 212 + "address, migrated, a2dpSupportsOptionalCodecs, " 213 + "a2dpOptionalCodecsEnabled, a2dp_connection_policy, " 214 + "a2dp_sink_connection_policy, hfp_connection_policy," 215 + "hfp_client_connection_policy, hid_host_connection_policy," 216 + "pan_connection_policy, pbap_connection_policy," 217 + "pbap_client_connection_policy, map_connection_policy, " 218 + "sap_connection_policy, hearing_aid_connection_policy, " 219 + "map_client_connection_policy, manufacturer_name, model_name, " 220 + "software_version, hardware_version, companion_app, main_icon, " 221 + "is_untethered_headset, untethered_left_icon, untethered_right_icon, " 222 + "untethered_case_icon, untethered_left_battery, " 223 + "untethered_right_battery, untethered_case_battery, " 224 + "untethered_left_charging, untethered_right_charging, " 225 + "untethered_case_charging, enhanced_settings_ui_uri) " 226 + "SELECT " 227 + "address, migrated, a2dpSupportsOptionalCodecs, " 228 + "a2dpOptionalCodecsEnabled, a2dp_priority, a2dp_sink_priority, " 229 + "hfp_priority, hfp_client_priority, hid_host_priority, pan_priority, " 230 + "pbap_priority, pbap_client_priority, map_priority, sap_priority, " 231 + "hearing_aid_priority, map_client_priority, " 232 + "CAST (manufacturer_name AS BLOB), " 233 + "CAST (model_name AS BLOB), " 234 + "CAST (software_version AS BLOB), " 235 + "CAST (hardware_version AS BLOB), " 236 + "CAST (companion_app AS BLOB), " 237 + "CAST (main_icon AS BLOB), " 238 + "CAST (is_untethered_headset AS BLOB), " 239 + "CAST (untethered_left_icon AS BLOB), " 240 + "CAST (untethered_right_icon AS BLOB), " 241 + "CAST (untethered_case_icon AS BLOB), " 242 + "CAST (untethered_left_battery AS BLOB), " 243 + "CAST (untethered_right_battery AS BLOB), " 244 + "CAST (untethered_case_battery AS BLOB), " 245 + "CAST (untethered_left_charging AS BLOB), " 246 + "CAST (untethered_right_charging AS BLOB), " 247 + "CAST (untethered_case_charging AS BLOB), " 248 + "CAST (enhanced_settings_ui_uri AS BLOB)" 249 + "FROM metadata"); 250 251 database.execSQL("UPDATE metadata_tmp SET a2dp_connection_policy = 100 " 252 + "WHERE a2dp_connection_policy = 1000"); 253 database.execSQL("UPDATE metadata_tmp SET a2dp_sink_connection_policy = 100 " 254 + "WHERE a2dp_sink_connection_policy = 1000"); 255 database.execSQL("UPDATE metadata_tmp SET hfp_connection_policy = 100 " 256 + "WHERE hfp_connection_policy = 1000"); 257 database.execSQL("UPDATE metadata_tmp SET hfp_client_connection_policy = 100 " 258 + "WHERE hfp_client_connection_policy = 1000"); 259 database.execSQL("UPDATE metadata_tmp SET hid_host_connection_policy = 100 " 260 + "WHERE hid_host_connection_policy = 1000"); 261 database.execSQL("UPDATE metadata_tmp SET pan_connection_policy = 100 " 262 + "WHERE pan_connection_policy = 1000"); 263 database.execSQL("UPDATE metadata_tmp SET pbap_connection_policy = 100 " 264 + "WHERE pbap_connection_policy = 1000"); 265 database.execSQL("UPDATE metadata_tmp SET pbap_client_connection_policy = 100 " 266 + "WHERE pbap_client_connection_policy = 1000"); 267 database.execSQL("UPDATE metadata_tmp SET map_connection_policy = 100 " 268 + "WHERE map_connection_policy = 1000"); 269 database.execSQL("UPDATE metadata_tmp SET sap_connection_policy = 100 " 270 + "WHERE sap_connection_policy = 1000"); 271 database.execSQL("UPDATE metadata_tmp SET hearing_aid_connection_policy = 100 " 272 + "WHERE hearing_aid_connection_policy = 1000"); 273 database.execSQL("UPDATE metadata_tmp SET map_client_connection_policy = 100 " 274 + "WHERE map_client_connection_policy = 1000"); 275 276 database.execSQL("DROP TABLE `metadata`"); 277 database.execSQL("ALTER TABLE `metadata_tmp` RENAME TO `metadata`"); 278 } catch (SQLException ex) { 279 // Check if user has new schema, but is just missing the version update 280 Cursor cursor = database.query("SELECT * FROM metadata"); 281 if (cursor == null || cursor.getColumnIndex("a2dp_connection_policy") == -1) { 282 throw ex; 283 } 284 } 285 } 286 }; 287 288 @VisibleForTesting 289 static final Migration MIGRATION_103_104 = new Migration(103, 104) { 290 @Override 291 public void migrate(SupportSQLiteDatabase database) { 292 try { 293 database.execSQL("ALTER TABLE metadata ADD COLUMN `last_active_time` " 294 + "INTEGER NOT NULL DEFAULT -1"); 295 database.execSQL("ALTER TABLE metadata ADD COLUMN `is_active_a2dp_device` " 296 + "INTEGER NOT NULL DEFAULT 0"); 297 } catch (SQLException ex) { 298 // Check if user has new schema, but is just missing the version update 299 Cursor cursor = database.query("SELECT * FROM metadata"); 300 if (cursor == null || cursor.getColumnIndex("last_active_time") == -1) { 301 throw ex; 302 } 303 } 304 } 305 }; 306 } 307