• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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