From 4ecc3c256e439e7779d1db4d84800a6b2f9b13e6 Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Fri, 7 Dec 2018 16:48:14 -0700 Subject: [PATCH] Add option to download with external program. https://redmine.stoutner.com/issues/333 --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 12 +- .../main/assets/ru/guide_bookmarks_light.html | 2 +- .../assets/ru/guide_domain_settings_dark.html | 2 +- .../ru/guide_domain_settings_light.html | 2 +- .../main/assets/ru/guide_javascript_dark.html | 6 +- .../assets/ru/guide_javascript_light.html | 6 +- .../assets/ru/guide_local_storage_dark.html | 42 +-- .../assets/ru/guide_local_storage_light.html | 42 +-- .../activities/MainWebViewActivity.java | 316 +++++++++--------- .../fragments/AboutTabFragment.java | 12 +- .../fragments/DomainSettingsFragment.java | 30 +- .../fragments/SettingsFragment.java | 71 ++-- .../helpers/ImportExportDatabaseHelper.java | 223 +++++++----- .../open_with_external_app_disabled_dark.xml | 26 ++ .../open_with_external_app_disabled_light.xml | 26 ++ ...> open_with_external_app_enabled_dark.xml} | 6 +- .../open_with_external_app_enabled_light.xml | 26 ++ .../res/layout/domain_settings_fragment.xml | 2 +- .../main/res/menu/webview_navigation_menu.xml | 2 +- app/src/main/res/values-de/strings.xml | 90 ++--- app/src/main/res/values-es/strings.xml | 106 +++--- app/src/main/res/values-it/strings.xml | 106 +++--- app/src/main/res/values-ru/strings.xml | 124 +++---- app/src/main/res/values/strings.xml | 106 +++--- app/src/main/res/xml/preferences.xml | 31 +- 26 files changed, 835 insertions(+), 584 deletions(-) create mode 100644 app/src/main/res/drawable/open_with_external_app_disabled_dark.xml create mode 100644 app/src/main/res/drawable/open_with_external_app_disabled_light.xml rename app/src/main/res/drawable/{clear_and_exit.xml => open_with_external_app_enabled_dark.xml} (82%) create mode 100644 app/src/main/res/drawable/open_with_external_app_enabled_light.xml diff --git a/app/build.gradle b/app/build.gradle index 436e4e1f..81da8f69 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c957349d..2acbca23 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -98,14 +98,24 @@ - + + + + + + + + + + + diff --git a/app/src/main/assets/ru/guide_bookmarks_light.html b/app/src/main/assets/ru/guide_bookmarks_light.html index 3637b3fc..51f9685c 100644 --- a/app/src/main/assets/ru/guide_bookmarks_light.html +++ b/app/src/main/assets/ru/guide_bookmarks_light.html @@ -24,7 +24,7 @@ -

Закладки

+

Закладки

К закладкам можно получить доступ из боковой панели, выполнив свайп от правой части экрана.

diff --git a/app/src/main/assets/ru/guide_domain_settings_dark.html b/app/src/main/assets/ru/guide_domain_settings_dark.html index 43088ad0..84f60471 100644 --- a/app/src/main/assets/ru/guide_domain_settings_dark.html +++ b/app/src/main/assets/ru/guide_domain_settings_dark.html @@ -26,7 +26,7 @@

Безопасный просмотр веб-страниц

-

По умолчанию в Privacy Browser отключены JavaScript, файлы cookie и хранилище DOM. Однако, для правильной работы, некоторым веб-сайтам эти опции необходимы. +

По умолчанию в Privacy Browser отключены JavaScript, файлы cookie и DOM-хранилище. Однако, для правильной работы, некоторым веб-сайтам эти опции необходимы. Настройки домена могут автоматически включать нужный набор опций при посещении определенного домена.

diff --git a/app/src/main/assets/ru/guide_domain_settings_light.html b/app/src/main/assets/ru/guide_domain_settings_light.html index 028f76d7..26f4bd50 100644 --- a/app/src/main/assets/ru/guide_domain_settings_light.html +++ b/app/src/main/assets/ru/guide_domain_settings_light.html @@ -26,7 +26,7 @@

Безопасный просмотр веб-страниц

-

По умолчанию в Privacy Browser отключены JavaScript, файлы cookie и хранилище DOM. Однако, для правильной работы, некоторым веб-сайтам эти опции необходимы. +

По умолчанию в Privacy Browser отключены JavaScript, файлы cookie и DOM-хранилище. Однако, для правильной работы, некоторым веб-сайтам эти опции необходимы. Настройки домена могут автоматически включать нужный набор опций при посещении определенного домена.

diff --git a/app/src/main/assets/ru/guide_javascript_dark.html b/app/src/main/assets/ru/guide_javascript_dark.html index 69cf9d09..e09b77bd 100644 --- a/app/src/main/assets/ru/guide_javascript_dark.html +++ b/app/src/main/assets/ru/guide_javascript_dark.html @@ -51,8 +51,8 @@ цветом (оба указывают на то, что JavaScript отключен) и красным (JavaScript включен). Можете просмотреть информацию на сайте webkay, которую можно собрать с включенным и отключенным JavaScript. -

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.

+

Просмотр сайтов с отключенным JavaScript и включение его только в случае необходимости, значительно повышает конфиденциальность пользователей. + Кроме того, JavaScript используется для загрузки большей части раздражающей рекламы, а также хлама, который отправляется на устройства с большинства современных веб-сайтов. + После его отключения веб-сайты будут загружаться быстрее, потреблять меньше сетевого трафика и меньше нагружать процессор, что приведет к увеличению времени автономной работы.

\ No newline at end of file diff --git a/app/src/main/assets/ru/guide_javascript_light.html b/app/src/main/assets/ru/guide_javascript_light.html index e3edac3d..26bbc0cc 100644 --- a/app/src/main/assets/ru/guide_javascript_light.html +++ b/app/src/main/assets/ru/guide_javascript_light.html @@ -51,8 +51,8 @@ цветом (оба указывают на то, что JavaScript отключен) и красным (JavaScript включен). Можете просмотреть информацию на сайте webkay, которую можно собрать с включенным и отключенным JavaScript. -

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.

+

Просмотр сайтов с отключенным JavaScript и включение его только в случае необходимости, значительно повышает конфиденциальность пользователей. + Кроме того, JavaScript используется для загрузки большей части раздражающей рекламы, а также хлама, который отправляется на устройства с большинства современных веб-сайтов. + После его отключения веб-сайты будут загружаться быстрее, потреблять меньше сетевого трафика и меньше нагружать процессор, что приведет к увеличению времени автономной работы.

\ No newline at end of file diff --git a/app/src/main/assets/ru/guide_local_storage_dark.html b/app/src/main/assets/ru/guide_local_storage_dark.html index 4ac2ba0a..bd073f74 100644 --- a/app/src/main/assets/ru/guide_local_storage_dark.html +++ b/app/src/main/assets/ru/guide_local_storage_dark.html @@ -28,17 +28,17 @@

Первичные файлы cookie устанавливаются тем веб-сайтом, который указан в строке URL.

-

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.

+

С первых дней интернета стало очевидным, что веб-сайтам было бы выгодно иметь возможность хранить информацию на компьютере для последующего доступа к ней. + Например, веб-сайт, предоставляющий информацию о погоде, может запросить у пользователя название города, а затем сохранить его в файле cookie. + При следующем посещении веб-сайта информация о погоде будет автоматически загружена для этого города, без необходимости вводить его снова.

-

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.

+

Как и со всем остальным в интернете, умные люди выяснили все способы злоупотребления cookie, чтобы делать то, что пользователи не одобрят, если узнают что именно происходит. + Например, веб-сайт может установить файл cookie на устройстве с уникальным номером. + Затем каждый раз, когда пользователь посещает веб-сайт с этого устройства, он может быть связан с уникальным профилем, который сервер хранит для этого номера, + даже если устройство подключается с разных IP-адресов.

-

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.

+

Почти все веб-сайты с формами авторизации требуют, чтобы для входа в систему у пользователя были включены первичные файлы cookie. + Именно так они убеждаются, что это все еще вы, когда вы переходите со страницы на страницу на сайте, и, на мой взгляд, это один из немногих законных способов использования файлов cookie.

Если первичные файлы cookie включены, но JavaScript отключен, значок конфиденциальности будет желтым как предупреждение.

@@ -68,24 +68,24 @@ Со временем такие компании, как Facebook (который также запустил рекламную сеть), создали довольно большое количество подробных профилей о людях, у которых даже не было аккаунта на сайте социальной сети.

-

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 - differentiate - between first-party and third-party cookies. Thus, enabling first-party cookies will also enable third-party cookies.

+

Нет ни одной веской причины когда-либо разрешать сторонние файлы cookie. На устройствах с Android KitKat или старше (версия <= 4.4.4 или API <= 20), WebView не + различает первичные и сторонние файлы cookie. + Таким образом, включение первичных файлов cookie также разрешит и сторонние.

-

Хранилище DOM

+

DOM-хранилище

-

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 megabytes per site. - Because DOM storage uses JavaScript to read and write data, it cannot be enabled unless JavaScript is also enabled.

+

Хранилище объектной модели документа (Document Object Model), также известное как веб-хранилище, похоже на cookie (печенье) на стероидах. + Если максимальный общий размер хранилища для всех файлов cookie с одного URL-адреса составляет 4 килобайта, + то DOM-хранилище вмещает в себя мегабайты на сайт. + Поскольку DOM-хранилище использует JavaScript для чтения и записи данных, включение его ни на что не влияет пока отключен JavaScript.

Данные формы

-

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 Autofill service. - As such, controls for form data no longer appear on newer Android devices.

+

Данные формы содержат информацию, введенную в веб-формы, например имена пользователей, адреса, номера телефонов и т.д., и доступную в раскрывающемся списке при будущих посещениях. + В отличие от других форм локального хранилища данные формы не отправляются на веб-сервер без специального взаимодействия с пользователем. + Начиная с Android Oreo (8.0), данные формы WebView были заменены на службу автозаполнения. + Таким образом, элементы управления данными формы больше не отображаются на новых устройствах Android.

\ No newline at end of file diff --git a/app/src/main/assets/ru/guide_local_storage_light.html b/app/src/main/assets/ru/guide_local_storage_light.html index 4a22fdf0..14579020 100644 --- a/app/src/main/assets/ru/guide_local_storage_light.html +++ b/app/src/main/assets/ru/guide_local_storage_light.html @@ -28,17 +28,17 @@

Первичные файлы cookie устанавливаются тем веб-сайтом, который указан в строке URL.

-

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.

+

С первых дней интернета стало очевидным, что веб-сайтам было бы выгодно иметь возможность хранить информацию на компьютере для последующего доступа к ней. + Например, веб-сайт, предоставляющий информацию о погоде, может запросить у пользователя название города, а затем сохранить его в файле cookie. + При следующем посещении веб-сайта информация о погоде будет автоматически загружена для этого города, без необходимости вводить его снова.

-

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.

+

Как и со всем остальным в интернете, умные люди выяснили все способы злоупотребления cookie, чтобы делать то, что пользователи не одобрят, если узнают что именно происходит. + Например, веб-сайт может установить файл cookie на устройстве с уникальным номером. + Затем каждый раз, когда пользователь посещает веб-сайт с этого устройства, он может быть связан с уникальным профилем, который сервер хранит для этого номера, + даже если устройство подключается с разных IP-адресов.

-

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.

+

Почти все веб-сайты с формами авторизации требуют, чтобы для входа в систему у пользователя были включены первичные файлы cookie. + Именно так они убеждаются, что это все еще вы, когда вы переходите со страницы на страницу на сайте, и, на мой взгляд, это один из немногих законных способов использования файлов cookie.

Если первичные файлы cookie включены, но JavaScript отключен, значок конфиденциальности будет желтым как предупреждение.

@@ -68,24 +68,24 @@ Со временем такие компании, как Facebook (который также запустил рекламную сеть), создали довольно большое количество подробных профилей о людях, у которых даже не было аккаунта на сайте социальной сети.

-

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 - differentiate - between first-party and third-party cookies. Thus, enabling first-party cookies will also enable third-party cookies.

+

Нет ни одной веской причины когда-либо разрешать сторонние файлы cookie. На устройствах с Android KitKat или старше (версия <= 4.4.4 или API <= 20), WebView не + различает первичные и сторонние файлы cookie. + Таким образом, включение первичных файлов cookie также разрешит и сторонние.

-

Хранилище DOM

+

DOM-хранилище

-

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 megabytes per site. - Because DOM storage uses JavaScript to read and write data, it cannot be enabled unless JavaScript is also enabled.

+

Хранилище объектной модели документа (Document Object Model), также известное как веб-хранилище, похоже на cookie (печенье) на стероидах. + Если максимальный общий размер хранилища для всех файлов cookie с одного URL-адреса составляет 4 килобайта, + то DOM-хранилище вмещает в себя мегабайты на сайт. + Поскольку DOM-хранилище использует JavaScript для чтения и записи данных, включение его ни на что не влияет пока отключен JavaScript.

Данные формы

-

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 Autofill service. - As such, controls for form data no longer appear on newer Android devices.

+

Данные формы содержат информацию, введенную в веб-формы, например имена пользователей, адреса, номера телефонов и т.д., и доступную в раскрывающемся списке при будущих посещениях. + В отличие от других форм локального хранилища данные формы не отправляются на веб-сервер без специального взаимодействия с пользователем. + Начиная с Android Oreo (8.0), данные формы WebView были заменены на службу автозаполнения. + Таким образом, элементы управления данными формы больше не отображаются на новых устройствах Android.

\ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index d3f0d016..03bf85ee 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -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(); diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTabFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTabFragment.java index 9f627b6c..2f206fe0 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTabFragment.java +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTabFragment.java @@ -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); diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java index 76dacba6..ad6653c5 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java @@ -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)); diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java index 65b18bae..210018cb 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java @@ -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)) { diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportDatabaseHelper.java b/app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportDatabaseHelper.java index 33887008..aea16827 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportDatabaseHelper.java +++ b/app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportDatabaseHelper.java @@ -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/open_with_external_app_disabled_dark.xml b/app/src/main/res/drawable/open_with_external_app_disabled_dark.xml new file mode 100644 index 00000000..478ad682 --- /dev/null +++ b/app/src/main/res/drawable/open_with_external_app_disabled_dark.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + \ 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 index 00000000..24af6a55 --- /dev/null +++ b/app/src/main/res/drawable/open_with_external_app_disabled_light.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/clear_and_exit.xml b/app/src/main/res/drawable/open_with_external_app_enabled_dark.xml similarity index 82% rename from app/src/main/res/drawable/clear_and_exit.xml rename to app/src/main/res/drawable/open_with_external_app_enabled_dark.xml index 54617499..c07cc724 100644 --- a/app/src/main/res/drawable/clear_and_exit.xml +++ b/app/src/main/res/drawable/open_with_external_app_enabled_dark.xml @@ -1,4 +1,4 @@ - @@ -14,13 +14,13 @@ \ 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 index 00000000..eda4a6e8 --- /dev/null +++ b/app/src/main/res/drawable/open_with_external_app_enabled_light.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/domain_settings_fragment.xml b/app/src/main/res/layout/domain_settings_fragment.xml index df08c91c..f2c8fb64 100644 --- a/app/src/main/res/layout/domain_settings_fragment.xml +++ b/app/src/main/res/layout/domain_settings_fragment.xml @@ -488,7 +488,7 @@ 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" /> \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8e789628..b24512a6 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -274,6 +274,50 @@ SSL-Zertifikate Verolgungs-IDs + + Download-Zielordner + Privacy Browser benötigt die Berechtigung zur Speicherung im öffentlichen Download-Ordner. Anderenfalls wird der Download-Ordner der App verwendet. + OK + + + Orbot-Proxy wird nicht funktionieren, solange Orbot nicht installiert ist. + Warte, bis sich Orbot verbindet... + + + Über Privacy Browser + Version + Versions-Code + Hardware + Marke: + Hersteller: + Modell: + Gerät: + Bootloader: + Radio: + Software + Android: + API + Build: + Sicherheits-Patch: + WebView: + Orbot: + EasyList: + EasyPrivacy: + Fanboy’s Annoyance List: + Fanboy’s Social Blocking List: + Paket-Signatur + Aussteller-DN: + Subject DN: + Zertifikat-Version: + Seriennummer: + Signaturalgorithmus: + Berechtigungen + Datenschutzrichtlinie + Changelog + Lizenzen + Mitwirkende + Links + Privatsphäre JavaScript standardmäßig aktivieren @@ -362,54 +406,10 @@ 175% 200% - Herunterziehen zum Aktualisieren + Herunterziehen zum Aktualisieren Einige Websites funktionieren nicht, wenn "Herunterziehen zum Aktualisieren" eingeschaltet ist. Weitere Icons in der Titelleiste - - Download-Zielordner - Privacy Browser benötigt die Berechtigung zur Speicherung im öffentlichen Download-Ordner. Anderenfalls wird der Download-Ordner der App verwendet. - OK - - - Orbot-Proxy wird nicht funktionieren, solange Orbot nicht installiert ist. - Warte, bis sich Orbot verbindet... - - - Über Privacy Browser - Version - Versions-Code - Hardware - Marke: - Hersteller: - Modell: - Gerät: - Bootloader: - Radio: - Software - Android: - API - Build: - Sicherheits-Patch: - WebView: - Orbot: - EasyList: - EasyPrivacy: - Fanboy’s Annoyance List: - Fanboy’s Social Blocking List: - Paket-Signatur - Aussteller-DN: - Subject DN: - Zertifikat-Version: - Seriennummer: - Signaturalgorithmus: - Berechtigungen - Datenschutzrichtlinie - Changelog - Lizenzen - Mitwirkende - Links - Zustimmung zur Werbung \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 13daf274..aa3e10f8 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -314,9 +314,20 @@ 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. + Cifrado + + Ninguno + Contraseña + OpenPGP + + El cifrado de contraseñas no funciona en Android KitKat. + El cifrado OpenPGP requiere que esté instalado OpenKeychain. + El archivo sin cifrar tendrá que ser importado en un paso separado después de ser descifrado. + Ubicación del archivo Navegar Exportar Importar + Descifrar Exportación exitosa. Exportación fallida: Importación fallida: @@ -336,6 +347,53 @@ Certificados SSL Rastreo de IDs + + Lugar de descarga + 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. + OK + + + Enviar a través de Orbot no funcionará a menos que se instale Orbot. + Esperando a Orbot para conectar... + + + Acerca de Navegador Privado + Versión + código de versión + Hardware + Marca: + Fabricante: + Modelo: + Dispositivo: + Cargador de arranque: + Radio: + Software + Android: + API + Versión de compilación: + Parche de seguridad: + WebView: + Orbot: + OpenKeychain: + EasyList: + EasyPrivacy: + Lista molesta de Fanboy: + Lista de bloqueo social de Fanboy: + Ultra Privacidad: + Firma del paquete + DN del emisor: + DN del sujeto: + Versión del certificado: + Número de serie: + Algoritmo de firma: + Permisos + Política de privacidad + Historial de cambios + Licencias + Colaboradores + Enlaces + Privacidad Habilitar Javascript por defecto @@ -472,7 +530,7 @@ 175% 200% - Deslizar para actualizar + Deslizar para actualizar Algunas webs no funcionan bien si la opción deslizar para actualizar está habilitada. Mostrar iconos adicionales en la barra de aplicación Mostrar iconos en la barra de aplicaciones para refrescar el WebView y, si hay espacio, para alternar entre cookies y almacenamiento DOM. @@ -483,52 +541,6 @@ Mostrar imágenes de la página web Deshabilitar para conservar ancho de banda. - - Lugar de descarga - 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. - OK - - - Enviar a través de Orbot no funcionará a menos que se instale Orbot. - Esperando a Orbot para conectar... - - - Acerca de Navegador Privado - Versión - código de versión - Hardware - Marca: - Fabricante: - Modelo: - Dispositivo: - Cargador de arranque: - Radio: - Software - Android: - API - Versión de compilación: - Parche de seguridad: - WebView: - Orbot: - EasyList: - EasyPrivacy: - Lista molesta de Fanboy: - Lista de bloqueo social de Fanboy: - Ultra Privacidad: - Firma del paquete - DN del emisor: - DN del sujeto: - Versión del certificado: - Número de serie: - Algoritmo de firma: - Permisos - Política de privacidad - Historial de cambios - Licencias - Colaboradores - Enlaces - Autorización publicitaria \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index f7feb023..f3908404 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -313,9 +313,20 @@ Carica un sito Web criptato prima di aprire le impostazioni dei domini per popolare il certificato SSL del sito attuale. + Cifratura + + Nessuna + Password + OpenPGP + + La cifratura delle Password non funziona su Android KitKat. + La cifratura OpenPGP richiede l\'installazione di OpenKeychain. + Il file non cifrato deve essere importato in un secondo momento dopo che è stato decriptato. + Posizione del File Sfoglia Esporta Importa + Decripta Esportazione riuscita Esportazione fallita: Importazione fallita: @@ -335,6 +346,53 @@ Certificati SSL Tracciamento utenti + + Cartella di Download + 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. + OK + + + Il Proxy con Orbot funziona solo se è installato Orbot. + In attesa della connessione di Orbot... + + + Informazioni su Privacy Browser + Versione + codice versione + Hardware + Brand: + Costruttore: + Modello: + Dispositivo: + Bootloader: + Radio: + Software + Android: + API + Build: + Patch si sicurezza: + WebView: + Orbot: + OpenKeychain: + EasyList: + EasyPrivacy: + Fanboy’s Annoyance List: + Fanboy’s Social Blocking List: + UltraPrivacy: + Firma del Pacchetto + Emittente: + Soggetto: + Versione del Certificato: + Numero di Serie: + Algoritmo di firma: + Autorizzazioni + Privacy Policy + Changelog + Licenze + Collaboratori + Links + Privacy Abilita JavaScript @@ -470,7 +528,7 @@ 175% 200% - Swipe per aggiornare + Swipe per aggiornare Alcuni siti non funzionano correttamente se questa opzione è abilitata. Mostra icone addizionali nella barra dell\'applicazione Mostra nella barra dell\'applicazione le icone per l\'aggiornamento di WebView e, se lo spazio è sufficiente, @@ -482,52 +540,6 @@ Mostra immagini delle pagine web Disabilita per ridurre il consumo di dati. - - Cartella di Download - 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. - OK - - - Il Proxy con Orbot funziona solo se è installato Orbot. - In attesa della connessione di Orbot... - - - Informazioni su Privacy Browser - Versione - codice versione - Hardware - Brand: - Costruttore: - Modello: - Dispositivo: - Bootloader: - Radio: - Software - Android: - API - Build: - Patch si sicurezza: - WebView: - Orbot: - EasyList: - EasyPrivacy: - Fanboy’s Annoyance List: - Fanboy’s Social Blocking List: - UltraPrivacy: - Firma del Pacchetto - Emittente: - Soggetto: - Versione del Certificato: - Numero di Serie: - Algoritmo di firma: - Autorizzazioni - Privacy Policy - Changelog - Licenze - Collaboratori - Links - Consenso agli annunci diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 4f450713..b1c50124 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -37,12 +37,12 @@ Первичные файлы cookie отключены Сторонние файлы cookie включены Сторонние файлы cookie отключены - Хранилище DOM включено - Хранилище DOM отключено + DOM-хранилище включено + DOM-хранилище отключено Данные формы включены Данные формы отключены Файлы cookie удалены - Хранилище DOM удалено + DOM-хранилище удалено Данные формы удалены Открыть панель навигации Закрыть панель навигации @@ -126,11 +126,11 @@ Изменение параметров домена Первичные файлы cookie Сторонние файлы cookie - Хранилище DOM + DOM-хранилище Данные формы Очистить данные Очистить cookie - Очистить хранилище DOM + Очистить DOM-хранилище Очистить данные формы Fanboy’s annoyance list Fanboy’s social blocking list @@ -314,9 +314,20 @@ Откройте зашифрованный сайт перед настройкой домена, чтобы заполнить текущий сертификат SSL веб-сайта. + Шифрование + + Нет + Пароль + OpenPGP + + Шифрование паролем не работает на Android KitKat. + Для использования шифрования OpenPGP необходимо приложение OpenKeychain. + Незашифрованный файл должен быть импортирован на отдельном шаге после его дешифрования. + Расположение файла Обзор Экспорт Импорт + Расшифровать Экспорт выполнен. Сбой при экспорте: Сбой при импорте: @@ -334,6 +345,53 @@ Сертификаты SSL Идентификаторы отслеживания + + Папка загрузок + Privacy Browser необходимо разрешение на доступ к хранилищу для использования общей папки загрузок. + Если разрешение получено не будет, то для загрузок будет использоваться папка приложения + OK + + + Проксирование Orbot работает только с установленным Orbot. + Ожидание Orbot для подключения... + + + О Privacy Browser + Версия + код версии + Оборудование + Бренд: + Производитель: + Модель: + Устройство: + Загрузчик: + Радио: + Программное обеспечение + Android: + API + Сборка: + Патч безопасности: + WebView: + Orbot: + OpenKeychain: + EasyList: + EasyPrivacy: + Fanboy’s Annoyance List: + Fanboy’s Social Blocking List: + UltraPrivacy: + Подпись пакета + DN эмитента: + DN субъекта: + Версия сертификата: + Серийный номер: + Алгоритм подписи: + Разрешения + Политика конфиденциальности + История изменений + Лицензии + Благодарности + Ссылки + Конфиденциальность Включить JavaScript по умолчанию @@ -342,7 +400,7 @@ На устройствах с версией Android старше Lollipop (версия 5.0) этим параметром также активируются сторонние файлы cookie. Включить сторонние файлы cookie по умолчанию Этот параметр требует Android Lollipop (версия 5.0) или выше. Он не действует, если первичные файлы cookie отключены. - Включить хранилище DOM по умолчанию + Включить DOM-хранилище по умолчанию Для работы хранилища DOM должен быть включен JavaScript. Разрешить сохранение данных формы по умолчанию Сохраненные данные формы могут автоматически заполнять поля на веб-сайтах. @@ -434,11 +492,11 @@ Полупрозрачная навигационная панель Панель навигации станет полупрозрачной в полноэкранном режиме просмотра. Очистить все - Очищает файлы cookie, хранилище DOM, данные формы и кэш WebView. Затем вручную удаляются все каталоги "app_webview" и "cache". + Очищает файлы cookie, DOM-хранилище, данные формы и кэш WebView. Затем вручную удаляются все каталоги "app_webview" и "cache". Очистить файлы cookie Очистить первичные и сторонние файлы cookie. - Очистить хранилище DOM - Очищает хранилище DOM. + Очистить DOM-хранилище + Очищает DOM-хранилище. Очистка данных формы Очищает данные формы. Очистить кэш @@ -467,7 +525,7 @@ 175% 200% - Потянуть для обновления + Потянуть для обновления Некоторые веб-сайты могут работать некорректно при включении данной опции. Отображать дополнительные значки на панели приложения Отображать значки на панели приложения для обновления WebView и, при наличии места, для переключения файлов cookie и хранилища DOM @@ -478,52 +536,6 @@ Показывать изображения веб-страницы Отключите для экономии трафика. - - Папка загрузок - Privacy Browser необходимо разрешение на доступ к хранилищу для использования общей папки загрузок. - Если разрешение получено не будет, то для загрузок будет использоваться папка приложения - OK - - - Проксирование Orbot работает только с установленным Orbot. - Ожидание Orbot для подключения... - - - О Privacy Browser - Версия - код версии - Оборудование - Бренд: - Производитель: - Модель: - Устройство: - Загрузчик: - Радио: - Программное обеспечение - Android: - API - Сборка: - Патч безопасности: - WebView: - Orbot: - EasyList: - EasyPrivacy: - Fanboy’s Annoyance List: - Fanboy’s Social Blocking List: - UltraPrivacy: - Подпись пакета - DN эмитента: - DN субъекта: - Версия сертификата: - Серийный номер: - Алгоритм подписи: - Разрешения - Политика конфиденциальности - История изменений - Лицензии - Благодарности - Ссылки - Согласие на рекламу diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3018e971..3db04d17 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -51,6 +51,7 @@ Close Navigation Drawer No title Unrecognized URL: + Open with Save As @@ -349,6 +350,52 @@ SSL Certificates Tracking IDs + + Download Location + 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. + OK + + + Orbot proxy will not work unless Orbot is installed. + Waiting for Orbot to connect... + + + About Privacy Browser + Version + version code + Hardware + Brand: + Manufacturer: + Model: + Device: + Bootloader: + Radio: + Software + Android: + API + Build: + Security Patch: + WebView: + Orbot: + OpenKeychain: + EasyList: + EasyPrivacy: + Fanboy’s Annoyance List: + Fanboy’s Social Blocking List: + UltraPrivacy: + Package Signature + Issuer DN: + Subject DN: + Certificate Version: + Serial Number: + Signature Algorithm: + Permissions + Privacy Policy + Changelog + Licenses + Contributors + Links + Privacy Enable JavaScript by default @@ -555,8 +602,10 @@ 175 200 - Swipe to refresh + Swipe to refresh Some websites don’t work well if swipe to refresh is enabled. + Download with external app + Android’s download manager doesn’t work well on some devices. Display additional app bar icons Display icons in the app bar for refreshing the WebView and, if there is room, for toggling cookies and DOM storage. Dark theme @@ -566,51 +615,16 @@ Display webpage images Disable to conserve bandwidth. - - Download Location - 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. - OK - - - Orbot proxy will not work unless Orbot is installed. - Waiting for Orbot to connect... - - - About Privacy Browser - Version - version code - Hardware - Brand: - Manufacturer: - Model: - Device: - Bootloader: - Radio: - Software - Android: - API - Build: - Security Patch: - WebView: - Orbot: - OpenKeychain: - EasyList: - EasyPrivacy: - Fanboy’s Annoyance List: - Fanboy’s Social Blocking List: - UltraPrivacy: - Package Signature - Issuer DN: - Subject DN: - Certificate Version: - Serial Number: - Signature Algorithm: - Permissions - Privacy Policy - Changelog - Licenses - Contributors - Links + + Privacy Browser + PrivacyBrowser/1.0 + http://ulrn6sryqaifefld.onion/ + http://ulrn6sryqaifefld.onion/?q= + + https://searx.me/?q= + + https://searx.me/ + 100 Null diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index ecf94094..7741cec8 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -59,14 +59,14 @@ 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:defaultValue="@string/tor_search_default_value" /> - - @@ -170,15 +168,13 @@ 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" /> - - @@ -227,6 +223,7 @@ android:summary="@string/clear_dom_storage_summary" android:defaultValue="true" /> + @@ -256,15 +253,21 @@ 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" /> + +