]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportDatabaseHelper.kt
Display the bookmark opened in background snackbar in from of the bookmark drawer...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / helpers / ImportExportDatabaseHelper.kt
1 /*
2  * Copyright © 2018-2022 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 private class constants.
38 private const val SCHEMA_VERSION = 14
39 private const val PREFERENCES_TABLE = "preferences"
40
41 // Define the private preferences constants.
42 private const val ID = "_id"
43 private const val JAVASCRIPT = "javascript"
44 private const val COOKIES = "cookies"
45 private const val DOM_STORAGE = "dom_storage"
46 private const val SAVE_FORM_DATA = "save_form_data"
47 private const val USER_AGENT = "user_agent"
48 private const val CUSTOM_USER_AGENT = "custom_user_agent"
49 private const val INCOGNITO_MODE = "incognito_mode"
50 private const val ALLOW_SCREENSHOTS = "allow_screenshots"
51 private const val EASYLIST = "easylist"
52 private const val EASYPRIVACY = "easyprivacy"
53 private const val FANBOYS_ANNOYANCE_LIST = "fanboys_annoyance_list"
54 private const val FANBOYS_SOCIAL_BLOCKING_LIST = "fanboys_social_blocking_list"
55 private const val ULTRALIST = "ultralist"
56 private const val ULTRAPRIVACY = "ultraprivacy"
57 private const val BLOCK_ALL_THIRD_PARTY_REQUESTS = "block_all_third_party_requests"
58 private const val GOOGLE_ANALYTICS = "google_analytics"
59 private const val FACEBOOK_CLICK_IDS = "facebook_click_ids"
60 private const val TWITTER_AMP_REDIRECTS = "twitter_amp_redirects"
61 private const val SEARCH = "search"
62 private const val SEARCH_CUSTOM_URL = "search_custom_url"
63 private const val PROXY = "proxy"
64 private const val PROXY_CUSTOM_URL = "proxy_custom_url"
65 private const val FULL_SCREEN_BROWSING_MODE = "full_screen_browsing_mode"
66 private const val HIDE_APP_BAR = "hide_app_bar"
67 private const val CLEAR_EVERYTHING = "clear_everything"
68 private const val CLEAR_COOKIES = "clear_cookies"
69 private const val CLEAR_DOM_STORAGE = "clear_dom_storage"
70 private const val CLEAR_FORM_DATA = "clear_form_data"
71 private const val CLEAR_LOGCAT = "clear_logcat"
72 private const val CLEAR_CACHE = "clear_cache"
73 private const val HOMEPAGE = "homepage"
74 private const val FONT_SIZE = "font_size"
75 private const val OPEN_INTENTS_IN_NEW_TAB = "open_intents_in_new_tab"
76 private const val SWIPE_TO_REFRESH = "swipe_to_refresh"
77 private const val DOWNLOAD_WITH_EXTERNAL_APP = "download_with_external_app"
78 private const val SCROLL_APP_BAR = "scroll_app_bar"
79 private const val BOTTOM_APP_BAR = "bottom_app_bar"
80 private const val DISPLAY_ADDITIONAL_APP_BAR_ICONS = "display_additional_app_bar_icons"
81 private const val APP_THEME = "app_theme"
82 private const val WEBVIEW_THEME = "webview_theme"
83 private const val WIDE_VIEWPORT = "wide_viewport"
84 private const val DISPLAY_WEBPAGE_IMAGES = "display_webpage_images"
85
86 class ImportExportDatabaseHelper {
87     // Define the public companion object constants.  These can be moved to public class constants once the entire project has migrated to Kotlin.
88     companion object {
89         // Define the public class constants.
90         const val EXPORT_SUCCESSFUL = "Export Successful"
91         const val IMPORT_SUCCESSFUL = "Import Successful"
92     }
93
94     fun importUnencrypted(importFileInputStream: InputStream, context: Context): String {
95         return try {
96             // Create a temporary import file.
97             val temporaryImportFile = File.createTempFile("temporary_import_file", null, context.cacheDir)
98
99             // 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>
100             // It can be copied in Android using `Files.copy` once the minimum API >= 26.
101             // <https://developer.android.com/reference/java/nio/file/Files#copy(java.nio.file.Path,%20java.nio.file.Path,%20java.nio.file.CopyOption...)>
102             // 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>
103
104             // Create a temporary file output stream.
105             val temporaryImportFileOutputStream = FileOutputStream(temporaryImportFile)
106
107             // Create a transfer byte array.
108             val transferByteArray = ByteArray(1024)
109
110             // Create an integer to track the number of bytes read.
111             var bytesRead: Int
112
113             // Copy the import file to the temporary import file.
114             while (importFileInputStream.read(transferByteArray).also { bytesRead = it } > 0) {
115                 temporaryImportFileOutputStream.write(transferByteArray, 0, bytesRead)
116             }
117
118             // Flush the temporary import file output stream.
119             temporaryImportFileOutputStream.flush()
120
121             // Close the file streams.
122             importFileInputStream.close()
123             temporaryImportFileOutputStream.close()
124
125
126             // Get a handle for the shared preference.
127             val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
128
129             // Open the import database.  Once the minimum API >= 27 the file can be opened directly without using the string.
130             val importDatabase = SQLiteDatabase.openDatabase(temporaryImportFile.toString(), null, SQLiteDatabase.OPEN_READWRITE)
131
132             // Get the database version.
133             val importDatabaseVersion = importDatabase.version
134
135             // Upgrade from schema version 1, first used in Privacy Browser 2.13, to schema version 2, first used in Privacy Browser 2.14.
136             // Previously this upgrade added `download_with_external_app` to the Preferences table.  But that is now removed in schema version 10.
137
138             // Upgrade from schema version 2, first used in Privacy Browser 2.14, to schema version 3, first used in Privacy Browser 2.15.
139             if (importDatabaseVersion < 3) {
140                 // Once the SQLite version is >= 3.25.0 (API >= 30) `ALTER TABLE RENAME COLUMN` can be used.  <https://www.sqlite.org/lang_altertable.html> <https://www.sqlite.org/changes.html>
141                 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
142                 // 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.
143
144                 // Create the new font size column.
145                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $FONT_SIZE TEXT")
146
147                 // Populate the preferences table with the current font size value.
148                 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $FONT_SIZE = default_font_size")
149             }
150
151             // Upgrade from schema version 3, first used in Privacy Browser 2.15, to schema version 4, first used in Privacy Browser 2.16.
152             if (importDatabaseVersion < 4) {
153                 // Add the Pinned IP Addresses columns to the domains table.
154                 importDatabase.execSQL("ALTER TABLE ${DomainsDatabaseHelper.DOMAINS_TABLE} ADD COLUMN ${DomainsDatabaseHelper.PINNED_IP_ADDRESSES}  BOOLEAN")
155                 importDatabase.execSQL("ALTER TABLE ${DomainsDatabaseHelper.DOMAINS_TABLE} ADD COLUMN ${DomainsDatabaseHelper.IP_ADDRESSES} TEXT")
156             }
157
158             // Upgrade from schema version 4, first used in Privacy Browser 2.16, to schema version 5, first used in Privacy Browser 2.17.
159             if (importDatabaseVersion < 5) {
160                 // Add the hide and scroll app bar columns to the preferences table.
161                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $HIDE_APP_BAR BOOLEAN")
162                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $SCROLL_APP_BAR BOOLEAN")
163
164                 // Get the current hide and scroll app bar settings.
165                 val hideAppBar = sharedPreferences.getBoolean(HIDE_APP_BAR, true)
166                 val scrollAppBar = sharedPreferences.getBoolean(SCROLL_APP_BAR, true)
167
168                 // Populate the preferences table with the current app bar values.
169                 // This can switch to using the variables directly once the API >= 30.  <https://www.sqlite.org/datatype3.html#boolean_datatype>
170                 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
171                 if (hideAppBar) {
172                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $HIDE_APP_BAR = 1")
173                 } else {
174                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $HIDE_APP_BAR = 0")
175                 }
176
177                 if (scrollAppBar) {
178                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $SCROLL_APP_BAR = 1")
179                 } else {
180                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $SCROLL_APP_BAR = 0")
181                 }
182             }
183
184             // Upgrade from schema version 5, first used in Privacy Browser 2.17, to schema version 6, first used in Privacy Browser 3.0.
185             if (importDatabaseVersion < 6) {
186                 // Add the open intents in new tab column to the preferences table.
187                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $OPEN_INTENTS_IN_NEW_TAB BOOLEAN")
188
189                 // Get the current open intents in new tab preference.
190                 val openIntentsInNewTab = sharedPreferences.getBoolean(OPEN_INTENTS_IN_NEW_TAB, true)
191
192                 // Populate the preferences table with the current open intents value.
193                 // This can switch to using the variables directly once the API >= 30.  <https://www.sqlite.org/datatype3.html#boolean_datatype>
194                 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
195                 if (openIntentsInNewTab) {
196                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $OPEN_INTENTS_IN_NEW_TAB = 1")
197                 } else {
198                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $OPEN_INTENTS_IN_NEW_TAB = 0")
199                 }
200             }
201
202             // Upgrade from schema version 6, first used in Privacy Browser 3.0, to schema version 7, first used in Privacy Browser 3.1.
203             if (importDatabaseVersion < 7) {
204                 // Add the wide viewport column to the domains table.
205                 importDatabase.execSQL("ALTER TABLE ${DomainsDatabaseHelper.DOMAINS_TABLE} ADD COLUMN ${DomainsDatabaseHelper.WIDE_VIEWPORT} INTEGER")
206
207                 // Add the Google Analytics, Facebook Click IDs, Twitter AMP redirects, and wide viewport columns to the preferences table.
208                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $GOOGLE_ANALYTICS BOOLEAN")
209                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $FACEBOOK_CLICK_IDS BOOLEAN")
210                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $TWITTER_AMP_REDIRECTS BOOLEAN")
211                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $WIDE_VIEWPORT BOOLEAN")
212
213                 // Get the current preference values.
214                 val googleAnalytics = sharedPreferences.getBoolean(GOOGLE_ANALYTICS, true)
215                 val facebookClickIds = sharedPreferences.getBoolean(FACEBOOK_CLICK_IDS, true)
216                 val twitterAmpRedirects = sharedPreferences.getBoolean(TWITTER_AMP_REDIRECTS, true)
217                 val wideViewport = sharedPreferences.getBoolean(WIDE_VIEWPORT, true)
218
219                 // Populate the preferences with the current Google Analytics value.
220                 // This can switch to using the variables directly once the API >= 30.  <https://www.sqlite.org/datatype3.html#boolean_datatype>
221                 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
222                 if (googleAnalytics) {
223                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $GOOGLE_ANALYTICS = 1")
224                 } else {
225                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $GOOGLE_ANALYTICS = 0")
226                 }
227
228                 // Populate the preferences with the current Facebook Click IDs value.
229                 if (facebookClickIds) {
230                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $FACEBOOK_CLICK_IDS = 1")
231                 } else {
232                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $FACEBOOK_CLICK_IDS = 0")
233                 }
234
235                 // Populate the preferences table with the current Twitter AMP redirects value.
236                 if (twitterAmpRedirects) {
237                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $TWITTER_AMP_REDIRECTS = 1")
238                 } else {
239                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $TWITTER_AMP_REDIRECTS = 0")
240                 }
241
242                 // Populate the preferences table with the current wide viewport value.
243                 if (wideViewport) {
244                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $WIDE_VIEWPORT = 1")
245                 } else {
246                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $WIDE_VIEWPORT = 0")
247                 }
248             }
249
250             // Upgrade from schema version 7, first used in Privacy Browser 3.1, to schema version 8, first used in Privacy Browser 3.2.
251             if (importDatabaseVersion < 8) {
252                 // Add the UltraList column to the tables.
253                 importDatabase.execSQL("ALTER TABLE ${DomainsDatabaseHelper.DOMAINS_TABLE} ADD COLUMN ${DomainsDatabaseHelper.ULTRALIST} BOOLEAN")
254                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $ULTRALIST BOOLEAN")
255
256                 // Get the current preference values.
257                 val ultraList = sharedPreferences.getBoolean(ULTRALIST, true)
258
259                 // Populate the tables with the current UltraList value.
260                 // This can switch to using the variables directly once the API >= 30.  <https://www.sqlite.org/datatype3.html#boolean_datatype>
261                 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
262                 if (ultraList) {
263                     importDatabase.execSQL("UPDATE ${DomainsDatabaseHelper.DOMAINS_TABLE} SET ${DomainsDatabaseHelper.ULTRALIST}  =  1")
264                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $ULTRALIST = 1")
265                 } else {
266                     importDatabase.execSQL("UPDATE ${DomainsDatabaseHelper.DOMAINS_TABLE} SET ${DomainsDatabaseHelper.ULTRALIST} = 0")
267                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $ULTRALIST = 0")
268                 }
269             }
270
271             // Upgrade from schema version 8, first used in Privacy Browser 3.2, to schema version 9, first used in Privacy Browser 3.3.
272             if (importDatabaseVersion < 9) {
273                 // Add the new proxy columns to the preferences table.
274                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $PROXY TEXT")
275                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $PROXY_CUSTOM_URL TEXT")
276
277                 // Get the current proxy values.
278                 val proxy = sharedPreferences.getString(PROXY, context.getString(R.string.proxy_default_value))
279                 var proxyCustomUrl = sharedPreferences.getString(PROXY_CUSTOM_URL, context.getString(R.string.proxy_custom_url_default_value))
280
281                 // SQL escape the proxy custom URL string.
282                 proxyCustomUrl = DatabaseUtils.sqlEscapeString(proxyCustomUrl)
283
284                 // 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.
285                 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $PROXY = '$proxy'")
286                 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $PROXY_CUSTOM_URL = $proxyCustomUrl")
287             }
288
289             // Upgrade from schema version 9, first used in Privacy Browser 3.3, to schema version 10, first used in Privacy Browser 3.4.
290             // Previously this upgrade added `download_location` and `download_custom_location` to the Preferences table.  But they were removed in schema version 13.
291
292             // Upgrade from schema version 10, first used in Privacy Browser 3.4, to schema version 11, first used in Privacy Browser 3.5.
293             if (importDatabaseVersion < 11) {
294                 // Add the app theme column to the preferences table.
295                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $APP_THEME TEXT")
296
297                 // Get a cursor for the dark theme preference.
298                 val darkThemePreferencesCursor = importDatabase.rawQuery("SELECT dark_theme FROM $PREFERENCES_TABLE", null)
299
300                 // Move to the first entry.
301                 darkThemePreferencesCursor.moveToFirst()
302
303                 // Get the old dark theme value, which is in column 0.
304                 val darkTheme = darkThemePreferencesCursor.getInt(0)
305
306                 // Close the dark theme preference cursor.
307                 darkThemePreferencesCursor.close()
308
309                 // Get the system default string.
310                 val systemDefault = context.getString(R.string.app_theme_default_value)
311
312                 // Get the theme entry values string array.
313                 val appThemeEntryValuesStringArray: Array<String> = context.resources.getStringArray(R.array.app_theme_entry_values)
314
315                 // Get the dark string.
316                 val dark = appThemeEntryValuesStringArray[2]
317
318                 // Populate the app theme according to the old dark theme preference.
319                 if (darkTheme == 0) {  // A light theme was selected.
320                     // Set the app theme to be the system default.
321                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $APP_THEME = '$systemDefault'")
322                 } else {  // A dark theme was selected.
323                     // Set the app theme to be dark.
324                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $APP_THEME = '$dark'")
325                 }
326
327                 // 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.
328                 importDatabase.execSQL("ALTER TABLE ${DomainsDatabaseHelper.DOMAINS_TABLE} ADD COLUMN ${DomainsDatabaseHelper.WEBVIEW_THEME} INTEGER")
329
330                 // Add the WebView theme to the preferences table.
331                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $WEBVIEW_THEME TEXT")
332
333                 // Get the WebView theme default value string.
334                 val webViewThemeDefaultValue = context.getString(R.string.webview_theme_default_value)
335
336                 // Set the WebView theme in the preferences table to be the default.
337                 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $WEBVIEW_THEME = '$webViewThemeDefaultValue'")
338             }
339
340             // Upgrade from schema version 11, first used in Privacy Browser 3.5, to schema version 12, first used in Privacy Browser 3.6.
341             if (importDatabaseVersion < 12) {
342                 // Add the clear logcat column to the preferences table.
343                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $CLEAR_LOGCAT BOOLEAN")
344
345                 // Get the current clear logcat value.
346                 val clearLogcat = sharedPreferences.getBoolean(CLEAR_LOGCAT, true)
347
348                 // Populate the preferences table with the current clear logcat value.
349                 // This can switch to using the variables directly once the API >= 30.  <https://www.sqlite.org/datatype3.html#boolean_datatype>
350                 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
351                 if (clearLogcat) {
352                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $CLEAR_LOGCAT = 1")
353                 } else {
354                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $CLEAR_LOGCAT = 0")
355                 }
356             }
357
358             // Upgrade from schema version 12, first used in Privacy Browser 3.6, to schema version 13, first used in Privacy Browser 3.7.
359             // 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.
360
361             // Upgrade from schema version 13, first used in Privacy Browser 3.7, to schema version 14, first used in Privacy Browser 3.8.
362             if (importDatabaseVersion < 14) {
363                 // `enabledthirdpartycookies` was removed from the domains table.  `do_not_track` and `third_party_cookies` were removed from the preferences table.
364
365                 // Once the SQLite version is >= 3.25.0 `ALTER TABLE RENAME COLUMN` can be used.  <https://www.sqlite.org/lang_altertable.html> <https://www.sqlite.org/changes.html>
366                 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
367                 // 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.
368
369                 // Create the new cookies columns.
370                 importDatabase.execSQL("ALTER TABLE ${DomainsDatabaseHelper.DOMAINS_TABLE} ADD COLUMN ${DomainsDatabaseHelper.COOKIES} BOOLEAN")
371                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $COOKIES BOOLEAN")
372
373                 // Copy the data from the old cookies columns to the new ones.
374                 importDatabase.execSQL("UPDATE ${DomainsDatabaseHelper.DOMAINS_TABLE} SET ${DomainsDatabaseHelper.COOKIES} = enablefirstpartycookies")
375                 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $COOKIES = first_party_cookies")
376
377                 // Create the new download with external app and bottom app bar columns.
378                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $DOWNLOAD_WITH_EXTERNAL_APP BOOLEAN")
379                 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $BOTTOM_APP_BAR BOOLEAN")
380
381                 // Get the current values for the new columns.
382                 val downloadWithExternalApp = sharedPreferences.getBoolean(DOWNLOAD_WITH_EXTERNAL_APP, false)
383                 val bottomAppBar = sharedPreferences.getBoolean(BOTTOM_APP_BAR, false)
384
385                 // Populate the preferences table with the current download with external app value.
386                 // This can switch to using the variables directly once the API >= 30.  <https://www.sqlite.org/datatype3.html#boolean_datatype>
387                 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
388                 if (downloadWithExternalApp) {
389                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $DOWNLOAD_WITH_EXTERNAL_APP = 1")
390                 } else {
391                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $DOWNLOAD_WITH_EXTERNAL_APP = 0")
392                 }
393
394                 // Populate the preferences table with the current bottom app bar value.
395                 if (bottomAppBar) {
396                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $BOTTOM_APP_BAR = 1")
397                 } else {
398                     importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $BOTTOM_APP_BAR = 0")
399                 }
400             }
401
402             // Get a cursor for the bookmarks table.
403             val importBookmarksCursor = importDatabase.rawQuery("SELECT * FROM ${BookmarksDatabaseHelper.BOOKMARKS_TABLE}", null)
404
405             // Delete the current bookmarks database.
406             context.deleteDatabase(BookmarksDatabaseHelper.BOOKMARKS_DATABASE)
407
408             // Create a new bookmarks database.
409             val bookmarksDatabaseHelper = BookmarksDatabaseHelper(context)
410
411             // Move to the first record.
412             importBookmarksCursor.moveToFirst()
413
414             // Copy the data from the import bookmarks cursor into the bookmarks database.
415             for (i in 0 until importBookmarksCursor.count) {
416                 // Create a bookmark content values.
417                 val bookmarkContentValues = ContentValues()
418
419                 // Add the information for this bookmark to the content values.
420                 bookmarkContentValues.put(BookmarksDatabaseHelper.BOOKMARK_NAME, importBookmarksCursor.getString(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME)))
421                 bookmarkContentValues.put(BookmarksDatabaseHelper.BOOKMARK_URL, importBookmarksCursor.getString(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)))
422                 bookmarkContentValues.put(BookmarksDatabaseHelper.PARENT_FOLDER, importBookmarksCursor.getString(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.PARENT_FOLDER)))
423                 bookmarkContentValues.put(BookmarksDatabaseHelper.DISPLAY_ORDER, importBookmarksCursor.getInt(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)))
424                 bookmarkContentValues.put(BookmarksDatabaseHelper.IS_FOLDER, importBookmarksCursor.getInt(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)))
425                 bookmarkContentValues.put(BookmarksDatabaseHelper.FAVORITE_ICON, importBookmarksCursor.getBlob(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON)))
426
427                 // Insert the content values into the bookmarks database.
428                 bookmarksDatabaseHelper.createBookmark(bookmarkContentValues)
429
430                 // Advance to the next record.
431                 importBookmarksCursor.moveToNext()
432             }
433
434             // Close the bookmarks cursor and database.
435             importBookmarksCursor.close()
436             bookmarksDatabaseHelper.close()
437
438
439             // Get a cursor for the domains table.
440             val importDomainsCursor = importDatabase.rawQuery("SELECT * FROM ${DomainsDatabaseHelper.DOMAINS_TABLE} ORDER BY ${DomainsDatabaseHelper.DOMAIN_NAME} ASC", null)
441
442             // Delete the current domains database.
443             context.deleteDatabase(DomainsDatabaseHelper.DOMAINS_DATABASE)
444
445             // Create a new domains database.
446             val domainsDatabaseHelper = DomainsDatabaseHelper(context)
447
448             // Move to the first record.
449             importDomainsCursor.moveToFirst()
450
451             // Copy the data from the import domains cursor into the domains database.
452             for (i in 0 until importDomainsCursor.count) {
453                 // Create a domain content values.
454                 val domainContentValues = ContentValues()
455
456                 // Populate the domain content values.
457                 domainContentValues.put(DomainsDatabaseHelper.DOMAIN_NAME, importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME)))
458                 domainContentValues.put(DomainsDatabaseHelper.ENABLE_JAVASCRIPT, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)))
459                 domainContentValues.put(DomainsDatabaseHelper.COOKIES, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.COOKIES)))
460                 domainContentValues.put(DomainsDatabaseHelper.ENABLE_DOM_STORAGE, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)))
461                 domainContentValues.put(DomainsDatabaseHelper.ENABLE_FORM_DATA, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FORM_DATA)))
462                 domainContentValues.put(DomainsDatabaseHelper.ENABLE_EASYLIST, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYLIST)))
463                 domainContentValues.put(DomainsDatabaseHelper.ENABLE_EASYPRIVACY, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)))
464                 domainContentValues.put(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST,
465                     importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)))
466                 domainContentValues.put(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST,
467                     importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)))
468                 domainContentValues.put(DomainsDatabaseHelper.ULTRALIST, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ULTRALIST)))
469                 domainContentValues.put(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)))
470                 domainContentValues.put(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS,
471                     importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)))
472                 domainContentValues.put(DomainsDatabaseHelper.USER_AGENT, importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.USER_AGENT)))
473                 domainContentValues.put(DomainsDatabaseHelper.FONT_SIZE, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.FONT_SIZE)))
474                 domainContentValues.put(DomainsDatabaseHelper.SWIPE_TO_REFRESH, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SWIPE_TO_REFRESH)))
475                 domainContentValues.put(DomainsDatabaseHelper.WEBVIEW_THEME, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WEBVIEW_THEME)))
476                 domainContentValues.put(DomainsDatabaseHelper.WIDE_VIEWPORT, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WIDE_VIEWPORT)))
477                 domainContentValues.put(DomainsDatabaseHelper.DISPLAY_IMAGES, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DISPLAY_IMAGES)))
478                 domainContentValues.put(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)))
479                 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME,
480                     importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)))
481                 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION,
482                     importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)))
483                 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT,
484                     importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)))
485                 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME,
486                     importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)))
487                 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION,
488                     importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)))
489                 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT,
490                     importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)))
491                 domainContentValues.put(DomainsDatabaseHelper.SSL_START_DATE, importDomainsCursor.getLong(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_START_DATE)))
492                 domainContentValues.put(DomainsDatabaseHelper.SSL_END_DATE, importDomainsCursor.getLong(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_END_DATE)))
493                 domainContentValues.put(DomainsDatabaseHelper.PINNED_IP_ADDRESSES, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)))
494                 domainContentValues.put(DomainsDatabaseHelper.IP_ADDRESSES, importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.IP_ADDRESSES)))
495
496                 // Insert the content values into the domains database.
497                 domainsDatabaseHelper.addDomain(domainContentValues)
498
499                 // Advance to the next record.
500                 importDomainsCursor.moveToNext()
501             }
502
503             // Close the domains cursor and database.
504             importDomainsCursor.close()
505             domainsDatabaseHelper.close()
506
507
508             // Get a cursor for the preferences table.
509             val importPreferencesCursor = importDatabase.rawQuery("SELECT * FROM $PREFERENCES_TABLE", null)
510
511             // Move to the first record.
512             importPreferencesCursor.moveToFirst()
513
514             // Import the preference data.
515             sharedPreferences.edit()
516                 .putBoolean(JAVASCRIPT, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(JAVASCRIPT)) == 1)
517                 .putBoolean(COOKIES, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(COOKIES)) == 1)
518                 .putBoolean(DOM_STORAGE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(DOM_STORAGE)) == 1)
519                 .putBoolean(SAVE_FORM_DATA, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(SAVE_FORM_DATA)) == 1)  // Save form data can be removed once the minimum API >= 26.
520                 .putString(USER_AGENT, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(USER_AGENT)))
521                 .putString(CUSTOM_USER_AGENT, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(CUSTOM_USER_AGENT)))
522                 .putBoolean(INCOGNITO_MODE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(INCOGNITO_MODE)) == 1)
523                 .putBoolean(ALLOW_SCREENSHOTS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(ALLOW_SCREENSHOTS)) == 1)
524                 .putBoolean(EASYLIST, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(EASYLIST)) == 1)
525                 .putBoolean(EASYPRIVACY, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(EASYPRIVACY)) == 1)
526                 .putBoolean(FANBOYS_ANNOYANCE_LIST, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(FANBOYS_ANNOYANCE_LIST)) == 1)
527                 .putBoolean(FANBOYS_SOCIAL_BLOCKING_LIST, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(FANBOYS_SOCIAL_BLOCKING_LIST)) == 1)
528                 .putBoolean(ULTRALIST, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(ULTRALIST)) == 1)
529                 .putBoolean(ULTRAPRIVACY, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(ULTRAPRIVACY)) == 1)
530                 .putBoolean(BLOCK_ALL_THIRD_PARTY_REQUESTS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1)
531                 .putBoolean(GOOGLE_ANALYTICS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(GOOGLE_ANALYTICS)) == 1)
532                 .putBoolean(FACEBOOK_CLICK_IDS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(FACEBOOK_CLICK_IDS)) == 1)
533                 .putBoolean(TWITTER_AMP_REDIRECTS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(TWITTER_AMP_REDIRECTS)) == 1)
534                 .putString(SEARCH, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(SEARCH)))
535                 .putString(SEARCH_CUSTOM_URL, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(SEARCH_CUSTOM_URL)))
536                 .putString(PROXY, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(PROXY)))
537                 .putString(PROXY_CUSTOM_URL, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(PROXY_CUSTOM_URL)))
538                 .putBoolean(FULL_SCREEN_BROWSING_MODE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(FULL_SCREEN_BROWSING_MODE)) == 1)
539                 .putBoolean(HIDE_APP_BAR, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(HIDE_APP_BAR)) == 1)
540                 .putBoolean(CLEAR_EVERYTHING, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_EVERYTHING)) == 1)
541                 .putBoolean(CLEAR_COOKIES, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_COOKIES)) == 1)
542                 .putBoolean(CLEAR_DOM_STORAGE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_DOM_STORAGE)) == 1)
543                 .putBoolean(CLEAR_FORM_DATA, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_FORM_DATA)) == 1)  // Clear form data can be removed once the minimum API >= 26.
544                 .putBoolean(CLEAR_LOGCAT, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_LOGCAT)) == 1)
545                 .putBoolean(CLEAR_CACHE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_CACHE)) == 1)
546                 .putString(HOMEPAGE, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(HOMEPAGE)))
547                 .putString(FONT_SIZE, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(FONT_SIZE)))
548                 .putBoolean(OPEN_INTENTS_IN_NEW_TAB, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(OPEN_INTENTS_IN_NEW_TAB)) == 1)
549                 .putBoolean(SWIPE_TO_REFRESH, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(SWIPE_TO_REFRESH)) == 1)
550                 .putBoolean(DOWNLOAD_WITH_EXTERNAL_APP, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(DOWNLOAD_WITH_EXTERNAL_APP)) == 1)
551                 .putBoolean(SCROLL_APP_BAR, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(SCROLL_APP_BAR)) == 1)
552                 .putBoolean(BOTTOM_APP_BAR, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(BOTTOM_APP_BAR)) == 1)
553                 .putBoolean(DISPLAY_ADDITIONAL_APP_BAR_ICONS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(DISPLAY_ADDITIONAL_APP_BAR_ICONS)) == 1)
554                 .putString(APP_THEME, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(APP_THEME)))
555                 .putString(WEBVIEW_THEME, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(WEBVIEW_THEME)))
556                 .putBoolean(WIDE_VIEWPORT, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(WIDE_VIEWPORT)) == 1)
557                 .putBoolean(DISPLAY_WEBPAGE_IMAGES, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(DISPLAY_WEBPAGE_IMAGES)) == 1)
558                 .apply()
559
560             // Close the preferences cursor and database.
561             importPreferencesCursor.close()
562             importDatabase.close()
563
564             // Delete the temporary import file database, journal, and other related auxiliary files.
565             SQLiteDatabase.deleteDatabase(temporaryImportFile)
566
567             // Return the import successful string.
568             IMPORT_SUCCESSFUL
569         } catch (exception: Exception) {
570             // Return the import error.
571             exception.toString()
572         }
573     }
574
575     fun exportUnencrypted(exportFileOutputStream: OutputStream, context: Context): String {
576         return try {
577             // Create a temporary export file.
578             val temporaryExportFile = File.createTempFile("temporary_export_file", null, context.cacheDir)
579
580             // Create the temporary export database.
581             val temporaryExportDatabase = SQLiteDatabase.openOrCreateDatabase(temporaryExportFile, null)
582
583             // Set the temporary export database version number.
584             temporaryExportDatabase.version = SCHEMA_VERSION
585
586
587             // Create the temporary export database bookmarks table.
588             temporaryExportDatabase.execSQL(BookmarksDatabaseHelper.CREATE_BOOKMARKS_TABLE)
589
590             // Open the bookmarks database.
591             val bookmarksDatabaseHelper = BookmarksDatabaseHelper(context)
592
593             // Get a full bookmarks cursor.
594             val bookmarksCursor = bookmarksDatabaseHelper.allBookmarks
595
596             // Move to the first record.
597             bookmarksCursor.moveToFirst()
598
599             // Copy the data from the bookmarks cursor into the export database.
600             for (i in 0 until bookmarksCursor.count) {
601                 // Create a bookmark content values.
602                 val bookmarkContentValues = ContentValues()
603
604                 // Populate the bookmark content values.
605                 bookmarkContentValues.put(BookmarksDatabaseHelper.BOOKMARK_NAME, bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME)))
606                 bookmarkContentValues.put(BookmarksDatabaseHelper.BOOKMARK_URL, bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)))
607                 bookmarkContentValues.put(BookmarksDatabaseHelper.PARENT_FOLDER, bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.PARENT_FOLDER)))
608                 bookmarkContentValues.put(BookmarksDatabaseHelper.DISPLAY_ORDER, bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)))
609                 bookmarkContentValues.put(BookmarksDatabaseHelper.IS_FOLDER, bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)))
610                 bookmarkContentValues.put(BookmarksDatabaseHelper.FAVORITE_ICON, bookmarksCursor.getBlob(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON)))
611
612                 // Insert the content values into the temporary export database.
613                 temporaryExportDatabase.insert(BookmarksDatabaseHelper.BOOKMARKS_TABLE, null, bookmarkContentValues)
614
615                 // Advance to the next record.
616                 bookmarksCursor.moveToNext()
617             }
618
619             // Close the bookmarks cursor and database.
620             bookmarksCursor.close()
621             bookmarksDatabaseHelper.close()
622
623
624             // Create the temporary export database domains table.
625             temporaryExportDatabase.execSQL(DomainsDatabaseHelper.CREATE_DOMAINS_TABLE)
626
627             // Open the domains database.
628             val domainsDatabaseHelper = DomainsDatabaseHelper(context)
629
630             // Get a full domains database cursor.
631             val domainsCursor = domainsDatabaseHelper.completeCursorOrderedByDomain
632
633             // Move to the first record.
634             domainsCursor.moveToFirst()
635
636             // Copy the data from the domains cursor into the export database.
637             for (i in 0 until domainsCursor.count) {
638                 // Create a domain content values.
639                 val domainContentValues = ContentValues()
640
641                 // Populate the domain content values.
642                 domainContentValues.put(DomainsDatabaseHelper.DOMAIN_NAME, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME)))
643                 domainContentValues.put(DomainsDatabaseHelper.ENABLE_JAVASCRIPT, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)))
644                 domainContentValues.put(DomainsDatabaseHelper.COOKIES, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.COOKIES)))
645                 domainContentValues.put(DomainsDatabaseHelper.ENABLE_DOM_STORAGE, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)))
646                 domainContentValues.put(DomainsDatabaseHelper.ENABLE_FORM_DATA, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FORM_DATA)))
647                 domainContentValues.put(DomainsDatabaseHelper.ENABLE_EASYLIST, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYLIST)))
648                 domainContentValues.put(DomainsDatabaseHelper.ENABLE_EASYPRIVACY, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)))
649                 domainContentValues.put(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)))
650                 domainContentValues.put(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST,
651                     domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)))
652                 domainContentValues.put(DomainsDatabaseHelper.ULTRALIST, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ULTRALIST)))
653                 domainContentValues.put(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)))
654                 domainContentValues.put(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)))
655                 domainContentValues.put(DomainsDatabaseHelper.USER_AGENT, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.USER_AGENT)))
656                 domainContentValues.put(DomainsDatabaseHelper.FONT_SIZE, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.FONT_SIZE)))
657                 domainContentValues.put(DomainsDatabaseHelper.SWIPE_TO_REFRESH, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SWIPE_TO_REFRESH)))
658                 domainContentValues.put(DomainsDatabaseHelper.WEBVIEW_THEME, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WEBVIEW_THEME)))
659                 domainContentValues.put(DomainsDatabaseHelper.WIDE_VIEWPORT, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WIDE_VIEWPORT)))
660                 domainContentValues.put(DomainsDatabaseHelper.DISPLAY_IMAGES, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DISPLAY_IMAGES)))
661                 domainContentValues.put(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)))
662                 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)))
663                 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)))
664                 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT,
665                     domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)))
666                 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)))
667                 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)))
668                 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT,
669                     domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)))
670                 domainContentValues.put(DomainsDatabaseHelper.SSL_START_DATE, domainsCursor.getLong(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_START_DATE)))
671                 domainContentValues.put(DomainsDatabaseHelper.SSL_END_DATE, domainsCursor.getLong(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_END_DATE)))
672                 domainContentValues.put(DomainsDatabaseHelper.PINNED_IP_ADDRESSES, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)))
673                 domainContentValues.put(DomainsDatabaseHelper.IP_ADDRESSES, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.IP_ADDRESSES)))
674
675                 // Insert the content values into the temporary export database.
676                 temporaryExportDatabase.insert(DomainsDatabaseHelper.DOMAINS_TABLE, null, domainContentValues)
677
678                 // Advance to the next record.
679                 domainsCursor.moveToNext()
680             }
681
682             // Close the domains cursor and database.
683             domainsCursor.close()
684             domainsDatabaseHelper.close()
685
686
687             // Prepare the preferences table SQL creation string.
688             val createPreferencesTable = "CREATE TABLE " + PREFERENCES_TABLE + " (" +
689                     ID + " INTEGER PRIMARY KEY, " +
690                     JAVASCRIPT + " BOOLEAN, " +
691                     COOKIES + " BOOLEAN, " +
692                     DOM_STORAGE + " BOOLEAN, " +
693                     SAVE_FORM_DATA + " BOOLEAN, " +
694                     USER_AGENT + " TEXT, " +
695                     CUSTOM_USER_AGENT + " TEXT, " +
696                     INCOGNITO_MODE + " BOOLEAN, " +
697                     ALLOW_SCREENSHOTS + " BOOLEAN, " +
698                     EASYLIST + " BOOLEAN, " +
699                     EASYPRIVACY + " BOOLEAN, " +
700                     FANBOYS_ANNOYANCE_LIST + " BOOLEAN, " +
701                     FANBOYS_SOCIAL_BLOCKING_LIST + " BOOLEAN, " +
702                     ULTRALIST + " BOOLEAN, " +
703                     ULTRAPRIVACY + " BOOLEAN, " +
704                     BLOCK_ALL_THIRD_PARTY_REQUESTS + " BOOLEAN, " +
705                     GOOGLE_ANALYTICS + " BOOLEAN, " +
706                     FACEBOOK_CLICK_IDS + " BOOLEAN, " +
707                     TWITTER_AMP_REDIRECTS + " BOOLEAN, " +
708                     SEARCH + " TEXT, " +
709                     SEARCH_CUSTOM_URL + " TEXT, " +
710                     PROXY + " TEXT, " +
711                     PROXY_CUSTOM_URL + " TEXT, " +
712                     FULL_SCREEN_BROWSING_MODE + " BOOLEAN, " +
713                     HIDE_APP_BAR + " BOOLEAN, " +
714                     CLEAR_EVERYTHING + " BOOLEAN, " +
715                     CLEAR_COOKIES + " BOOLEAN, " +
716                     CLEAR_DOM_STORAGE + " BOOLEAN, " +
717                     CLEAR_FORM_DATA + " BOOLEAN, " +
718                     CLEAR_LOGCAT + " BOOLEAN, " +
719                     CLEAR_CACHE + " BOOLEAN, " +
720                     HOMEPAGE + " TEXT, " +
721                     FONT_SIZE + " TEXT, " +
722                     OPEN_INTENTS_IN_NEW_TAB + " BOOLEAN, " +
723                     SWIPE_TO_REFRESH + " BOOLEAN, " +
724                     DOWNLOAD_WITH_EXTERNAL_APP + " BOOLEAN, " +
725                     SCROLL_APP_BAR + " BOOLEAN, " +
726                     BOTTOM_APP_BAR + " BOOLEAN, " +
727                     DISPLAY_ADDITIONAL_APP_BAR_ICONS + " BOOLEAN, " +
728                     APP_THEME + " TEXT, " +
729                     WEBVIEW_THEME + " TEXT, " +
730                     WIDE_VIEWPORT + " BOOLEAN, " +
731                     DISPLAY_WEBPAGE_IMAGES + " BOOLEAN)"
732
733             // Create the temporary export database preferences table.
734             temporaryExportDatabase.execSQL(createPreferencesTable)
735
736             // Get a handle for the shared preference.
737             val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
738
739             // Create a preferences content values.
740             val preferencesContentValues = ContentValues()
741
742             // Populate the preferences content values.
743             preferencesContentValues.put(JAVASCRIPT, sharedPreferences.getBoolean(JAVASCRIPT, false))
744             preferencesContentValues.put(COOKIES, sharedPreferences.getBoolean(COOKIES, false))
745             preferencesContentValues.put(DOM_STORAGE, sharedPreferences.getBoolean(DOM_STORAGE, false))
746             preferencesContentValues.put(SAVE_FORM_DATA, sharedPreferences.getBoolean(SAVE_FORM_DATA, false))  // Save form data can be removed once the minimum API >= 26.
747             preferencesContentValues.put(USER_AGENT, sharedPreferences.getString(USER_AGENT, context.getString(R.string.user_agent_default_value)))
748             preferencesContentValues.put(CUSTOM_USER_AGENT, sharedPreferences.getString(CUSTOM_USER_AGENT, context.getString(R.string.custom_user_agent_default_value)))
749             preferencesContentValues.put(INCOGNITO_MODE, sharedPreferences.getBoolean(INCOGNITO_MODE, false))
750             preferencesContentValues.put(ALLOW_SCREENSHOTS, sharedPreferences.getBoolean(ALLOW_SCREENSHOTS, false))
751             preferencesContentValues.put(EASYLIST, sharedPreferences.getBoolean(EASYLIST, true))
752             preferencesContentValues.put(EASYPRIVACY, sharedPreferences.getBoolean(EASYPRIVACY, true))
753             preferencesContentValues.put(FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean(FANBOYS_ANNOYANCE_LIST, true))
754             preferencesContentValues.put(FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean(FANBOYS_SOCIAL_BLOCKING_LIST, true))
755             preferencesContentValues.put(ULTRALIST, sharedPreferences.getBoolean(ULTRALIST, true))
756             preferencesContentValues.put(ULTRAPRIVACY, sharedPreferences.getBoolean(ULTRAPRIVACY, true))
757             preferencesContentValues.put(BLOCK_ALL_THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean(BLOCK_ALL_THIRD_PARTY_REQUESTS, false))
758             preferencesContentValues.put(GOOGLE_ANALYTICS, sharedPreferences.getBoolean(GOOGLE_ANALYTICS, true))
759             preferencesContentValues.put(FACEBOOK_CLICK_IDS, sharedPreferences.getBoolean(FACEBOOK_CLICK_IDS, true))
760             preferencesContentValues.put(TWITTER_AMP_REDIRECTS, sharedPreferences.getBoolean(TWITTER_AMP_REDIRECTS, true))
761             preferencesContentValues.put(SEARCH, sharedPreferences.getString(SEARCH, context.getString(R.string.search_default_value)))
762             preferencesContentValues.put(SEARCH_CUSTOM_URL, sharedPreferences.getString(SEARCH_CUSTOM_URL, context.getString(R.string.search_custom_url_default_value)))
763             preferencesContentValues.put(PROXY, sharedPreferences.getString(PROXY, context.getString(R.string.proxy_default_value)))
764             preferencesContentValues.put(PROXY_CUSTOM_URL, sharedPreferences.getString(PROXY_CUSTOM_URL, context.getString(R.string.proxy_custom_url_default_value)))
765             preferencesContentValues.put(FULL_SCREEN_BROWSING_MODE, sharedPreferences.getBoolean(FULL_SCREEN_BROWSING_MODE, false))
766             preferencesContentValues.put(HIDE_APP_BAR, sharedPreferences.getBoolean(HIDE_APP_BAR, true))
767             preferencesContentValues.put(CLEAR_EVERYTHING, sharedPreferences.getBoolean(CLEAR_EVERYTHING, true))
768             preferencesContentValues.put(CLEAR_COOKIES, sharedPreferences.getBoolean(CLEAR_COOKIES, true))
769             preferencesContentValues.put(CLEAR_DOM_STORAGE, sharedPreferences.getBoolean(CLEAR_DOM_STORAGE, true))
770             preferencesContentValues.put(CLEAR_FORM_DATA, sharedPreferences.getBoolean(CLEAR_FORM_DATA, true))  // Clear form data can be removed once the minimum API >= 26.
771             preferencesContentValues.put(CLEAR_LOGCAT, sharedPreferences.getBoolean(CLEAR_LOGCAT, true))
772             preferencesContentValues.put(CLEAR_CACHE, sharedPreferences.getBoolean(CLEAR_CACHE, true))
773             preferencesContentValues.put(HOMEPAGE, sharedPreferences.getString(HOMEPAGE, context.getString(R.string.homepage_default_value)))
774             preferencesContentValues.put(FONT_SIZE, sharedPreferences.getString(FONT_SIZE, context.getString(R.string.font_size_default_value)))
775             preferencesContentValues.put(OPEN_INTENTS_IN_NEW_TAB, sharedPreferences.getBoolean(OPEN_INTENTS_IN_NEW_TAB, true))
776             preferencesContentValues.put(SWIPE_TO_REFRESH, sharedPreferences.getBoolean(SWIPE_TO_REFRESH, true))
777             preferencesContentValues.put(DOWNLOAD_WITH_EXTERNAL_APP, sharedPreferences.getBoolean(DOWNLOAD_WITH_EXTERNAL_APP, false))
778             preferencesContentValues.put(SCROLL_APP_BAR, sharedPreferences.getBoolean(SCROLL_APP_BAR, true))
779             preferencesContentValues.put(BOTTOM_APP_BAR, sharedPreferences.getBoolean(BOTTOM_APP_BAR, false))
780             preferencesContentValues.put(DISPLAY_ADDITIONAL_APP_BAR_ICONS, sharedPreferences.getBoolean(DISPLAY_ADDITIONAL_APP_BAR_ICONS, false))
781             preferencesContentValues.put(APP_THEME, sharedPreferences.getString(APP_THEME, context.getString(R.string.app_theme_default_value)))
782             preferencesContentValues.put(WEBVIEW_THEME, sharedPreferences.getString(WEBVIEW_THEME, context.getString(R.string.webview_theme_default_value)))
783             preferencesContentValues.put(WIDE_VIEWPORT, sharedPreferences.getBoolean(WIDE_VIEWPORT, true))
784             preferencesContentValues.put(DISPLAY_WEBPAGE_IMAGES, sharedPreferences.getBoolean(DISPLAY_WEBPAGE_IMAGES, true))
785
786             // Insert the preferences content values into the temporary export database.
787             temporaryExportDatabase.insert(PREFERENCES_TABLE, null, preferencesContentValues)
788
789             // Close the temporary export database.
790             temporaryExportDatabase.close()
791
792
793             // 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>
794             // It can be copied in Android using `Files.copy` once the minimum API >= 26.
795             // <https://developer.android.com/reference/java/nio/file/Files#copy(java.nio.file.Path,%20java.nio.file.Path,%20java.nio.file.CopyOption...)>
796             // 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>
797
798             // Create the temporary export file input stream.
799             val temporaryExportFileInputStream = FileInputStream(temporaryExportFile)
800
801             // Create a byte array.
802             val transferByteArray = ByteArray(1024)
803
804             // Create an integer to track the number of bytes read.
805             var bytesRead: Int
806
807             // Copy the temporary export file to the export file output stream.
808             while (temporaryExportFileInputStream.read(transferByteArray).also { bytesRead = it } > 0) {
809                 exportFileOutputStream.write(transferByteArray, 0, bytesRead)
810             }
811
812             // Flush the export file output stream.
813             exportFileOutputStream.flush()
814
815             // Close the file streams.
816             temporaryExportFileInputStream.close()
817             exportFileOutputStream.close()
818
819             // Delete the temporary export file database, journal, and other related auxiliary files.
820             SQLiteDatabase.deleteDatabase(temporaryExportFile)
821
822             // Return the export successful string.
823             EXPORT_SUCCESSFUL
824         } catch (exception: Exception) {
825             // Return the export error.
826             exception.toString()
827         }
828     }
829 }