2 * Copyright 2018-2023 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
6 * Privacy Browser Android is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser Android is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser Android. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser.helpers
22 import android.content.ContentValues
23 import android.content.Context
24 import android.database.DatabaseUtils
25 import android.database.sqlite.SQLiteDatabase
27 import androidx.preference.PreferenceManager
29 import com.stoutner.privacybrowser.R
32 import java.io.FileInputStream
33 import java.io.FileOutputStream
34 import java.io.InputStream
35 import java.io.OutputStream
37 // Define the public constants.
38 const val EXPORT_SUCCESSFUL = "A"
39 const val IMPORT_SUCCESSFUL = "B"
40 const val IMPORT_EXPORT_SCHEMA_VERSION = 17
42 // Define the private class constants.
43 private const val ALLOW_SCREENSHOTS = "allow_screenshots"
44 private const val AMP_REDIRECTS = "amp_redirects"
45 private const val APP_THEME = "app_theme"
46 private const val BOTTOM_APP_BAR = "bottom_app_bar"
47 private const val CLEAR_CACHE = "clear_cache"
48 private const val CLEAR_COOKIES = "clear_cookies"
49 private const val CLEAR_DOM_STORAGE = "clear_dom_storage"
50 private const val CLEAR_EVERYTHING = "clear_everything"
51 private const val CLEAR_FORM_DATA = "clear_form_data" // Clear form data can be removed once the minimum API >= 26.
52 private const val CLEAR_LOGCAT = "clear_logcat"
53 private const val CUSTOM_USER_AGENT = "custom_user_agent"
54 private const val DISPLAY_ADDITIONAL_APP_BAR_ICONS = "display_additional_app_bar_icons"
55 private const val DISPLAY_WEBPAGE_IMAGES = "display_webpage_images"
56 private const val DOM_STORAGE = "dom_storage"
57 private const val DOWNLOAD_WITH_EXTERNAL_APP = "download_with_external_app"
58 private const val EASYLIST = "easylist"
59 private const val EASYPRIVACY = "easyprivacy"
60 private const val FANBOYS_ANNOYANCE_LIST = "fanboys_annoyance_list"
61 private const val FANBOYS_SOCIAL_BLOCKING_LIST = "fanboys_social_blocking_list"
62 private const val FULL_SCREEN_BROWSING_MODE = "full_screen_browsing_mode"
63 private const val HIDE_APP_BAR = "hide_app_bar"
64 private const val HOMEPAGE = "homepage"
65 private const val INCOGNITO_MODE = "incognito_mode"
66 private const val JAVASCRIPT = "javascript"
67 private const val OPEN_INTENTS_IN_NEW_TAB = "open_intents_in_new_tab"
68 private const val PREFERENCES_BLOCK_ALL_THIRD_PARTY_REQUESTS = "block_all_third_party_requests"
69 private const val PREFERENCES_FONT_SIZE = "font_size"
70 private const val PREFERENCES_TABLE = "preferences"
71 private const val PREFERENCES_USER_AGENT = "user_agent"
72 private const val PROXY = "proxy"
73 private const val PROXY_CUSTOM_URL = "proxy_custom_url"
74 private const val SAVE_FORM_DATA = "save_form_data"
75 private const val SEARCH = "search"
76 private const val SEARCH_CUSTOM_URL = "search_custom_url"
77 private const val SCROLL_APP_BAR = "scroll_app_bar"
78 private const val PREFERENCES_SWIPE_TO_REFRESH = "swipe_to_refresh"
79 private const val TRACKING_QUERIES = "tracking_queries"
80 private const val ULTRAPRIVACY = "ultraprivacy"
82 class ImportExportDatabaseHelper {
83 fun importUnencrypted(importFileInputStream: InputStream, context: Context): String {
85 // Create a temporary import file.
86 val temporaryImportFile = File.createTempFile("temporary_import_file", null, context.cacheDir)
88 // The file may be copied directly in Kotlin using `File.copyTo`. <https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/java.io.-file/copy-to.html>
89 // It can be copied in Android using `Files.copy` once the minimum API >= 26.
90 // <https://developer.android.com/reference/java/nio/file/Files#copy(java.nio.file.Path,%20java.nio.file.Path,%20java.nio.file.CopyOption...)>
91 // However, the file cannot be acquired from the content URI until the minimum API >= 29. <https://developer.android.com/reference/kotlin/android/content/ContentResolver#openfile>
93 // Create a temporary file output stream.
94 val temporaryImportFileOutputStream = FileOutputStream(temporaryImportFile)
96 // Create a transfer byte array.
97 val transferByteArray = ByteArray(1024)
99 // Create an integer to track the number of bytes read.
102 // Copy the import file to the temporary import file.
103 while (importFileInputStream.read(transferByteArray).also { bytesRead = it } > 0) {
104 temporaryImportFileOutputStream.write(transferByteArray, 0, bytesRead)
107 // Flush the temporary import file output stream.
108 temporaryImportFileOutputStream.flush()
110 // Close the file streams.
111 importFileInputStream.close()
112 temporaryImportFileOutputStream.close()
115 // Get a handle for the shared preference.
116 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
118 // Open the import database. Once the minimum API >= 27 the file can be opened directly without using the string.
119 val importDatabase = SQLiteDatabase.openDatabase(temporaryImportFile.toString(), null, SQLiteDatabase.OPEN_READWRITE)
121 // Get the database version.
122 val importDatabaseVersion = importDatabase.version
124 // Upgrade from schema version 1, first used in Privacy Browser 2.13, to schema version 2, first used in Privacy Browser 2.14.
125 // Previously this upgrade added `download_with_external_app` to the Preferences table. But that is now removed in schema version 10.
127 // Upgrade from schema version 2, first used in Privacy Browser 2.14, to schema version 3, first used in Privacy Browser 2.15.
128 if (importDatabaseVersion < 3) {
129 // `default_font_size` was renamed `font_size`.
130 // Once the SQLite version is >= 3.25.0 (Android API >= 30) `ALTER TABLE RENAME COLUMN` can be used. <https://www.sqlite.org/lang_altertable.html> <https://www.sqlite.org/changes.html>
131 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
132 // In the meantime, a new column must be created with the new name. There is no need to delete the old column on the temporary import database.
134 // Create the new font size column.
135 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $PREFERENCES_FONT_SIZE TEXT")
137 // Populate the preferences table with the current font size value.
138 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $PREFERENCES_FONT_SIZE = default_font_size")
141 // Upgrade from schema version 3, first used in Privacy Browser 2.15, to schema version 4, first used in Privacy Browser 2.16.
142 if (importDatabaseVersion < 4) {
143 // Add the Pinned IP Addresses columns to the domains table.
144 importDatabase.execSQL("ALTER TABLE $DOMAINS_TABLE ADD COLUMN $PINNED_IP_ADDRESSES BOOLEAN")
145 importDatabase.execSQL("ALTER TABLE $DOMAINS_TABLE ADD COLUMN $IP_ADDRESSES TEXT")
148 // Upgrade from schema version 4, first used in Privacy Browser 2.16, to schema version 5, first used in Privacy Browser 2.17.
149 if (importDatabaseVersion < 5) {
150 // Add the hide and scroll app bar columns to the preferences table.
151 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $HIDE_APP_BAR BOOLEAN")
152 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $SCROLL_APP_BAR BOOLEAN")
154 // Get the current hide and scroll app bar settings.
155 val hideAppBar = sharedPreferences.getBoolean(HIDE_APP_BAR, true)
156 val scrollAppBar = sharedPreferences.getBoolean(SCROLL_APP_BAR, true)
158 // Populate the preferences table with the current app bar values.
159 // This can switch to using the variables directly once the minimum API >= 30. <https://www.sqlite.org/datatype3.html#boolean_datatype>
160 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
162 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $HIDE_APP_BAR = 1")
164 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $HIDE_APP_BAR = 0")
167 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $SCROLL_APP_BAR = 1")
169 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $SCROLL_APP_BAR = 0")
172 // Upgrade from schema version 5, first used in Privacy Browser 2.17, to schema version 6, first used in Privacy Browser 3.0.
173 if (importDatabaseVersion < 6) {
174 // Add the open intents in new tab column to the preferences table.
175 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $OPEN_INTENTS_IN_NEW_TAB BOOLEAN")
177 // Get the current open intents in new tab preference.
178 val openIntentsInNewTab = sharedPreferences.getBoolean(OPEN_INTENTS_IN_NEW_TAB, true)
180 // Populate the preferences table with the current open intents value.
181 // This can switch to using the variables directly once the API >= 30. <https://www.sqlite.org/datatype3.html#boolean_datatype>
182 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
183 if (openIntentsInNewTab)
184 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $OPEN_INTENTS_IN_NEW_TAB = 1")
186 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $OPEN_INTENTS_IN_NEW_TAB = 0")
189 // Upgrade from schema version 6, first used in Privacy Browser 3.0, to schema version 7, first used in Privacy Browser 3.1.
190 if (importDatabaseVersion < 7) {
191 // Previously this upgrade added `facebook_click_ids` to the Preferences table. But that is now removed in schema version 15.
193 // Add the wide viewport column to the domains table.
194 importDatabase.execSQL("ALTER TABLE $DOMAINS_TABLE ADD COLUMN $WIDE_VIEWPORT INTEGER")
196 // Add the Google Analytics, Twitter AMP redirects, and wide viewport columns to the preferences table.
197 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN google_analytics BOOLEAN")
198 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN twitter_amp_redirects BOOLEAN")
199 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $WIDE_VIEWPORT BOOLEAN")
201 // Get the current preference values.
202 val trackingQueries = sharedPreferences.getBoolean(TRACKING_QUERIES, true)
203 val ampRedirects = sharedPreferences.getBoolean(AMP_REDIRECTS, true)
204 val wideViewport = sharedPreferences.getBoolean(WIDE_VIEWPORT, true)
206 // Populate the preferences with the current Tracking Queries value. Google Analytics was renamed Tracking Queries in schema version 15.
207 // This can switch to using the variables directly once the API >= 30. <https://www.sqlite.org/datatype3.html#boolean_datatype>
208 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
210 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET google_analytics = 1")
212 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET google_analytics = 0")
214 // Populate the preferences table with the current AMP Redirects value. Twitter AMP Redirects was renamed AMP Redirects in schema version 15.
216 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET twitter_amp_redirects = 1")
218 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET twitter_amp_redirects = 0")
220 // Populate the preferences table with the current wide viewport value.
222 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $WIDE_VIEWPORT = 1")
224 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $WIDE_VIEWPORT = 0")
227 // Upgrade from schema version 7, first used in Privacy Browser 3.1, to schema version 8, first used in Privacy Browser 3.2.
228 if (importDatabaseVersion < 8) {
229 // Add the UltraList column to the tables.
230 importDatabase.execSQL("ALTER TABLE $DOMAINS_TABLE ADD COLUMN $ULTRALIST INTEGER")
231 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $ULTRALIST BOOLEAN")
233 // Get the current preference values.
234 val ultraList = sharedPreferences.getBoolean(ULTRALIST, true)
236 // Populate the tables with the current UltraList value.
237 // This can switch to using the variables directly once the API >= 30. <https://www.sqlite.org/datatype3.html#boolean_datatype>
238 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
240 importDatabase.execSQL("UPDATE $DOMAINS_TABLE SET $ULTRALIST = 1")
241 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $ULTRALIST = 1")
243 importDatabase.execSQL("UPDATE $DOMAINS_TABLE SET $ULTRALIST = 0")
244 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $ULTRALIST = 0")
248 // Upgrade from schema version 8, first used in Privacy Browser 3.2, to schema version 9, first used in Privacy Browser 3.3.
249 if (importDatabaseVersion < 9) {
250 // Add the new proxy columns to the preferences table.
251 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $PROXY TEXT")
252 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $PROXY_CUSTOM_URL TEXT")
254 // Get the current proxy values.
255 val proxy = sharedPreferences.getString(PROXY, context.getString(R.string.proxy_default_value))
256 var proxyCustomUrl = sharedPreferences.getString(PROXY_CUSTOM_URL, context.getString(R.string.proxy_custom_url_default_value))
258 // SQL escape the proxy custom URL string.
259 proxyCustomUrl = DatabaseUtils.sqlEscapeString(proxyCustomUrl)
261 // Populate the preferences table with the current proxy values. The proxy custom URL does not need to be surrounded by `'` because it was SLQ escaped above.
262 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $PROXY = '$proxy'")
263 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $PROXY_CUSTOM_URL = $proxyCustomUrl")
266 // Upgrade from schema version 9, first used in Privacy Browser 3.3, to schema version 10, first used in Privacy Browser 3.4.
267 // Previously this upgrade added `download_location` and `download_custom_location` to the Preferences table. But they were removed in schema version 13.
269 // Upgrade from schema version 10, first used in Privacy Browser 3.4, to schema version 11, first used in Privacy Browser 3.5.
270 if (importDatabaseVersion < 11) {
271 // Add the app theme column to the preferences table.
272 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $APP_THEME TEXT")
274 // Get a cursor for the dark theme preference.
275 val darkThemePreferencesCursor = importDatabase.rawQuery("SELECT dark_theme FROM $PREFERENCES_TABLE", null)
277 // Move to the first entry.
278 darkThemePreferencesCursor.moveToFirst()
280 // Get the old dark theme value, which is in column 0.
281 val darkTheme = darkThemePreferencesCursor.getInt(0)
283 // Close the dark theme preference cursor.
284 darkThemePreferencesCursor.close()
286 // Get the system default string.
287 val systemDefault = context.getString(R.string.app_theme_default_value)
289 // Get the theme entry values string array.
290 val appThemeEntryValuesStringArray: Array<String> = context.resources.getStringArray(R.array.app_theme_entry_values)
292 // Get the dark string.
293 val dark = appThemeEntryValuesStringArray[2]
295 // Populate the app theme according to the old dark theme preference.
296 if (darkTheme == 0) { // A light theme was selected.
297 // Set the app theme to be the system default.
298 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $APP_THEME = '$systemDefault'")
299 } else { // A dark theme was selected.
300 // Set the app theme to be dark.
301 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $APP_THEME = '$dark'")
304 // Add the WebView theme to the domains table. This defaults to 0, which is `System default`, so a separate step isn't needed to populate the database.
305 importDatabase.execSQL("ALTER TABLE $DOMAINS_TABLE ADD COLUMN $WEBVIEW_THEME INTEGER")
307 // Add the WebView theme to the preferences table.
308 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $WEBVIEW_THEME TEXT")
310 // Get the WebView theme default value string.
311 val webViewThemeDefaultValue = context.getString(R.string.webview_theme_default_value)
313 // Set the WebView theme in the preferences table to be the default.
314 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $WEBVIEW_THEME = '$webViewThemeDefaultValue'")
317 // Upgrade from schema version 11, first used in Privacy Browser 3.5, to schema version 12, first used in Privacy Browser 3.6.
318 if (importDatabaseVersion < 12) {
319 // Add the clear logcat column to the preferences table.
320 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $CLEAR_LOGCAT BOOLEAN")
322 // Get the current clear logcat value.
323 val clearLogcat = sharedPreferences.getBoolean(CLEAR_LOGCAT, true)
325 // Populate the preferences table with the current clear logcat value.
326 // This can switch to using the variables directly once the API >= 30. <https://www.sqlite.org/datatype3.html#boolean_datatype>
327 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
329 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $CLEAR_LOGCAT = 1")
331 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $CLEAR_LOGCAT = 0")
334 // Upgrade from schema version 12, first used in Privacy Browser 3.6, to schema version 13, first used in Privacy Browser 3.7.
335 // Do nothing. `download_location` and `download_custom_location` were removed from the preferences table, but they can be left in the temporary import database without issue.
337 // Upgrade from schema version 13, first used in Privacy Browser 3.7, to schema version 14, first used in Privacy Browser 3.8.
338 if (importDatabaseVersion < 14) {
339 // `enabledthirdpartycookies` was removed from the domains table. `do_not_track` and `third_party_cookies` were removed from the preferences table.
340 // There is no need to delete the columns as they will simply be ignored by the import.
342 // `enablefirstpartycookies` was renamed `cookies`.
343 // Once the SQLite version is >= 3.25.0 (Android API >= 30) `ALTER TABLE RENAME COLUMN` can be used. <https://www.sqlite.org/lang_altertable.html> <https://www.sqlite.org/changes.html>
344 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
345 // In the meantime, a new column must be created with the new name. There is no need to delete the old column on the temporary import database.
347 // Create the new cookies columns.
348 importDatabase.execSQL("ALTER TABLE $DOMAINS_TABLE ADD COLUMN $COOKIES INTEGER")
349 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $COOKIES BOOLEAN")
351 // Copy the data from the old cookies columns to the new ones.
352 importDatabase.execSQL("UPDATE $DOMAINS_TABLE SET $COOKIES = enablefirstpartycookies")
353 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $COOKIES = first_party_cookies")
355 // Create the new download with external app and bottom app bar columns.
356 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $DOWNLOAD_WITH_EXTERNAL_APP BOOLEAN")
357 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $BOTTOM_APP_BAR BOOLEAN")
359 // Get the current values for the new columns.
360 val downloadWithExternalApp = sharedPreferences.getBoolean(DOWNLOAD_WITH_EXTERNAL_APP, false)
361 val bottomAppBar = sharedPreferences.getBoolean(BOTTOM_APP_BAR, false)
363 // Populate the preferences table with the current download with external app value.
364 // This can switch to using the variables directly once the API >= 30. <https://www.sqlite.org/datatype3.html#boolean_datatype>
365 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
366 if (downloadWithExternalApp)
367 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $DOWNLOAD_WITH_EXTERNAL_APP = 1")
369 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $DOWNLOAD_WITH_EXTERNAL_APP = 0")
371 // Populate the preferences table with the current bottom app bar value.
373 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $BOTTOM_APP_BAR = 1")
375 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $BOTTOM_APP_BAR = 0")
378 // Upgrade from schema version 14, first used in Privacy Browser 3.8, to schema version 15, first used in Privacy Browser 3.11.
379 if (importDatabaseVersion < 15) {
380 // `facebook_click_ids` was removed from the preferences table.
381 // There is no need to delete the columns as they will simply be ignored by the import.
383 // `x_requested_with_header` was previously added to the preferences and domains tables in this version, but it was removed later in schema version 16.
385 // Create the new URL modification columns.
386 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $TRACKING_QUERIES BOOLEAN")
387 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $AMP_REDIRECTS BOOLEAN")
389 // Copy the data from the old columns to the new ones.
390 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $TRACKING_QUERIES = google_analytics")
391 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $AMP_REDIRECTS = twitter_amp_redirects")
394 // Upgrade from schema version 15, first used in Privacy Browser 3.11, to schema version 16, first used in Privacy Browser 3.12.
395 // This upgrade removed the `x_requested_with_header` from the domains and preferences tables.
396 // There is no need to delete the columns as they will simply be ignored by the import.
398 // Get a cursor for the bookmarks table.
399 val importBookmarksCursor = importDatabase.rawQuery("SELECT * FROM ${BookmarksDatabaseHelper.BOOKMARKS_TABLE}", null)
401 // Delete the current bookmarks database.
402 context.deleteDatabase(BookmarksDatabaseHelper.BOOKMARKS_DATABASE)
404 // Create a new bookmarks database.
405 val bookmarksDatabaseHelper = BookmarksDatabaseHelper(context)
407 // Move to the first record.
408 importBookmarksCursor.moveToFirst()
410 // Copy the data from the import bookmarks cursor into the bookmarks database.
411 for (i in 0 until importBookmarksCursor.count) {
412 // Create a bookmark content values.
413 val bookmarkContentValues = ContentValues()
415 // Add the information for this bookmark to the content values.
416 bookmarkContentValues.put(BookmarksDatabaseHelper.BOOKMARK_NAME, importBookmarksCursor.getString(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME)))
417 bookmarkContentValues.put(BookmarksDatabaseHelper.BOOKMARK_URL, importBookmarksCursor.getString(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)))
418 bookmarkContentValues.put(BookmarksDatabaseHelper.PARENT_FOLDER, importBookmarksCursor.getString(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.PARENT_FOLDER)))
419 bookmarkContentValues.put(BookmarksDatabaseHelper.DISPLAY_ORDER, importBookmarksCursor.getInt(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)))
420 bookmarkContentValues.put(BookmarksDatabaseHelper.IS_FOLDER, importBookmarksCursor.getInt(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)))
421 bookmarkContentValues.put(BookmarksDatabaseHelper.FAVORITE_ICON, importBookmarksCursor.getBlob(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON)))
423 // Insert the content values into the bookmarks database.
424 bookmarksDatabaseHelper.createBookmark(bookmarkContentValues)
426 // Advance to the next record.
427 importBookmarksCursor.moveToNext()
430 // Upgrade from schema version 16, first used in Privacy Browser 3.12, to schema version 17, first used in Privacy Browser 3.15.
431 if (importDatabaseVersion < 16) {
432 // Get the current switch default values.
433 val javaScriptDefaultValue = sharedPreferences.getBoolean(context.getString(R.string.javascript_key), false)
434 val cookiesDefaultValue = sharedPreferences.getBoolean(context.getString(R.string.cookies_key), false)
435 val domStorageDefaultValue = sharedPreferences.getBoolean(context.getString(R.string.dom_storage_key), false)
436 val formDataDefaultValue = sharedPreferences.getBoolean(context.getString(R.string.save_form_data_key), false)
437 val easyListDefaultValue = sharedPreferences.getBoolean(context.getString(R.string.easylist_key), true)
438 val easyPrivacyDefaultValue = sharedPreferences.getBoolean(context.getString(R.string.easyprivacy_key), true)
439 val fanboysAnnoyanceListDefaultValue = sharedPreferences.getBoolean(context.getString(R.string.fanboys_annoyance_list_key), true)
440 val fanboysSocialBlockingListDefaultValue = sharedPreferences.getBoolean(context.getString(R.string.fanboys_social_blocking_list), true)
441 val ultraListDefaultValue = sharedPreferences.getBoolean(context.getString(R.string.ultralist_key), true)
442 val ultraPrivacyDefaultValue = sharedPreferences.getBoolean(context.getString(R.string.ultraprivacy_key), true)
443 val blockAllThirdPartyRequestsDefaultValue = sharedPreferences.getBoolean(context.getString(R.string.block_all_third_party_requests_key), false)
445 // Get a domains cursor.
446 val importDomainsConversionCursor = importDatabase.rawQuery("SELECT * FROM $DOMAINS_TABLE", null)
448 // Get the domains column indexes.
449 val javaScriptColumnIndex = importDomainsConversionCursor.getColumnIndexOrThrow(ENABLE_JAVASCRIPT)
450 val cookiesColumnIndex = importDomainsConversionCursor.getColumnIndexOrThrow(COOKIES)
451 val domStorageColumnIndex = importDomainsConversionCursor.getColumnIndexOrThrow(ENABLE_DOM_STORAGE)
452 val formDataColumnIndex = importDomainsConversionCursor.getColumnIndexOrThrow(ENABLE_FORM_DATA)
453 val easyListColumnIndex = importDomainsConversionCursor.getColumnIndexOrThrow(ENABLE_EASYLIST)
454 val easyPrivacyColumnIndex = importDomainsConversionCursor.getColumnIndexOrThrow(ENABLE_EASYPRIVACY)
455 val fanboysAnnoyanceListColumnIndex = importDomainsConversionCursor.getColumnIndexOrThrow(ENABLE_FANBOYS_ANNOYANCE_LIST)
456 val fanboysSocialBlockingListColumnIndex = importDomainsConversionCursor.getColumnIndexOrThrow(ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)
457 val ultraListColumnIndex = importDomainsConversionCursor.getColumnIndexOrThrow(ULTRALIST)
458 val ultraPrivacyColumnIndex = importDomainsConversionCursor.getColumnIndexOrThrow(ENABLE_ULTRAPRIVACY)
459 val blockAllThirdPartyRequestsColumnIndex = importDomainsConversionCursor.getColumnIndexOrThrow(BLOCK_ALL_THIRD_PARTY_REQUESTS)
461 // Convert the domain from the switch booleans to the spinner integers.
462 for (i in 0 until importDomainsConversionCursor.count) {
463 // Move to the current record.
464 importDomainsConversionCursor.moveToPosition(i)
466 // Get the domain current values.
467 val javaScriptDomainCurrentValue = importDomainsConversionCursor.getInt(javaScriptColumnIndex)
468 val cookiesDomainCurrentValue = importDomainsConversionCursor.getInt(cookiesColumnIndex)
469 val domStorageDomainCurrentValue = importDomainsConversionCursor.getInt(domStorageColumnIndex)
470 val formDataDomainCurrentValue = importDomainsConversionCursor.getInt(formDataColumnIndex)
471 val easyListDomainCurrentValue = importDomainsConversionCursor.getInt(easyListColumnIndex)
472 val easyPrivacyDomainCurrentValue = importDomainsConversionCursor.getInt(easyPrivacyColumnIndex)
473 val fanboysAnnoyanceListCurrentValue = importDomainsConversionCursor.getInt(fanboysAnnoyanceListColumnIndex)
474 val fanboysSocialBlockingListCurrentValue = importDomainsConversionCursor.getInt(fanboysSocialBlockingListColumnIndex)
475 val ultraListCurrentValue = importDomainsConversionCursor.getInt(ultraListColumnIndex)
476 val ultraPrivacyCurrentValue = importDomainsConversionCursor.getInt(ultraPrivacyColumnIndex)
477 val blockAllThirdPartyRequestsCurrentValue = importDomainsConversionCursor.getInt(blockAllThirdPartyRequestsColumnIndex)
479 // Instantiate a domain content values.
480 val domainContentValues = ContentValues()
482 // Populate the domain content values.
483 domainContentValues.put(ENABLE_JAVASCRIPT, convertFromSwitchToSpinner(javaScriptDefaultValue, javaScriptDomainCurrentValue))
484 domainContentValues.put(COOKIES, convertFromSwitchToSpinner(cookiesDefaultValue, cookiesDomainCurrentValue))
485 domainContentValues.put(ENABLE_DOM_STORAGE, convertFromSwitchToSpinner(domStorageDefaultValue, domStorageDomainCurrentValue))
486 domainContentValues.put(ENABLE_FORM_DATA, convertFromSwitchToSpinner(formDataDefaultValue, formDataDomainCurrentValue))
487 domainContentValues.put(ENABLE_EASYLIST, convertFromSwitchToSpinner(easyListDefaultValue, easyListDomainCurrentValue))
488 domainContentValues.put(ENABLE_EASYPRIVACY, convertFromSwitchToSpinner(easyPrivacyDefaultValue, easyPrivacyDomainCurrentValue))
489 domainContentValues.put(ENABLE_FANBOYS_ANNOYANCE_LIST, convertFromSwitchToSpinner(fanboysAnnoyanceListDefaultValue, fanboysAnnoyanceListCurrentValue))
490 domainContentValues.put(ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST, convertFromSwitchToSpinner(fanboysSocialBlockingListDefaultValue, fanboysSocialBlockingListCurrentValue))
491 domainContentValues.put(ULTRALIST, convertFromSwitchToSpinner(ultraListDefaultValue, ultraListCurrentValue))
492 domainContentValues.put(ENABLE_ULTRAPRIVACY, convertFromSwitchToSpinner(ultraPrivacyDefaultValue, ultraPrivacyCurrentValue))
493 domainContentValues.put(BLOCK_ALL_THIRD_PARTY_REQUESTS, convertFromSwitchToSpinner(blockAllThirdPartyRequestsDefaultValue, blockAllThirdPartyRequestsCurrentValue))
495 // Get the current database ID.
496 val currentDatabaseId = importDomainsConversionCursor.getInt(importDomainsConversionCursor.getColumnIndexOrThrow(ID))
498 // Update the row for the specified database ID.
499 importDatabase.update(DOMAINS_TABLE, domainContentValues, "$ID = $currentDatabaseId", null)
503 importDomainsConversionCursor.close()
506 // Close the bookmarks cursor and database.
507 importBookmarksCursor.close()
508 bookmarksDatabaseHelper.close()
511 // Get a cursor for the domains table.
512 val importDomainsCursor = importDatabase.rawQuery("SELECT * FROM $DOMAINS_TABLE ORDER BY $DOMAIN_NAME ASC", null)
514 // Delete the current domains database.
515 context.deleteDatabase(DOMAINS_DATABASE)
517 // Create a new domains database.
518 val domainsDatabaseHelper = DomainsDatabaseHelper(context)
520 // Move to the first record.
521 importDomainsCursor.moveToFirst()
523 // Copy the data from the import domains cursor into the domains database.
524 for (i in 0 until importDomainsCursor.count) {
525 // Create a domain content values.
526 val domainContentValues = ContentValues()
528 // Populate the domain content values.
529 domainContentValues.put(DOMAIN_NAME, importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DOMAIN_NAME)))
530 domainContentValues.put(ENABLE_JAVASCRIPT, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(ENABLE_JAVASCRIPT)))
531 domainContentValues.put(COOKIES, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(COOKIES)))
532 domainContentValues.put(ENABLE_DOM_STORAGE, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(ENABLE_DOM_STORAGE)))
533 domainContentValues.put(ENABLE_FORM_DATA, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(ENABLE_FORM_DATA)))
534 domainContentValues.put(ENABLE_EASYLIST, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(ENABLE_EASYLIST)))
535 domainContentValues.put(ENABLE_EASYPRIVACY, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(ENABLE_EASYPRIVACY)))
536 domainContentValues.put(ENABLE_FANBOYS_ANNOYANCE_LIST, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(ENABLE_FANBOYS_ANNOYANCE_LIST)))
537 domainContentValues.put(ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)))
538 domainContentValues.put(ULTRALIST, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(ULTRALIST)))
539 domainContentValues.put(ENABLE_ULTRAPRIVACY, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(ENABLE_ULTRAPRIVACY)))
540 domainContentValues.put(BLOCK_ALL_THIRD_PARTY_REQUESTS, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(BLOCK_ALL_THIRD_PARTY_REQUESTS)))
541 domainContentValues.put(USER_AGENT, importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(USER_AGENT)))
542 domainContentValues.put(FONT_SIZE, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(FONT_SIZE)))
543 domainContentValues.put(SWIPE_TO_REFRESH, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(SWIPE_TO_REFRESH)))
544 domainContentValues.put(WEBVIEW_THEME, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(WEBVIEW_THEME)))
545 domainContentValues.put(WIDE_VIEWPORT, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(WIDE_VIEWPORT)))
546 domainContentValues.put(DISPLAY_IMAGES, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DISPLAY_IMAGES)))
547 domainContentValues.put(PINNED_SSL_CERTIFICATE, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(PINNED_SSL_CERTIFICATE)))
548 domainContentValues.put(SSL_ISSUED_TO_COMMON_NAME, importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(SSL_ISSUED_TO_COMMON_NAME)))
549 domainContentValues.put(SSL_ISSUED_TO_ORGANIZATION, importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(SSL_ISSUED_TO_ORGANIZATION)))
550 domainContentValues.put(SSL_ISSUED_TO_ORGANIZATIONAL_UNIT, importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)))
551 domainContentValues.put(SSL_ISSUED_BY_COMMON_NAME, importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(SSL_ISSUED_BY_COMMON_NAME)))
552 domainContentValues.put(SSL_ISSUED_BY_ORGANIZATION, importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(SSL_ISSUED_BY_ORGANIZATION)))
553 domainContentValues.put(SSL_ISSUED_BY_ORGANIZATIONAL_UNIT, importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)))
554 domainContentValues.put(SSL_START_DATE, importDomainsCursor.getLong(importDomainsCursor.getColumnIndexOrThrow(SSL_START_DATE)))
555 domainContentValues.put(SSL_END_DATE, importDomainsCursor.getLong(importDomainsCursor.getColumnIndexOrThrow(SSL_END_DATE)))
556 domainContentValues.put(PINNED_IP_ADDRESSES, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(PINNED_IP_ADDRESSES)))
557 domainContentValues.put(IP_ADDRESSES, importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(IP_ADDRESSES)))
559 // Insert the content values into the domains database.
560 domainsDatabaseHelper.addDomain(domainContentValues)
562 // Advance to the next record.
563 importDomainsCursor.moveToNext()
566 // Close the domains cursor and database.
567 importDomainsCursor.close()
568 domainsDatabaseHelper.close()
571 // Get a cursor for the preferences table.
572 val importPreferencesCursor = importDatabase.rawQuery("SELECT * FROM $PREFERENCES_TABLE", null)
574 // Move to the first record.
575 importPreferencesCursor.moveToFirst()
577 // Import the preference data.
578 sharedPreferences.edit()
579 .putBoolean(JAVASCRIPT, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(JAVASCRIPT)) == 1)
580 .putBoolean(COOKIES, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(COOKIES)) == 1)
581 .putBoolean(DOM_STORAGE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(DOM_STORAGE)) == 1)
582 .putBoolean(SAVE_FORM_DATA, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(SAVE_FORM_DATA)) == 1) // Save form data can be removed once the minimum API >= 26.
583 .putString(PREFERENCES_USER_AGENT, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(PREFERENCES_USER_AGENT)))
584 .putString(CUSTOM_USER_AGENT, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(CUSTOM_USER_AGENT)))
585 .putBoolean(INCOGNITO_MODE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(INCOGNITO_MODE)) == 1)
586 .putBoolean(ALLOW_SCREENSHOTS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(ALLOW_SCREENSHOTS)) == 1)
587 .putBoolean(EASYLIST, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(EASYLIST)) == 1)
588 .putBoolean(EASYPRIVACY, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(EASYPRIVACY)) == 1)
589 .putBoolean(FANBOYS_ANNOYANCE_LIST, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(FANBOYS_ANNOYANCE_LIST)) == 1)
590 .putBoolean(FANBOYS_SOCIAL_BLOCKING_LIST, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(FANBOYS_SOCIAL_BLOCKING_LIST)) == 1)
591 .putBoolean(ULTRALIST, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(ULTRALIST)) == 1)
592 .putBoolean(ULTRAPRIVACY, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(ULTRAPRIVACY)) == 1)
593 .putBoolean(PREFERENCES_BLOCK_ALL_THIRD_PARTY_REQUESTS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(PREFERENCES_BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1)
594 .putBoolean(TRACKING_QUERIES, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(TRACKING_QUERIES)) == 1)
595 .putBoolean(AMP_REDIRECTS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(AMP_REDIRECTS)) == 1)
596 .putString(SEARCH, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(SEARCH)))
597 .putString(SEARCH_CUSTOM_URL, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(SEARCH_CUSTOM_URL)))
598 .putString(PROXY, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(PROXY)))
599 .putString(PROXY_CUSTOM_URL, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(PROXY_CUSTOM_URL)))
600 .putBoolean(FULL_SCREEN_BROWSING_MODE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(FULL_SCREEN_BROWSING_MODE)) == 1)
601 .putBoolean(HIDE_APP_BAR, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(HIDE_APP_BAR)) == 1)
602 .putBoolean(CLEAR_EVERYTHING, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_EVERYTHING)) == 1)
603 .putBoolean(CLEAR_COOKIES, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_COOKIES)) == 1)
604 .putBoolean(CLEAR_DOM_STORAGE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_DOM_STORAGE)) == 1)
605 .putBoolean(CLEAR_FORM_DATA, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_FORM_DATA)) == 1) // Clear form data can be removed once the minimum API >= 26.
606 .putBoolean(CLEAR_LOGCAT, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_LOGCAT)) == 1)
607 .putBoolean(CLEAR_CACHE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_CACHE)) == 1)
608 .putString(HOMEPAGE, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(HOMEPAGE)))
609 .putString(PREFERENCES_FONT_SIZE, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(PREFERENCES_FONT_SIZE)))
610 .putBoolean(OPEN_INTENTS_IN_NEW_TAB, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(OPEN_INTENTS_IN_NEW_TAB)) == 1)
611 .putBoolean(PREFERENCES_SWIPE_TO_REFRESH, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(PREFERENCES_SWIPE_TO_REFRESH)) == 1)
612 .putBoolean(DOWNLOAD_WITH_EXTERNAL_APP, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(DOWNLOAD_WITH_EXTERNAL_APP)) == 1)
613 .putBoolean(SCROLL_APP_BAR, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(SCROLL_APP_BAR)) == 1)
614 .putBoolean(BOTTOM_APP_BAR, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(BOTTOM_APP_BAR)) == 1)
615 .putBoolean(DISPLAY_ADDITIONAL_APP_BAR_ICONS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(DISPLAY_ADDITIONAL_APP_BAR_ICONS)) == 1)
616 .putString(APP_THEME, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(APP_THEME)))
617 .putString(WEBVIEW_THEME, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(WEBVIEW_THEME)))
618 .putBoolean(WIDE_VIEWPORT, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(WIDE_VIEWPORT)) == 1)
619 .putBoolean(DISPLAY_WEBPAGE_IMAGES, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(DISPLAY_WEBPAGE_IMAGES)) == 1)
622 // Close the preferences cursor and database.
623 importPreferencesCursor.close()
624 importDatabase.close()
626 // Delete the temporary import file database, journal, and other related auxiliary files.
627 SQLiteDatabase.deleteDatabase(temporaryImportFile)
629 // Return the import successful string.
631 } catch (exception: Exception) {
632 // Return the import error.
637 fun exportUnencrypted(exportFileOutputStream: OutputStream, context: Context): String {
639 // Create a temporary export file.
640 val temporaryExportFile = File.createTempFile("temporary_export_file", null, context.cacheDir)
642 // Create the temporary export database.
643 val temporaryExportDatabase = SQLiteDatabase.openOrCreateDatabase(temporaryExportFile, null)
645 // Set the temporary export database version number.
646 temporaryExportDatabase.version = IMPORT_EXPORT_SCHEMA_VERSION
649 // Create the temporary export database bookmarks table.
650 temporaryExportDatabase.execSQL(BookmarksDatabaseHelper.CREATE_BOOKMARKS_TABLE)
652 // Open the bookmarks database.
653 val bookmarksDatabaseHelper = BookmarksDatabaseHelper(context)
655 // Get a full bookmarks cursor.
656 val bookmarksCursor = bookmarksDatabaseHelper.allBookmarks
658 // Move to the first record.
659 bookmarksCursor.moveToFirst()
661 // Copy the data from the bookmarks cursor into the export database.
662 for (i in 0 until bookmarksCursor.count) {
663 // Create a bookmark content values.
664 val bookmarkContentValues = ContentValues()
666 // Populate the bookmark content values.
667 bookmarkContentValues.put(BookmarksDatabaseHelper.BOOKMARK_NAME, bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME)))
668 bookmarkContentValues.put(BookmarksDatabaseHelper.BOOKMARK_URL, bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)))
669 bookmarkContentValues.put(BookmarksDatabaseHelper.PARENT_FOLDER, bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.PARENT_FOLDER)))
670 bookmarkContentValues.put(BookmarksDatabaseHelper.DISPLAY_ORDER, bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)))
671 bookmarkContentValues.put(BookmarksDatabaseHelper.IS_FOLDER, bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)))
672 bookmarkContentValues.put(BookmarksDatabaseHelper.FAVORITE_ICON, bookmarksCursor.getBlob(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON)))
674 // Insert the content values into the temporary export database.
675 temporaryExportDatabase.insert(BookmarksDatabaseHelper.BOOKMARKS_TABLE, null, bookmarkContentValues)
677 // Advance to the next record.
678 bookmarksCursor.moveToNext()
681 // Close the bookmarks cursor and database.
682 bookmarksCursor.close()
683 bookmarksDatabaseHelper.close()
686 // Create the temporary export database domains table.
687 temporaryExportDatabase.execSQL(CREATE_DOMAINS_TABLE)
689 // Open the domains database.
690 val domainsDatabaseHelper = DomainsDatabaseHelper(context)
692 // Get a full domains database cursor.
693 val domainsCursor = domainsDatabaseHelper.completeCursorOrderedByDomain
695 // Move to the first record.
696 domainsCursor.moveToFirst()
698 // Copy the data from the domains cursor into the export database.
699 for (i in 0 until domainsCursor.count) {
700 // Create a domain content values.
701 val domainContentValues = ContentValues()
703 // Populate the domain content values.
704 domainContentValues.put(DOMAIN_NAME, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DOMAIN_NAME)))
705 domainContentValues.put(ENABLE_JAVASCRIPT, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(ENABLE_JAVASCRIPT)))
706 domainContentValues.put(COOKIES, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(COOKIES)))
707 domainContentValues.put(ENABLE_DOM_STORAGE, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(ENABLE_DOM_STORAGE)))
708 domainContentValues.put(ENABLE_FORM_DATA, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(ENABLE_FORM_DATA)))
709 domainContentValues.put(ENABLE_EASYLIST, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(ENABLE_EASYLIST)))
710 domainContentValues.put(ENABLE_EASYPRIVACY, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(ENABLE_EASYPRIVACY)))
711 domainContentValues.put(ENABLE_FANBOYS_ANNOYANCE_LIST, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(ENABLE_FANBOYS_ANNOYANCE_LIST)))
712 domainContentValues.put(ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)))
713 domainContentValues.put(ULTRALIST, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(ULTRALIST)))
714 domainContentValues.put(ENABLE_ULTRAPRIVACY, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(ENABLE_ULTRAPRIVACY)))
715 domainContentValues.put(BLOCK_ALL_THIRD_PARTY_REQUESTS, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(BLOCK_ALL_THIRD_PARTY_REQUESTS)))
716 domainContentValues.put(USER_AGENT, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(USER_AGENT)))
717 domainContentValues.put(FONT_SIZE, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(FONT_SIZE)))
718 domainContentValues.put(SWIPE_TO_REFRESH, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(SWIPE_TO_REFRESH)))
719 domainContentValues.put(WEBVIEW_THEME, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(WEBVIEW_THEME)))
720 domainContentValues.put(WIDE_VIEWPORT, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(WIDE_VIEWPORT)))
721 domainContentValues.put(DISPLAY_IMAGES, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DISPLAY_IMAGES)))
722 domainContentValues.put(PINNED_SSL_CERTIFICATE, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(PINNED_SSL_CERTIFICATE)))
723 domainContentValues.put(SSL_ISSUED_TO_COMMON_NAME, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(SSL_ISSUED_TO_COMMON_NAME)))
724 domainContentValues.put(SSL_ISSUED_TO_ORGANIZATION, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(SSL_ISSUED_TO_ORGANIZATION)))
725 domainContentValues.put(SSL_ISSUED_TO_ORGANIZATIONAL_UNIT, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)))
726 domainContentValues.put(SSL_ISSUED_BY_COMMON_NAME, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(SSL_ISSUED_BY_COMMON_NAME)))
727 domainContentValues.put(SSL_ISSUED_BY_ORGANIZATION, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(SSL_ISSUED_BY_ORGANIZATION)))
728 domainContentValues.put(SSL_ISSUED_BY_ORGANIZATIONAL_UNIT, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)))
729 domainContentValues.put(SSL_START_DATE, domainsCursor.getLong(domainsCursor.getColumnIndexOrThrow(SSL_START_DATE)))
730 domainContentValues.put(SSL_END_DATE, domainsCursor.getLong(domainsCursor.getColumnIndexOrThrow(SSL_END_DATE)))
731 domainContentValues.put(PINNED_IP_ADDRESSES, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(PINNED_IP_ADDRESSES)))
732 domainContentValues.put(IP_ADDRESSES, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(IP_ADDRESSES)))
734 // Insert the content values into the temporary export database.
735 temporaryExportDatabase.insert(DOMAINS_TABLE, null, domainContentValues)
737 // Advance to the next record.
738 domainsCursor.moveToNext()
741 // Close the domains cursor and database.
742 domainsCursor.close()
743 domainsDatabaseHelper.close()
746 // Prepare the preferences table SQL creation string.
747 val createPreferencesTable = "CREATE TABLE $PREFERENCES_TABLE (" +
748 "$ID INTEGER PRIMARY KEY, " +
749 "$JAVASCRIPT BOOLEAN, " +
750 "$COOKIES BOOLEAN, " +
751 "$DOM_STORAGE BOOLEAN, " +
752 "$SAVE_FORM_DATA BOOLEAN, " +
753 "$PREFERENCES_USER_AGENT TEXT, " +
754 "$CUSTOM_USER_AGENT TEXT, " +
755 "$INCOGNITO_MODE BOOLEAN, " +
756 "$ALLOW_SCREENSHOTS BOOLEAN, " +
757 "$EASYLIST BOOLEAN, " +
758 "$EASYPRIVACY BOOLEAN, " +
759 "$FANBOYS_ANNOYANCE_LIST BOOLEAN, " +
760 "$FANBOYS_SOCIAL_BLOCKING_LIST BOOLEAN, " +
761 "$ULTRALIST BOOLEAN, " +
762 "$ULTRAPRIVACY BOOLEAN, " +
763 "$PREFERENCES_BLOCK_ALL_THIRD_PARTY_REQUESTS BOOLEAN, " +
764 "$TRACKING_QUERIES BOOLEAN, " +
765 "$AMP_REDIRECTS BOOLEAN, " +
767 "$SEARCH_CUSTOM_URL TEXT, " +
769 "$PROXY_CUSTOM_URL TEXT, " +
770 "$FULL_SCREEN_BROWSING_MODE BOOLEAN, " +
771 "$HIDE_APP_BAR BOOLEAN, " +
772 "$CLEAR_EVERYTHING BOOLEAN, " +
773 "$CLEAR_COOKIES BOOLEAN, " +
774 "$CLEAR_DOM_STORAGE BOOLEAN, " +
775 "$CLEAR_FORM_DATA BOOLEAN, " +
776 "$CLEAR_LOGCAT BOOLEAN, " +
777 "$CLEAR_CACHE BOOLEAN, " +
779 "$PREFERENCES_FONT_SIZE TEXT, " +
780 "$OPEN_INTENTS_IN_NEW_TAB BOOLEAN, " +
781 "$PREFERENCES_SWIPE_TO_REFRESH BOOLEAN, " +
782 "$DOWNLOAD_WITH_EXTERNAL_APP BOOLEAN, " +
783 "$SCROLL_APP_BAR BOOLEAN, " +
784 "$BOTTOM_APP_BAR BOOLEAN, " +
785 "$DISPLAY_ADDITIONAL_APP_BAR_ICONS BOOLEAN, " +
786 "$APP_THEME TEXT, " +
787 "$WEBVIEW_THEME TEXT, " +
788 "$WIDE_VIEWPORT BOOLEAN, " +
789 "$DISPLAY_WEBPAGE_IMAGES BOOLEAN)"
791 // Create the temporary export database preferences table.
792 temporaryExportDatabase.execSQL(createPreferencesTable)
794 // Get a handle for the shared preference.
795 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
797 // Create a preferences content values.
798 val preferencesContentValues = ContentValues()
800 // Populate the preferences content values.
801 preferencesContentValues.put(JAVASCRIPT, sharedPreferences.getBoolean(JAVASCRIPT, false))
802 preferencesContentValues.put(COOKIES, sharedPreferences.getBoolean(COOKIES, false))
803 preferencesContentValues.put(DOM_STORAGE, sharedPreferences.getBoolean(DOM_STORAGE, false))
804 preferencesContentValues.put(SAVE_FORM_DATA, sharedPreferences.getBoolean(SAVE_FORM_DATA, false)) // Save form data can be removed once the minimum API >= 26.
805 preferencesContentValues.put(PREFERENCES_USER_AGENT, sharedPreferences.getString(PREFERENCES_USER_AGENT, context.getString(R.string.user_agent_default_value)))
806 preferencesContentValues.put(CUSTOM_USER_AGENT, sharedPreferences.getString(CUSTOM_USER_AGENT, context.getString(R.string.custom_user_agent_default_value)))
807 preferencesContentValues.put(INCOGNITO_MODE, sharedPreferences.getBoolean(INCOGNITO_MODE, false))
808 preferencesContentValues.put(ALLOW_SCREENSHOTS, sharedPreferences.getBoolean(ALLOW_SCREENSHOTS, false))
809 preferencesContentValues.put(EASYLIST, sharedPreferences.getBoolean(EASYLIST, true))
810 preferencesContentValues.put(EASYPRIVACY, sharedPreferences.getBoolean(EASYPRIVACY, true))
811 preferencesContentValues.put(FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean(FANBOYS_ANNOYANCE_LIST, true))
812 preferencesContentValues.put(FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean(FANBOYS_SOCIAL_BLOCKING_LIST, true))
813 preferencesContentValues.put(ULTRALIST, sharedPreferences.getBoolean(ULTRALIST, true))
814 preferencesContentValues.put(ULTRAPRIVACY, sharedPreferences.getBoolean(ULTRAPRIVACY, true))
815 preferencesContentValues.put(PREFERENCES_BLOCK_ALL_THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean(PREFERENCES_BLOCK_ALL_THIRD_PARTY_REQUESTS, false))
816 preferencesContentValues.put(TRACKING_QUERIES, sharedPreferences.getBoolean(TRACKING_QUERIES, true))
817 preferencesContentValues.put(AMP_REDIRECTS, sharedPreferences.getBoolean(AMP_REDIRECTS, true))
818 preferencesContentValues.put(SEARCH, sharedPreferences.getString(SEARCH, context.getString(R.string.search_default_value)))
819 preferencesContentValues.put(SEARCH_CUSTOM_URL, sharedPreferences.getString(SEARCH_CUSTOM_URL, context.getString(R.string.search_custom_url_default_value)))
820 preferencesContentValues.put(PROXY, sharedPreferences.getString(PROXY, context.getString(R.string.proxy_default_value)))
821 preferencesContentValues.put(PROXY_CUSTOM_URL, sharedPreferences.getString(PROXY_CUSTOM_URL, context.getString(R.string.proxy_custom_url_default_value)))
822 preferencesContentValues.put(FULL_SCREEN_BROWSING_MODE, sharedPreferences.getBoolean(FULL_SCREEN_BROWSING_MODE, false))
823 preferencesContentValues.put(HIDE_APP_BAR, sharedPreferences.getBoolean(HIDE_APP_BAR, true))
824 preferencesContentValues.put(CLEAR_EVERYTHING, sharedPreferences.getBoolean(CLEAR_EVERYTHING, true))
825 preferencesContentValues.put(CLEAR_COOKIES, sharedPreferences.getBoolean(CLEAR_COOKIES, true))
826 preferencesContentValues.put(CLEAR_DOM_STORAGE, sharedPreferences.getBoolean(CLEAR_DOM_STORAGE, true))
827 preferencesContentValues.put(CLEAR_FORM_DATA, sharedPreferences.getBoolean(CLEAR_FORM_DATA, true)) // Clear form data can be removed once the minimum API >= 26.
828 preferencesContentValues.put(CLEAR_LOGCAT, sharedPreferences.getBoolean(CLEAR_LOGCAT, true))
829 preferencesContentValues.put(CLEAR_CACHE, sharedPreferences.getBoolean(CLEAR_CACHE, true))
830 preferencesContentValues.put(HOMEPAGE, sharedPreferences.getString(HOMEPAGE, context.getString(R.string.homepage_default_value)))
831 preferencesContentValues.put(PREFERENCES_FONT_SIZE, sharedPreferences.getString(PREFERENCES_FONT_SIZE, context.getString(R.string.font_size_default_value)))
832 preferencesContentValues.put(OPEN_INTENTS_IN_NEW_TAB, sharedPreferences.getBoolean(OPEN_INTENTS_IN_NEW_TAB, true))
833 preferencesContentValues.put(PREFERENCES_SWIPE_TO_REFRESH, sharedPreferences.getBoolean(PREFERENCES_SWIPE_TO_REFRESH, true))
834 preferencesContentValues.put(DOWNLOAD_WITH_EXTERNAL_APP, sharedPreferences.getBoolean(DOWNLOAD_WITH_EXTERNAL_APP, false))
835 preferencesContentValues.put(SCROLL_APP_BAR, sharedPreferences.getBoolean(SCROLL_APP_BAR, true))
836 preferencesContentValues.put(BOTTOM_APP_BAR, sharedPreferences.getBoolean(BOTTOM_APP_BAR, false))
837 preferencesContentValues.put(DISPLAY_ADDITIONAL_APP_BAR_ICONS, sharedPreferences.getBoolean(DISPLAY_ADDITIONAL_APP_BAR_ICONS, false))
838 preferencesContentValues.put(APP_THEME, sharedPreferences.getString(APP_THEME, context.getString(R.string.app_theme_default_value)))
839 preferencesContentValues.put(WEBVIEW_THEME, sharedPreferences.getString(WEBVIEW_THEME, context.getString(R.string.webview_theme_default_value)))
840 preferencesContentValues.put(WIDE_VIEWPORT, sharedPreferences.getBoolean(WIDE_VIEWPORT, true))
841 preferencesContentValues.put(DISPLAY_WEBPAGE_IMAGES, sharedPreferences.getBoolean(DISPLAY_WEBPAGE_IMAGES, true))
843 // Insert the preferences content values into the temporary export database.
844 temporaryExportDatabase.insert(PREFERENCES_TABLE, null, preferencesContentValues)
846 // Close the temporary export database.
847 temporaryExportDatabase.close()
850 // The file may be copied directly in Kotlin using `File.copyTo`. <https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/java.io.-file/copy-to.html>
851 // It can be copied in Android using `Files.copy` once the minimum API >= 26.
852 // <https://developer.android.com/reference/java/nio/file/Files#copy(java.nio.file.Path,%20java.nio.file.Path,%20java.nio.file.CopyOption...)>
853 // However, the file cannot be acquired from the content URI until the minimum API >= 29. <https://developer.android.com/reference/kotlin/android/content/ContentResolver#openfile>
855 // Create the temporary export file input stream.
856 val temporaryExportFileInputStream = FileInputStream(temporaryExportFile)
858 // Create a byte array.
859 val transferByteArray = ByteArray(1024)
861 // Create an integer to track the number of bytes read.
864 // Copy the temporary export file to the export file output stream.
865 while (temporaryExportFileInputStream.read(transferByteArray).also { bytesRead = it } > 0) {
866 exportFileOutputStream.write(transferByteArray, 0, bytesRead)
869 // Flush the export file output stream.
870 exportFileOutputStream.flush()
872 // Close the file streams.
873 temporaryExportFileInputStream.close()
874 exportFileOutputStream.close()
876 // Delete the temporary export file database, journal, and other related auxiliary files.
877 SQLiteDatabase.deleteDatabase(temporaryExportFile)
879 // Return the export successful string.
881 } catch (exception: Exception) {
882 // Return the export error.
887 // This method is used to convert the old domain settings switches to spinners.
888 private fun convertFromSwitchToSpinner(systemDefault: Boolean, currentDatabaseInteger: Int): Int {
889 // Return the new spinner integer.
890 return if ((!systemDefault && (currentDatabaseInteger == 0)) ||
891 (systemDefault && (currentDatabaseInteger == 1))) // The system default is currently selected.
893 else if (currentDatabaseInteger == 0) // The switch is currently disabled and that is not the system default.
895 else // The switch is currently enabled and that is not the system default.