]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportDatabaseHelper.kt
Change the domain settings switches to spinners. https://redmine.stoutner.com/issues/407
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / helpers / ImportExportDatabaseHelper.kt
1 /*
2  * Copyright 2018-2023 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 package com.stoutner.privacybrowser.helpers
21
22 import android.content.ContentValues
23 import android.content.Context
24 import android.database.DatabaseUtils
25 import android.database.sqlite.SQLiteDatabase
26
27 import androidx.preference.PreferenceManager
28
29 import com.stoutner.privacybrowser.R
30
31 import java.io.File
32 import java.io.FileInputStream
33 import java.io.FileOutputStream
34 import java.io.InputStream
35 import java.io.OutputStream
36
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
41
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"
81
82 class ImportExportDatabaseHelper {
83     fun importUnencrypted(importFileInputStream: InputStream, context: Context): String {
84         return try {
85             // Create a temporary import file.
86             val temporaryImportFile = File.createTempFile("temporary_import_file", null, context.cacheDir)
87
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>
92
93             // Create a temporary file output stream.
94             val temporaryImportFileOutputStream = FileOutputStream(temporaryImportFile)
95
96             // Create a transfer byte array.
97             val transferByteArray = ByteArray(1024)
98
99             // Create an integer to track the number of bytes read.
100             var bytesRead: Int
101
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)
105             }
106
107             // Flush the temporary import file output stream.
108             temporaryImportFileOutputStream.flush()
109
110             // Close the file streams.
111             importFileInputStream.close()
112             temporaryImportFileOutputStream.close()
113
114
115             // Get a handle for the shared preference.
116             val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
117
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)
120
121             // Get the database version.
122             val importDatabaseVersion = importDatabase.version
123
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.
126
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.
133
134                 // Create the new font size column.
135                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $PREFERENCES_FONT_SIZE TEXT")
136
137                 // Populate the preferences table with the current font size value.
138                 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $PREFERENCES_FONT_SIZE = default_font_size")
139             }
140
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")
146             }
147
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")
153
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)
157
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>
161                 if (hideAppBar)
162                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $HIDE_APP_BAR = 1")
163                 else
164                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $HIDE_APP_BAR = 0")
165
166                 if (scrollAppBar)
167                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $SCROLL_APP_BAR = 1")
168                 else
169                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $SCROLL_APP_BAR = 0")
170             }
171
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")
176
177                 // Get the current open intents in new tab preference.
178                 val openIntentsInNewTab = sharedPreferences.getBoolean(OPEN_INTENTS_IN_NEW_TAB, true)
179
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")
185                 else
186                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $OPEN_INTENTS_IN_NEW_TAB = 0")
187             }
188
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.
192
193                 // Add the wide viewport column to the domains table.
194                 importDatabase.execSQL("ALTER TABLE $DOMAINS_TABLE ADD COLUMN $WIDE_VIEWPORT INTEGER")
195
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")
200
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)
205
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>
209                 if (trackingQueries)
210                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET google_analytics = 1")
211                 else
212                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET google_analytics = 0")
213
214                 // Populate the preferences table with the current AMP Redirects value.  Twitter AMP Redirects was renamed AMP Redirects in schema version 15.
215                 if (ampRedirects)
216                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET twitter_amp_redirects = 1")
217                 else
218                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET twitter_amp_redirects = 0")
219
220                 // Populate the preferences table with the current wide viewport value.
221                 if (wideViewport)
222                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $WIDE_VIEWPORT = 1")
223                 else
224                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $WIDE_VIEWPORT = 0")
225             }
226
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")
232
233                 // Get the current preference values.
234                 val ultraList = sharedPreferences.getBoolean(ULTRALIST, true)
235
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>
239                 if (ultraList) {
240                     importDatabase.execSQL("UPDATE $DOMAINS_TABLE SET $ULTRALIST = 1")
241                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $ULTRALIST = 1")
242                 } else {
243                     importDatabase.execSQL("UPDATE $DOMAINS_TABLE SET $ULTRALIST = 0")
244                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $ULTRALIST = 0")
245                 }
246             }
247
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")
253
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))
257
258                 // SQL escape the proxy custom URL string.
259                 proxyCustomUrl = DatabaseUtils.sqlEscapeString(proxyCustomUrl)
260
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")
264             }
265
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.
268
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")
273
274                 // Get a cursor for the dark theme preference.
275                 val darkThemePreferencesCursor = importDatabase.rawQuery("SELECT dark_theme FROM $PREFERENCES_TABLE", null)
276
277                 // Move to the first entry.
278                 darkThemePreferencesCursor.moveToFirst()
279
280                 // Get the old dark theme value, which is in column 0.
281                 val darkTheme = darkThemePreferencesCursor.getInt(0)
282
283                 // Close the dark theme preference cursor.
284                 darkThemePreferencesCursor.close()
285
286                 // Get the system default string.
287                 val systemDefault = context.getString(R.string.app_theme_default_value)
288
289                 // Get the theme entry values string array.
290                 val appThemeEntryValuesStringArray: Array<String> = context.resources.getStringArray(R.array.app_theme_entry_values)
291
292                 // Get the dark string.
293                 val dark = appThemeEntryValuesStringArray[2]
294
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'")
302                 }
303
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")
306
307                 // Add the WebView theme to the preferences table.
308                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $WEBVIEW_THEME TEXT")
309
310                 // Get the WebView theme default value string.
311                 val webViewThemeDefaultValue = context.getString(R.string.webview_theme_default_value)
312
313                 // Set the WebView theme in the preferences table to be the default.
314                 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $WEBVIEW_THEME = '$webViewThemeDefaultValue'")
315             }
316
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")
321
322                 // Get the current clear logcat value.
323                 val clearLogcat = sharedPreferences.getBoolean(CLEAR_LOGCAT, true)
324
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>
328                 if (clearLogcat)
329                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $CLEAR_LOGCAT = 1")
330                 else
331                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $CLEAR_LOGCAT = 0")
332             }
333
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.
336
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.
341
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.
346
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")
350
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")
354
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")
358
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)
362
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")
368                 else
369                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $DOWNLOAD_WITH_EXTERNAL_APP = 0")
370
371                 // Populate the preferences table with the current bottom app bar value.
372                 if (bottomAppBar)
373                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $BOTTOM_APP_BAR = 1")
374                 else
375                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $BOTTOM_APP_BAR = 0")
376             }
377
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.
382
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.
384
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")
388
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")
392             }
393
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.
397
398             // Get a cursor for the bookmarks table.
399             val importBookmarksCursor = importDatabase.rawQuery("SELECT * FROM ${BookmarksDatabaseHelper.BOOKMARKS_TABLE}", null)
400
401             // Delete the current bookmarks database.
402             context.deleteDatabase(BookmarksDatabaseHelper.BOOKMARKS_DATABASE)
403
404             // Create a new bookmarks database.
405             val bookmarksDatabaseHelper = BookmarksDatabaseHelper(context)
406
407             // Move to the first record.
408             importBookmarksCursor.moveToFirst()
409
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()
414
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)))
422
423                 // Insert the content values into the bookmarks database.
424                 bookmarksDatabaseHelper.createBookmark(bookmarkContentValues)
425
426                 // Advance to the next record.
427                 importBookmarksCursor.moveToNext()
428             }
429
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)
444
445                 // Get a domains cursor.
446                 val importDomainsConversionCursor = importDatabase.rawQuery("SELECT * FROM $DOMAINS_TABLE", null)
447
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)
460
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)
465
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)
478
479                     // Instantiate a domain content values.
480                     val domainContentValues = ContentValues()
481
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))
494
495                     // Get the current database ID.
496                     val currentDatabaseId = importDomainsConversionCursor.getInt(importDomainsConversionCursor.getColumnIndexOrThrow(ID))
497
498                     // Update the row for the specified database ID.
499                     importDatabase.update(DOMAINS_TABLE, domainContentValues, "$ID = $currentDatabaseId", null)
500                 }
501
502                 // Close the cursor.
503                 importDomainsConversionCursor.close()
504             }
505
506             // Close the bookmarks cursor and database.
507             importBookmarksCursor.close()
508             bookmarksDatabaseHelper.close()
509
510
511             // Get a cursor for the domains table.
512             val importDomainsCursor = importDatabase.rawQuery("SELECT * FROM $DOMAINS_TABLE ORDER BY $DOMAIN_NAME ASC", null)
513
514             // Delete the current domains database.
515             context.deleteDatabase(DOMAINS_DATABASE)
516
517             // Create a new domains database.
518             val domainsDatabaseHelper = DomainsDatabaseHelper(context)
519
520             // Move to the first record.
521             importDomainsCursor.moveToFirst()
522
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()
527
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)))
558
559                 // Insert the content values into the domains database.
560                 domainsDatabaseHelper.addDomain(domainContentValues)
561
562                 // Advance to the next record.
563                 importDomainsCursor.moveToNext()
564             }
565
566             // Close the domains cursor and database.
567             importDomainsCursor.close()
568             domainsDatabaseHelper.close()
569
570
571             // Get a cursor for the preferences table.
572             val importPreferencesCursor = importDatabase.rawQuery("SELECT * FROM $PREFERENCES_TABLE", null)
573
574             // Move to the first record.
575             importPreferencesCursor.moveToFirst()
576
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)
620                 .apply()
621
622             // Close the preferences cursor and database.
623             importPreferencesCursor.close()
624             importDatabase.close()
625
626             // Delete the temporary import file database, journal, and other related auxiliary files.
627             SQLiteDatabase.deleteDatabase(temporaryImportFile)
628
629             // Return the import successful string.
630             IMPORT_SUCCESSFUL
631         } catch (exception: Exception) {
632             // Return the import error.
633             exception.toString()
634         }
635     }
636
637     fun exportUnencrypted(exportFileOutputStream: OutputStream, context: Context): String {
638         return try {
639             // Create a temporary export file.
640             val temporaryExportFile = File.createTempFile("temporary_export_file", null, context.cacheDir)
641
642             // Create the temporary export database.
643             val temporaryExportDatabase = SQLiteDatabase.openOrCreateDatabase(temporaryExportFile, null)
644
645             // Set the temporary export database version number.
646             temporaryExportDatabase.version = IMPORT_EXPORT_SCHEMA_VERSION
647
648
649             // Create the temporary export database bookmarks table.
650             temporaryExportDatabase.execSQL(BookmarksDatabaseHelper.CREATE_BOOKMARKS_TABLE)
651
652             // Open the bookmarks database.
653             val bookmarksDatabaseHelper = BookmarksDatabaseHelper(context)
654
655             // Get a full bookmarks cursor.
656             val bookmarksCursor = bookmarksDatabaseHelper.allBookmarks
657
658             // Move to the first record.
659             bookmarksCursor.moveToFirst()
660
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()
665
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)))
673
674                 // Insert the content values into the temporary export database.
675                 temporaryExportDatabase.insert(BookmarksDatabaseHelper.BOOKMARKS_TABLE, null, bookmarkContentValues)
676
677                 // Advance to the next record.
678                 bookmarksCursor.moveToNext()
679             }
680
681             // Close the bookmarks cursor and database.
682             bookmarksCursor.close()
683             bookmarksDatabaseHelper.close()
684
685
686             // Create the temporary export database domains table.
687             temporaryExportDatabase.execSQL(CREATE_DOMAINS_TABLE)
688
689             // Open the domains database.
690             val domainsDatabaseHelper = DomainsDatabaseHelper(context)
691
692             // Get a full domains database cursor.
693             val domainsCursor = domainsDatabaseHelper.completeCursorOrderedByDomain
694
695             // Move to the first record.
696             domainsCursor.moveToFirst()
697
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()
702
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)))
733
734                 // Insert the content values into the temporary export database.
735                 temporaryExportDatabase.insert(DOMAINS_TABLE, null, domainContentValues)
736
737                 // Advance to the next record.
738                 domainsCursor.moveToNext()
739             }
740
741             // Close the domains cursor and database.
742             domainsCursor.close()
743             domainsDatabaseHelper.close()
744
745
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, " +
766                     "$SEARCH TEXT, " +
767                     "$SEARCH_CUSTOM_URL TEXT, " +
768                     "$PROXY 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, " +
778                     "$HOMEPAGE TEXT, " +
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)"
790
791             // Create the temporary export database preferences table.
792             temporaryExportDatabase.execSQL(createPreferencesTable)
793
794             // Get a handle for the shared preference.
795             val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
796
797             // Create a preferences content values.
798             val preferencesContentValues = ContentValues()
799
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))
842
843             // Insert the preferences content values into the temporary export database.
844             temporaryExportDatabase.insert(PREFERENCES_TABLE, null, preferencesContentValues)
845
846             // Close the temporary export database.
847             temporaryExportDatabase.close()
848
849
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>
854
855             // Create the temporary export file input stream.
856             val temporaryExportFileInputStream = FileInputStream(temporaryExportFile)
857
858             // Create a byte array.
859             val transferByteArray = ByteArray(1024)
860
861             // Create an integer to track the number of bytes read.
862             var bytesRead: Int
863
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)
867             }
868
869             // Flush the export file output stream.
870             exportFileOutputStream.flush()
871
872             // Close the file streams.
873             temporaryExportFileInputStream.close()
874             exportFileOutputStream.close()
875
876             // Delete the temporary export file database, journal, and other related auxiliary files.
877             SQLiteDatabase.deleteDatabase(temporaryExportFile)
878
879             // Return the export successful string.
880             EXPORT_SUCCESSFUL
881         } catch (exception: Exception) {
882             // Return the export error.
883             exception.toString()
884         }
885     }
886
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.
892             SYSTEM_DEFAULT
893         else if (currentDatabaseInteger == 0)  // The switch is currently disabled and that is not the system default.
894             DISABLED
895         else  // The switch is currently enabled and that is not the system default.
896             ENABLED
897     }
898 }