2 * Copyright © 2018-2022 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
6 * Privacy Browser Android is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser Android is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser Android. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser.helpers
22 import android.content.ContentValues
23 import android.content.Context
24 import android.database.DatabaseUtils
25 import android.database.sqlite.SQLiteDatabase
27 import androidx.preference.PreferenceManager
29 import com.stoutner.privacybrowser.R
32 import java.io.FileInputStream
33 import java.io.FileOutputStream
34 import java.io.InputStream
35 import java.io.OutputStream
37 // Define the private class constants.
38 private const val SCHEMA_VERSION = 15
39 private const val PREFERENCES_TABLE = "preferences"
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 X_REQUESTED_WITH_HEADER = "x_requested_with_header"
50 private const val INCOGNITO_MODE = "incognito_mode"
51 private const val ALLOW_SCREENSHOTS = "allow_screenshots"
52 private const val EASYLIST = "easylist"
53 private const val EASYPRIVACY = "easyprivacy"
54 private const val FANBOYS_ANNOYANCE_LIST = "fanboys_annoyance_list"
55 private const val FANBOYS_SOCIAL_BLOCKING_LIST = "fanboys_social_blocking_list"
56 private const val ULTRALIST = "ultralist"
57 private const val ULTRAPRIVACY = "ultraprivacy"
58 private const val BLOCK_ALL_THIRD_PARTY_REQUESTS = "block_all_third_party_requests"
59 private const val TRACKING_QUERIES = "tracking_queries"
60 private const val AMP_REDIRECTS = "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"
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.
89 // Define the public class constants.
90 const val EXPORT_SUCCESSFUL = "Export Successful"
91 const val IMPORT_SUCCESSFUL = "Import Successful"
94 fun importUnencrypted(importFileInputStream: InputStream, context: Context): String {
96 // Create a temporary import file.
97 val temporaryImportFile = File.createTempFile("temporary_import_file", null, context.cacheDir)
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>
104 // Create a temporary file output stream.
105 val temporaryImportFileOutputStream = FileOutputStream(temporaryImportFile)
107 // Create a transfer byte array.
108 val transferByteArray = ByteArray(1024)
110 // Create an integer to track the number of bytes read.
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)
118 // Flush the temporary import file output stream.
119 temporaryImportFileOutputStream.flush()
121 // Close the file streams.
122 importFileInputStream.close()
123 temporaryImportFileOutputStream.close()
126 // Get a handle for the shared preference.
127 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
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)
132 // Get the database version.
133 val importDatabaseVersion = importDatabase.version
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.
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 (Android 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.
144 // Create the new font size column.
145 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $FONT_SIZE TEXT")
147 // Populate the preferences table with the current font size value.
148 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $FONT_SIZE = default_font_size")
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")
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")
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)
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>
172 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $HIDE_APP_BAR = 1")
174 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $HIDE_APP_BAR = 0")
177 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $SCROLL_APP_BAR = 1")
179 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $SCROLL_APP_BAR = 0")
182 // Upgrade from schema version 5, first used in Privacy Browser 2.17, to schema version 6, first used in Privacy Browser 3.0.
183 if (importDatabaseVersion < 6) {
184 // Add the open intents in new tab column to the preferences table.
185 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $OPEN_INTENTS_IN_NEW_TAB BOOLEAN")
187 // Get the current open intents in new tab preference.
188 val openIntentsInNewTab = sharedPreferences.getBoolean(OPEN_INTENTS_IN_NEW_TAB, true)
190 // Populate the preferences table with the current open intents value.
191 // This can switch to using the variables directly once the API >= 30. <https://www.sqlite.org/datatype3.html#boolean_datatype>
192 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
193 if (openIntentsInNewTab)
194 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $OPEN_INTENTS_IN_NEW_TAB = 1")
196 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $OPEN_INTENTS_IN_NEW_TAB = 0")
199 // Upgrade from schema version 6, first used in Privacy Browser 3.0, to schema version 7, first used in Privacy Browser 3.1.
200 if (importDatabaseVersion < 7) {
201 // Previously this upgrade added `facebook_click_ids` to the Preferences table. But that is now removed in schema version 15.
203 // Add the wide viewport column to the domains table.
204 importDatabase.execSQL("ALTER TABLE ${DomainsDatabaseHelper.DOMAINS_TABLE} ADD COLUMN ${DomainsDatabaseHelper.WIDE_VIEWPORT} INTEGER")
206 // Add the Google Analytics, Twitter AMP redirects, and wide viewport columns to the preferences table.
207 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN google_analytics BOOLEAN")
208 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN twitter_amp_redirects BOOLEAN")
209 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $WIDE_VIEWPORT BOOLEAN")
211 // Get the current preference values.
212 val trackingQueries = sharedPreferences.getBoolean(TRACKING_QUERIES, true)
213 val ampRedirects = sharedPreferences.getBoolean(AMP_REDIRECTS, true)
214 val wideViewport = sharedPreferences.getBoolean(WIDE_VIEWPORT, true)
216 // Populate the preferences with the current Tracking Queries value. Google Analytics was renamed Tracking Queries in schema version 15.
217 // This can switch to using the variables directly once the API >= 30. <https://www.sqlite.org/datatype3.html#boolean_datatype>
218 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
220 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET google_analytics = 1")
222 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET google_analytics = 0")
224 // Populate the preferences table with the current AMP Redirects value. Twitter AMP Redirects was renamed AMP Redirects in schema version 15.
226 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET twitter_amp_redirects = 1")
228 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET twitter_amp_redirects = 0")
230 // Populate the preferences table with the current wide viewport value.
232 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $WIDE_VIEWPORT = 1")
234 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $WIDE_VIEWPORT = 0")
237 // Upgrade from schema version 7, first used in Privacy Browser 3.1, to schema version 8, first used in Privacy Browser 3.2.
238 if (importDatabaseVersion < 8) {
239 // Add the UltraList column to the tables.
240 importDatabase.execSQL("ALTER TABLE ${DomainsDatabaseHelper.DOMAINS_TABLE} ADD COLUMN ${DomainsDatabaseHelper.ULTRALIST} BOOLEAN")
241 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $ULTRALIST BOOLEAN")
243 // Get the current preference values.
244 val ultraList = sharedPreferences.getBoolean(ULTRALIST, true)
246 // Populate the tables with the current UltraList value.
247 // This can switch to using the variables directly once the API >= 30. <https://www.sqlite.org/datatype3.html#boolean_datatype>
248 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
250 importDatabase.execSQL("UPDATE ${DomainsDatabaseHelper.DOMAINS_TABLE} SET ${DomainsDatabaseHelper.ULTRALIST} = 1")
251 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $ULTRALIST = 1")
253 importDatabase.execSQL("UPDATE ${DomainsDatabaseHelper.DOMAINS_TABLE} SET ${DomainsDatabaseHelper.ULTRALIST} = 0")
254 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $ULTRALIST = 0")
258 // Upgrade from schema version 8, first used in Privacy Browser 3.2, to schema version 9, first used in Privacy Browser 3.3.
259 if (importDatabaseVersion < 9) {
260 // Add the new proxy columns to the preferences table.
261 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $PROXY TEXT")
262 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $PROXY_CUSTOM_URL TEXT")
264 // Get the current proxy values.
265 val proxy = sharedPreferences.getString(PROXY, context.getString(R.string.proxy_default_value))
266 var proxyCustomUrl = sharedPreferences.getString(PROXY_CUSTOM_URL, context.getString(R.string.proxy_custom_url_default_value))
268 // SQL escape the proxy custom URL string.
269 proxyCustomUrl = DatabaseUtils.sqlEscapeString(proxyCustomUrl)
271 // 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.
272 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $PROXY = '$proxy'")
273 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $PROXY_CUSTOM_URL = $proxyCustomUrl")
276 // Upgrade from schema version 9, first used in Privacy Browser 3.3, to schema version 10, first used in Privacy Browser 3.4.
277 // Previously this upgrade added `download_location` and `download_custom_location` to the Preferences table. But they were removed in schema version 13.
279 // Upgrade from schema version 10, first used in Privacy Browser 3.4, to schema version 11, first used in Privacy Browser 3.5.
280 if (importDatabaseVersion < 11) {
281 // Add the app theme column to the preferences table.
282 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $APP_THEME TEXT")
284 // Get a cursor for the dark theme preference.
285 val darkThemePreferencesCursor = importDatabase.rawQuery("SELECT dark_theme FROM $PREFERENCES_TABLE", null)
287 // Move to the first entry.
288 darkThemePreferencesCursor.moveToFirst()
290 // Get the old dark theme value, which is in column 0.
291 val darkTheme = darkThemePreferencesCursor.getInt(0)
293 // Close the dark theme preference cursor.
294 darkThemePreferencesCursor.close()
296 // Get the system default string.
297 val systemDefault = context.getString(R.string.app_theme_default_value)
299 // Get the theme entry values string array.
300 val appThemeEntryValuesStringArray: Array<String> = context.resources.getStringArray(R.array.app_theme_entry_values)
302 // Get the dark string.
303 val dark = appThemeEntryValuesStringArray[2]
305 // Populate the app theme according to the old dark theme preference.
306 if (darkTheme == 0) { // A light theme was selected.
307 // Set the app theme to be the system default.
308 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $APP_THEME = '$systemDefault'")
309 } else { // A dark theme was selected.
310 // Set the app theme to be dark.
311 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $APP_THEME = '$dark'")
314 // 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.
315 importDatabase.execSQL("ALTER TABLE ${DomainsDatabaseHelper.DOMAINS_TABLE} ADD COLUMN ${DomainsDatabaseHelper.WEBVIEW_THEME} INTEGER")
317 // Add the WebView theme to the preferences table.
318 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $WEBVIEW_THEME TEXT")
320 // Get the WebView theme default value string.
321 val webViewThemeDefaultValue = context.getString(R.string.webview_theme_default_value)
323 // Set the WebView theme in the preferences table to be the default.
324 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $WEBVIEW_THEME = '$webViewThemeDefaultValue'")
327 // Upgrade from schema version 11, first used in Privacy Browser 3.5, to schema version 12, first used in Privacy Browser 3.6.
328 if (importDatabaseVersion < 12) {
329 // Add the clear logcat column to the preferences table.
330 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $CLEAR_LOGCAT BOOLEAN")
332 // Get the current clear logcat value.
333 val clearLogcat = sharedPreferences.getBoolean(CLEAR_LOGCAT, true)
335 // Populate the preferences table with the current clear logcat value.
336 // This can switch to using the variables directly once the API >= 30. <https://www.sqlite.org/datatype3.html#boolean_datatype>
337 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
339 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $CLEAR_LOGCAT = 1")
341 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $CLEAR_LOGCAT = 0")
344 // Upgrade from schema version 12, first used in Privacy Browser 3.6, to schema version 13, first used in Privacy Browser 3.7.
345 // 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.
347 // Upgrade from schema version 13, first used in Privacy Browser 3.7, to schema version 14, first used in Privacy Browser 3.8.
348 if (importDatabaseVersion < 14) {
349 // `enabledthirdpartycookies` was removed from the domains table. `do_not_track` and `third_party_cookies` were removed from the preferences table.
351 // 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>
352 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
353 // 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.
355 // Create the new cookies columns.
356 importDatabase.execSQL("ALTER TABLE ${DomainsDatabaseHelper.DOMAINS_TABLE} ADD COLUMN ${DomainsDatabaseHelper.COOKIES} BOOLEAN")
357 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $COOKIES BOOLEAN")
359 // Copy the data from the old cookies columns to the new ones.
360 importDatabase.execSQL("UPDATE ${DomainsDatabaseHelper.DOMAINS_TABLE} SET ${DomainsDatabaseHelper.COOKIES} = enablefirstpartycookies")
361 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $COOKIES = first_party_cookies")
363 // Create the new download with external app and bottom app bar columns.
364 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $DOWNLOAD_WITH_EXTERNAL_APP BOOLEAN")
365 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $BOTTOM_APP_BAR BOOLEAN")
367 // Get the current values for the new columns.
368 val downloadWithExternalApp = sharedPreferences.getBoolean(DOWNLOAD_WITH_EXTERNAL_APP, false)
369 val bottomAppBar = sharedPreferences.getBoolean(BOTTOM_APP_BAR, false)
371 // Populate the preferences table with the current download with external app value.
372 // This can switch to using the variables directly once the API >= 30. <https://www.sqlite.org/datatype3.html#boolean_datatype>
373 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
374 if (downloadWithExternalApp)
375 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $DOWNLOAD_WITH_EXTERNAL_APP = 1")
377 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $DOWNLOAD_WITH_EXTERNAL_APP = 0")
379 // Populate the preferences table with the current bottom app bar value.
381 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $BOTTOM_APP_BAR = 1")
383 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $BOTTOM_APP_BAR = 0")
386 // Upgrade from schema version 14, first used in Privacy Browser 3.8, to schema version 15, first used in Privacy Browser 3.11.
387 if (importDatabaseVersion < 15) {
388 // `facebook_click_ids` was removed from the preferences table.
390 // 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>
391 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
392 // 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.
394 // Create the new URL modification columns.
395 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $TRACKING_QUERIES BOOLEAN")
396 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $AMP_REDIRECTS BOOLEAN")
398 // Copy the data from the old columns to the new ones.
399 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $TRACKING_QUERIES = google_analytics")
400 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $AMP_REDIRECTS = twitter_amp_redirects")
402 // Create the new X-Requested-with header columns.
403 importDatabase.execSQL("ALTER TABLE $PREFERENCES_TABLE ADD COLUMN $X_REQUESTED_WITH_HEADER BOOLEAN")
404 importDatabase.execSQL("ALTER TABLE ${DomainsDatabaseHelper.DOMAINS_TABLE} ADD COLUMN ${DomainsDatabaseHelper.X_REQUESTED_WITH_HEADER} INTEGER")
406 // Get the current X-Requested-With header preferences value.
407 val xRequestedWithHeader = sharedPreferences.getBoolean(X_REQUESTED_WITH_HEADER, true)
409 // Populate the Preferences X-Requested-With header with the current value. The domains X-Requested-With header will default to 0, which is `System default`.
410 // This can switch to using the variables directly once the API >= 30. <https://www.sqlite.org/datatype3.html#boolean_datatype>
411 // <https://developer.android.com/reference/android/database/sqlite/package-summary>
412 if (xRequestedWithHeader)
413 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $X_REQUESTED_WITH_HEADER = 1")
415 importDatabase.execSQL("UPDATE $PREFERENCES_TABLE SET $X_REQUESTED_WITH_HEADER = 0")
418 // Get a cursor for the bookmarks table.
419 val importBookmarksCursor = importDatabase.rawQuery("SELECT * FROM ${BookmarksDatabaseHelper.BOOKMARKS_TABLE}", null)
421 // Delete the current bookmarks database.
422 context.deleteDatabase(BookmarksDatabaseHelper.BOOKMARKS_DATABASE)
424 // Create a new bookmarks database.
425 val bookmarksDatabaseHelper = BookmarksDatabaseHelper(context)
427 // Move to the first record.
428 importBookmarksCursor.moveToFirst()
430 // Copy the data from the import bookmarks cursor into the bookmarks database.
431 for (i in 0 until importBookmarksCursor.count) {
432 // Create a bookmark content values.
433 val bookmarkContentValues = ContentValues()
435 // Add the information for this bookmark to the content values.
436 bookmarkContentValues.put(BookmarksDatabaseHelper.BOOKMARK_NAME, importBookmarksCursor.getString(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME)))
437 bookmarkContentValues.put(BookmarksDatabaseHelper.BOOKMARK_URL, importBookmarksCursor.getString(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)))
438 bookmarkContentValues.put(BookmarksDatabaseHelper.PARENT_FOLDER, importBookmarksCursor.getString(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.PARENT_FOLDER)))
439 bookmarkContentValues.put(BookmarksDatabaseHelper.DISPLAY_ORDER, importBookmarksCursor.getInt(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)))
440 bookmarkContentValues.put(BookmarksDatabaseHelper.IS_FOLDER, importBookmarksCursor.getInt(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)))
441 bookmarkContentValues.put(BookmarksDatabaseHelper.FAVORITE_ICON, importBookmarksCursor.getBlob(importBookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON)))
443 // Insert the content values into the bookmarks database.
444 bookmarksDatabaseHelper.createBookmark(bookmarkContentValues)
446 // Advance to the next record.
447 importBookmarksCursor.moveToNext()
450 // Close the bookmarks cursor and database.
451 importBookmarksCursor.close()
452 bookmarksDatabaseHelper.close()
455 // Get a cursor for the domains table.
456 val importDomainsCursor = importDatabase.rawQuery("SELECT * FROM ${DomainsDatabaseHelper.DOMAINS_TABLE} ORDER BY ${DomainsDatabaseHelper.DOMAIN_NAME} ASC", null)
458 // Delete the current domains database.
459 context.deleteDatabase(DomainsDatabaseHelper.DOMAINS_DATABASE)
461 // Create a new domains database.
462 val domainsDatabaseHelper = DomainsDatabaseHelper(context)
464 // Move to the first record.
465 importDomainsCursor.moveToFirst()
467 // Copy the data from the import domains cursor into the domains database.
468 for (i in 0 until importDomainsCursor.count) {
469 // Create a domain content values.
470 val domainContentValues = ContentValues()
472 // Populate the domain content values.
473 domainContentValues.put(DomainsDatabaseHelper.DOMAIN_NAME, importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME)))
474 domainContentValues.put(DomainsDatabaseHelper.ENABLE_JAVASCRIPT, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)))
475 domainContentValues.put(DomainsDatabaseHelper.COOKIES, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.COOKIES)))
476 domainContentValues.put(DomainsDatabaseHelper.ENABLE_DOM_STORAGE, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)))
477 domainContentValues.put(DomainsDatabaseHelper.ENABLE_FORM_DATA, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FORM_DATA)))
478 domainContentValues.put(DomainsDatabaseHelper.ENABLE_EASYLIST, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYLIST)))
479 domainContentValues.put(DomainsDatabaseHelper.ENABLE_EASYPRIVACY, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)))
480 domainContentValues.put(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST,
481 importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)))
482 domainContentValues.put(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST,
483 importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)))
484 domainContentValues.put(DomainsDatabaseHelper.ULTRALIST, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ULTRALIST)))
485 domainContentValues.put(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)))
486 domainContentValues.put(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS,
487 importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)))
488 domainContentValues.put(DomainsDatabaseHelper.USER_AGENT, importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.USER_AGENT)))
489 domainContentValues.put(DomainsDatabaseHelper.X_REQUESTED_WITH_HEADER, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.X_REQUESTED_WITH_HEADER)))
490 domainContentValues.put(DomainsDatabaseHelper.FONT_SIZE, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.FONT_SIZE)))
491 domainContentValues.put(DomainsDatabaseHelper.SWIPE_TO_REFRESH, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SWIPE_TO_REFRESH)))
492 domainContentValues.put(DomainsDatabaseHelper.WEBVIEW_THEME, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WEBVIEW_THEME)))
493 domainContentValues.put(DomainsDatabaseHelper.WIDE_VIEWPORT, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WIDE_VIEWPORT)))
494 domainContentValues.put(DomainsDatabaseHelper.DISPLAY_IMAGES, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DISPLAY_IMAGES)))
495 domainContentValues.put(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)))
496 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME,
497 importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)))
498 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION,
499 importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)))
500 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT,
501 importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)))
502 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME,
503 importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)))
504 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION,
505 importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)))
506 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT,
507 importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)))
508 domainContentValues.put(DomainsDatabaseHelper.SSL_START_DATE, importDomainsCursor.getLong(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_START_DATE)))
509 domainContentValues.put(DomainsDatabaseHelper.SSL_END_DATE, importDomainsCursor.getLong(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_END_DATE)))
510 domainContentValues.put(DomainsDatabaseHelper.PINNED_IP_ADDRESSES, importDomainsCursor.getInt(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)))
511 domainContentValues.put(DomainsDatabaseHelper.IP_ADDRESSES, importDomainsCursor.getString(importDomainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.IP_ADDRESSES)))
513 // Insert the content values into the domains database.
514 domainsDatabaseHelper.addDomain(domainContentValues)
516 // Advance to the next record.
517 importDomainsCursor.moveToNext()
520 // Close the domains cursor and database.
521 importDomainsCursor.close()
522 domainsDatabaseHelper.close()
525 // Get a cursor for the preferences table.
526 val importPreferencesCursor = importDatabase.rawQuery("SELECT * FROM $PREFERENCES_TABLE", null)
528 // Move to the first record.
529 importPreferencesCursor.moveToFirst()
531 // Import the preference data.
532 sharedPreferences.edit()
533 .putBoolean(JAVASCRIPT, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(JAVASCRIPT)) == 1)
534 .putBoolean(COOKIES, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(COOKIES)) == 1)
535 .putBoolean(DOM_STORAGE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(DOM_STORAGE)) == 1)
536 .putBoolean(SAVE_FORM_DATA, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(SAVE_FORM_DATA)) == 1) // Save form data can be removed once the minimum API >= 26.
537 .putString(USER_AGENT, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(USER_AGENT)))
538 .putString(CUSTOM_USER_AGENT, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(CUSTOM_USER_AGENT)))
539 .putBoolean(X_REQUESTED_WITH_HEADER, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(X_REQUESTED_WITH_HEADER)) == 1)
540 .putBoolean(INCOGNITO_MODE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(INCOGNITO_MODE)) == 1)
541 .putBoolean(ALLOW_SCREENSHOTS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(ALLOW_SCREENSHOTS)) == 1)
542 .putBoolean(EASYLIST, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(EASYLIST)) == 1)
543 .putBoolean(EASYPRIVACY, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(EASYPRIVACY)) == 1)
544 .putBoolean(FANBOYS_ANNOYANCE_LIST, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(FANBOYS_ANNOYANCE_LIST)) == 1)
545 .putBoolean(FANBOYS_SOCIAL_BLOCKING_LIST, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(FANBOYS_SOCIAL_BLOCKING_LIST)) == 1)
546 .putBoolean(ULTRALIST, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(ULTRALIST)) == 1)
547 .putBoolean(ULTRAPRIVACY, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(ULTRAPRIVACY)) == 1)
548 .putBoolean(BLOCK_ALL_THIRD_PARTY_REQUESTS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1)
549 .putBoolean(TRACKING_QUERIES, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(TRACKING_QUERIES)) == 1)
550 .putBoolean(AMP_REDIRECTS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(AMP_REDIRECTS)) == 1)
551 .putString(SEARCH, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(SEARCH)))
552 .putString(SEARCH_CUSTOM_URL, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(SEARCH_CUSTOM_URL)))
553 .putString(PROXY, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(PROXY)))
554 .putString(PROXY_CUSTOM_URL, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(PROXY_CUSTOM_URL)))
555 .putBoolean(FULL_SCREEN_BROWSING_MODE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(FULL_SCREEN_BROWSING_MODE)) == 1)
556 .putBoolean(HIDE_APP_BAR, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(HIDE_APP_BAR)) == 1)
557 .putBoolean(CLEAR_EVERYTHING, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_EVERYTHING)) == 1)
558 .putBoolean(CLEAR_COOKIES, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_COOKIES)) == 1)
559 .putBoolean(CLEAR_DOM_STORAGE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_DOM_STORAGE)) == 1)
560 .putBoolean(CLEAR_FORM_DATA, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_FORM_DATA)) == 1) // Clear form data can be removed once the minimum API >= 26.
561 .putBoolean(CLEAR_LOGCAT, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_LOGCAT)) == 1)
562 .putBoolean(CLEAR_CACHE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(CLEAR_CACHE)) == 1)
563 .putString(HOMEPAGE, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(HOMEPAGE)))
564 .putString(FONT_SIZE, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(FONT_SIZE)))
565 .putBoolean(OPEN_INTENTS_IN_NEW_TAB, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(OPEN_INTENTS_IN_NEW_TAB)) == 1)
566 .putBoolean(SWIPE_TO_REFRESH, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(SWIPE_TO_REFRESH)) == 1)
567 .putBoolean(DOWNLOAD_WITH_EXTERNAL_APP, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(DOWNLOAD_WITH_EXTERNAL_APP)) == 1)
568 .putBoolean(SCROLL_APP_BAR, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(SCROLL_APP_BAR)) == 1)
569 .putBoolean(BOTTOM_APP_BAR, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(BOTTOM_APP_BAR)) == 1)
570 .putBoolean(DISPLAY_ADDITIONAL_APP_BAR_ICONS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(DISPLAY_ADDITIONAL_APP_BAR_ICONS)) == 1)
571 .putString(APP_THEME, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(APP_THEME)))
572 .putString(WEBVIEW_THEME, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndexOrThrow(WEBVIEW_THEME)))
573 .putBoolean(WIDE_VIEWPORT, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(WIDE_VIEWPORT)) == 1)
574 .putBoolean(DISPLAY_WEBPAGE_IMAGES, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndexOrThrow(DISPLAY_WEBPAGE_IMAGES)) == 1)
577 // Close the preferences cursor and database.
578 importPreferencesCursor.close()
579 importDatabase.close()
581 // Delete the temporary import file database, journal, and other related auxiliary files.
582 SQLiteDatabase.deleteDatabase(temporaryImportFile)
584 // Return the import successful string.
586 } catch (exception: Exception) {
587 // Return the import error.
592 fun exportUnencrypted(exportFileOutputStream: OutputStream, context: Context): String {
594 // Create a temporary export file.
595 val temporaryExportFile = File.createTempFile("temporary_export_file", null, context.cacheDir)
597 // Create the temporary export database.
598 val temporaryExportDatabase = SQLiteDatabase.openOrCreateDatabase(temporaryExportFile, null)
600 // Set the temporary export database version number.
601 temporaryExportDatabase.version = SCHEMA_VERSION
604 // Create the temporary export database bookmarks table.
605 temporaryExportDatabase.execSQL(BookmarksDatabaseHelper.CREATE_BOOKMARKS_TABLE)
607 // Open the bookmarks database.
608 val bookmarksDatabaseHelper = BookmarksDatabaseHelper(context)
610 // Get a full bookmarks cursor.
611 val bookmarksCursor = bookmarksDatabaseHelper.allBookmarks
613 // Move to the first record.
614 bookmarksCursor.moveToFirst()
616 // Copy the data from the bookmarks cursor into the export database.
617 for (i in 0 until bookmarksCursor.count) {
618 // Create a bookmark content values.
619 val bookmarkContentValues = ContentValues()
621 // Populate the bookmark content values.
622 bookmarkContentValues.put(BookmarksDatabaseHelper.BOOKMARK_NAME, bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME)))
623 bookmarkContentValues.put(BookmarksDatabaseHelper.BOOKMARK_URL, bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)))
624 bookmarkContentValues.put(BookmarksDatabaseHelper.PARENT_FOLDER, bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.PARENT_FOLDER)))
625 bookmarkContentValues.put(BookmarksDatabaseHelper.DISPLAY_ORDER, bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)))
626 bookmarkContentValues.put(BookmarksDatabaseHelper.IS_FOLDER, bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)))
627 bookmarkContentValues.put(BookmarksDatabaseHelper.FAVORITE_ICON, bookmarksCursor.getBlob(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON)))
629 // Insert the content values into the temporary export database.
630 temporaryExportDatabase.insert(BookmarksDatabaseHelper.BOOKMARKS_TABLE, null, bookmarkContentValues)
632 // Advance to the next record.
633 bookmarksCursor.moveToNext()
636 // Close the bookmarks cursor and database.
637 bookmarksCursor.close()
638 bookmarksDatabaseHelper.close()
641 // Create the temporary export database domains table.
642 temporaryExportDatabase.execSQL(DomainsDatabaseHelper.CREATE_DOMAINS_TABLE)
644 // Open the domains database.
645 val domainsDatabaseHelper = DomainsDatabaseHelper(context)
647 // Get a full domains database cursor.
648 val domainsCursor = domainsDatabaseHelper.completeCursorOrderedByDomain
650 // Move to the first record.
651 domainsCursor.moveToFirst()
653 // Copy the data from the domains cursor into the export database.
654 for (i in 0 until domainsCursor.count) {
655 // Create a domain content values.
656 val domainContentValues = ContentValues()
658 // Populate the domain content values.
659 domainContentValues.put(DomainsDatabaseHelper.DOMAIN_NAME, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME)))
660 domainContentValues.put(DomainsDatabaseHelper.ENABLE_JAVASCRIPT, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)))
661 domainContentValues.put(DomainsDatabaseHelper.COOKIES, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.COOKIES)))
662 domainContentValues.put(DomainsDatabaseHelper.ENABLE_DOM_STORAGE, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)))
663 domainContentValues.put(DomainsDatabaseHelper.ENABLE_FORM_DATA, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FORM_DATA)))
664 domainContentValues.put(DomainsDatabaseHelper.ENABLE_EASYLIST, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYLIST)))
665 domainContentValues.put(DomainsDatabaseHelper.ENABLE_EASYPRIVACY, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)))
666 domainContentValues.put(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)))
667 domainContentValues.put(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST,
668 domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)))
669 domainContentValues.put(DomainsDatabaseHelper.ULTRALIST, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ULTRALIST)))
670 domainContentValues.put(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)))
671 domainContentValues.put(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)))
672 domainContentValues.put(DomainsDatabaseHelper.USER_AGENT, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.USER_AGENT)))
673 domainContentValues.put(DomainsDatabaseHelper.X_REQUESTED_WITH_HEADER, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.X_REQUESTED_WITH_HEADER)))
674 domainContentValues.put(DomainsDatabaseHelper.FONT_SIZE, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.FONT_SIZE)))
675 domainContentValues.put(DomainsDatabaseHelper.SWIPE_TO_REFRESH, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SWIPE_TO_REFRESH)))
676 domainContentValues.put(DomainsDatabaseHelper.WEBVIEW_THEME, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WEBVIEW_THEME)))
677 domainContentValues.put(DomainsDatabaseHelper.WIDE_VIEWPORT, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WIDE_VIEWPORT)))
678 domainContentValues.put(DomainsDatabaseHelper.DISPLAY_IMAGES, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DISPLAY_IMAGES)))
679 domainContentValues.put(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)))
680 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)))
681 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)))
682 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT,
683 domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)))
684 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)))
685 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)))
686 domainContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT,
687 domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)))
688 domainContentValues.put(DomainsDatabaseHelper.SSL_START_DATE, domainsCursor.getLong(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_START_DATE)))
689 domainContentValues.put(DomainsDatabaseHelper.SSL_END_DATE, domainsCursor.getLong(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_END_DATE)))
690 domainContentValues.put(DomainsDatabaseHelper.PINNED_IP_ADDRESSES, domainsCursor.getInt(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)))
691 domainContentValues.put(DomainsDatabaseHelper.IP_ADDRESSES, domainsCursor.getString(domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.IP_ADDRESSES)))
693 // Insert the content values into the temporary export database.
694 temporaryExportDatabase.insert(DomainsDatabaseHelper.DOMAINS_TABLE, null, domainContentValues)
696 // Advance to the next record.
697 domainsCursor.moveToNext()
700 // Close the domains cursor and database.
701 domainsCursor.close()
702 domainsDatabaseHelper.close()
705 // Prepare the preferences table SQL creation string.
706 val createPreferencesTable = "CREATE TABLE $PREFERENCES_TABLE (" +
707 "$ID INTEGER PRIMARY KEY, " +
708 "$JAVASCRIPT BOOLEAN, " +
709 "$COOKIES BOOLEAN, " +
710 "$DOM_STORAGE BOOLEAN, " +
711 "$SAVE_FORM_DATA BOOLEAN, " +
712 "$USER_AGENT TEXT, " +
713 "$CUSTOM_USER_AGENT TEXT, " +
714 "$X_REQUESTED_WITH_HEADER BOOLEAN, " +
715 "$INCOGNITO_MODE BOOLEAN, " +
716 "$ALLOW_SCREENSHOTS BOOLEAN, " +
717 "$EASYLIST BOOLEAN, " +
718 "$EASYPRIVACY BOOLEAN, " +
719 "$FANBOYS_ANNOYANCE_LIST BOOLEAN, " +
720 "$FANBOYS_SOCIAL_BLOCKING_LIST BOOLEAN, " +
721 "$ULTRALIST BOOLEAN, " +
722 "$ULTRAPRIVACY BOOLEAN, " +
723 "$BLOCK_ALL_THIRD_PARTY_REQUESTS BOOLEAN, " +
724 "$TRACKING_QUERIES BOOLEAN, " +
725 "$AMP_REDIRECTS BOOLEAN, " +
727 "$SEARCH_CUSTOM_URL TEXT, " +
729 "$PROXY_CUSTOM_URL TEXT, " +
730 "$FULL_SCREEN_BROWSING_MODE BOOLEAN, " +
731 "$HIDE_APP_BAR BOOLEAN, " +
732 "$CLEAR_EVERYTHING BOOLEAN, " +
733 "$CLEAR_COOKIES BOOLEAN, " +
734 "$CLEAR_DOM_STORAGE BOOLEAN, " +
735 "$CLEAR_FORM_DATA BOOLEAN, " +
736 "$CLEAR_LOGCAT BOOLEAN, " +
737 "$CLEAR_CACHE BOOLEAN, " +
739 "$FONT_SIZE TEXT, " +
740 "$OPEN_INTENTS_IN_NEW_TAB BOOLEAN, " +
741 "$SWIPE_TO_REFRESH BOOLEAN, " +
742 "$DOWNLOAD_WITH_EXTERNAL_APP BOOLEAN, " +
743 "$SCROLL_APP_BAR BOOLEAN, " +
744 "$BOTTOM_APP_BAR BOOLEAN, " +
745 "$DISPLAY_ADDITIONAL_APP_BAR_ICONS BOOLEAN, " +
746 "$APP_THEME TEXT, " +
747 "$WEBVIEW_THEME TEXT, " +
748 "$WIDE_VIEWPORT BOOLEAN, " +
749 "$DISPLAY_WEBPAGE_IMAGES BOOLEAN)"
751 // Create the temporary export database preferences table.
752 temporaryExportDatabase.execSQL(createPreferencesTable)
754 // Get a handle for the shared preference.
755 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
757 // Create a preferences content values.
758 val preferencesContentValues = ContentValues()
760 // Populate the preferences content values.
761 preferencesContentValues.put(JAVASCRIPT, sharedPreferences.getBoolean(JAVASCRIPT, false))
762 preferencesContentValues.put(COOKIES, sharedPreferences.getBoolean(COOKIES, false))
763 preferencesContentValues.put(DOM_STORAGE, sharedPreferences.getBoolean(DOM_STORAGE, false))
764 preferencesContentValues.put(SAVE_FORM_DATA, sharedPreferences.getBoolean(SAVE_FORM_DATA, false)) // Save form data can be removed once the minimum API >= 26.
765 preferencesContentValues.put(USER_AGENT, sharedPreferences.getString(USER_AGENT, context.getString(R.string.user_agent_default_value)))
766 preferencesContentValues.put(CUSTOM_USER_AGENT, sharedPreferences.getString(CUSTOM_USER_AGENT, context.getString(R.string.custom_user_agent_default_value)))
767 preferencesContentValues.put(X_REQUESTED_WITH_HEADER, sharedPreferences.getBoolean(X_REQUESTED_WITH_HEADER, true))
768 preferencesContentValues.put(INCOGNITO_MODE, sharedPreferences.getBoolean(INCOGNITO_MODE, false))
769 preferencesContentValues.put(ALLOW_SCREENSHOTS, sharedPreferences.getBoolean(ALLOW_SCREENSHOTS, false))
770 preferencesContentValues.put(EASYLIST, sharedPreferences.getBoolean(EASYLIST, true))
771 preferencesContentValues.put(EASYPRIVACY, sharedPreferences.getBoolean(EASYPRIVACY, true))
772 preferencesContentValues.put(FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean(FANBOYS_ANNOYANCE_LIST, true))
773 preferencesContentValues.put(FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean(FANBOYS_SOCIAL_BLOCKING_LIST, true))
774 preferencesContentValues.put(ULTRALIST, sharedPreferences.getBoolean(ULTRALIST, true))
775 preferencesContentValues.put(ULTRAPRIVACY, sharedPreferences.getBoolean(ULTRAPRIVACY, true))
776 preferencesContentValues.put(BLOCK_ALL_THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean(BLOCK_ALL_THIRD_PARTY_REQUESTS, false))
777 preferencesContentValues.put(TRACKING_QUERIES, sharedPreferences.getBoolean(TRACKING_QUERIES, true))
778 preferencesContentValues.put(AMP_REDIRECTS, sharedPreferences.getBoolean(AMP_REDIRECTS, true))
779 preferencesContentValues.put(SEARCH, sharedPreferences.getString(SEARCH, context.getString(R.string.search_default_value)))
780 preferencesContentValues.put(SEARCH_CUSTOM_URL, sharedPreferences.getString(SEARCH_CUSTOM_URL, context.getString(R.string.search_custom_url_default_value)))
781 preferencesContentValues.put(PROXY, sharedPreferences.getString(PROXY, context.getString(R.string.proxy_default_value)))
782 preferencesContentValues.put(PROXY_CUSTOM_URL, sharedPreferences.getString(PROXY_CUSTOM_URL, context.getString(R.string.proxy_custom_url_default_value)))
783 preferencesContentValues.put(FULL_SCREEN_BROWSING_MODE, sharedPreferences.getBoolean(FULL_SCREEN_BROWSING_MODE, false))
784 preferencesContentValues.put(HIDE_APP_BAR, sharedPreferences.getBoolean(HIDE_APP_BAR, true))
785 preferencesContentValues.put(CLEAR_EVERYTHING, sharedPreferences.getBoolean(CLEAR_EVERYTHING, true))
786 preferencesContentValues.put(CLEAR_COOKIES, sharedPreferences.getBoolean(CLEAR_COOKIES, true))
787 preferencesContentValues.put(CLEAR_DOM_STORAGE, sharedPreferences.getBoolean(CLEAR_DOM_STORAGE, true))
788 preferencesContentValues.put(CLEAR_FORM_DATA, sharedPreferences.getBoolean(CLEAR_FORM_DATA, true)) // Clear form data can be removed once the minimum API >= 26.
789 preferencesContentValues.put(CLEAR_LOGCAT, sharedPreferences.getBoolean(CLEAR_LOGCAT, true))
790 preferencesContentValues.put(CLEAR_CACHE, sharedPreferences.getBoolean(CLEAR_CACHE, true))
791 preferencesContentValues.put(HOMEPAGE, sharedPreferences.getString(HOMEPAGE, context.getString(R.string.homepage_default_value)))
792 preferencesContentValues.put(FONT_SIZE, sharedPreferences.getString(FONT_SIZE, context.getString(R.string.font_size_default_value)))
793 preferencesContentValues.put(OPEN_INTENTS_IN_NEW_TAB, sharedPreferences.getBoolean(OPEN_INTENTS_IN_NEW_TAB, true))
794 preferencesContentValues.put(SWIPE_TO_REFRESH, sharedPreferences.getBoolean(SWIPE_TO_REFRESH, true))
795 preferencesContentValues.put(DOWNLOAD_WITH_EXTERNAL_APP, sharedPreferences.getBoolean(DOWNLOAD_WITH_EXTERNAL_APP, false))
796 preferencesContentValues.put(SCROLL_APP_BAR, sharedPreferences.getBoolean(SCROLL_APP_BAR, true))
797 preferencesContentValues.put(BOTTOM_APP_BAR, sharedPreferences.getBoolean(BOTTOM_APP_BAR, false))
798 preferencesContentValues.put(DISPLAY_ADDITIONAL_APP_BAR_ICONS, sharedPreferences.getBoolean(DISPLAY_ADDITIONAL_APP_BAR_ICONS, false))
799 preferencesContentValues.put(APP_THEME, sharedPreferences.getString(APP_THEME, context.getString(R.string.app_theme_default_value)))
800 preferencesContentValues.put(WEBVIEW_THEME, sharedPreferences.getString(WEBVIEW_THEME, context.getString(R.string.webview_theme_default_value)))
801 preferencesContentValues.put(WIDE_VIEWPORT, sharedPreferences.getBoolean(WIDE_VIEWPORT, true))
802 preferencesContentValues.put(DISPLAY_WEBPAGE_IMAGES, sharedPreferences.getBoolean(DISPLAY_WEBPAGE_IMAGES, true))
804 // Insert the preferences content values into the temporary export database.
805 temporaryExportDatabase.insert(PREFERENCES_TABLE, null, preferencesContentValues)
807 // Close the temporary export database.
808 temporaryExportDatabase.close()
811 // 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>
812 // It can be copied in Android using `Files.copy` once the minimum API >= 26.
813 // <https://developer.android.com/reference/java/nio/file/Files#copy(java.nio.file.Path,%20java.nio.file.Path,%20java.nio.file.CopyOption...)>
814 // 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>
816 // Create the temporary export file input stream.
817 val temporaryExportFileInputStream = FileInputStream(temporaryExportFile)
819 // Create a byte array.
820 val transferByteArray = ByteArray(1024)
822 // Create an integer to track the number of bytes read.
825 // Copy the temporary export file to the export file output stream.
826 while (temporaryExportFileInputStream.read(transferByteArray).also { bytesRead = it } > 0) {
827 exportFileOutputStream.write(transferByteArray, 0, bytesRead)
830 // Flush the export file output stream.
831 exportFileOutputStream.flush()
833 // Close the file streams.
834 temporaryExportFileInputStream.close()
835 exportFileOutputStream.close()
837 // Delete the temporary export file database, journal, and other related auxiliary files.
838 SQLiteDatabase.deleteDatabase(temporaryExportFile)
840 // Return the export successful string.
842 } catch (exception: Exception) {
843 // Return the export error.