Add option to download with external program. https://redmine.stoutner.com/issues/333
authorSoren Stoutner <soren@stoutner.com>
Fri, 7 Dec 2018 23:48:14 +0000 (16:48 -0700)
committerSoren Stoutner <soren@stoutner.com>
Fri, 7 Dec 2018 23:48:14 +0000 (16:48 -0700)
27 files changed:
app/build.gradle
app/src/main/AndroidManifest.xml
app/src/main/assets/ru/guide_bookmarks_light.html
app/src/main/assets/ru/guide_domain_settings_dark.html
app/src/main/assets/ru/guide_domain_settings_light.html
app/src/main/assets/ru/guide_javascript_dark.html
app/src/main/assets/ru/guide_javascript_light.html
app/src/main/assets/ru/guide_local_storage_dark.html
app/src/main/assets/ru/guide_local_storage_light.html
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTabFragment.java
app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java
app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java
app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportDatabaseHelper.java
app/src/main/res/drawable/clear_and_exit.xml [deleted file]
app/src/main/res/drawable/open_with_external_app_disabled_dark.xml [new file with mode: 0644]
app/src/main/res/drawable/open_with_external_app_disabled_light.xml [new file with mode: 0644]
app/src/main/res/drawable/open_with_external_app_enabled_dark.xml [new file with mode: 0644]
app/src/main/res/drawable/open_with_external_app_enabled_light.xml [new file with mode: 0644]
app/src/main/res/layout/domain_settings_fragment.xml
app/src/main/res/menu/webview_navigation_menu.xml
app/src/main/res/values-de/strings.xml
app/src/main/res/values-es/strings.xml
app/src/main/res/values-it/strings.xml
app/src/main/res/values-ru/strings.xml
app/src/main/res/values/strings.xml
app/src/main/res/xml/preferences.xml

index 436e4e1..81da8f6 100644 (file)
@@ -74,7 +74,7 @@ dependencies {
     implementation 'com.android.support:design:28.0.0'
 
     // Only compile Firebase ads for the free flavor.
-    freeImplementation 'com.google.firebase:firebase-ads:17.0.0'
+    freeImplementation 'com.google.firebase:firebase-ads:17.1.2'
 
     // Only compile the consent library for the free flavor.  It is used to comply with the GDPR in Europe.
     freeImplementation 'com.google.android.ads.consent:consent-library:1.0.6'
index c957349..2acbca2 100644 (file)
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
 
-            <!-- `android.intent.action.VIEW` with the two data schemes enables processing of web intents. -->
+            <!-- Process web intents. -->
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
+
                 <category android:name="android.intent.category.BROWSABLE" />
                 <category android:name="android.intent.category.DEFAULT" />
+
                 <data android:scheme="http" />
                 <data android:scheme="https" />
             </intent-filter>
+
+            <!-- Process web search intents. -->
+            <intent-filter>
+                <action android:name="android.intent.action.WEB_SEARCH" />
+
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
         </activity>
 
 
index 3637b3f..51f9685 100644 (file)
@@ -24,7 +24,7 @@
     </head>
 
     <body>
-        <h3><img class="title" src="../shared_images/bookmarks_blue_guide_dark.png"> Закладки</h3>
+        <h3><img class="title" src="../shared_images/bookmarks_blue_light.png"> Закладки</h3>
 
         <p>К закладкам можно получить доступ из боковой панели, выполнив свайп от правой части экрана.</p>
 
index 43088ad..84f6047 100644 (file)
@@ -26,7 +26,7 @@
     <body>
         <h3><img class="title" src="../shared_images/dns_blue_dark.png"> Безопасный просмотр веб-страниц</h3>
 
-        <p>По умолчанию в Privacy Browser отключены JavaScript, файлы cookie и хранилище DOM. Однако, для правильной работы, некоторым веб-сайтам эти опции необходимы.
+        <p>По умолчанию в Privacy Browser отключены JavaScript, файлы cookie и DOM-хранилище. Однако, для правильной работы, некоторым веб-сайтам эти опции необходимы.
             Настройки домена могут автоматически включать нужный набор опций при посещении определенного домена.</p>
 
         <p><img class="center21" src="images/domain_settings.png"></p>
index 028f76d..26f4bd5 100644 (file)
@@ -26,7 +26,7 @@
     <body>
         <h3><img class="title" src="../shared_images/dns_blue_light.png"> Безопасный просмотр веб-страниц</h3>
 
-        <p>По умолчанию в Privacy Browser отключены JavaScript, файлы cookie и хранилище DOM. Однако, для правильной работы, некоторым веб-сайтам эти опции необходимы.
+        <p>По умолчанию в Privacy Browser отключены JavaScript, файлы cookie и DOM-хранилище. Однако, для правильной работы, некоторым веб-сайтам эти опции необходимы.
             Настройки домена могут автоматически включать нужный набор опций при посещении определенного домена.</p>
 
         <p><img class="center21" src="images/domain_settings.png"></p>
index 69cf9d0..e09b77b 100644 (file)
@@ -51,8 +51,8 @@
             цветом (оба указывают на то, что JavaScript отключен) и красным <img class = "inline" src="../shared_images/javascript_enabled.png"> (JavaScript включен).
             Можете просмотреть информацию на сайте <a href="http://webkay.robinlinus.com">webkay</a>, которую можно собрать с включенным и отключенным JavaScript.
 
-        <p>Browsing the internet with JavaScript disabled, and only enabling it if needed, goes a long way toward protecting privacy.
-            In addition, JavaScript is used to load much of the annoying advertisements and extra cruft that comes along with most modern websites.
-            With it disabled, websites will load faster, consume less network traffic, and use less CPU power, which leads to longer battery life.</p>
+        <p>Просмотр сайтов с отключенным JavaScript и включение его только в случае необходимости, значительно повышает конфиденциальность пользователей.
+            Кроме того, JavaScript используется для загрузки большей части раздражающей рекламы, а также хлама, который отправляется на устройства с большинства современных веб-сайтов.
+            После его отключения веб-сайты будут загружаться быстрее, потреблять меньше сетевого трафика и меньше нагружать процессор, что приведет к увеличению времени автономной работы.</p>
     </body>
 </html>
\ No newline at end of file
index e3edac3..26bbc0c 100644 (file)
@@ -51,8 +51,8 @@
             цветом (оба указывают на то, что JavaScript отключен) и красным <img class = "inline" src="../shared_images/javascript_enabled.png"> (JavaScript включен).
             Можете просмотреть информацию на сайте <a href="http://webkay.robinlinus.com">webkay</a>, которую можно собрать с включенным и отключенным JavaScript.
 
-        <p>Browsing the internet with JavaScript disabled, and only enabling it if needed, goes a long way toward protecting privacy.
-            In addition, JavaScript is used to load much of the annoying advertisements and extra cruft that comes along with most modern websites.
-            With it disabled, websites will load faster, consume less network traffic, and use less CPU power, which leads to longer battery life.</p>
+        <p>Просмотр сайтов с отключенным JavaScript и включение его только в случае необходимости, значительно повышает конфиденциальность пользователей.
+            Кроме того, JavaScript используется для загрузки большей части раздражающей рекламы, а также хлама, который отправляется на устройства с большинства современных веб-сайтов.
+            После его отключения веб-сайты будут загружаться быстрее, потреблять меньше сетевого трафика и меньше нагружать процессор, что приведет к увеличению времени автономной работы.</p>
     </body>
 </html>
\ No newline at end of file
index 4ac2ba0..bd073f7 100644 (file)
 
         <p>Первичные файлы cookie устанавливаются тем веб-сайтом, который указан в строке URL.</p>
 
-        <p>From the early days of the internet, it became obvious that it would be advantageous for websites to be able to store information on a computer for future access.
-            For example, a website that displays weather information could ask the user for a zip code, and then store it in a cookie.
-            The next time the user visited the website, weather information would automatically load for that zip code, without the user having to enter it again.</p>
+        <p>С первых дней интернета стало очевидным, что веб-сайтам было бы выгодно иметь возможность хранить информацию на компьютере для последующего доступа к ней.
+            Например, веб-сайт, предоставляющий информацию о погоде, может запросить у пользователя название города, а затем сохранить его в файле cookie.
+            При следующем посещении веб-сайта информация о погоде будет автоматически загружена для этого города, без необходимости вводить его снова.</p>
 
-        <p>Like everything else on the web, clever people figured out all types of ways to abuse cookies to do things that users would not approve of if they knew they were happening.
-            For example, a website can set a cookie with a unique serial number on a device.
-            Then, every time a user visits the website on that device, it can be linked to a unique profile the server maintains for that serial number,
-            even if the device connects from different IP addresses.</p>
+        <p>Как и со всем остальным в интернете, умные люди выяснили все способы злоупотребления cookie, чтобы делать то, что пользователи не одобрят, если узнают что именно происходит.
+            Например, веб-сайт может установить файл cookie на устройстве с уникальным номером.
+            Затем каждый раз, когда пользователь посещает веб-сайт с этого устройства, он может быть связан с уникальным профилем, который сервер хранит для этого номера,
+            даже если устройство подключается с разных IP-адресов.</p>
 
-        <p>Almost all websites with logins require first-party cookies to be enabled for a user to log in.
-            That is how they make sure it is still you as you move from page to page on the site, and is, in my opinion, one of the few legitimate uses for cookies.</p>
+        <p>Почти все веб-сайты с формами авторизации требуют, чтобы для входа в систему у пользователя были включены первичные файлы cookie.
+            Именно так они убеждаются, что это все еще вы, когда вы переходите со страницы на страницу на сайте, и, на мой взгляд, это один из немногих законных способов использования файлов cookie.</p>
 
         <p>Если первичные файлы cookie включены, но JavaScript отключен, значок конфиденциальности будет желтым <img class="inline" src="../shared_images/warning.png"> как предупреждение.</p>
 
             Со временем такие компании, как Facebook (который также запустил рекламную сеть), создали довольно большое количество подробных профилей о людях,
             у которых <a href = "http://www.theverge.com/2016/5/27/11795248/facebook-ad-network-non-users-cookies-plug-ins"> даже не было аккаунта на сайте социальной сети</a>.</p>
 
-        <p>There is no good reason to ever enable third-party cookies. On devices with Android KitKat or older (version <= 4.4.4 or API <= 20), WebView does not
-            <a href="https://developer.android.com/reference/android/webkit/CookieManager.html#setAcceptThirdPartyCookies(android.webkit.WebView, boolean)">differentiate
-            between first-party and third-party cookies</a>. Thus, enabling first-party cookies will also enable third-party cookies.</p>
+        <p>Нет ни одной веской причины когда-либо разрешать сторонние файлы cookie. На устройствах с Android KitKat или старше (версия <= 4.4.4 или API <= 20), WebView не
+            <a href="https://developer.android.com/reference/android/webkit/CookieManager.html#setAcceptThirdPartyCookies(android.webkit.WebView, boolean)">различает первичные и сторонние файлы cookie</a>.
+            Таким образом, включение первичных файлов cookie также разрешит и сторонние.</p>
 
 
-        <h3><img class="title" src="../shared_images/web_blue_dark.png"> Хранилище DOM</h3>
+        <h3><img class="title" src="../shared_images/web_blue_dark.png"> DOM-хранилище</h3>
 
-        <p>Document Object Model storage, also known as web storage, is like cookies on steroids.
-            Whereas the maximum combined storage size for all cookies from a single URL is 4 kilobytes,
-            DOM storage can hold <a href="https://en.wikipedia.org/wiki/Web_storage#Storage_size">megabytes per site</a>.
-            Because DOM storage uses JavaScript to read and write data, it cannot be enabled unless JavaScript is also enabled.</p>
+        <p>Хранилище объектной модели документа (Document Object Model), также известное как веб-хранилище, похоже на cookie (печенье) на стероидах.
+            Если максимальный общий размер хранилища для всех файлов cookie с одного URL-адреса составляет 4 килобайта,
+            то DOM-хранилище вмещает в себя <a href="https://en.wikipedia.org/wiki/Web_storage#Storage_size">мегабайты на сайт</a>.
+            Поскольку DOM-хранилище использует JavaScript для чтения и записи данных, включение его ни на что не влияет пока отключен JavaScript.</p>
 
 
         <h3><img class="title" src="../shared_images/subtitles_blue_dark.png"> Данные формы</h3>
 
-        <p>Form data contains information typed into web forms, like user names, addresses, phone numbers, etc., and lists them in a drop-down box on future visits.
-            Unlike the other forms of local storage, form data is not sent to the web server without specific user interaction.
-            Beginning in Android Oreo (8.0), WebView’s form data was replaced by the <a href="https://medium.com/@bherbst/getting-androids-autofill-to-work-for-you-21435debea1">Autofill service</a>.
-            As such, controls for form data no longer appear on newer Android devices.</p>
+        <p>Данные формы содержат информацию, введенную в веб-формы, например имена пользователей, адреса, номера телефонов и т.д., и доступную в раскрывающемся списке при будущих посещениях.
+            В отличие от других форм локального хранилища данные формы не отправляются на веб-сервер без специального взаимодействия с пользователем.
+            Начиная с Android Oreo (8.0), данные формы WebView были заменены на <a href="https://medium.com/@bherbst/getting-androids-autofill-to-work-for-you-21435debea1">службу автозаполнения</a>.
+            Таким образом, элементы управления данными формы больше не отображаются на новых устройствах Android.</p>
     </body>
 </html>
\ No newline at end of file
index 4a22fdf..1457902 100644 (file)
 
         <p>Первичные файлы cookie устанавливаются тем веб-сайтом, который указан в строке URL.</p>
 
-        <p>From the early days of the internet, it became obvious that it would be advantageous for websites to be able to store information on a computer for future access.
-            For example, a website that displays weather information could ask the user for a zip code, and then store it in a cookie.
-            The next time the user visited the website, weather information would automatically load for that zip code, without the user having to enter it again.</p>
+        <p>С первых дней интернета стало очевидным, что веб-сайтам было бы выгодно иметь возможность хранить информацию на компьютере для последующего доступа к ней.
+            Например, веб-сайт, предоставляющий информацию о погоде, может запросить у пользователя название города, а затем сохранить его в файле cookie.
+            При следующем посещении веб-сайта информация о погоде будет автоматически загружена для этого города, без необходимости вводить его снова.</p>
 
-        <p>Like everything else on the web, clever people figured out all types of ways to abuse cookies to do things that users would not approve of if they knew they were happening.
-            For example, a website can set a cookie with a unique serial number on a device.
-            Then, every time a user visits the website on that device, it can be linked to a unique profile the server maintains for that serial number,
-            even if the device connects from different IP addresses.</p>
+        <p>Как и со всем остальным в интернете, умные люди выяснили все способы злоупотребления cookie, чтобы делать то, что пользователи не одобрят, если узнают что именно происходит.
+            Например, веб-сайт может установить файл cookie на устройстве с уникальным номером.
+            Затем каждый раз, когда пользователь посещает веб-сайт с этого устройства, он может быть связан с уникальным профилем, который сервер хранит для этого номера,
+            даже если устройство подключается с разных IP-адресов.</p>
 
-        <p>Almost all websites with logins require first-party cookies to be enabled for a user to log in.
-            That is how they make sure it is still you as you move from page to page on the site, and is, in my opinion, one of the few legitimate uses for cookies.</p>
+        <p>Почти все веб-сайты с формами авторизации требуют, чтобы для входа в систему у пользователя были включены первичные файлы cookie.
+            Именно так они убеждаются, что это все еще вы, когда вы переходите со страницы на страницу на сайте, и, на мой взгляд, это один из немногих законных способов использования файлов cookie.</p>
 
         <p>Если первичные файлы cookie включены, но JavaScript отключен, значок конфиденциальности будет желтым <img class="inline" src="../shared_images/warning.png"> как предупреждение.</p>
 
             Со временем такие компании, как Facebook (который также запустил рекламную сеть), создали довольно большое количество подробных профилей о людях,
             у которых <a href = "http://www.theverge.com/2016/5/27/11795248/facebook-ad-network-non-users-cookies-plug-ins"> даже не было аккаунта на сайте социальной сети</a>.</p>
 
-        <p>There is no good reason to ever enable third-party cookies. On devices with Android KitKat or older (version <= 4.4.4 or API <= 20), WebView does not
-            <a href="https://developer.android.com/reference/android/webkit/CookieManager.html#setAcceptThirdPartyCookies(android.webkit.WebView, boolean)">differentiate
-            between first-party and third-party cookies</a>. Thus, enabling first-party cookies will also enable third-party cookies.</p>
+        <p>Нет ни одной веской причины когда-либо разрешать сторонние файлы cookie. На устройствах с Android KitKat или старше (версия <= 4.4.4 или API <= 20), WebView не
+            <a href="https://developer.android.com/reference/android/webkit/CookieManager.html#setAcceptThirdPartyCookies(android.webkit.WebView, boolean)">различает первичные и сторонние файлы cookie</a>.
+            Таким образом, включение первичных файлов cookie также разрешит и сторонние.</p>
 
 
-        <h3><img class="title" src="../shared_images/web_blue_light.png"> Хранилище DOM</h3>
+        <h3><img class="title" src="../shared_images/web_blue_light.png"> DOM-хранилище</h3>
 
-        <p>Document Object Model storage, also known as web storage, is like cookies on steroids.
-            Whereas the maximum combined storage size for all cookies from a single URL is 4 kilobytes,
-            DOM storage can hold <a href="https://en.wikipedia.org/wiki/Web_storage#Storage_size">megabytes per site</a>.
-            Because DOM storage uses JavaScript to read and write data, it cannot be enabled unless JavaScript is also enabled.</p>
+        <p>Хранилище объектной модели документа (Document Object Model), также известное как веб-хранилище, похоже на cookie (печенье) на стероидах.
+            Если максимальный общий размер хранилища для всех файлов cookie с одного URL-адреса составляет 4 килобайта,
+            то DOM-хранилище вмещает в себя <a href="https://en.wikipedia.org/wiki/Web_storage#Storage_size">мегабайты на сайт</a>.
+            Поскольку DOM-хранилище использует JavaScript для чтения и записи данных, включение его ни на что не влияет пока отключен JavaScript.</p>
 
 
         <h3><img class="title" src="../shared_images/subtitles_blue_light.png"> Данные формы</h3>
 
-        <p>Form data contains information typed into web forms, like user names, addresses, phone numbers, etc., and lists them in a drop-down box on future visits.
-            Unlike the other forms of local storage, form data is not sent to the web server without specific user interaction.
-            Beginning in Android Oreo (8.0), WebView’s form data was replaced by the <a href="https://medium.com/@bherbst/getting-androids-autofill-to-work-for-you-21435debea1">Autofill service</a>.
-            As such, controls for form data no longer appear on newer Android devices.</p>
+        <p>Данные формы содержат информацию, введенную в веб-формы, например имена пользователей, адреса, номера телефонов и т.д., и доступную в раскрывающемся списке при будущих посещениях.
+            В отличие от других форм локального хранилища данные формы не отправляются на веб-сервер без специального взаимодействия с пользователем.
+            Начиная с Android Oreo (8.0), данные формы WebView были заменены на <a href="https://medium.com/@bherbst/getting-androids-autofill-to-work-for-you-21435debea1">службу автозаполнения</a>.
+            Таким образом, элементы управления данными формы больше не отображаются на новых устройствах Android.</p>
     </body>
 </html>
\ No newline at end of file
index d3f0d01..03bf85e 100644 (file)
@@ -292,7 +292,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private CoordinatorLayout rootCoordinatorLayout;
 
     // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
-    // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `setDisplayWebpageImages()`, and `applyProxyThroughOrbot()`.
+    // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `applyProxyThroughOrbot()`.
     private WebView mainWebView;
 
     // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
@@ -331,9 +331,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `nightMode` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and  `applyDomainSettings()`.
     private boolean nightMode;
 
-    // `displayWebpageImagesBoolean` is used in `applyAppSettings()` and `applyDomainSettings()`.
-    private boolean displayWebpageImagesBoolean;
-
     // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applyProxyThroughOrbot()`.
     private String homepage;
 
@@ -398,6 +395,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
     private boolean displayingFullScreenVideo;
 
+    // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`.
+    private boolean downloadWithExternalApp;
+
     // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
     private String currentDomainName;
 
@@ -410,18 +410,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
     private boolean waitingForOrbot;
 
-    // `domainSettingsApplied` is used in `prepareOptionsMenu()`, `applyDomainSettings()`, and `setDisplayWebpageImages()`.
+    // `domainSettingsApplied` is used in `prepareOptionsMenu()` and `applyDomainSettings()`.
     private boolean domainSettingsApplied;
 
     // `domainSettingsJavaScriptEnabled` is used in `onOptionsItemSelected()` and `applyDomainSettings()`.
     private Boolean domainSettingsJavaScriptEnabled;
 
-    // `displayWebpageImagesInt` is used in `applyDomainSettings()` and `setDisplayWebpageImages()`.
-    private int displayWebpageImagesInt;
-
-    // `onTheFlyDisplayImagesSet` is used in `applyDomainSettings()` and `setDisplayWebpageImages()`.
-    private boolean onTheFlyDisplayImagesSet;
-
     // `waitingForOrbotHtmlString` is used in `onCreate()` and `applyProxyThroughOrbot()`.
     private String waitingForOrbotHtmlString;
 
@@ -1147,32 +1141,37 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Allow the downloading of files.
         mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
-            // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
-            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
-                // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
-
-                // Store the variables for future use by `onRequestPermissionsResult()`.
-                downloadUrl = url;
-                downloadContentDisposition = contentDisposition;
-                downloadContentLength = contentLength;
-
-                // Show a dialog if the user has previously denied the permission.
-                if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                    // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
-                    DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
-
-                    // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
-                    downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
-                } else {  // Show the permission request directly.
-                    // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
-                    ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
-                }
-            } else {  // The storage permission has already been granted.
-                // Get a handle for the download file alert dialog.
-                AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
+            // Check if the download should be processed by an external app.
+            if (downloadWithExternalApp) {  // Download with an external app.
+                openUrlWithExternalApp(url);
+            } else {  // Download with Android's download manager.
+                // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
+                if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission has not been granted.
+                    // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
+
+                    // Store the variables for future use by `onRequestPermissionsResult()`.
+                    downloadUrl = url;
+                    downloadContentDisposition = contentDisposition;
+                    downloadContentLength = contentLength;
+
+                    // Show a dialog if the user has previously denied the permission.
+                    if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                        // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
+                        DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
+
+                        // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
+                        downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
+                    } else {  // Show the permission request directly.
+                        // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
+                        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
+                    }
+                } else {  // The storage permission has already been granted.
+                    // Get a handle for the download file alert dialog.
+                    AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
 
-                // Show the download file alert dialog.
-                downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                    // Show the download file alert dialog.
+                    downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                }
             }
         });
 
@@ -2725,9 +2724,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 } else {  // Images are not currently loaded automatically.
                     mainWebView.getSettings().setLoadsImagesAutomatically(true);
                 }
-
-                // Set `onTheFlyDisplayImagesSet`.
-                onTheFlyDisplayImagesSet = true;
                 return true;
 
             case R.id.night_mode:
@@ -2792,8 +2788,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
 
                 // Create the share intent.
-                Intent shareIntent = new Intent();
-                shareIntent.setAction(Intent.ACTION_SEND);
+                Intent shareIntent = new Intent(Intent.ACTION_SEND);
                 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
                 shareIntent.setType("text/plain");
 
@@ -3145,32 +3140,35 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a Download URL entry.
                 menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
-                    if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
-                        // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
-
-                        // Store the variables for future use by `onRequestPermissionsResult()`.
-                        downloadUrl = linkUrl;
-                        downloadContentDisposition = "none";
-                        downloadContentLength = -1;
-
-                        // Show a dialog if the user has previously denied the permission.
-                        if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                            // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
-                            DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
-
-                            // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
-                            downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
-                        } else {  // Show the permission request directly.
-                            // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
-                            ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
-                        }
-                    } else {  // The WRITE_EXTERNAL_STORAGE permission has already been granted.
-                        // Get a handle for the download file alert dialog.
-                        AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
+                    // Check if the download should be processed by an external app.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        openUrlWithExternalApp(linkUrl);
+                    } else {  // Download with Android's download manager.
+                        // Check to see if the storage permission has already been granted.
+                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
+                            // Store the variables for future use by `onRequestPermissionsResult()`.
+                            downloadUrl = linkUrl;
+                            downloadContentDisposition = "none";
+                            downloadContentLength = -1;
+
+                            // Show a dialog if the user has previously denied the permission.
+                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
+                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
+
+                                // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
+                                downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
+                            } else {  // Show the permission request directly.
+                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
+                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
+                            }
+                        } else {  // The storage permission has already been granted.
+                            // Get a handle for the download file alert dialog.
+                            AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
 
-                        // Show the download file alert dialog.
-                        downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                            // Show the download file alert dialog.
+                            downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                        }
                     }
                     return false;
                 });
@@ -3188,7 +3186,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a `Write Email` entry.
                 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
-                    // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
+                    // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
 
                     // Parse the url and set it as the data for the `Intent`.
@@ -3232,30 +3230,33 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a `Download Image` entry.
                 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
-                    if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
-                        // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
-
-                        // Store the image URL for use by `onRequestPermissionResult()`.
-                        downloadImageUrl = imageUrl;
-
-                        // Show a dialog if the user has previously denied the permission.
-                        if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                            // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
-                            DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
-
-                            // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
-                            downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
-                        } else {  // Show the permission request directly.
-                            // Request the permission.  The download dialog will be launched by `onRequestPermissionResult().
-                            ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
-                        }
-                    } else {  // The WRITE_EXTERNAL_STORAGE permission has already been granted.
-                        // Get a handle for the download image alert dialog.
-                        AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+                    // Check if the download should be processed by an external app.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        openUrlWithExternalApp(imageUrl);
+                    } else {  // Download with Android's download manager.
+                        // Check to see if the storage permission has already been granted.
+                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
+                            // Store the image URL for use by `onRequestPermissionResult()`.
+                            downloadImageUrl = imageUrl;
+
+                            // Show a dialog if the user has previously denied the permission.
+                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
+                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
+
+                                // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
+                                downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
+                            } else {  // Show the permission request directly.
+                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult().
+                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
+                            }
+                        } else {  // The storage permission has already been granted.
+                            // Get a handle for the download image alert dialog.
+                            AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
 
-                        // Show the download image alert dialog.
-                        downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                            // Show the download image alert dialog.
+                            downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                        }
                     }
                     return false;
                 });
@@ -3291,30 +3292,33 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a `Download Image` entry.
                 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
-                    if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
-                        // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
-
-                        // Store the image URL for use by `onRequestPermissionResult()`.
-                        downloadImageUrl = imageUrl;
-
-                        // Show a dialog if the user has previously denied the permission.
-                        if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                            // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
-                            DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
-
-                            // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
-                            downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
-                        } else {  // Show the permission request directly.
-                            // Request the permission.  The download dialog will be launched by `onRequestPermissionResult().
-                            ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
-                        }
-                    } else {  // The WRITE_EXTERNAL_STORAGE permission has already been granted.
-                        // Get a handle for the download image alert dialog.
-                        AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+                    // Check if the download should be processed by an external app.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        openUrlWithExternalApp(imageUrl);
+                    } else {  // Download with Android's download manager.
+                        // Check to see if the storage permission has already been granted.
+                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
+                            // Store the image URL for use by `onRequestPermissionResult()`.
+                            downloadImageUrl = imageUrl;
+
+                            // Show a dialog if the user has previously denied the permission.
+                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
+                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
+
+                                // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
+                                downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
+                            } else {  // Show the permission request directly.
+                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult().
+                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
+                            }
+                        } else {  // The storage permission has already been granted.
+                            // Get a handle for the download image alert dialog.
+                            AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
 
-                        // Show the download image alert dialog.
-                        downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                            // Show the download image alert dialog.
+                            downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                        }
                     }
                     return false;
                 });
@@ -3421,8 +3425,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         IconCompat favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap);
 
         // Setup the shortcut intent.
-        Intent shortcutIntent = new Intent();
-        shortcutIntent.setAction(Intent.ACTION_VIEW);
+        Intent shortcutIntent = new Intent(Intent.ACTION_VIEW);
         shortcutIntent.setData(Uri.parse(formattedUrlString));
 
         // Create a shortcut info builder.  The shortcut name becomes the shortcut ID.
@@ -3906,7 +3909,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
         hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false);
         translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true);
-        displayWebpageImagesBoolean = sharedPreferences.getBoolean("display_webpage_images", true);
+        downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
 
         // Apply the proxy through Orbot settings.
         applyProxyThroughOrbot(false);
@@ -4073,13 +4076,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
             // Store the general preference information.
-            String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
-            String defaultUserAgentName = sharedPreferences.getString("user_agent", "Privacy Browser");
-            defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
+            String defaultFontSizeString = sharedPreferences.getString("default_font_size", getString(R.string.font_size_default_value));
+            String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
+            defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value));
             boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
             nightMode = sharedPreferences.getBoolean("night_mode", false);
+            boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
 
-            if (domainSettingsApplied) {  // The url we are loading has custom domain settings.
+            if (domainSettingsApplied) {  // The url has custom domain settings.
                 // Get a cursor for the current host and move it to the first position.
                 Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
                 currentHostDomainSettingsCursor.moveToFirst();
@@ -4102,7 +4106,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
                 int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
                 int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
-                displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
+                int displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
                 pinnedDomainSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
                 pinnedDomainSslIssuedToCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
                 pinnedDomainSslIssuedToONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
@@ -4239,6 +4243,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
                 }
 
+                // Set the loading of webpage images.
+                switch (displayWebpageImagesInt) {
+                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
+                        mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
+                        break;
+
+                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
+                        mainWebView.getSettings().setLoadsImagesAutomatically(true);
+                        break;
+
+                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
+                        mainWebView.getSettings().setLoadsImagesAutomatically(false);
+                        break;
+                }
+
                 // Set a green background on `urlTextBox` to indicate that custom domain settings are being used.  We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
                 if (darkTheme) {
                     urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
@@ -4325,6 +4344,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
                 }
 
+                // Set the loading of webpage images.
+                mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
+
                 // Set a transparent background on `urlTextBox`.  We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
                 urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
             }
@@ -4332,10 +4354,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Close the domains database helper.
             domainsDatabaseHelper.close();
 
-            // Remove the `onTheFlyDisplayImagesSet` flag and set the display webpage images mode.  `true` indicates that custom domain settings are applied.
-            onTheFlyDisplayImagesSet = false;
-            setDisplayWebpageImages();
-
             // Update the privacy icons, but only if `mainMenu` has already been populated.
             if (mainMenu != null) {
                 updatePrivacyIcons(true);
@@ -4353,12 +4371,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
         // Get the search preferences.
-        String homepageString = sharedPreferences.getString("homepage", "https://searx.me/");
-        String torHomepageString = sharedPreferences.getString("tor_homepage", "http://ulrn6sryqaifefld.onion/");
-        String torSearchString = sharedPreferences.getString("tor_search", "http://ulrn6sryqaifefld.onion/?q=");
-        String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", "");
-        String searchString = sharedPreferences.getString("search", "https://searx.me/?q=");
-        String searchCustomUrlString = sharedPreferences.getString("search_custom_url", "");
+        String homepageString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
+        String torHomepageString = sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value));
+        String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
+        String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value));
+        String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
+        String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
 
         // Set the homepage, search, and proxy options.
         if (proxyThroughOrbot) {  // Set the Tor options.
@@ -4437,28 +4455,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
     }
 
-    private void setDisplayWebpageImages() {
-        if (!onTheFlyDisplayImagesSet) {
-            if (domainSettingsApplied) {  // Custom domain settings are applied.
-                switch (displayWebpageImagesInt) {
-                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
-                        mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImagesBoolean);
-                        break;
-
-                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
-                        mainWebView.getSettings().setLoadsImagesAutomatically(true);
-                        break;
-
-                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
-                        mainWebView.getSettings().setLoadsImagesAutomatically(false);
-                        break;
-                }
-            } else {  // Default settings are applied.
-                mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImagesBoolean);
-            }
-        }
-    }
-
     private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
         // Get handles for the menu items.
         MenuItem privacyMenuItem = mainMenu.findItem(R.id.toggle_javascript);
@@ -4516,6 +4512,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
     }
 
+    private void openUrlWithExternalApp(String url) {
+        // Create a download intent.  Not specifying the action type will display the maximum number of options.
+        Intent downloadIntent = new Intent();
+
+        // Set the URI and the mime type.  `"*/*"` will display the maximum number of options.
+        downloadIntent.setDataAndType(Uri.parse(url), "text/html");
+
+        // Flag the intent to open in a new task.
+        downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        // Show the chooser.
+        startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
+    }
+
     private void highlightUrlText() {
         String urlString = urlTextBox.getText().toString();
 
index 9f627b6..2f206fe 100644 (file)
@@ -249,7 +249,7 @@ public class AboutTabFragment extends Fragment {
                 securityPatchTextView.setVisibility(View.GONE);
             }
 
-            // Only populate the radio TextView if there is a radio in the device.
+            // Only populate the radio text view if there is a radio in the device.
             if (!radio.isEmpty()) {
                 String radioLabel = getString(R.string.radio) + "  ";
                 SpannableStringBuilder radioStringBuilder = new SpannableStringBuilder(radioLabel + radio);
@@ -259,7 +259,7 @@ public class AboutTabFragment extends Fragment {
                 radioTextView.setVisibility(View.GONE);
             }
 
-            // Only populate the Orbot TextView if it is installed.
+            // Only populate the Orbot text view if it is installed.
             if (!orbot.isEmpty()) {
                 String orbotLabel = getString(R.string.orbot) + "  ";
                 SpannableStringBuilder orbotStringBuilder = new SpannableStringBuilder(orbotLabel + orbot);
@@ -269,11 +269,11 @@ public class AboutTabFragment extends Fragment {
                 orbotTextView.setVisibility(View.GONE);
             }
 
-            // Only populate the EasyKeychain TextView if it is installed.
+            // Only populate the OpenKeychain text view if it is installed.
             if (!openKeychain.isEmpty()) {
-                String openKeychainlabel = getString(R.string.open_keychain) + "  ";
-                SpannableStringBuilder openKeychainStringBuilder = new SpannableStringBuilder(openKeychainlabel + openKeychain);
-                openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainlabel.length(), openKeychainStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                String openKeychainLabel = getString(R.string.openkeychain) + "  ";
+                SpannableStringBuilder openKeychainStringBuilder = new SpannableStringBuilder(openKeychainLabel + openKeychain);
+                openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length(), openKeychainStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
                 openKeychainTextView.setText(openKeychainStringBuilder);
             } else {  //OpenKeychain is not installed.
                 openKeychainTextView.setVisibility(View.GONE);
index 76dacba..ad6653c 100644 (file)
@@ -27,7 +27,7 @@ import android.database.Cursor;
 import android.net.http.SslCertificate;
 import android.os.Build;
 import android.os.Bundle;
-// We have to use `android.support.v4.app.Fragment` until minimum API >= 23.  Otherwise we cannot call `getContext()`.
+// `android.support.v4.app.Fragment` must be used until minimum API >= 23.  Otherwise `getContext()` does not work.
 import android.preference.PreferenceManager;
 import android.support.annotation.NonNull;
 import android.support.v4.app.Fragment;
@@ -95,9 +95,9 @@ public class DomainSettingsFragment extends Fragment {
         final String defaultUserAgentName = sharedPreferences.getString("user_agent", "Privacy Browser");
         final String defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
         String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
-        boolean defaultSwipeToRefreshBoolean = sharedPreferences.getBoolean("swipe_to_refresh", true);
-        final boolean defaultNightModeBoolean = sharedPreferences.getBoolean("night_mode", false);
-        final boolean defaultDisplayWebpageImagesBoolean = sharedPreferences.getBoolean("display_website_images", true);
+        boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
+        final boolean defaultNightMode = sharedPreferences.getBoolean("night_mode", false);
+        final boolean defaultDisplayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
 
         // Get handles for the views in the fragment.
         final EditText domainNameEditText = domainSettingsView.findViewById(R.id.domain_settings_name_edittext);
@@ -341,7 +341,7 @@ public class DomainSettingsFragment extends Fragment {
         });
 
         // Create a `boolean` to track if night mode is enabled.
-        boolean nightModeEnabled = (nightModeInt == DomainsDatabaseHelper.NIGHT_MODE_ENABLED) || ((nightModeInt == DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT) && defaultNightModeBoolean);
+        boolean nightModeEnabled = (nightModeInt == DomainsDatabaseHelper.NIGHT_MODE_ENABLED) || ((nightModeInt == DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT) && defaultNightMode);
 
         // Disable the JavaScript switch if night mode is enabled.
         if (nightModeEnabled) {
@@ -744,7 +744,7 @@ public class DomainSettingsFragment extends Fragment {
         swipeToRefreshSpinner.setSelection(swipeToRefreshInt);
 
         // Set the swipe to refresh text.
-        if (defaultSwipeToRefreshBoolean) {
+        if (defaultSwipeToRefresh) {
             swipeToRefreshTextView.setText(swipeToRefreshArrayAdapter.getItem(DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED));
         } else {
             swipeToRefreshTextView.setText(swipeToRefreshArrayAdapter.getItem(DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED));
@@ -753,7 +753,7 @@ public class DomainSettingsFragment extends Fragment {
         // Set the swipe to refresh icon and TextView settings.  Once the minimum API >= 21 a selector can be used as the tint mode instead of specifying different icons.
         switch (swipeToRefreshInt) {
             case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT:
-                if (defaultSwipeToRefreshBoolean) {  // Swipe to refresh is enabled by default.
+                if (defaultSwipeToRefresh) {  // Swipe to refresh is enabled by default.
                     // Set the icon according to the theme.
                     if (MainWebViewActivity.darkTheme) {
                         swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_enabled_dark));
@@ -807,7 +807,7 @@ public class DomainSettingsFragment extends Fragment {
         nightModeSpinner.setSelection(nightModeInt);
 
         // Set the default night mode text.
-        if (defaultNightModeBoolean) {
+        if (defaultNightMode) {
             nightModeTextView.setText(nightModeArrayAdapter.getItem(DomainsDatabaseHelper.NIGHT_MODE_ENABLED));
         } else {
             nightModeTextView.setText(nightModeArrayAdapter.getItem(DomainsDatabaseHelper.NIGHT_MODE_DISABLED));
@@ -816,7 +816,7 @@ public class DomainSettingsFragment extends Fragment {
         // Set the night mode icon and TextView settings.  Once the minimum API >= 21 a selector can be used as the tint mode instead of specifying different icons.
         switch (nightModeInt) {
             case DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT:
-                if (defaultNightModeBoolean) {  // Night mode enabled by default.
+                if (defaultNightMode) {  // Night mode enabled by default.
                     // Set the icon according to the theme.
                     if (MainWebViewActivity.darkTheme) {
                         nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_enabled_dark));
@@ -871,7 +871,7 @@ public class DomainSettingsFragment extends Fragment {
         displayWebpageImagesSpinner.setSelection(displayImagesInt);
 
         // Set the default display images text.
-        if (defaultDisplayWebpageImagesBoolean) {
+        if (defaultDisplayWebpageImages) {
             displayImagesTextView.setText(displayImagesArrayAdapter.getItem(DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED));
         } else {
             displayImagesTextView.setText(displayImagesArrayAdapter.getItem(DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED));
@@ -880,7 +880,7 @@ public class DomainSettingsFragment extends Fragment {
         // Set the display website images icon and TextView settings.  Once the minimum API >= 21 a selector can be used as the tint mode instead of specifying different icons.
         switch (displayImagesInt) {
             case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
-                if (defaultDisplayWebpageImagesBoolean) {  // Display webpage images enabled by default.
+                if (defaultDisplayWebpageImages) {  // Display webpage images enabled by default.
                     // Set the icon according to the theme.
                     if (MainWebViewActivity.darkTheme) {
                         displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_dark));
@@ -1480,7 +1480,7 @@ public class DomainSettingsFragment extends Fragment {
                 // Update the icon and the visibility of `nightModeTextView`.  Once the minimum API >= 21 a selector can be used as the tint mode instead of specifying different icons.
                 switch (position) {
                     case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT:
-                        if (defaultSwipeToRefreshBoolean) {  // Swipe to refresh enabled by default.
+                        if (defaultSwipeToRefresh) {  // Swipe to refresh enabled by default.
                             // Set the icon according to the theme.
                             if (MainWebViewActivity.darkTheme) {
                                 swipeToRefreshImageView.setImageDrawable(resources.getDrawable(R.drawable.refresh_enabled_dark));
@@ -1538,7 +1538,7 @@ public class DomainSettingsFragment extends Fragment {
                 // Update the icon and the visibility of `nightModeTextView`.  Once the minimum API >= 21 a selector can be used as the tint mode instead of specifying different icons.
                 switch (position) {
                     case DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT:
-                        if (defaultNightModeBoolean) {  // Night mode enabled by default.
+                        if (defaultNightMode) {  // Night mode enabled by default.
                             // Set the icon according to the theme.
                             if (MainWebViewActivity.darkTheme) {
                                 nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_enabled_dark));
@@ -1584,7 +1584,7 @@ public class DomainSettingsFragment extends Fragment {
                 }
 
                 // Create a `boolean` to store the current night mode setting.
-                boolean currentNightModeEnabled = (position == DomainsDatabaseHelper.NIGHT_MODE_ENABLED) || ((position == DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT) && defaultNightModeBoolean);
+                boolean currentNightModeEnabled = (position == DomainsDatabaseHelper.NIGHT_MODE_ENABLED) || ((position == DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT) && defaultNightMode);
 
                 // Disable the JavaScript `Switch` if night mode is enabled.
                 if (currentNightModeEnabled) {
@@ -1653,7 +1653,7 @@ public class DomainSettingsFragment extends Fragment {
                 // Update the icon and the visibility of `displayImagesTextView`.
                 switch (position) {
                     case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
-                        if (defaultDisplayWebpageImagesBoolean) {
+                        if (defaultDisplayWebpageImages) {
                             // Set the icon according to the theme.
                             if (MainWebViewActivity.darkTheme) {
                                 displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_dark));
index 65b18ba..210018c 100644 (file)
@@ -88,6 +88,7 @@ public class SettingsFragment extends PreferenceFragment {
         final Preference homepagePreference = findPreference("homepage");
         final Preference defaultFontSizePreference = findPreference("default_font_size");
         final Preference swipeToRefreshPreference = findPreference("swipe_to_refresh");
+        final Preference downloadWithExternalAppPreference = findPreference("download_with_external_app");
         final Preference displayAdditionalAppBarIconsPreference = findPreference("display_additional_app_bar_icons");
         final Preference darkThemePreference = findPreference("dark_theme");
         final Preference nightModePreference = findPreference("night_mode");
@@ -99,8 +100,8 @@ public class SettingsFragment extends PreferenceFragment {
         hideSystemBarsPreference.setDependency("full_screen_browsing_mode");
 
         // Get Strings from the preferences.
-        String torSearchString = savedPreferences.getString("tor_search", "http://ulrn6sryqaifefld.onion/?q=");
-        String searchString = savedPreferences.getString("search", "https://searx.me/?q=");
+        String torSearchString = savedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
+        String searchString = savedPreferences.getString("search", getString(R.string.search_default_value));
 
         // Get booleans that are used in multiple places from the preferences.
         final boolean javaScriptEnabled = savedPreferences.getBoolean("javascript_enabled", false);
@@ -146,7 +147,7 @@ public class SettingsFragment extends PreferenceFragment {
         String[] userAgentDataArray = getResources().getStringArray(R.array.user_agent_data);
 
         // Get the current user agent name from the preference.
-        String userAgentName = savedPreferences.getString("user_agent", "Privacy Browser");
+        String userAgentName = savedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
 
         // Get the array position of the user agent name.
         int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
@@ -174,12 +175,12 @@ public class SettingsFragment extends PreferenceFragment {
         }
 
         // Set the summary text for the custom user agent preference and enable it if user agent preference is set to custom.
-        customUserAgentPreference.setSummary(savedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"));
+        customUserAgentPreference.setSummary(savedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
         customUserAgentPreference.setEnabled(userAgentPreference.getSummary().equals(getString(R.string.custom_user_agent)));
 
 
         // Set the Tor homepage URL as the summary text for the `tor_homepage` preference when the preference screen is loaded.  The default is Searx: `http://ulrn6sryqaifefld.onion/`.
-        torHomepagePreference.setSummary(savedPreferences.getString("tor_homepage", "http://ulrn6sryqaifefld.onion/"));
+        torHomepagePreference.setSummary(savedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
 
 
         // Set the Tor search URL as the summary text for the Tor preference when the preference screen is loaded.
@@ -192,7 +193,7 @@ public class SettingsFragment extends PreferenceFragment {
         }
 
         // Set the summary text for `tor_search_custom_url`.  The default is `""`.
-        torSearchCustomURLPreference.setSummary(savedPreferences.getString("tor_search_custom_url", ""));
+        torSearchCustomURLPreference.setSummary(savedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value)));
 
         // Enable the Tor custom URL search options only if proxying through Orbot and the search is set to `Custom URL`.
         torSearchCustomURLPreference.setEnabled(proxyThroughOrbot && torSearchString.equals("Custom URL"));
@@ -208,7 +209,7 @@ public class SettingsFragment extends PreferenceFragment {
         }
 
         // Set the summary text for `search_custom_url` (the default is `""`) and enable it if `search` is set to `Custom URL`.
-        searchCustomURLPreference.setSummary(savedPreferences.getString("search_custom_url", ""));
+        searchCustomURLPreference.setSummary(savedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value)));
         searchCustomURLPreference.setEnabled(searchString.equals("Custom URL"));
 
 
@@ -222,10 +223,10 @@ public class SettingsFragment extends PreferenceFragment {
         clearCachePreference.setEnabled(!clearEverything);
 
         // Set the homepage URL as the summary text for the homepage preference.
-        homepagePreference.setSummary(savedPreferences.getString("homepage", "https://searx.me/"));
+        homepagePreference.setSummary(savedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
 
         // Set the default font size as the summary text for the preference.
-        defaultFontSizePreference.setSummary(savedPreferences.getString("default_font_size", "100") + "%%");
+        defaultFontSizePreference.setSummary(savedPreferences.getString("default_font_size", getString(R.string.font_size_default_value)) + "%%");
 
         // Disable the JavaScript preference if Night Mode is enabled.  JavaScript will be enabled for all web pages.
         javaScriptPreference.setEnabled(!nightMode);
@@ -639,6 +640,21 @@ public class SettingsFragment extends PreferenceFragment {
             }
         }
 
+        // Set the download with external app preference icon.
+        if (savedPreferences.getBoolean("download_with_external_app", false)) {
+            if (MainWebViewActivity.darkTheme) {
+                downloadWithExternalAppPreference.setIcon(R.drawable.open_with_external_app_enabled_dark);
+            } else {
+                downloadWithExternalAppPreference.setIcon(R.drawable.open_with_external_app_enabled_light);
+            }
+        } else {
+            if (MainWebViewActivity.darkTheme) {
+                downloadWithExternalAppPreference.setIcon(R.drawable.open_with_external_app_disabled_dark);
+            } else {
+                downloadWithExternalAppPreference.setIcon(R.drawable.open_with_external_app_disabled_light);
+            }
+        }
+
         // Set the display additional app bar icons preference icon.
         if (savedPreferences.getBoolean("display_additional_app_bar_icons", false)) {
             if (MainWebViewActivity.darkTheme) {
@@ -815,7 +831,7 @@ public class SettingsFragment extends PreferenceFragment {
 
                 case "user_agent":
                     // Get the new user agent name.
-                    String newUserAgentName = sharedPreferences.getString("user_agent", "Privacy Browser");
+                    String newUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
 
                     // Get the array position for the new user agent name.
                     int newUserAgentArrayPosition = userAgentNamesArray.getPosition(newUserAgentName);
@@ -873,7 +889,7 @@ public class SettingsFragment extends PreferenceFragment {
 
                 case "custom_user_agent":
                     // Set the new custom user agent as the summary text for the preference.
-                    customUserAgentPreference.setSummary(sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"));
+                    customUserAgentPreference.setSummary(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
                     break;
 
                 case "incognito_mode":
@@ -1075,7 +1091,7 @@ public class SettingsFragment extends PreferenceFragment {
                 case "proxy_through_orbot":
                     // Get current settings.
                     boolean currentProxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
-                    String currentTorSearchString = sharedPreferences.getString("tor_search", "http://ulrn6sryqaifefld.onion/?q=");
+                    String currentTorSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
 
                     // Enable the Tor custom URL search option only if `currentProxyThroughOrbot` is true and the search is set to `Custom URL`.
                     torSearchCustomURLPreference.setEnabled(currentProxyThroughOrbot && currentTorSearchString.equals("Custom URL"));
@@ -1123,12 +1139,12 @@ public class SettingsFragment extends PreferenceFragment {
 
                 case "tor_homepage":
                     // Set the new tor homepage URL as the summary text for the `tor_homepage` preference.  The default is Searx:  `http://ulrn6sryqaifefld.onion/`.
-                    torHomepagePreference.setSummary(sharedPreferences.getString("tor_homepage", "http://ulrn6sryqaifefld.onion/"));
+                    torHomepagePreference.setSummary(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
                     break;
 
                 case "tor_search":
                     // Get the present search string.
-                    String presentTorSearchString = sharedPreferences.getString("tor_search", "http://ulrn6sryqaifefld.onion/?q=");
+                    String presentTorSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
 
                     // Update the preferences.
                     if (presentTorSearchString.equals("Custom URL")) {
@@ -1162,12 +1178,12 @@ public class SettingsFragment extends PreferenceFragment {
 
                 case "tor_search_custom_url":
                     // Set the summary text for `tor_search_custom_url`.
-                    torSearchCustomURLPreference.setSummary(sharedPreferences.getString("tor_search_custom_url", ""));
+                    torSearchCustomURLPreference.setSummary(sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value)));
                     break;
 
                 case "search":
                     // Store the new search string.
-                    String newSearchString = sharedPreferences.getString("search", "https://searx.me/?q=");
+                    String newSearchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
 
                     // Update `searchPreference` and `searchCustomURLPreference`.
                     if (newSearchString.equals("Custom URL")) {  // `Custom URL` is selected.
@@ -1201,7 +1217,7 @@ public class SettingsFragment extends PreferenceFragment {
 
                 case "search_custom_url":
                     // Set the new custom search URL as the summary text for `search_custom_url`.  The default is `""`.
-                    searchCustomURLPreference.setSummary(sharedPreferences.getString("search_custom_url", ""));
+                    searchCustomURLPreference.setSummary(sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value)));
                     break;
 
                 case "full_screen_browsing_mode":
@@ -1451,12 +1467,12 @@ public class SettingsFragment extends PreferenceFragment {
 
                 case "homepage":
                     // Set the new homepage URL as the summary text for the Homepage preference.
-                    homepagePreference.setSummary(sharedPreferences.getString("homepage", "https://searx.me/"));
+                    homepagePreference.setSummary(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
                     break;
 
                 case "default_font_size":
                     // Update the summary text of `default_font_size`.
-                    defaultFontSizePreference.setSummary(sharedPreferences.getString("default_font_size", "100") + "%%");
+                    defaultFontSizePreference.setSummary(sharedPreferences.getString("default_font_size", getString(R.string.font_size_default_value)) + "%%");
                     break;
 
                 case "swipe_to_refresh":
@@ -1476,6 +1492,23 @@ public class SettingsFragment extends PreferenceFragment {
                     }
                     break;
 
+                case "download_with_external_app":
+                    // Update the icon.
+                    if (sharedPreferences.getBoolean("download_with_external_app", false)) {
+                        if (MainWebViewActivity.darkTheme) {
+                            downloadWithExternalAppPreference.setIcon(R.drawable.open_with_external_app_enabled_dark);
+                        } else {
+                            downloadWithExternalAppPreference.setIcon(R.drawable.open_with_external_app_enabled_light);
+                        }
+                    } else {
+                        if (MainWebViewActivity.darkTheme) {
+                            downloadWithExternalAppPreference.setIcon(R.drawable.open_with_external_app_disabled_dark);
+                        } else {
+                            downloadWithExternalAppPreference.setIcon(R.drawable.open_with_external_app_disabled_light);
+                        }
+                    }
+                    break;
+
                 case "display_additional_app_bar_icons":
                     // Update the icon.
                     if (sharedPreferences.getBoolean("display_additional_app_bar_icons", false)) {
index 3388700..aea1682 100644 (file)
@@ -26,13 +26,19 @@ import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.preference.PreferenceManager;
 
+import com.stoutner.privacybrowser.R;
+
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
 
 public class ImportExportDatabaseHelper {
     public static final String EXPORT_SUCCESSFUL = "Export Successful";
     public static final String IMPORT_SUCCESSFUL = "Import Successful";
 
-    private static final int SCHEMA_VERSION = 1;
+    private static final int SCHEMA_VERSION = 2;
     private static final String PREFERENCES_TABLE = "preferences";
 
     // The preferences constants.
@@ -70,21 +76,22 @@ public class ImportExportDatabaseHelper {
     private static final String HOMEPAGE = "homepage";
     private static final String DEFAULT_FONT_SIZE = "default_font_size";
     private static final String SWIPE_TO_REFRESH = "swipe_to_refresh";
+    private static final String DOWNLOAD_WITH_EXTERNAL_APP = "download_with_external_app";
     private static final String DISPLAY_ADDITIONAL_APP_BAR_ICONS = "display_additional_app_bar_icons";
     private static final String DARK_THEME = "dark_theme";
     private static final String NIGHT_MODE = "night_mode";
     private static final String DISPLAY_WEBPAGE_IMAGES = "display_webpage_images";
 
-    public String exportUnencrypted(File databaseFile, Context context) {
+    public String exportUnencrypted(File exportFile, Context context) {
         try {
             // Delete the current file if it exists.
-            if (databaseFile.exists()) {
+            if (exportFile.exists()) {
                 //noinspection ResultOfMethodCallIgnored
-                databaseFile.delete();
+                exportFile.delete();
             }
 
             // Create the export database.
-            SQLiteDatabase exportDatabase = SQLiteDatabase.openOrCreateDatabase(databaseFile, null);
+            SQLiteDatabase exportDatabase = SQLiteDatabase.openOrCreateDatabase(exportFile, null);
 
             // Set the export database version number.
             exportDatabase.setVersion(SCHEMA_VERSION);
@@ -215,6 +222,7 @@ public class ImportExportDatabaseHelper {
                     HOMEPAGE + " TEXT, " +
                     DEFAULT_FONT_SIZE + " TEXT, " +
                     SWIPE_TO_REFRESH + " BOOLEAN, " +
+                    DOWNLOAD_WITH_EXTERNAL_APP + " BOOLEAN, " +
                     DISPLAY_ADDITIONAL_APP_BAR_ICONS + " BOOLEAN, " +
                     DARK_THEME + " BOOLEAN, " +
                     NIGHT_MODE + " BOOLEAN, " +
@@ -233,38 +241,39 @@ public class ImportExportDatabaseHelper {
             preferencesContentValues.put(THIRD_PARTY_COOKIES, sharedPreferences.getBoolean("third_party_cookies_enabled", false));
             preferencesContentValues.put(DOM_STORAGE, sharedPreferences.getBoolean("dom_storage_enabled", false));
             preferencesContentValues.put(SAVE_FORM_DATA, sharedPreferences.getBoolean("save_form_data_enabled", false));  // Save form data can be removed once the minimum API >= 26.
-            preferencesContentValues.put(USER_AGENT, sharedPreferences.getString("user_agent", "Privacy Browser"));
-            preferencesContentValues.put(CUSTOM_USER_AGENT, sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"));
-            preferencesContentValues.put(INCOGNITO_MODE, sharedPreferences.getBoolean("incognito_mode", false));
-            preferencesContentValues.put(DO_NOT_TRACK, sharedPreferences.getBoolean("do_not_track", false));
-            preferencesContentValues.put(ALLOW_SCREENSHOTS, sharedPreferences.getBoolean("allow_screenshots", false));
-            preferencesContentValues.put(EASYLIST, sharedPreferences.getBoolean("easylist", true));
-            preferencesContentValues.put(EASYPRIVACY, sharedPreferences.getBoolean("easyprivacy", true));
+            preferencesContentValues.put(USER_AGENT, sharedPreferences.getString(USER_AGENT, context.getString(R.string.user_agent_default_value)));
+            preferencesContentValues.put(CUSTOM_USER_AGENT, sharedPreferences.getString(CUSTOM_USER_AGENT, context.getString(R.string.custom_user_agent_default_value)));
+            preferencesContentValues.put(INCOGNITO_MODE, sharedPreferences.getBoolean(INCOGNITO_MODE, false));
+            preferencesContentValues.put(DO_NOT_TRACK, sharedPreferences.getBoolean(DO_NOT_TRACK, false));
+            preferencesContentValues.put(ALLOW_SCREENSHOTS, sharedPreferences.getBoolean(ALLOW_SCREENSHOTS, false));
+            preferencesContentValues.put(EASYLIST, sharedPreferences.getBoolean(EASYLIST, true));
+            preferencesContentValues.put(EASYPRIVACY, sharedPreferences.getBoolean(EASYPRIVACY, true));
             preferencesContentValues.put(FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean("fanboy_annoyance_list", true));
             preferencesContentValues.put(FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean("fanboy_social_blocking_list", true));
-            preferencesContentValues.put(ULTRAPRIVACY, sharedPreferences.getBoolean("ultraprivacy", true));
-            preferencesContentValues.put(BLOCK_ALL_THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false));
-            preferencesContentValues.put(PROXY_THROUGH_ORBOT, sharedPreferences.getBoolean("proxy_through_orbot", false));
-            preferencesContentValues.put(TOR_HOMEPAGE, sharedPreferences.getString("tor_homepage", "http://ulrn6sryqaifefld.onion/"));
-            preferencesContentValues.put(TOR_SEARCH, sharedPreferences.getString("tor_search", "http://ulrn6sryqaifefld.onion/?q="));
-            preferencesContentValues.put(TOR_SEARCH_CUSTOM_URL, sharedPreferences.getString("tor_search_custom_url", ""));
-            preferencesContentValues.put(SEARCH, sharedPreferences.getString("search", "https://searx.me/?q="));
-            preferencesContentValues.put(SEARCH_CUSTOM_URL, sharedPreferences.getString("search_custom_url", ""));
-            preferencesContentValues.put(FULL_SCREEN_BROWSING_MODE, sharedPreferences.getBoolean("full_screen_browsing_mode", false));
-            preferencesContentValues.put(HIDE_SYSTEM_BARS, sharedPreferences.getBoolean("hide_system_bars", false));
-            preferencesContentValues.put(TRANSLUCENT_NAVIGATION_BAR, sharedPreferences.getBoolean("translucent_navigation_bar", true));
-            preferencesContentValues.put(CLEAR_EVERYTHING, sharedPreferences.getBoolean("clear_everything", true));
-            preferencesContentValues.put(CLEAR_COOKIES, sharedPreferences.getBoolean("clear_cookies", true));
-            preferencesContentValues.put(CLEAR_DOM_STORAGE, sharedPreferences.getBoolean("clear_dom_storage", true));
-            preferencesContentValues.put(CLEAR_FORM_DATA, sharedPreferences.getBoolean("clear_form_data", true));  // Clear form data can be removed once the minimum API >= 26.
-            preferencesContentValues.put(CLEAR_CACHE, sharedPreferences.getBoolean("clear_cache", true));
-            preferencesContentValues.put(HOMEPAGE, sharedPreferences.getString("homepage", "https://searx.me"));
-            preferencesContentValues.put(DEFAULT_FONT_SIZE, sharedPreferences.getString("default_font_size", "100"));
-            preferencesContentValues.put(SWIPE_TO_REFRESH, sharedPreferences.getBoolean("swipe_to_refresh", true));
-            preferencesContentValues.put(DISPLAY_ADDITIONAL_APP_BAR_ICONS, sharedPreferences.getBoolean("display_additional_app_bar_icons", false));
-            preferencesContentValues.put(DARK_THEME, sharedPreferences.getBoolean("dark_theme", false));
-            preferencesContentValues.put(NIGHT_MODE, sharedPreferences.getBoolean("night_mode", false));
-            preferencesContentValues.put(DISPLAY_WEBPAGE_IMAGES, sharedPreferences.getBoolean("display_webpage_images", true));
+            preferencesContentValues.put(ULTRAPRIVACY, sharedPreferences.getBoolean(ULTRAPRIVACY, true));
+            preferencesContentValues.put(BLOCK_ALL_THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean(BLOCK_ALL_THIRD_PARTY_REQUESTS, false));
+            preferencesContentValues.put(PROXY_THROUGH_ORBOT, sharedPreferences.getBoolean(PROXY_THROUGH_ORBOT, false));
+            preferencesContentValues.put(TOR_HOMEPAGE, sharedPreferences.getString(TOR_HOMEPAGE, context.getString(R.string.tor_homepage_default_value)));
+            preferencesContentValues.put(TOR_SEARCH, sharedPreferences.getString(TOR_SEARCH, context.getString(R.string.tor_search_default_value)));
+            preferencesContentValues.put(TOR_SEARCH_CUSTOM_URL, sharedPreferences.getString(TOR_SEARCH_CUSTOM_URL, context.getString(R.string.tor_search_custom_url_default_value)));
+            preferencesContentValues.put(SEARCH, sharedPreferences.getString(SEARCH, context.getString(R.string.search_default_value)));
+            preferencesContentValues.put(SEARCH_CUSTOM_URL, sharedPreferences.getString(SEARCH_CUSTOM_URL, context.getString(R.string.search_custom_url_default_value)));
+            preferencesContentValues.put(FULL_SCREEN_BROWSING_MODE, sharedPreferences.getBoolean(FULL_SCREEN_BROWSING_MODE, false));
+            preferencesContentValues.put(HIDE_SYSTEM_BARS, sharedPreferences.getBoolean(HIDE_SYSTEM_BARS, false));
+            preferencesContentValues.put(TRANSLUCENT_NAVIGATION_BAR, sharedPreferences.getBoolean(TRANSLUCENT_NAVIGATION_BAR, true));
+            preferencesContentValues.put(CLEAR_EVERYTHING, sharedPreferences.getBoolean(CLEAR_EVERYTHING, true));
+            preferencesContentValues.put(CLEAR_COOKIES, sharedPreferences.getBoolean(CLEAR_COOKIES, true));
+            preferencesContentValues.put(CLEAR_DOM_STORAGE, sharedPreferences.getBoolean(CLEAR_DOM_STORAGE, true));
+            preferencesContentValues.put(CLEAR_FORM_DATA, sharedPreferences.getBoolean(CLEAR_FORM_DATA, true));  // Clear form data can be removed once the minimum API >= 26.
+            preferencesContentValues.put(CLEAR_CACHE, sharedPreferences.getBoolean(CLEAR_CACHE, true));
+            preferencesContentValues.put(HOMEPAGE, sharedPreferences.getString(HOMEPAGE, context.getString(R.string.homepage_default_value)));
+            preferencesContentValues.put(DEFAULT_FONT_SIZE, sharedPreferences.getString(DEFAULT_FONT_SIZE, context.getString(R.string.font_size_default_value)));
+            preferencesContentValues.put(SWIPE_TO_REFRESH, sharedPreferences.getBoolean(SWIPE_TO_REFRESH, true));
+            preferencesContentValues.put(DOWNLOAD_WITH_EXTERNAL_APP, sharedPreferences.getBoolean(DOWNLOAD_WITH_EXTERNAL_APP, false));
+            preferencesContentValues.put(DISPLAY_ADDITIONAL_APP_BAR_ICONS, sharedPreferences.getBoolean(DISPLAY_ADDITIONAL_APP_BAR_ICONS, false));
+            preferencesContentValues.put(DARK_THEME, sharedPreferences.getBoolean(DARK_THEME, false));
+            preferencesContentValues.put(NIGHT_MODE, sharedPreferences.getBoolean(NIGHT_MODE, false));
+            preferencesContentValues.put(DISPLAY_WEBPAGE_IMAGES, sharedPreferences.getBoolean(DISPLAY_WEBPAGE_IMAGES, true));
 
             // Insert the preferences into the export database.
             exportDatabase.insert(PREFERENCES_TABLE, null, preferencesContentValues);
@@ -273,10 +282,10 @@ public class ImportExportDatabaseHelper {
             exportDatabase.close();
 
             // Convert the database file to a string.
-            String databaseString = databaseFile.toString();
+            String exportFileString = exportFile.toString();
 
             // Create strings for the temporary database files.
-            String journalFileString = databaseString + "-journal";
+            String journalFileString = exportFileString + "-journal";
 
             // Get `Files` for the temporary database files.
             File journalFile = new File(journalFileString);
@@ -295,13 +304,68 @@ public class ImportExportDatabaseHelper {
         }
     }
 
-    public String importUnencrypted(File databaseFile, Context context){
+    public String importUnencrypted(File importFile, Context context){
         try {
-            // Convert the database file to a string.  Once API >= 27 the file can be opened directly.
-            String databaseString = databaseFile.toString();
+            // Create a temporary import file string.
+            String temporaryImportFileString = context.getCacheDir() + "/" + "temporary_import_file";
+
+            // Get a handle for a temporary import file.
+            File temporaryImportFile = new File(temporaryImportFileString);
+
+            // Delete the temporary import file if it already exists.
+            if (temporaryImportFile.exists()) {
+                //noinspection ResultOfMethodCallIgnored
+                temporaryImportFile.delete();
+            }
+
+            // Create input and output streams.
+            InputStream importFileInputStream = new FileInputStream(importFile);
+            OutputStream temporaryImportFileOutputStream = new FileOutputStream(temporaryImportFile);
+
+            // Create a byte array.
+            byte[] transferByteArray = new byte[1024];
+
+            // Create an integer to track the number of bytes read.
+            int bytesRead;
+
+            // Copy the import file to the temporary import file.  Once API >= 26 `Files.copy` can be used instead.
+            while ((bytesRead = importFileInputStream.read(transferByteArray)) > 0) {
+                temporaryImportFileOutputStream.write(transferByteArray, 0, bytesRead);
+            }
 
-            // Open the import database.
-            SQLiteDatabase importDatabase = SQLiteDatabase.openDatabase(databaseString, null, SQLiteDatabase.OPEN_READONLY);
+            // Close the file streams.
+            importFileInputStream.close();
+            temporaryImportFileOutputStream.close();
+
+
+            // Get a handle for the shared preference.
+            SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+
+            // Open the import database.  Once API >= 27 the file can be opened directly without using the string.
+            SQLiteDatabase importDatabase = SQLiteDatabase.openDatabase(temporaryImportFileString, null, SQLiteDatabase.OPEN_READWRITE);
+
+            // Get the database version.
+            int importDatabaseVersion = importDatabase.getVersion();
+
+            // Upgrade the database if needed.
+            if (importDatabaseVersion < SCHEMA_VERSION) {
+                switch (importDatabaseVersion){
+                    // Upgrade from schema version 1.
+                    case 1:
+                        // Add the download with external app preference.
+                        importDatabase.execSQL("ALTER TABLE " + PREFERENCES_TABLE + " ADD COLUMN " + DOWNLOAD_WITH_EXTERNAL_APP + " BOOLEAN");
+
+                        // Get the current setting for downloading with an external app.
+                        boolean downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
+
+                        // Set the download with external app preference to the current default.
+                        if (downloadWithExternalApp) {
+                            importDatabase.execSQL("UPDATE " + PREFERENCES_TABLE + " SET " + DOWNLOAD_WITH_EXTERNAL_APP + " = " + 1);
+                        } else {
+                            importDatabase.execSQL("UPDATE " + PREFERENCES_TABLE + " SET " + DOWNLOAD_WITH_EXTERNAL_APP + " = " + 0);
+                        }
+                }
+            }
 
             // Get a cursor for the domains table.
             Cursor importDomainsCursor = importDatabase.rawQuery("SELECT * FROM " + DomainsDatabaseHelper.DOMAINS_TABLE + " ORDER BY " + DomainsDatabaseHelper.DOMAIN_NAME + " ASC", null);
@@ -402,9 +466,6 @@ public class ImportExportDatabaseHelper {
             bookmarksDatabaseHelper.close();
 
 
-            // Get a handle for the shared preference.
-            SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
-
             // Get a cursor for the bookmarks table.
             Cursor importPreferencesCursor = importDatabase.rawQuery("SELECT * FROM " + PREFERENCES_TABLE, null);
 
@@ -419,39 +480,41 @@ public class ImportExportDatabaseHelper {
                     .putBoolean("dom_storage_enabled", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DOM_STORAGE)) == 1)
                     // Save form data can be removed once the minimum API >= 26.
                     .putBoolean("save_form_data_enabled", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(SAVE_FORM_DATA)) == 1)
-                    .putString("user_agent", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(USER_AGENT)))
-                    .putString("custom_user_agent", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(CUSTOM_USER_AGENT)))
-                    .putBoolean("incognito_mode", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(INCOGNITO_MODE)) == 1)
-                    .putBoolean("do_not_track", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DO_NOT_TRACK)) == 1)
-                    .putBoolean("allow_screenshots", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(ALLOW_SCREENSHOTS)) == 1)
-                    .putBoolean("easylist", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(EASYLIST)) == 1)
-                    .putBoolean("easyprivacy", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(EASYPRIVACY)) == 1)
+                    .putString(USER_AGENT, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(USER_AGENT)))
+                    .putString(CUSTOM_USER_AGENT, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(CUSTOM_USER_AGENT)))
+                    .putBoolean(INCOGNITO_MODE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(INCOGNITO_MODE)) == 1)
+                    .putBoolean(DO_NOT_TRACK, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DO_NOT_TRACK)) == 1)
+                    .putBoolean(ALLOW_SCREENSHOTS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(ALLOW_SCREENSHOTS)) == 1)
+                    .putBoolean(EASYLIST, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(EASYLIST)) == 1)
+                    .putBoolean(EASYPRIVACY, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(EASYPRIVACY)) == 1)
                     .putBoolean("fanboy_annoyance_list", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(FANBOYS_ANNOYANCE_LIST)) == 1)
                     .putBoolean("fanboy_social_blocking_list", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(FANBOYS_SOCIAL_BLOCKING_LIST)) == 1)
-                    .putBoolean("ultraprivacy", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(ULTRAPRIVACY)) == 1)
-                    .putBoolean("block_all_third_party_requests", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1)
-                    .putBoolean("proxy_through_orbot", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(PROXY_THROUGH_ORBOT)) == 1)
-                    .putString("tor_homepage", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(TOR_HOMEPAGE)))
-                    .putString("tor_search", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(TOR_SEARCH)))
-                    .putString("tor_search_custom_url", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(TOR_SEARCH_CUSTOM_URL)))
-                    .putString("search", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(SEARCH)))
-                    .putString("search_custom_url", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(SEARCH_CUSTOM_URL)))
-                    .putBoolean("full_screen_browsing_mode", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(FULL_SCREEN_BROWSING_MODE)) == 1)
-                    .putBoolean("hide_system_bars", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(HIDE_SYSTEM_BARS)) == 1)
-                    .putBoolean("translucent_navigation_bar", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(TRANSLUCENT_NAVIGATION_BAR)) == 1)
-                    .putBoolean("clear_everything", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_EVERYTHING)) == 1)
-                    .putBoolean("clear_cookies", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_COOKIES)) == 1)
-                    .putBoolean("clear_dom_storage", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_DOM_STORAGE)) == 1)
+                    .putBoolean(ULTRAPRIVACY, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(ULTRAPRIVACY)) == 1)
+                    .putBoolean(BLOCK_ALL_THIRD_PARTY_REQUESTS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1)
+                    .putBoolean(PROXY_THROUGH_ORBOT, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(PROXY_THROUGH_ORBOT)) == 1)
+                    .putString(TOR_HOMEPAGE, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(TOR_HOMEPAGE)))
+                    .putString(TOR_SEARCH, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(TOR_SEARCH)))
+                    .putString(TOR_SEARCH_CUSTOM_URL, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(TOR_SEARCH_CUSTOM_URL)))
+                    .putString(SEARCH, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(SEARCH)))
+                    .putString(SEARCH_CUSTOM_URL, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(SEARCH_CUSTOM_URL)))
+                    .putBoolean(FULL_SCREEN_BROWSING_MODE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(FULL_SCREEN_BROWSING_MODE)) == 1)
+                    .putBoolean(HIDE_SYSTEM_BARS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(HIDE_SYSTEM_BARS)) == 1)
+                    .putBoolean(TRANSLUCENT_NAVIGATION_BAR, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(TRANSLUCENT_NAVIGATION_BAR)) == 1)
+                    .putBoolean(CLEAR_EVERYTHING, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_EVERYTHING)) == 1)
+                    .putBoolean(CLEAR_COOKIES, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_COOKIES)) == 1)
+                    .putBoolean(CLEAR_DOM_STORAGE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_DOM_STORAGE)) == 1)
                     // Clear form data can be removed once the minimum API >= 26.
-                    .putBoolean("clear_form_data", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_FORM_DATA)) == 1)
-                    .putBoolean("clear_cache", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_CACHE)) == 1)
-                    .putString("homepage", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(HOMEPAGE)))
-                    .putString("default_font_size", importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(DEFAULT_FONT_SIZE)))
-                    .putBoolean("swipe_to_refresh", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(SWIPE_TO_REFRESH)) == 1)
-                    .putBoolean("display_additional_app_bar_icons", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DISPLAY_ADDITIONAL_APP_BAR_ICONS)) == 1)
-                    .putBoolean("dark_theme", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DARK_THEME)) == 1)
-                    .putBoolean("night_mode", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(NIGHT_MODE)) == 1)
-                    .putBoolean("display_webpage_images", importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DISPLAY_WEBPAGE_IMAGES)) == 1).apply();
+                    .putBoolean(CLEAR_FORM_DATA, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_FORM_DATA)) == 1)
+                    .putBoolean(CLEAR_CACHE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(CLEAR_CACHE)) == 1)
+                    .putString(HOMEPAGE, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(HOMEPAGE)))
+                    .putString(DEFAULT_FONT_SIZE, importPreferencesCursor.getString(importPreferencesCursor.getColumnIndex(DEFAULT_FONT_SIZE)))
+                    .putBoolean(SWIPE_TO_REFRESH, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(SWIPE_TO_REFRESH)) == 1)
+                    .putBoolean(DOWNLOAD_WITH_EXTERNAL_APP, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DOWNLOAD_WITH_EXTERNAL_APP)) == 1)
+                    .putBoolean(DISPLAY_ADDITIONAL_APP_BAR_ICONS, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DISPLAY_ADDITIONAL_APP_BAR_ICONS)) == 1)
+                    .putBoolean(DARK_THEME, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DARK_THEME)) == 1)
+                    .putBoolean(NIGHT_MODE, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(NIGHT_MODE)) == 1)
+                    .putBoolean(DISPLAY_WEBPAGE_IMAGES, importPreferencesCursor.getInt(importPreferencesCursor.getColumnIndex(DISPLAY_WEBPAGE_IMAGES)) == 1)
+                    .apply();
 
             // Close the preferences cursor.
             importPreferencesCursor.close();
@@ -461,9 +524,9 @@ public class ImportExportDatabaseHelper {
             importDatabase.close();
 
             // Create strings for the temporary database files.
-            String shmFileString = databaseString + "-shm";
-            String walFileString = databaseString + "-wal";
-            String journalFileString = databaseString + "-journal";
+            String shmFileString = temporaryImportFileString + "-shm";
+            String walFileString = temporaryImportFileString + "-wal";
+            String journalFileString = temporaryImportFileString + "-journal";
 
             // Get `Files` for the temporary database files.
             File shmFile = new File(shmFileString);
@@ -488,6 +551,10 @@ public class ImportExportDatabaseHelper {
                 journalFile.delete();
             }
 
+            // Delete the temporary import file.
+            //noinspection ResultOfMethodCallIgnored
+            temporaryImportFile.delete();
+
             // Import successful.
             return IMPORT_SUCCESSFUL;
         } catch (Exception exception) {
diff --git a/app/src/main/res/drawable/clear_and_exit.xml b/app/src/main/res/drawable/clear_and_exit.xml
deleted file mode 100644 (file)
index 5461749..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<!-- `clear_and_exit.xml` is derived from `exit_to_app`, which is part of the Android Material icon set.  It is released under the Apache License 2.0.
-    Modifications copyright © 2017 Soren Stoutner <soren@stoutner.com>.  The resulting image is released under the GPLv3+ license. -->
-
-<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
-<vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:height="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0"
-    android:width="24dp"
-    android:autoMirrored="true"
-    tools:ignore="VectorRaster" >
-
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M2.096,3.377H15.51c1.064,0 1.916,0.862 1.916,1.916V9.126H15.51V5.293H2.096V18.707H15.51v-3.833h1.916v3.833c0,1.054 -0.853,1.916 -1.916,1.916H2.096c-1.054,0 -1.916,-0.862 -1.916,-1.916V5.293c0,-1.054 0.862,-1.916 1.916,-1.916z"
-        android:strokeWidth="0.95815897" />
-
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="m17.845,15.44 l1.351,1.351 4.791,-4.791 -4.791,-4.791 -1.351,1.351 2.472,2.482H8.096v1.916H20.317Z"
-        android:strokeWidth="0.95815897" />
-</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/open_with_external_app_disabled_dark.xml b/app/src/main/res/drawable/open_with_external_app_disabled_dark.xml
new file mode 100644 (file)
index 0000000..478ad68
--- /dev/null
@@ -0,0 +1,26 @@
+<!-- `open_with_external_app_disabled_dark.xml` is derived from `exit_to_app`, which is part of the Android Material icon set.  It is released under the Apache License 2.0.
+    Modifications copyright © 2017 Soren Stoutner <soren@stoutner.com>.  The resulting image is released under the GPLv3+ license. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:width="24dp"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FF9E9E9E"
+        android:pathData="M2.096,3.377H15.51c1.064,0 1.916,0.862 1.916,1.916V9.126H15.51V5.293H2.096V18.707H15.51v-3.833h1.916v3.833c0,1.054 -0.853,1.916 -1.916,1.916H2.096c-1.054,0 -1.916,-0.862 -1.916,-1.916V5.293c0,-1.054 0.862,-1.916 1.916,-1.916z"
+        android:strokeWidth="0.95815897" />
+
+    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FF9E9E9E"
+        android:pathData="m17.845,15.44 l1.351,1.351 4.791,-4.791 -4.791,-4.791 -1.351,1.351 2.472,2.482H8.096v1.916H20.317Z"
+        android:strokeWidth="0.95815897" />
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/open_with_external_app_disabled_light.xml b/app/src/main/res/drawable/open_with_external_app_disabled_light.xml
new file mode 100644 (file)
index 0000000..24af6a5
--- /dev/null
@@ -0,0 +1,26 @@
+<!-- `open_with_external_app_disabled_light.xml` is derived from `exit_to_app`, which is part of the Android Material icon set.  It is released under the Apache License 2.0.
+    Modifications copyright © 2017 Soren Stoutner <soren@stoutner.com>.  The resulting image is released under the GPLv3+ license. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:width="24dp"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FF757575"
+        android:pathData="M2.096,3.377H15.51c1.064,0 1.916,0.862 1.916,1.916V9.126H15.51V5.293H2.096V18.707H15.51v-3.833h1.916v3.833c0,1.054 -0.853,1.916 -1.916,1.916H2.096c-1.054,0 -1.916,-0.862 -1.916,-1.916V5.293c0,-1.054 0.862,-1.916 1.916,-1.916z"
+        android:strokeWidth="0.95815897" />
+
+    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FF757575"
+        android:pathData="m17.845,15.44 l1.351,1.351 4.791,-4.791 -4.791,-4.791 -1.351,1.351 2.472,2.482H8.096v1.916H20.317Z"
+        android:strokeWidth="0.95815897" />
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/open_with_external_app_enabled_dark.xml b/app/src/main/res/drawable/open_with_external_app_enabled_dark.xml
new file mode 100644 (file)
index 0000000..c07cc72
--- /dev/null
@@ -0,0 +1,26 @@
+<!-- `open_with_external_app_enabled_dark.xml` is derived from `exit_to_app`, which is part of the Android Material icon set.  It is released under the Apache License 2.0.
+    Modifications copyright © 2017 Soren Stoutner <soren@stoutner.com>.  The resulting image is released under the GPLv3+ license. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:width="24dp"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FF1E88E5"
+        android:pathData="M2.096,3.377H15.51c1.064,0 1.916,0.862 1.916,1.916V9.126H15.51V5.293H2.096V18.707H15.51v-3.833h1.916v3.833c0,1.054 -0.853,1.916 -1.916,1.916H2.096c-1.054,0 -1.916,-0.862 -1.916,-1.916V5.293c0,-1.054 0.862,-1.916 1.916,-1.916z"
+        android:strokeWidth="0.95815897" />
+
+    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FF1E88E5"
+        android:pathData="m17.845,15.44 l1.351,1.351 4.791,-4.791 -4.791,-4.791 -1.351,1.351 2.472,2.482H8.096v1.916H20.317Z"
+        android:strokeWidth="0.95815897" />
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/open_with_external_app_enabled_light.xml b/app/src/main/res/drawable/open_with_external_app_enabled_light.xml
new file mode 100644 (file)
index 0000000..eda4a6e
--- /dev/null
@@ -0,0 +1,26 @@
+<!-- `open_with_external_app_enabled_light.xml` is derived from `exit_to_app`, which is part of the Android Material icon set.  It is released under the Apache License 2.0.
+    Modifications copyright © 2017 Soren Stoutner <soren@stoutner.com>.  The resulting image is released under the GPLv3+ license. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:width="24dp"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FF1565C0"
+        android:pathData="M2.096,3.377H15.51c1.064,0 1.916,0.862 1.916,1.916V9.126H15.51V5.293H2.096V18.707H15.51v-3.833h1.916v3.833c0,1.054 -0.853,1.916 -1.916,1.916H2.096c-1.054,0 -1.916,-0.862 -1.916,-1.916V5.293c0,-1.054 0.862,-1.916 1.916,-1.916z"
+        android:strokeWidth="0.95815897" />
+
+    <!-- A hard coded color must be used until API >= 21.  Then `@color` may be used. -->
+    <path
+        android:fillColor="#FF1565C0"
+        android:pathData="m17.845,15.44 l1.351,1.351 4.791,-4.791 -4.791,-4.791 -1.351,1.351 2.472,2.482H8.096v1.916H20.317Z"
+        android:strokeWidth="0.95815897" />
+</vector>
\ No newline at end of file
index df08c91..f2c8fb6 100644 (file)
                     android:layout_marginTop="1dp"
                     android:layout_marginEnd="10dp"
                     android:layout_gravity="center_vertical"
-                    android:contentDescription="@string/swipe_to_refresh_preference" />
+                    android:contentDescription="@string/swipe_to_refresh" />
 
                 <Spinner
                     android:id="@+id/domain_settings_swipe_to_refresh_spinner"
index d17d658..ba07e1a 100644 (file)
         <item
             android:id="@+id/clearAndExit"
             android:title="@string/clear_and_exit"
-            android:icon="@drawable/clear_and_exit"
+            android:icon="@drawable/open_with_external_app_enabled_light"
             android:orderInCategory="120" />
     </group>
 </menu>
\ No newline at end of file
index 8e78962..b24512a 100644 (file)
     <string name="ssl_certificates">SSL-Zertifikate</string>
     <string name="tracking_ids">Verolgungs-IDs</string>
 
+    <!-- Download Location -->
+    <string name="download_location">Download-Zielordner</string>
+    <string name="download_location_message">Privacy Browser benötigt die Berechtigung zur Speicherung im öffentlichen Download-Ordner. Anderenfalls wird der Download-Ordner der App verwendet.</string>
+    <string name="ok">OK</string>
+
+    <!-- Orbot. -->
+    <string name="orbot_proxy_not_installed">Orbot-Proxy wird nicht funktionieren, solange Orbot nicht installiert ist.</string>
+    <string name="waiting_for_orbot">Warte, bis sich Orbot verbindet...</string>
+
+    <!-- About Activity. -->
+    <string name="about_privacy_browser">Über Privacy Browser</string>
+    <string name="version">Version</string>
+    <string name="version_code">Versions-Code</string>
+    <string name="hardware">Hardware</string>
+    <string name="brand">Marke:</string>
+    <string name="manufacturer">Hersteller:</string>
+    <string name="model">Modell:</string>
+    <string name="device">Gerät:</string>
+    <string name="bootloader">Bootloader:</string>
+    <string name="radio">Radio:</string>
+    <string name="software">Software</string>
+    <string name="android">Android:</string>
+    <string name="api">API</string>
+    <string name="build">Build:</string>
+    <string name="security_patch">Sicherheits-Patch:</string>
+    <string name="webview">WebView:</string>
+    <string name="orbot">Orbot:</string>
+    <string name="easylist_label">EasyList:</string>
+    <string name="easyprivacy_label">EasyPrivacy:</string>
+    <string name="fanboy_annoyance_label">Fanboy’s Annoyance List:</string>
+    <string name="fanboy_social_label">Fanboy’s Social Blocking List:</string>
+    <string name="package_signature">Paket-Signatur</string>
+    <string name="issuer_dn">Aussteller-DN:</string>
+    <string name="subject_dn">Subject DN:</string>
+    <string name="certificate_version">Zertifikat-Version:</string>
+    <string name="serial_number">Seriennummer:</string>
+    <string name="signature_algorithm">Signaturalgorithmus:</string>
+    <string name="permissions">Berechtigungen</string>
+    <string name="privacy_policy">Datenschutzrichtlinie</string>
+    <string name="changelog">Changelog</string>
+    <string name="licenses">Lizenzen</string>
+    <string name="contributors">Mitwirkende</string>
+    <string name="links">Links</string>
+
     <!-- Preferences. -->
     <string name="privacy">Privatsphäre</string>
         <string name="javascript_preference">JavaScript standardmäßig aktivieren</string>
             <item>175%</item>
             <item>200%</item>
         </string-array>
-        <string name="swipe_to_refresh_preference">Herunterziehen zum Aktualisieren</string>
+        <string name="swipe_to_refresh">Herunterziehen zum Aktualisieren</string>
         <string name="swipe_to_refresh_summary">Einige Websites funktionieren nicht, wenn "Herunterziehen zum Aktualisieren" eingeschaltet ist.</string>
         <string name="display_additional_app_bar_icons">Weitere Icons in der Titelleiste</string>
 
-    <!-- Download Location -->
-    <string name="download_location">Download-Zielordner</string>
-    <string name="download_location_message">Privacy Browser benötigt die Berechtigung zur Speicherung im öffentlichen Download-Ordner. Anderenfalls wird der Download-Ordner der App verwendet.</string>
-    <string name="ok">OK</string>
-
-    <!-- Orbot. -->
-    <string name="orbot_proxy_not_installed">Orbot-Proxy wird nicht funktionieren, solange Orbot nicht installiert ist.</string>
-    <string name="waiting_for_orbot">Warte, bis sich Orbot verbindet...</string>
-
-    <!-- About Activity. -->
-    <string name="about_privacy_browser">Über Privacy Browser</string>
-    <string name="version">Version</string>
-        <string name="version_code">Versions-Code</string>
-        <string name="hardware">Hardware</string>
-            <string name="brand">Marke:</string>
-            <string name="manufacturer">Hersteller:</string>
-            <string name="model">Modell:</string>
-            <string name="device">Gerät:</string>
-            <string name="bootloader">Bootloader:</string>
-            <string name="radio">Radio:</string>
-        <string name="software">Software</string>
-            <string name="android">Android:</string>
-                <string name="api">API</string>
-            <string name="build">Build:</string>
-            <string name="security_patch">Sicherheits-Patch:</string>
-            <string name="webview">WebView:</string>
-            <string name="orbot">Orbot:</string>
-            <string name="easylist_label">EasyList:</string>
-            <string name="easyprivacy_label">EasyPrivacy:</string>
-            <string name="fanboy_annoyance_label">Fanboy’s Annoyance List:</string>
-            <string name="fanboy_social_label">Fanboy’s Social Blocking List:</string>
-        <string name="package_signature">Paket-Signatur</string>
-            <string name="issuer_dn">Aussteller-DN:</string>
-            <string name="subject_dn">Subject DN:</string>
-            <string name="certificate_version">Zertifikat-Version:</string>
-            <string name="serial_number">Seriennummer:</string>
-            <string name="signature_algorithm">Signaturalgorithmus:</string>
-    <string name="permissions">Berechtigungen</string>
-    <string name="privacy_policy">Datenschutzrichtlinie</string>
-    <string name="changelog">Changelog</string>
-    <string name="licenses">Lizenzen</string>
-    <string name="contributors">Mitwirkende</string>
-    <string name="links">Links</string>
-
     <!-- Ad Control. There are no ads in the standard flavor, but this string must exist because it is referenced in the code. -->
     <string name="ad_consent">Zustimmung zur Werbung</string>
 </resources>
\ No newline at end of file
index 13daf27..aa3e10f 100644 (file)
     <string name="load_an_encrypted_website">Cargar una página web cifrada antes de abrir la configuración de dominio para rellenar el certificado SSL de la página web actual.</string>
 
     <!-- Import/Export. -->
+    <string name="encryption">Cifrado</string>
+    <string-array name="encryption_type">
+        <item>Ninguno</item>
+        <item>Contraseña</item>
+        <item>OpenPGP</item>
+    </string-array>
+    <string name="kitkat_password_encryption_message">El cifrado de contraseñas no funciona en Android KitKat.</string>
+    <string name="openkeychain_required">El cifrado OpenPGP requiere que esté instalado OpenKeychain.</string>
+    <string name="openkeychain_import_instructions">El archivo sin cifrar tendrá que ser importado en un paso separado después de ser descifrado.</string>
+    <string name="file_location">Ubicación del archivo</string>
     <string name="browse">Navegar</string>
     <string name="export">Exportar</string>
     <string name="import_button">Importar</string>  <!-- `import` is a reserved word and cannot be used as the name -->
+    <string name="decrypt">Descifrar</string>
     <string name="export_successful">Exportación exitosa.</string>
     <string name="export_failed">Exportación fallida:</string>
     <string name="import_failed">Importación fallida:</string>
     <string name="ssl_certificates">Certificados SSL</string>
     <string name="tracking_ids">Rastreo de IDs</string>
 
+    <!-- Download Location -->
+    <string name="download_location">Lugar de descarga</string>
+    <string name="download_location_message">Navegador Privado necesita el permiso de almacenamiento para utilizar el directorio de descarga público.
+        Si se deniega, se utilizará el directorio de descarga de la aplicación.</string>
+    <string name="ok">OK</string>
+
+    <!-- Orbot. -->
+    <string name="orbot_proxy_not_installed">Enviar a través de Orbot no funcionará a menos que se instale Orbot.</string>
+    <string name="waiting_for_orbot">Esperando a Orbot para conectar...</string>
+
+    <!-- About Activity. -->
+    <string name="about_privacy_browser">Acerca de Navegador Privado</string>
+    <string name="version">Versión</string>
+    <string name="version_code">código de versión</string>
+    <string name="hardware">Hardware</string>
+    <string name="brand">Marca:</string>
+    <string name="manufacturer">Fabricante:</string>
+    <string name="model">Modelo:</string>
+    <string name="device">Dispositivo:</string>
+    <string name="bootloader">Cargador de arranque:</string>
+    <string name="radio">Radio:</string>
+    <string name="software">Software</string>
+    <string name="android">Android:</string>
+    <string name="api">API</string>
+    <string name="build">Versión de compilación:</string>
+    <string name="security_patch">Parche de seguridad:</string>
+    <string name="webview">WebView:</string>
+    <string name="orbot">Orbot:</string>
+    <string name="openkeychain">OpenKeychain:</string>
+    <string name="easylist_label">EasyList:</string>
+    <string name="easyprivacy_label">EasyPrivacy:</string>
+    <string name="fanboy_annoyance_label">Lista molesta de Fanboy:</string>
+    <string name="fanboy_social_label">Lista de bloqueo social de Fanboy:</string>
+    <string name="ultraprivacy_label">Ultra Privacidad:</string>
+    <string name="package_signature">Firma del paquete</string>
+    <string name="issuer_dn">DN del emisor:</string>
+    <string name="subject_dn">DN del sujeto:</string>
+    <string name="certificate_version">Versión del certificado:</string>
+    <string name="serial_number">Número de serie:</string>
+    <string name="signature_algorithm">Algoritmo de firma:</string>
+    <string name="permissions">Permisos</string>
+    <string name="privacy_policy">Política de privacidad</string>
+    <string name="changelog">Historial de cambios</string>
+    <string name="licenses">Licencias</string>
+    <string name="contributors">Colaboradores</string>
+    <string name="links">Enlaces</string>
+
     <!-- Preferences. -->
     <string name="privacy">Privacidad</string>
         <string name="javascript_preference">Habilitar Javascript por defecto</string>
                 <item>175%</item>
                 <item>200%</item>
             </string-array>
-        <string name="swipe_to_refresh_preference">Deslizar para actualizar</string>
+        <string name="swipe_to_refresh">Deslizar para actualizar</string>
         <string name="swipe_to_refresh_summary">Algunas webs no funcionan bien si la opción deslizar para actualizar está habilitada.</string>
         <string name="display_additional_app_bar_icons">Mostrar iconos adicionales en la barra de aplicación</string>
         <string name="display_additional_app_bar_icons_summary">Mostrar iconos en la barra de aplicaciones para refrescar el WebView y, si hay espacio, para alternar entre cookies y almacenamiento DOM.</string>
         <string name="display_webpage_images">Mostrar imágenes de la página web</string>
         <string name="display_webpage_images_summary">Deshabilitar para conservar ancho de banda.</string>
 
-    <!-- Download Location -->
-    <string name="download_location">Lugar de descarga</string>
-    <string name="download_location_message">Navegador Privado necesita el permiso de almacenamiento para utilizar el directorio de descarga público.
-        Si se deniega, se utilizará el directorio de descarga de la aplicación.</string>
-    <string name="ok">OK</string>
-
-    <!-- Orbot. -->
-    <string name="orbot_proxy_not_installed">Enviar a través de Orbot no funcionará a menos que se instale Orbot.</string>
-    <string name="waiting_for_orbot">Esperando a Orbot para conectar...</string>
-
-    <!-- About Activity. -->
-    <string name="about_privacy_browser">Acerca de Navegador Privado</string>
-    <string name="version">Versión</string>
-        <string name="version_code">código de versión</string>
-        <string name="hardware">Hardware</string>
-            <string name="brand">Marca:</string>
-            <string name="manufacturer">Fabricante:</string>
-            <string name="model">Modelo:</string>
-            <string name="device">Dispositivo:</string>
-            <string name="bootloader">Cargador de arranque:</string>
-            <string name="radio">Radio:</string>
-        <string name="software">Software</string>
-            <string name="android">Android:</string>
-                <string name="api">API</string>
-            <string name="build">Versión de compilación:</string>
-            <string name="security_patch">Parche de seguridad:</string>
-            <string name="webview">WebView:</string>
-            <string name="orbot">Orbot:</string>
-            <string name="easylist_label">EasyList:</string>
-            <string name="easyprivacy_label">EasyPrivacy:</string>
-            <string name="fanboy_annoyance_label">Lista molesta de Fanboy:</string>
-            <string name="fanboy_social_label">Lista de bloqueo social de Fanboy:</string>
-            <string name="ultraprivacy_label">Ultra Privacidad:</string>
-        <string name="package_signature">Firma del paquete</string>
-            <string name="issuer_dn">DN del emisor:</string>
-            <string name="subject_dn">DN del sujeto:</string>
-            <string name="certificate_version">Versión del certificado:</string>
-            <string name="serial_number">Número de serie:</string>
-            <string name="signature_algorithm">Algoritmo de firma:</string>
-    <string name="permissions">Permisos</string>
-    <string name="privacy_policy">Política de privacidad</string>
-    <string name="changelog">Historial de cambios</string>
-    <string name="licenses">Licencias</string>
-    <string name="contributors">Colaboradores</string>
-    <string name="links">Enlaces</string>
-
     <!-- Ad Control. There are no ads in the standard flavor, but this string must exist because it is referenced in the code. -->
     <string name="ad_consent">Autorización publicitaria</string>
 </resources>
\ No newline at end of file
index f7feb02..f390840 100644 (file)
     <string name="load_an_encrypted_website">Carica un sito Web criptato prima di aprire le impostazioni dei domini per popolare il certificato SSL del sito attuale.</string>
 
     <!-- Import/Export. -->
+    <string name="encryption">Cifratura</string>
+    <string-array name="encryption_type">
+        <item>Nessuna</item>
+        <item>Password</item>
+        <item>OpenPGP</item>
+    </string-array>
+    <string name="kitkat_password_encryption_message">La cifratura delle Password non funziona su Android KitKat.</string>
+    <string name="openkeychain_required">La cifratura OpenPGP richiede l\'installazione di OpenKeychain.</string>
+    <string name="openkeychain_import_instructions">Il file non cifrato deve essere importato in un secondo momento dopo che è stato decriptato.</string>
+    <string name="file_location">Posizione del File</string>
     <string name="browse">Sfoglia</string>
     <string name="export">Esporta</string>
     <string name="import_button">Importa</string>  <!-- `import` is a reserved word and cannot be used as the name -->
+    <string name="decrypt">Decripta</string>
     <string name="export_successful">Esportazione riuscita</string>
     <string name="export_failed">Esportazione fallita:</string>
     <string name="import_failed">Importazione fallita:</string>
     <string name="ssl_certificates">Certificati SSL</string>
     <string name="tracking_ids">Tracciamento utenti</string>
 
+    <!-- Download Location -->
+    <string name="download_location">Cartella di Download</string>
+    <string name="download_location_message">Privacy Browser necessita del permesso di accesso alla memoria di storage per utilizzare la cartella pubblica di download.
+        Nel caso in cui il permesso sia negato sarà utilizzata la cartella di download dell\'applicazione.</string>
+    <string name="ok">OK</string>
+
+    <!-- Orbot. -->
+    <string name="orbot_proxy_not_installed">Il Proxy con Orbot funziona solo se è installato Orbot.</string>
+    <string name="waiting_for_orbot">In attesa della connessione di Orbot...</string>
+
+    <!-- About Activity. -->
+    <string name="about_privacy_browser">Informazioni su Privacy Browser</string>
+    <string name="version">Versione</string>
+    <string name="version_code">codice versione</string>
+    <string name="hardware">Hardware</string>
+    <string name="brand">Brand:</string>
+    <string name="manufacturer">Costruttore:</string>
+    <string name="model">Modello:</string>
+    <string name="device">Dispositivo:</string>
+    <string name="bootloader">Bootloader:</string>
+    <string name="radio">Radio:</string>
+    <string name="software">Software</string>
+    <string name="android">Android:</string>
+    <string name="api">API</string>
+    <string name="build">Build:</string>
+    <string name="security_patch">Patch si sicurezza:</string>
+    <string name="webview">WebView:</string>
+    <string name="orbot">Orbot:</string>
+    <string name="openkeychain">OpenKeychain:</string>
+    <string name="easylist_label">EasyList:</string>
+    <string name="easyprivacy_label">EasyPrivacy:</string>
+    <string name="fanboy_annoyance_label">Fanboy’s Annoyance List:</string>
+    <string name="fanboy_social_label">Fanboy’s Social Blocking List:</string>
+    <string name="ultraprivacy_label">UltraPrivacy:</string>
+    <string name="package_signature">Firma del Pacchetto</string>
+    <string name="issuer_dn">Emittente:</string>
+    <string name="subject_dn">Soggetto:</string>
+    <string name="certificate_version">Versione del Certificato:</string>
+    <string name="serial_number">Numero di Serie:</string>
+    <string name="signature_algorithm">Algoritmo di firma:</string>
+    <string name="permissions">Autorizzazioni</string>
+    <string name="privacy_policy">Privacy Policy</string>
+    <string name="changelog">Changelog</string>
+    <string name="licenses">Licenze</string>
+    <string name="contributors">Collaboratori</string>
+    <string name="links">Links</string>
+
     <!-- Preferences. -->
     <string name="privacy">Privacy</string>
         <string name="javascript_preference">Abilita JavaScript</string>
                 <item>175%</item>
                 <item>200%</item>
             </string-array>
-        <string name="swipe_to_refresh_preference">Swipe per aggiornare</string>
+        <string name="swipe_to_refresh">Swipe per aggiornare</string>
         <string name="swipe_to_refresh_summary">Alcuni siti non funzionano correttamente se questa opzione è abilitata.</string>
         <string name="display_additional_app_bar_icons">Mostra icone addizionali nella barra dell\'applicazione</string>
         <string name="display_additional_app_bar_icons_summary">Mostra nella barra dell\'applicazione le icone per l\'aggiornamento di WebView e, se lo spazio è sufficiente,
         <string name="display_webpage_images">Mostra immagini delle pagine web</string>
         <string name="display_webpage_images_summary">Disabilita per ridurre il consumo di dati.</string>
 
-    <!-- Download Location -->
-    <string name="download_location">Cartella di Download</string>
-    <string name="download_location_message">Privacy Browser necessita del permesso di accesso alla memoria di storage per utilizzare la cartella pubblica di download.
-        Nel caso in cui il permesso sia negato sarà utilizzata la cartella di download dell\'applicazione.</string>
-    <string name="ok">OK</string>
-
-    <!-- Orbot. -->
-    <string name="orbot_proxy_not_installed">Il Proxy con Orbot funziona solo se è installato Orbot.</string>
-    <string name="waiting_for_orbot">In attesa della connessione di Orbot...</string>
-
-    <!-- About Activity. -->
-    <string name="about_privacy_browser">Informazioni su Privacy Browser</string>
-    <string name="version">Versione</string>
-        <string name="version_code">codice versione</string>
-        <string name="hardware">Hardware</string>
-            <string name="brand">Brand:</string>
-            <string name="manufacturer">Costruttore:</string>
-            <string name="model">Modello:</string>
-            <string name="device">Dispositivo:</string>
-            <string name="bootloader">Bootloader:</string>
-            <string name="radio">Radio:</string>
-        <string name="software">Software</string>
-            <string name="android">Android:</string>
-                <string name="api">API</string>
-            <string name="build">Build:</string>
-            <string name="security_patch">Patch si sicurezza:</string>
-            <string name="webview">WebView:</string>
-            <string name="orbot">Orbot:</string>
-            <string name="easylist_label">EasyList:</string>
-            <string name="easyprivacy_label">EasyPrivacy:</string>
-            <string name="fanboy_annoyance_label">Fanboy’s Annoyance List:</string>
-            <string name="fanboy_social_label">Fanboy’s Social Blocking List:</string>
-            <string name="ultraprivacy_label">UltraPrivacy:</string>
-        <string name="package_signature">Firma del Pacchetto</string>
-            <string name="issuer_dn">Emittente:</string>
-            <string name="subject_dn">Soggetto:</string>
-            <string name="certificate_version">Versione del Certificato:</string>
-            <string name="serial_number">Numero di Serie:</string>
-            <string name="signature_algorithm">Algoritmo di firma:</string>
-    <string name="permissions">Autorizzazioni</string>
-    <string name="privacy_policy">Privacy Policy</string>
-    <string name="changelog">Changelog</string>
-    <string name="licenses">Licenze</string>
-    <string name="contributors">Collaboratori</string>
-    <string name="links">Links</string>
-
     <!-- Ad Control. There are no ads in the standard flavor, but this string must exist because it is  referenced in the code. -->
     <string name="ad_consent">Consenso agli annunci</string>
 </resources>
index 4f45071..b1c5012 100644 (file)
     <string name="first_party_cookies_disabled">Первичные файлы cookie отключены</string>
     <string name="third_party_cookies_enabled">Сторонние файлы cookie включены</string>
     <string name="third_party_cookies_disabled">Сторонние файлы cookie отключены</string>
-    <string name="dom_storage_enabled">Хранилище DOM включено</string>
-    <string name="dom_storage_disabled">Хранилище DOM отключено</string>
+    <string name="dom_storage_enabled">DOM-хранилище включено</string>
+    <string name="dom_storage_disabled">DOM-хранилище отключено</string>
     <string name="form_data_enabled">Данные формы включены</string>
     <string name="form_data_disabled">Данные формы отключены</string>
     <string name="cookies_deleted">Файлы cookie удалены</string>
-    <string name="dom_storage_deleted">Хранилище DOM удалено</string>
+    <string name="dom_storage_deleted">DOM-хранилище удалено</string>
     <string name="form_data_deleted">Данные формы удалены</string>
     <string name="open_navigation_drawer">Открыть панель навигации</string>
     <string name="close_navigation_drawer">Закрыть панель навигации</string>
     <string name="edit_domain_settings">Изменение параметров домена</string>
     <string name="first_party_cookies">Первичные файлы cookie</string>
     <string name="third_party_cookies">Сторонние файлы cookie</string>
-    <string name="dom_storage">Хранилище DOM</string>
+    <string name="dom_storage">DOM-хранилище</string>
     <string name="form_data">Данные формы</string>
     <string name="clear_data">Очистить данные</string>
         <string name="clear_cookies">Очистить cookie</string>
-        <string name="clear_dom_storage"> Очистить хранилище DOM</string>
+        <string name="clear_dom_storage"> Очистить DOM-хранилище</string>
         <string name="clear_form_data">Очистить данные формы</string>
     <string name="options_fanboys_annoyance_list">Fanboy’s annoyance list</string>
     <string name="options_fanboys_social_blocking_list">Fanboy’s social blocking list</string>
     <string name="load_an_encrypted_website">Откройте зашифрованный сайт перед настройкой домена, чтобы заполнить текущий сертификат SSL веб-сайта.</string>
 
     <!-- Import/Export. -->
+    <string name="encryption">Шифрование</string>
+    <string-array name="encryption_type">
+        <item>Нет</item>
+        <item>Пароль</item>
+        <item>OpenPGP</item>
+    </string-array>
+    <string name="kitkat_password_encryption_message">Шифрование паролем не работает на Android KitKat.</string>
+    <string name="openkeychain_required">Для использования шифрования OpenPGP необходимо приложение OpenKeychain.</string>
+    <string name="openkeychain_import_instructions">Незашифрованный файл должен быть импортирован на отдельном шаге после его дешифрования.</string>
+    <string name="file_location">Расположение файла</string>
     <string name="browse">Обзор</string>
     <string name="export">Экспорт</string>
     <string name="import_button">Импорт</string>  <!-- `import` is a reserved word and cannot be used as the name -->
+    <string name="decrypt">Расшифровать</string>
     <string name="export_successful">Экспорт выполнен.</string>
     <string name="export_failed">Сбой при экспорте:</string>
     <string name="import_failed">Сбой при импорте:</string>
     <string name="ssl_certificates">Сертификаты SSL</string>
     <string name="tracking_ids">Идентификаторы отслеживания</string>
 
+    <!-- Download Location -->
+    <string name="download_location">Папка загрузок</string>
+    <string name="download_location_message">Privacy Browser необходимо разрешение на доступ к хранилищу для использования общей папки загрузок.
+        Если разрешение получено не будет, то для загрузок будет использоваться папка приложения</string>
+    <string name="ok">OK</string>
+
+    <!-- Orbot. -->
+    <string name="orbot_proxy_not_installed">Проксирование Orbot работает только с установленным Orbot.</string>
+    <string name="waiting_for_orbot">Ожидание Orbot для подключения...</string>
+
+    <!-- About Activity. -->
+    <string name="about_privacy_browser">О Privacy Browser</string>
+    <string name="version">Версия</string>
+    <string name="version_code">код версии</string>
+    <string name="hardware">Оборудование</string>
+    <string name="brand">Бренд:</string>
+    <string name="manufacturer">Производитель:</string>
+    <string name="model">Модель:</string>
+    <string name="device">Устройство:</string>
+    <string name="bootloader">Загрузчик:</string>
+    <string name="radio">Радио:</string>
+    <string name="software">Программное обеспечение</string>
+    <string name="android">Android:</string>
+    <string name="api">API</string>
+    <string name="build">Сборка:</string>
+    <string name="security_patch">Патч безопасности:</string>
+    <string name="webview">WebView:</string>
+    <string name="orbot">Orbot:</string>
+    <string name="openkeychain">OpenKeychain:</string>
+    <string name="easylist_label">EasyList:</string>
+    <string name="easyprivacy_label">EasyPrivacy:</string>
+    <string name="fanboy_annoyance_label">Fanboy’s Annoyance List:</string>
+    <string name="fanboy_social_label">Fanboy’s Social Blocking List:</string>
+    <string name="ultraprivacy_label">UltraPrivacy:</string>
+    <string name="package_signature">Подпись пакета</string>
+    <string name="issuer_dn">DN эмитента:</string>
+    <string name="subject_dn">DN субъекта:</string>
+    <string name="certificate_version">Версия сертификата:</string>
+    <string name="serial_number">Серийный номер:</string>
+    <string name="signature_algorithm">Алгоритм подписи:</string>
+    <string name="permissions">Разрешения</string>
+    <string name="privacy_policy">Политика конфиденциальности</string>
+    <string name="changelog">История изменений</string>
+    <string name="licenses">Лицензии</string>
+    <string name="contributors">Благодарности</string>
+    <string name="links">Ссылки</string>
+
     <!-- Preferences. -->
     <string name="privacy">Конфиденциальность</string>
         <string name="javascript_preference">Включить JavaScript по умолчанию</string>
         <string name="first_party_cookies_preference_summary">На устройствах с версией Android старше Lollipop (версия 5.0) этим параметром также активируются сторонние файлы cookie.</string>
         <string name="third_party_cookies_preference">Включить сторонние файлы cookie по умолчанию</string>
         <string name="third_party_cookies_summary">Этот параметр требует Android Lollipop (версия 5.0) или выше. Он не действует, если первичные файлы cookie отключены.</string>
-        <string name="dom_storage_preference">Включить хранилище DOM по умолчанию</string>
+        <string name="dom_storage_preference">Включить DOM-хранилище по умолчанию</string>
         <string name="dom_storage_preference_summary">Для работы хранилища DOM должен быть включен JavaScript.</string>
         <string name="save_form_data_preference">Разрешить сохранение данных формы по умолчанию</string>
         <string name="save_form_data_preference_summary">Сохраненные данные формы могут автоматически заполнять поля на веб-сайтах.</string>
         <string name="translucent_navigation_bar">Полупрозрачная навигационная панель</string>
         <string name="translucent_navigation_bar_summary">Панель навигации станет полупрозрачной в полноэкранном режиме просмотра.</string>
     <string name="clear_everything">Очистить все</string>
-        <string name="clear_everything_summary">Очищает файлы cookie, хранилище DOM, данные формы и кэш WebView. Затем вручную удаляются все каталоги "app_webview" и "cache".</string>
+        <string name="clear_everything_summary">Очищает файлы cookie, DOM-хранилище, данные формы и кэш WebView. Затем вручную удаляются все каталоги "app_webview" и "cache".</string>
         <string name="clear_cookies_preference">Очистить файлы cookie</string>
         <string name="clear_cookies_summary">Очистить первичные и сторонние файлы cookie.</string>
-        <string name="clear_dom_storage_preference">Очистить хранилище DOM</string>
-        <string name="clear_dom_storage_summary">Очищает хранилище DOM.</string>
+        <string name="clear_dom_storage_preference">Очистить DOM-хранилище</string>
+        <string name="clear_dom_storage_summary">Очищает DOM-хранилище.</string>
         <string name="clear_form_data_preference">Очистка данных формы</string>
         <string name="clear_form_data_summary">Очищает данные формы.</string>
         <string name="clear_cache">Очистить кэш</string>
                 <item>175%</item>
                 <item>200%</item>
             </string-array>
-        <string name="swipe_to_refresh_preference">Потянуть для обновления</string>
+        <string name="swipe_to_refresh">Потянуть для обновления</string>
         <string name="swipe_to_refresh_summary">Некоторые веб-сайты могут работать некорректно при включении данной опции.</string>
         <string name="display_additional_app_bar_icons">Отображать дополнительные значки на панели приложения</string>
         <string name="display_additional_app_bar_icons_summary">Отображать значки на панели приложения для обновления WebView и, при наличии места, для переключения файлов cookie и хранилища DOM</string>
         <string name="display_webpage_images">Показывать изображения веб-страницы</string>
         <string name="display_webpage_images_summary">Отключите для экономии трафика.</string>
 
-    <!-- Download Location -->
-    <string name="download_location">Папка загрузок</string>
-    <string name="download_location_message">Privacy Browser необходимо разрешение на доступ к хранилищу для использования общей папки загрузок.
-        Если разрешение получено не будет, то для загрузок будет использоваться папка приложения</string>
-    <string name="ok">OK</string>
-
-    <!-- Orbot. -->
-    <string name="orbot_proxy_not_installed">Проксирование Orbot работает только с установленным Orbot.</string>
-    <string name="waiting_for_orbot">Ожидание Orbot для подключения...</string>
-
-    <!-- About Activity. -->
-    <string name="about_privacy_browser">О Privacy Browser</string>
-    <string name="version">Версия</string>
-        <string name="version_code">код версии</string>
-        <string name="hardware">Оборудование</string>
-            <string name="brand">Бренд:</string>
-            <string name="manufacturer">Производитель:</string>
-            <string name="model">Модель:</string>
-            <string name="device">Устройство:</string>
-            <string name="bootloader">Загрузчик:</string>
-            <string name="radio">Радио:</string>
-        <string name="software">Программное обеспечение</string>
-            <string name="android">Android:</string>
-                <string name="api">API</string>
-            <string name="build">Сборка:</string>
-            <string name="security_patch">Патч безопасности:</string>
-            <string name="webview">WebView:</string>
-            <string name="orbot">Orbot:</string>
-            <string name="easylist_label">EasyList:</string>
-            <string name="easyprivacy_label">EasyPrivacy:</string>
-            <string name="fanboy_annoyance_label">Fanboy’s Annoyance List:</string>
-            <string name="fanboy_social_label">Fanboy’s Social Blocking List:</string>
-            <string name="ultraprivacy_label">UltraPrivacy:</string>
-        <string name="package_signature">Подпись пакета</string>
-            <string name="issuer_dn">DN эмитента:</string>
-            <string name="subject_dn">DN субъекта:</string>
-            <string name="certificate_version">Версия сертификата:</string>
-            <string name="serial_number">Серийный номер:</string>
-            <string name="signature_algorithm">Алгоритм подписи:</string>
-    <string name="permissions">Разрешения</string>
-    <string name="privacy_policy">Политика конфиденциальности</string>
-    <string name="changelog">История изменений</string>
-    <string name="licenses">Лицензии</string>
-    <string name="contributors">Благодарности</string>
-    <string name="links">Ссылки</string>
-
     <!-- Ad Control. There are no ads in the standard flavor, but this string must exist because it is referenced in the code. -->
     <string name="ad_consent">Согласие на рекламу</string>
 </resources>
index 3018e97..3db04d1 100644 (file)
@@ -51,6 +51,7 @@
     <string name="close_navigation_drawer">Close Navigation Drawer</string>
     <string name="no_title">No title</string>
     <string name="unrecognized_url">Unrecognized URL:</string>
+    <string name="open_with">Open with</string>
 
     <!-- Save As. -->
     <string name="save_as">Save As</string>
     <string name="ssl_certificates">SSL Certificates</string>
     <string name="tracking_ids">Tracking IDs</string>
 
+    <!-- Download Location -->
+    <string name="download_location">Download Location</string>
+    <string name="download_location_message">Privacy Browser needs the storage permission to use the public download directory. If it is denied, the app’s download directory will be used instead.</string>
+    <string name="ok">OK</string>
+
+    <!-- Orbot. -->
+    <string name="orbot_proxy_not_installed">Orbot proxy will not work unless Orbot is installed.</string>
+    <string name="waiting_for_orbot">Waiting for Orbot to connect...</string>
+
+    <!-- About Activity. -->
+    <string name="about_privacy_browser">About Privacy Browser</string>
+    <string name="version">Version</string>
+    <string name="version_code">version code</string>
+    <string name="hardware">Hardware</string>
+    <string name="brand">Brand:</string>
+    <string name="manufacturer">Manufacturer:</string>
+    <string name="model">Model:</string>
+    <string name="device">Device:</string>
+    <string name="bootloader">Bootloader:</string>
+    <string name="radio">Radio:</string>
+    <string name="software">Software</string>
+    <string name="android">Android:</string>
+    <string name="api">API</string>
+    <string name="build">Build:</string>
+    <string name="security_patch">Security Patch:</string>
+    <string name="webview">WebView:</string>
+    <string name="orbot">Orbot:</string>
+    <string name="openkeychain">OpenKeychain:</string>
+    <string name="easylist_label">EasyList:</string>
+    <string name="easyprivacy_label">EasyPrivacy:</string>
+    <string name="fanboy_annoyance_label">Fanboy’s Annoyance List:</string>
+    <string name="fanboy_social_label">Fanboy’s Social Blocking List:</string>
+    <string name="ultraprivacy_label">UltraPrivacy:</string>
+    <string name="package_signature">Package Signature</string>
+    <string name="issuer_dn">Issuer DN:</string>
+    <string name="subject_dn">Subject DN:</string>
+    <string name="certificate_version">Certificate Version:</string>
+    <string name="serial_number">Serial Number:</string>
+    <string name="signature_algorithm">Signature Algorithm:</string>
+    <string name="permissions">Permissions</string>
+    <string name="privacy_policy">Privacy Policy</string>
+    <string name="changelog">Changelog</string>
+    <string name="licenses">Licenses</string>
+    <string name="contributors">Contributors</string>
+    <string name="links">Links</string>
+
     <!-- Preferences. -->
     <string name="privacy">Privacy</string>
         <string name="javascript_preference">Enable JavaScript by default</string>
                 <item>175</item>
                 <item>200</item>
             </string-array>
-        <string name="swipe_to_refresh_preference">Swipe to refresh</string>
+        <string name="swipe_to_refresh">Swipe to refresh</string>
         <string name="swipe_to_refresh_summary">Some websites don’t work well if swipe to refresh is enabled.</string>
+        <string name="download_with_external_app">Download with external app</string>
+        <string name="download_with_external_app_summary">Android’s download manager doesn’t work well on some devices.</string>
         <string name="display_additional_app_bar_icons">Display additional app bar icons</string>
         <string name="display_additional_app_bar_icons_summary">Display icons in the app bar for refreshing the WebView and, if there is room, for toggling cookies and DOM storage.</string>
         <string name="dark_theme">Dark theme</string>
         <string name="display_webpage_images">Display webpage images</string>
         <string name="display_webpage_images_summary">Disable to conserve bandwidth.</string>
 
-    <!-- Download Location -->
-    <string name="download_location">Download Location</string>
-    <string name="download_location_message">Privacy Browser needs the storage permission to use the public download directory. If it is denied, the app’s download directory will be used instead.</string>
-    <string name="ok">OK</string>
-
-    <!-- Orbot. -->
-    <string name="orbot_proxy_not_installed">Orbot proxy will not work unless Orbot is installed.</string>
-    <string name="waiting_for_orbot">Waiting for Orbot to connect...</string>
-
-    <!-- About Activity. -->
-    <string name="about_privacy_browser">About Privacy Browser</string>
-    <string name="version">Version</string>
-        <string name="version_code">version code</string>
-        <string name="hardware">Hardware</string>
-            <string name="brand">Brand:</string>
-            <string name="manufacturer">Manufacturer:</string>
-            <string name="model">Model:</string>
-            <string name="device">Device:</string>
-            <string name="bootloader">Bootloader:</string>
-            <string name="radio">Radio:</string>
-        <string name="software">Software</string>
-            <string name="android">Android:</string>
-                <string name="api">API</string>
-            <string name="build">Build:</string>
-            <string name="security_patch">Security Patch:</string>
-            <string name="webview">WebView:</string>
-            <string name="orbot">Orbot:</string>
-            <string name="open_keychain">OpenKeychain:</string>
-            <string name="easylist_label">EasyList:</string>
-            <string name="easyprivacy_label">EasyPrivacy:</string>
-            <string name="fanboy_annoyance_label">Fanboy’s Annoyance List:</string>
-            <string name="fanboy_social_label">Fanboy’s Social Blocking List:</string>
-            <string name="ultraprivacy_label">UltraPrivacy:</string>
-        <string name="package_signature">Package Signature</string>
-            <string name="issuer_dn">Issuer DN:</string>
-            <string name="subject_dn">Subject DN:</string>
-            <string name="certificate_version">Certificate Version:</string>
-            <string name="serial_number">Serial Number:</string>
-            <string name="signature_algorithm">Signature Algorithm:</string>
-    <string name="permissions">Permissions</string>
-    <string name="privacy_policy">Privacy Policy</string>
-    <string name="changelog">Changelog</string>
-    <string name="licenses">Licenses</string>
-    <string name="contributors">Contributors</string>
-    <string name="links">Links</string>
+    <!-- Non-translatable preference items. -->
+    <string name="user_agent_default_value" translatable="false">Privacy Browser</string>
+    <string name="custom_user_agent_default_value" translatable="false">PrivacyBrowser/1.0</string>
+    <string name="tor_homepage_default_value" translatable="false">http://ulrn6sryqaifefld.onion/</string>
+    <string name="tor_search_default_value" translatable="false">http://ulrn6sryqaifefld.onion/?q=</string>
+    <string name="tor_search_custom_url_default_value" translatable="false" />
+    <string name="search_default_value" translatable="false">https://searx.me/?q=</string>
+    <string name="search_custom_url_default_value" translatable="false" />
+    <string name="homepage_default_value" translatable="false">https://searx.me/</string>
+    <string name="font_size_default_value" translatable="false">100</string>
 
     <!-- Ad Control. There are no ads in the standard flavor, but these strings must exist because they are referenced in the code. -->
     <string name="google_app_id" translatable="false">Null</string>
index ecf9409..7741cec 100644 (file)
             android:title="@string/user_agent"
             android:entries="@array/translated_user_agent_names"
             android:entryValues="@array/user_agent_names"
-            android:defaultValue="Privacy Browser"
+            android:defaultValue="@string/user_agent_default_value"
             android:icon="?attr/userAgentIcon" />
 
         <!-- android:inputType="textVisiblePassword" sets the keyboard to have a dedicated number row.-->
         <EditTextPreference
             android:key="custom_user_agent"
             android:title="@string/custom_user_agent"
-            android:defaultValue="PrivacyBrowser/1.0"
+            android:defaultValue="@string/custom_user_agent_default_value"
             android:inputType="textVisiblePassword|textMultiLine" />
 
         <SwitchPreference
         <EditTextPreference
             android:key="tor_homepage"
             android:title="@string/tor_homepage"
-            android:defaultValue="http://ulrn6sryqaifefld.onion/"
+            android:defaultValue="@string/tor_homepage_default_value"
             android:inputType="textUri" />
 
         <ListPreference
             android:title="@string/tor_search"
             android:entries="@array/tor_search_entries"
             android:entryValues="@array/tor_search_entry_values"
-            android:defaultValue="http://ulrn6sryqaifefld.onion/?q=" />
+            android:defaultValue="@string/tor_search_default_value" />
 
-        <!--suppress AndroidDomInspection -->
-        <!--`suppress AndroidDomInspection` removes the error about the default value being blank. -->
         <EditTextPreference
             android:key="tor_search_custom_url"
             android:title="@string/tor_search_custom_url"
-            android:defaultValue=""
+            android:defaultValue="@string/tor_search_custom_url_default_value"
             android:inputType="textUri" />
     </PreferenceCategory>
 
             android:title="@string/search"
             android:entries="@array/search_entries"
             android:entryValues="@array/search_entry_values"
-            android:defaultValue="https://searx.me/?q="
+            android:defaultValue="@string/search_default_value"
             android:icon="?attr/searchIcon" />
 
-        <!--suppress AndroidDomInspection -->
-        <!--`suppress AndroidDomInspection` removes the error about the default value being blank. -->
         <EditTextPreference
             android:key="search_custom_url"
             android:title="@string/search_custom_url"
-            android:defaultValue=""
+            android:defaultValue="@string/search_custom_url_default_value"
             android:inputType="textUri" />
     </PreferenceCategory>
 
             android:summary="@string/clear_dom_storage_summary"
             android:defaultValue="true" />
 
+        <!-- Clear form data can be removed once the minimum API >= 26. -->
         <SwitchPreference
             android:key="clear_form_data"
             android:title="@string/clear_form_data_preference"
         <EditTextPreference
             android:key="homepage"
             android:title="@string/homepage"
-            android:defaultValue="https://searx.me/"
+            android:defaultValue="@string/homepage_default_value"
             android:inputType="textUri"
             android:icon="?attr/homepageIcon" />
 
             android:title="@string/default_font_size"
             android:entries="@array/default_font_size_entries"
             android:entryValues="@array/default_font_size_entry_values"
-            android:defaultValue="100"
+            android:defaultValue="@string/font_size_default_value"
             android:icon="?attr/fontSizeIcon" />
 
         <SwitchPreference
             android:key="swipe_to_refresh"
-            android:title="@string/swipe_to_refresh_preference"
+            android:title="@string/swipe_to_refresh"
             android:summary="@string/swipe_to_refresh_summary"
             android:defaultValue="true" />
 
+        <SwitchPreference
+            android:key="download_with_external_app"
+            android:title="@string/download_with_external_app"
+            android:summary="@string/download_with_external_app_summary"
+            android:defaultValue="false" />
+
         <SwitchPreference
             android:key="display_additional_app_bar_icons"
             android:title="@string/display_additional_app_bar_icons"