36 static final int DB_VERSION = 16;
37 private static final String DB_NAME =
"bookmarks.db";
41 System.loadLibrary(
"sqlcipher");
48 public static AppDatabase getInstance(Context context)
56 byte[] key = getOrCreateDbKey(context);
57 migrateUnencryptedIfNeeded(context, key);
59 instance = Room.databaseBuilder(context.getApplicationContext(),
61 .openHelperFactory(
new SupportOpenHelperFactory(key))
62 .addMigrations(MIGRATION_10_11)
63 .addMigrations(MIGRATION_11_12)
64 .addMigrations(MIGRATION_12_13)
65 .addMigrations(MIGRATION_13_14)
66 .addMigrations(MIGRATION_14_15)
67 .addMigrations(MIGRATION_15_16)
75 private static byte[] getOrCreateDbKey(Context context)
79 return KeystoreHelper.getInstance(context).getOrCreateDbKey();
81 catch (KeystoreHelper.KeystoreException e)
83 throw new RuntimeException(
"Cannot obtain database encryption key", e);
88 private static void migrateUnencryptedIfNeeded(Context context,
byte[] key)
90 File dbFile = context.getDatabasePath(DB_NAME);
94 String path = dbFile.getAbsolutePath();
99 SQLiteDatabase.openDatabase(path, key,
null, SQLiteDatabase.OPEN_READONLY,
null,
null)
103 catch (Exception ignored)
107 SQLiteDatabase db =
null;
111 db = SQLiteDatabase.openDatabase(path,
new byte[0],
null, SQLiteDatabase.OPEN_READWRITE,
117 SQLiteDatabase.deleteDatabase(dbFile);
123 String tmpPath = path +
".migrating";
124 File tmpFile =
new File(tmpPath);
125 SQLiteDatabase.deleteDatabase(tmpFile);
131 .openDatabase(tmpPath, key,
null,
132 SQLiteDatabase.OPEN_READWRITE | SQLiteDatabase.CREATE_IF_NECESSARY,
136 catch (Exception ignored)
144 db.execSQL(
"ATTACH DATABASE '" + tmpPath +
"' AS encrypted KEY X'" + toHex(key) +
146 db.rawQuery(
"SELECT sqlcipher_export('encrypted')",
null).moveToFirst();
147 db.execSQL(
"DETACH DATABASE encrypted");
155 SQLiteDatabase.deleteDatabase(dbFile);
156 if (!tmpFile.renameTo(dbFile))
157 throw new RuntimeException(
"Could not replace database file after encryption");
161 SQLiteDatabase.deleteDatabase(tmpFile);
162 throw new RuntimeException(
"Failed to encrypt existing database", e);
166 private static String toHex(
byte[] bytes)
168 StringBuilder sb =
new StringBuilder(bytes.length * 2);
170 sb.append(String.format(java.util.Locale.US,
"%02x", b));
171 return sb.toString();
174 private static final Migration MIGRATION_15_16 =
new Migration(15, 16) {
175 @Override
public void migrate(@NonNull SupportSQLiteDatabase db)
178 "ALTER TABLE 'bookmarks' ADD 'redirect_printer' INTEGER NOT NULL DEFAULT false;");
182 private static final Migration MIGRATION_14_15 =
new Migration(14, 15) {
183 @Override
public void migrate(@NonNull SupportSQLiteDatabase db)
185 db.execSQL(
"ALTER TABLE 'bookmarks' ADD 'scale_mode' TEXT NOT NULL DEFAULT '100';");
186 db.execSQL(
"ALTER TABLE 'bookmarks' ADD 'scale_desktop' INTEGER NOT NULL DEFAULT 100;");
187 db.execSQL(
"ALTER TABLE 'bookmarks' ADD 'scale_device' INTEGER NOT NULL DEFAULT 100;");
189 "ALTER TABLE 'bookmarks' ADD 'vmconnect_mode' INTEGER NOT NULL DEFAULT false;");
190 db.execSQL(
"ALTER TABLE 'bookmarks' ADD 'vmconnect_guid' TEXT NOT NULL DEFAULT '';");
194 private static final Migration MIGRATION_13_14 =
new Migration(13, 14) {
195 @Override
public void migrate(@NonNull SupportSQLiteDatabase db)
197 final String[] columns =
new String[] {
198 "label",
"hostname",
"username",
"password",
199 "domain",
"gateway_hostname",
"gateway_username",
"gateway_password",
200 "gateway_domain",
"remote_program",
"work_dir",
"loadbalanceinfo"
202 for (String column : columns)
204 db.execSQL(
"ALTER TABLE 'bookmarks' ADD '" + column +
205 "_with_default' TEXT NOT NULL DEFAULT '';");
206 db.execSQL(
"UPDATE bookmarks SET " + column +
"_with_default = " + column);
207 db.execSQL(
"ALTER TABLE 'bookmarks' DROP '" + column +
"';");
208 db.execSQL(
"ALTER TABLE 'bookmarks' RENAME COLUMN '" + column +
209 "_with_default' to '" + column +
"';");
212 final String[] debugColumns =
new String[] {
"debug_level" };
213 for (String column : debugColumns)
215 db.execSQL(
"ALTER TABLE 'bookmarks' ADD '" + column +
216 "_with_default' TEXT NOT NULL DEFAULT 'INFO';");
217 db.execSQL(
"UPDATE bookmarks SET " + column +
"_with_default = " + column);
218 db.execSQL(
"ALTER TABLE 'bookmarks' DROP '" + column +
"';");
219 db.execSQL(
"ALTER TABLE 'bookmarks' RENAME COLUMN '" + column +
220 "_with_default' to '" + column +
"';");
222 final Map<String, Integer> intColumns =
new HashMap<>();
223 intColumns.put(
"port", 3389);
224 intColumns.put(
"colors", 32);
225 intColumns.put(
"resolution", -1);
226 intColumns.put(
"width", 0);
227 intColumns.put(
"height", 0);
228 intColumns.put(
"gateway_port", 443);
229 intColumns.put(
"redirect_sound", 0);
230 intColumns.put(
"security", 0);
231 intColumns.put(
"tlsSecLevel", -1);
232 intColumns.put(
"tlsMinLevel", -1);
234 for (Map.Entry<String, Integer> column : intColumns.entrySet())
236 db.execSQL(
"ALTER TABLE 'bookmarks' ADD '" + column.getKey() +
237 "_with_default' INTEGER NOT NULL DEFAULT " +
238 column.getValue().toString() +
";");
239 db.execSQL(
"UPDATE bookmarks SET " + column.getKey() +
240 "_with_default = " + column.getKey());
241 db.execSQL(
"ALTER TABLE 'bookmarks' DROP '" + column.getKey() +
"';");
242 db.execSQL(
"ALTER TABLE 'bookmarks' RENAME COLUMN '" + column.getKey() +
243 "_with_default' to '" + column.getKey() +
"';");
246 final Map<String, Boolean> boolColumns =
new HashMap<>();
247 boolColumns.put(
"perf_remotefx",
true);
248 boolColumns.put(
"perf_gfx",
true);
249 boolColumns.put(
"perf_gfx_h264",
true);
250 boolColumns.put(
"perf_wallpaper",
true);
251 boolColumns.put(
"perf_theming",
true);
252 boolColumns.put(
"perf_full_window_drag",
true);
253 boolColumns.put(
"perf_menu_animations",
true);
254 boolColumns.put(
"perf_font_smoothing",
true);
255 boolColumns.put(
"perf_desktop_composition",
true);
256 boolColumns.put(
"enable_gateway_settings",
false);
257 boolColumns.put(
"redirect_sdcard",
false);
258 boolColumns.put(
"redirect_microphone",
false);
259 boolColumns.put(
"console_mode",
false);
260 boolColumns.put(
"async_channel",
false);
261 boolColumns.put(
"async_update",
false);
263 for (Map.Entry<String, Boolean> column : boolColumns.entrySet())
265 db.execSQL(
"ALTER TABLE 'bookmarks' ADD '" + column.getKey() +
266 "_with_default' INTEGER NOT NULL DEFAULT " +
267 column.getValue().toString() +
";");
268 db.execSQL(
"UPDATE bookmarks SET " + column.getKey() +
269 "_with_default = " + column.getKey());
270 db.execSQL(
"ALTER TABLE 'bookmarks' DROP '" + column.getKey() +
"';");
271 db.execSQL(
"ALTER TABLE 'bookmarks' RENAME COLUMN '" + column.getKey() +
272 "_with_default' to '" + column.getKey() +
"';");
277 private static final Migration MIGRATION_12_13 =
new Migration(12, 13) {
278 @Override
public void migrate(@NonNull SupportSQLiteDatabase db)
280 db.execSQL(
"ALTER TABLE 'bookmarks' ADD 'loadbalanceinfo' TEXT NOT NULL DEFAULT '';");
284 private static final Migration MIGRATION_11_12 =
new Migration(11, 12) {
285 @Override
public void migrate(@NonNull SupportSQLiteDatabase db)
287 db.execSQL(
"ALTER TABLE 'bookmarks' ADD 'tlsSecLevel' INTEGER NOT NULL CONSTRAINT "
289 +
"CHECK (tlsSecLevel >= -1 AND tlsSecLevel <= 5) DEFAULT -1;");
290 db.execSQL(
"ALTER TABLE 'bookmarks' ADD 'tlsMinLevel' INTEGER NOT NULL CONSTRAINT "
292 +
"CHECK (tlsMinLevel >= -1) DEFAULT -1;");
293 final String[] list = {
"screen_3g_colors",
294 "screen_3g_resolution",
302 "perf_3g_full_window_drag",
303 "perf_3g_menu_animations",
304 "perf_3g_font_smoothing",
305 "perf_3g_desktop_composition",
306 "enable_3g_settings" };
308 for (String s : list)
310 db.execSQL(
"ALTER TABLE 'bookmarks' DROP COLUMN '" + s +
"';");
317 private static final Migration MIGRATION_10_11 =
new Migration(10, 11) {
318 @Override
public void migrate(@NonNull SupportSQLiteDatabase db)
320 db.execSQL(
"CREATE TABLE IF NOT EXISTS `bookmarks` ("
321 +
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
327 +
"`port` INTEGER NOT NULL,"
328 +
"`colors` INTEGER NOT NULL,"
329 +
"`resolution` INTEGER NOT NULL,"
330 +
"`width` INTEGER NOT NULL,"
331 +
"`height` INTEGER NOT NULL,"
332 +
"`perf_remotefx` INTEGER NOT NULL,"
333 +
"`perf_gfx` INTEGER NOT NULL,"
334 +
"`perf_gfx_h264` INTEGER NOT NULL,"
335 +
"`perf_wallpaper` INTEGER NOT NULL,"
336 +
"`perf_theming` INTEGER NOT NULL,"
337 +
"`perf_full_window_drag` INTEGER NOT NULL,"
338 +
"`perf_menu_animations` INTEGER NOT NULL,"
339 +
"`perf_font_smoothing` INTEGER NOT NULL,"
340 +
"`perf_desktop_composition` INTEGER NOT NULL,"
341 +
"`screen_3g_colors` INTEGER NOT NULL,"
342 +
"`screen_3g_resolution` INTEGER NOT NULL,"
343 +
"`screen_3g_width` INTEGER NOT NULL,"
344 +
"`screen_3g_height` INTEGER NOT NULL,"
345 +
"`perf_3g_remotefx` INTEGER NOT NULL,"
346 +
"`perf_3g_gfx` INTEGER NOT NULL,"
347 +
"`perf_3g_gfx_h264` INTEGER NOT NULL,"
348 +
"`perf_3g_wallpaper` INTEGER NOT NULL,"
349 +
"`perf_3g_theming` INTEGER NOT NULL,"
350 +
"`perf_3g_full_window_drag` INTEGER NOT NULL,"
351 +
"`perf_3g_menu_animations` INTEGER NOT NULL,"
352 +
"`perf_3g_font_smoothing` INTEGER NOT NULL,"
353 +
"`perf_3g_desktop_composition` INTEGER NOT NULL,"
354 +
"`enable_3g_settings` INTEGER NOT NULL,"
355 +
"`enable_gateway_settings` INTEGER NOT NULL,"
356 +
"`gateway_hostname` TEXT,"
357 +
"`gateway_port` INTEGER NOT NULL,"
358 +
"`gateway_username` TEXT,"
359 +
"`gateway_password` TEXT,"
360 +
"`gateway_domain` TEXT,"
361 +
"`redirect_sdcard` INTEGER NOT NULL,"
362 +
"`redirect_sound` INTEGER NOT NULL,"
363 +
"`redirect_microphone` INTEGER NOT NULL,"
364 +
"`security` INTEGER NOT NULL,"
365 +
"`remote_program` TEXT,"
367 +
"`console_mode` INTEGER NOT NULL,"
368 +
"`debug_level` TEXT,"
369 +
"`async_channel` INTEGER NOT NULL,"
370 +
"`async_update` INTEGER NOT NULL"
375 "INSERT INTO bookmarks ("
376 +
" id, label, hostname, username, password, domain, port,"
377 +
" colors, resolution, width, height,"
378 +
" perf_remotefx, perf_gfx, perf_gfx_h264, perf_wallpaper, perf_theming,"
379 +
" perf_full_window_drag, perf_menu_animations, perf_font_smoothing, "
380 +
"perf_desktop_composition,"
381 +
" screen_3g_colors, screen_3g_resolution, screen_3g_width, screen_3g_height,"
382 +
" perf_3g_remotefx, perf_3g_gfx, perf_3g_gfx_h264, perf_3g_wallpaper, "
384 +
" perf_3g_full_window_drag, perf_3g_menu_animations, perf_3g_font_smoothing, "
385 +
"perf_3g_desktop_composition,"
386 +
" enable_3g_settings, enable_gateway_settings,"
387 +
" gateway_hostname, gateway_port, gateway_username, gateway_password, "
389 +
" redirect_sdcard, redirect_sound, redirect_microphone,"
390 +
" security, remote_program, work_dir, console_mode,"
391 +
" debug_level, async_channel, async_update"
393 +
" b._id, IFNULL(b.label, ''), IFNULL(b.hostname, ''), IFNULL(b.username, ''), "
394 +
"b.password, b.domain,"
395 +
" IFNULL(CAST(NULLIF(b.port, '') AS INTEGER), 3389),"
396 +
" IFNULL(s.colors, 32), IFNULL(s.resolution, -1), IFNULL(s.width, 0), "
397 +
"IFNULL(s.height, 0),"
398 +
" IFNULL(p.perf_remotefx, 0), IFNULL(p.perf_gfx, 1), IFNULL(p.perf_gfx_h264, "
399 +
"0), IFNULL(p.perf_wallpaper, 0), IFNULL(p.perf_theming, 0),"
400 +
" IFNULL(p.perf_full_window_drag, 0), IFNULL(p.perf_menu_animations, 0), "
401 +
"IFNULL(p.perf_font_smoothing, 0), IFNULL(p.perf_desktop_composition, 0),"
402 +
" IFNULL(s3.colors, 16), IFNULL(s3.resolution, -1), IFNULL(s3.width, 0), "
403 +
"IFNULL(s3.height, 0),"
404 +
" IFNULL(p3.perf_remotefx, 0), IFNULL(p3.perf_gfx, 0), IFNULL(p3.perf_gfx_h264, "
405 +
"0), IFNULL(p3.perf_wallpaper, 0), IFNULL(p3.perf_theming, 0),"
406 +
" IFNULL(p3.perf_full_window_drag, 0), IFNULL(p3.perf_menu_animations, 0), "
407 +
"IFNULL(p3.perf_font_smoothing, 0), IFNULL(p3.perf_desktop_composition, 0),"
408 +
" IFNULL(b.enable_3g_settings, 0), IFNULL(b.enable_gateway_settings, 0),"
409 +
" b.gateway_hostname, IFNULL(b.gateway_port, 443), b.gateway_username, "
410 +
"b.gateway_password, b.gateway_domain,"
411 +
" IFNULL(b.redirect_sdcard, 0), IFNULL(b.redirect_sound, 0), "
412 +
"IFNULL(b.redirect_microphone, 0),"
414 " IFNULL(b.security, 0), b.remote_program, b.work_dir, IFNULL(b.console_mode, 0),"
415 +
" IFNULL(b.debug_level, 'INFO'), IFNULL(b.async_channel, 0), "
416 +
"IFNULL(b.async_update, 0)"
417 +
" FROM tbl_manual_bookmarks b"
418 +
" LEFT JOIN tbl_screen_settings s ON s._id = b.screen_settings"
419 +
" LEFT JOIN tbl_screen_settings s3 ON s3._id = b.screen_3g"
420 +
" LEFT JOIN tbl_performance_flags p ON p._id = b.performance_flags"
421 +
" LEFT JOIN tbl_performance_flags p3 ON p3._id = b.performance_3g");
423 db.execSQL(
"DROP TABLE IF EXISTS tbl_manual_bookmarks");
424 db.execSQL(
"DROP TABLE IF EXISTS tbl_screen_settings");
425 db.execSQL(
"DROP TABLE IF EXISTS tbl_performance_flags");