From 6696d6946875515ae71c46a62dede4df44ad4351 Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Sat, 3 Feb 2024 13:01:22 -0700 Subject: [PATCH] Expand the options for selecting a download provider. https://redmine.stoutner.com/issues/717 --- .../activities/MainWebViewActivity.kt | 127 ++++++++++---- .../PopulateFilterListsCoroutine.kt | 2 +- .../coroutines/SaveUrlCoroutine.kt | 59 ++++--- .../privacybrowser/dialogs/SaveDialog.kt | 166 +++++++++++++----- .../fragments/SettingsFragment.kt | 34 ++-- app/src/main/res/drawable/download.xml | 2 +- .../download_with_external_app_disabled.xml | 4 +- .../download_with_external_app_enabled.xml | 4 +- app/src/main/res/drawable/home.xml | 2 +- .../layout/main_framelayout_bottom_appbar.xml | 20 +-- .../layout/main_framelayout_top_appbar.xml | 22 +-- app/src/main/res/layout/save_dialog.xml | 96 +++++++++- app/src/main/res/values-de/strings.xml | 2 - app/src/main/res/values-es/strings.xml | 2 - app/src/main/res/values-fr/strings.xml | 2 - app/src/main/res/values-it/strings.xml | 8 +- app/src/main/res/values-pt-rBR/strings.xml | 4 +- app/src/main/res/values-ru/strings.xml | 4 +- app/src/main/res/values-zh-rCN/strings.xml | 3 +- app/src/main/res/values/strings.xml | 27 ++- app/src/main/res/xml/preferences.xml | 14 +- 21 files changed, 434 insertions(+), 170 deletions(-) diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt index 0d9755f1..782116a9 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt @@ -87,6 +87,7 @@ import android.widget.LinearLayout import android.widget.ListView import android.widget.ProgressBar import android.widget.RadioButton +import android.widget.RadioGroup import android.widget.RelativeLayout import android.widget.TextView @@ -532,7 +533,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES } - // Get the theme entry values string array. + // Get the entry values string arrays. val appThemeEntryValuesStringArray = resources.getStringArray(R.array.app_theme_entry_values) // Get the current theme status. @@ -1976,7 +1977,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook R.id.save_url -> { // Save URL. // Check the download preference. if (downloadWithExternalApp) // Download with an external app. - downloadUrlWithExternalApp(currentWebView!!.currentUrl) + saveWithExternalApp(currentWebView!!.currentUrl) else // Handle the download inside of Privacy Browser. The dialog will be displayed once the file size and the content disposition have been acquired. PrepareSaveDialogCoroutine.prepareSaveDialog(this, supportFragmentManager, currentWebView!!.currentUrl, currentWebView!!.settings.userAgentString, currentWebView!!.acceptCookies) @@ -2660,7 +2661,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook contextMenu.add(R.string.save_url).setOnMenuItemClickListener { // Check the download preference. if (downloadWithExternalApp) // Download with an external app. - downloadUrlWithExternalApp(linkUrl) + saveWithExternalApp(linkUrl) else // Handle the download inside of Privacy Browser. The dialog will be displayed once the file size and the content disposition have been acquired. PrepareSaveDialogCoroutine.prepareSaveDialog(this, supportFragmentManager, linkUrl, currentWebView!!.settings.userAgentString, currentWebView!!.acceptCookies) @@ -2744,7 +2745,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook contextMenu.add(R.string.save_image).setOnMenuItemClickListener { // Check the download preference. if (downloadWithExternalApp) { // Download with an external app. - downloadUrlWithExternalApp(imageUrl) + saveWithExternalApp(imageUrl) } else { // Handle the download inside of Privacy Browser. The dialog will be displayed once the file size and the content disposition have been acquired. PrepareSaveDialogCoroutine.prepareSaveDialog(this, supportFragmentManager, imageUrl, currentWebView!!.settings.userAgentString, currentWebView!!.acceptCookies) } @@ -2868,7 +2869,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook contextMenu.add(R.string.save_image).setOnMenuItemClickListener { // Check the download preference. if (downloadWithExternalApp) // Download with an external app. - downloadUrlWithExternalApp(imageUrl) + saveWithExternalApp(imageUrl) else // Handle the download inside of Privacy Browser. The dialog will be displayed once the file size and the content disposition have been acquired. PrepareSaveDialogCoroutine.prepareSaveDialog(this, supportFragmentManager, imageUrl, currentWebView!!.settings.userAgentString, currentWebView!!.acceptCookies) @@ -2913,7 +2914,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook contextMenu.add(R.string.save_url).setOnMenuItemClickListener { // Check the download preference. if (downloadWithExternalApp) // Download with an external app. - downloadUrlWithExternalApp(linkUrl) + saveWithExternalApp(linkUrl) else // Handle the download inside of Privacy Browser. The dialog will be displayed once the file size and the content disposition have been acquired. PrepareSaveDialogCoroutine.prepareSaveDialog(this, supportFragmentManager, linkUrl, currentWebView!!.settings.userAgentString, currentWebView!!.acceptCookies) @@ -3086,12 +3087,11 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook defaultWideViewport = sharedPreferences.getBoolean(getString(R.string.wide_viewport_key), true) defaultDisplayWebpageImages = sharedPreferences.getBoolean(getString(R.string.display_webpage_images_key), true) - // Get the WebView theme entry values string array. This is done here so that expensive resource requests are not made each time a domain is loaded. + // Get the string arrays. These are done here so that expensive resource requests are not made each time a domain is loaded. webViewThemeEntryValuesStringArray = resources.getStringArray(R.array.webview_theme_entry_values) - - // Get the user agent string arrays. These are done here so that expensive resource requests are not made each time a domain is loaded. userAgentDataArray = resources.getStringArray(R.array.user_agent_data) userAgentNamesArray = resources.getStringArray(R.array.user_agent_names) + val downloadProviderEntryValuesStringArray = resources.getStringArray(R.array.download_provider_entry_values) // Get the user agent array adapters. These are done here so that expensive resource requests are not made each time a domain is loaded. userAgentDataArrayAdapter = ArrayAdapter.createFromResource(this, R.array.user_agent_data, R.layout.spinner_item) @@ -3104,9 +3104,12 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook proxyMode = sharedPreferences.getString(getString(R.string.proxy_key), getString(R.string.proxy_default_value))!! fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean(getString(R.string.full_screen_browsing_mode_key), false) hideAppBar = sharedPreferences.getBoolean(getString(R.string.hide_app_bar_key), true) - downloadWithExternalApp = sharedPreferences.getBoolean(getString(R.string.download_with_external_app_key), false) + val downloadProvider = sharedPreferences.getString(getString(R.string.download_provider_key), getString(R.string.download_provider_default_value))!! scrollAppBar = sharedPreferences.getBoolean(getString(R.string.scroll_app_bar_key), false) + // Determine if downloading should be handled by an external app. + downloadWithExternalApp = (downloadProvider == downloadProviderEntryValuesStringArray[2]) + // Apply the saved proxy mode if the app has been restarted. if (savedProxyMode != null) { // Apply the saved proxy mode. @@ -4174,20 +4177,6 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook bookmarksListView.setSelection(0) } - private fun downloadUrlWithExternalApp(url: String) { - // Create a download intent. Not specifying the action type will display the maximum number of options. - val downloadIntent = Intent() - - // Set the URI and the mime type. - downloadIntent.setDataAndType(Uri.parse(url), "text/html") - - // Flag the intent to open in a new task. - downloadIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - - // Show the chooser. - startActivity(Intent.createChooser(downloadIntent, getString(R.string.download_with_external_app))) - } - private fun exitFullScreenVideo() { // Re-enable the screen timeout. fullScreenVideoFrameLayout.keepScreenOn = false @@ -4970,13 +4959,12 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook registerForContextMenu(nestedScrollWebView) // Allow the downloading of files. - nestedScrollWebView.setDownloadListener { downloadUrlString: String?, userAgent: String?, contentDisposition: String?, mimetype: String?, contentLength: Long -> - // Check the download preference. + nestedScrollWebView.setDownloadListener { downloadUrlString: String, userAgent: String, contentDisposition: String, mimetype: String, contentLength: Long -> + // Use the specified download provider. if (downloadWithExternalApp) { // Download with an external app. - downloadUrlWithExternalApp(downloadUrlString!!) - } else { // Handle the download inside of Privacy Browser. - // Define a formatted file size string. - + // Download with an external app. + saveWithExternalApp(downloadUrlString) + } else { // Download with Privacy Browser or Android's download manager. // Process the content length if it contains data. val formattedFileSizeString = if (contentLength > 0) { // The content length is greater than 0. // Format the content length as a string. @@ -4987,10 +4975,10 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook } // Get the file name from the content disposition. - val fileNameString = UrlHelper.getFileName(this, contentDisposition, mimetype, downloadUrlString!!) + val fileNameString = UrlHelper.getFileName(this, contentDisposition, mimetype, downloadUrlString) - // Instantiate the save dialog. - val saveDialogFragment = SaveDialog.saveUrl(downloadUrlString, fileNameString, formattedFileSizeString, userAgent!!, nestedScrollWebView.acceptCookies) + // Instantiate the save dialog according. + val saveDialogFragment = SaveDialog.saveUrl(downloadUrlString, fileNameString, formattedFileSizeString, userAgent, nestedScrollWebView.acceptCookies) // Try to show the dialog. The download listener continues to function even when the WebView is paused. Attempting to display a dialog in that state leads to a crash. try { @@ -6230,7 +6218,76 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook return sanitizedUrlString } - override fun saveUrl(originalUrlString: String, fileNameString: String, dialogFragment: DialogFragment) { + override fun saveWithAndroidDownloadManager(dialogFragment: DialogFragment) { + // Get the dialog. + val dialog = dialogFragment.dialog!! + + // Get handles for the dialog views. + val dialogUrlEditText = dialog.findViewById(R.id.url_edittext) + val downloadDirectoryRadioGroup = dialog.findViewById(R.id.download_directory_radiogroup) + val dialogFileNameEditText = dialog.findViewById(R.id.file_name_edittext) + + // Get the string from the edit texts, which may have been modified by the user. + val saveUrlString = dialogUrlEditText.text.toString() + val fileNameString = dialogFileNameEditText.text.toString() + + // Get a handle for the system download service. + val downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager + + // Parse the URL. + val downloadRequest = DownloadManager.Request(Uri.parse(saveUrlString)) + + // Pass cookies to download manager if cookies are enabled. This is required to download files from websites that require a login. + // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner . + if (cookieManager.acceptCookie()) { + // Get the cookies for the URL. + val cookiesString = cookieManager.getCookie(saveUrlString) + + // Add the cookies to the download request. In the HTTP request header, cookies are named `Cookie`. + downloadRequest.addRequestHeader("Cookie", cookiesString) + } + + // Get the download directory. + val downloadDirectory = when (downloadDirectoryRadioGroup.checkedRadioButtonId) { + R.id.downloads_radiobutton -> Environment.DIRECTORY_DOWNLOADS + R.id.documents_radiobutton -> Environment.DIRECTORY_DOCUMENTS + R.id.pictures_radiobutton -> Environment.DIRECTORY_PICTURES + else -> Environment.DIRECTORY_MUSIC + } + + // Set the download destination. + downloadRequest.setDestinationInExternalPublicDir(downloadDirectory, fileNameString) + + // Allow media scanner to index the download if it is a media file. This is automatic for API >= 29. + @Suppress("DEPRECATION") + if (Build.VERSION.SDK_INT <= 28) + downloadRequest.allowScanningByMediaScanner() + + // Add the URL as the description for the download. + downloadRequest.setDescription(saveUrlString) + + // Show the download notification after the download is completed. + downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + + // Initiate the download. + downloadManager.enqueue(downloadRequest) + } + + private fun saveWithExternalApp(url: String) { + // Create a download intent. Not specifying the action type will display the maximum number of options. + val downloadIntent = Intent() + + // Set the URI and the mime type. + downloadIntent.setDataAndType(Uri.parse(url), "text/html") + + // Flag the intent to open in a new task. + downloadIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + + // Show the chooser. + startActivity(Intent.createChooser(downloadIntent, getString(R.string.download_with_external_app))) + } + + override fun saveWithPrivacyBrowser(originalUrlString: String, fileNameString: String, dialogFragment: DialogFragment) { // Store the URL. This will be used in the save URL activity result launcher. saveUrlString = if (originalUrlString.startsWith("data:")) { // Save the original URL. @@ -6242,7 +6299,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Get a handle for the dialog URL edit text. val dialogUrlEditText = dialog.findViewById(R.id.url_edittext) - // Get the URL from the edit text, which may have been modified. + // Get the URL from the edit text, which may have been modified by the user. dialogUrlEditText.text.toString() } diff --git a/app/src/main/java/com/stoutner/privacybrowser/coroutines/PopulateFilterListsCoroutine.kt b/app/src/main/java/com/stoutner/privacybrowser/coroutines/PopulateFilterListsCoroutine.kt index 94711533..e5530926 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/coroutines/PopulateFilterListsCoroutine.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/coroutines/PopulateFilterListsCoroutine.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019,2021-2024 Soren Stoutner . + * Copyright 2019, 2021-2024 Soren Stoutner . * * This file is part of Privacy Browser Android . * diff --git a/app/src/main/java/com/stoutner/privacybrowser/coroutines/SaveUrlCoroutine.kt b/app/src/main/java/com/stoutner/privacybrowser/coroutines/SaveUrlCoroutine.kt index f6b7a832..7ce88146 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/coroutines/SaveUrlCoroutine.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/coroutines/SaveUrlCoroutine.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 Soren Stoutner . + * Copyright 2020-2024 Soren Stoutner . * * This file is part of Privacy Browser Android . * @@ -44,6 +44,7 @@ import java.io.InputStream import java.net.HttpURLConnection import java.net.URL import java.text.NumberFormat +import java.util.Date class SaveUrlCoroutine { fun save(context: Context, activity: Activity, urlString: String, fileUri: Uri, userAgent: String, cookiesEnabled: Boolean) { @@ -154,11 +155,13 @@ class SaveUrlCoroutine { val inputStream: InputStream = BufferedInputStream(httpUrlConnection.inputStream) // Initialize the conversion buffer byte array. - // This is set to a megabyte so that frequent updating of the snackbar doesn't freeze the interface on download. - val conversionBufferByteArray = ByteArray(1048576) + // This is set to a 100,000 bytes so that frequent updating of the snackbar doesn't freeze the interface on download, although `inputStream.read` currently used 8,000 as an upper limit. + // + val conversionBufferByteArray = ByteArray(100_000) - // Initialize the downloaded kilobytes counter. - var downloadedKilobytesCounter: Long = 0 + // Initialize the longs. + var downloadedBytesCounterLong: Long = 0 + var lastSnackbarUpdateLong: Long = 0 // Define the buffer length variable. var bufferLength: Int @@ -168,26 +171,36 @@ class SaveUrlCoroutine { // Write the contents of the conversion buffer to the file output stream. outputStream.write(conversionBufferByteArray, 0, bufferLength) - // Update the downloaded kilobytes counter. - downloadedKilobytesCounter += bufferLength + // Update the downloaded bytes counter. + downloadedBytesCounterLong += bufferLength // Format the number of bytes downloaded. - val formattedNumberOfBytesDownloadedString = NumberFormat.getInstance().format(downloadedKilobytesCounter) - - // Update the UI. - withContext(Dispatchers.Main) { - // Check to see if the file size is known. - if (fileSize == -1L) { // The size of the download file is not known. - // Update the snackbar. - savingFileSnackbar.setText(activity.getString(R.string.saving_file_progress, formattedNumberOfBytesDownloadedString, fileNameString)) - } else { // The size of the download file is known. - // Calculate the download percentage. - val downloadPercentage = downloadedKilobytesCounter * 100 / fileSize - - // Update the snackbar. - savingFileSnackbar.setText(activity.getString(R.string.saving_file_percentage_progress, downloadPercentage, formattedNumberOfBytesDownloadedString, formattedFileSize, - fileNameString) - ) + val formattedNumberOfBytesDownloadedString = NumberFormat.getInstance().format(downloadedBytesCounterLong) + + // Get the current time. + val currentTimeLong = Date().time + + // Update the snackbar if more than 100 milliseconds have passed since the last update. + // Updating the snackbar is so resource intensive that it will throttle the download if it is done too frequently. + if (currentTimeLong - lastSnackbarUpdateLong > 100) { + // Store the update time. + lastSnackbarUpdateLong = currentTimeLong + + // Update the UI. + withContext(Dispatchers.Main) { + // Check to see if the file size is known. + if (fileSize == -1L) { // The size of the download file is not known. + // Update the snackbar. + savingFileSnackbar.setText(activity.getString(R.string.saving_file_progress, formattedNumberOfBytesDownloadedString, fileNameString)) + } else { // The size of the download file is known. + // Calculate the download percentage. + val downloadPercentage = downloadedBytesCounterLong * 100 / fileSize + + // Update the snackbar. + savingFileSnackbar.setText(activity.getString(R.string.saving_file_percentage_progress, downloadPercentage, formattedNumberOfBytesDownloadedString, formattedFileSize, + fileNameString) + ) + } } } } diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt index faae1819..c6dd6447 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 Soren Stoutner . + * Copyright 2019-2024 Soren Stoutner . * * This file is part of Privacy Browser Android . * @@ -26,11 +26,14 @@ import android.os.Bundle import android.text.Editable import android.text.InputType import android.text.TextWatcher +import android.view.View import android.view.WindowManager import android.widget.EditText +import android.widget.LinearLayout import android.widget.TextView import androidx.appcompat.app.AlertDialog +import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.preference.PreferenceManager @@ -43,11 +46,11 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext // Define the private class constants. -private const val URL_STRING = "url_string" -private const val FILE_SIZE_STRING = "file_size_string" -private const val FILE_NAME_STRING = "file_name_string" -private const val USER_AGENT_STRING = "user_agent_string" -private const val COOKIES_ENABLED = "cookies_enabled" +private const val URL_STRING = "A" +private const val FILE_SIZE_STRING = "B" +private const val FILE_NAME_STRING = "C" +private const val USER_AGENT_STRING = "D" +private const val COOKIES_ENABLED = "E" class SaveDialog : DialogFragment() { companion object { @@ -78,7 +81,11 @@ class SaveDialog : DialogFragment() { // The public interface is used to send information back to the parent activity. interface SaveListener { - fun saveUrl(originalUrlString: String, fileNameString: String, dialogFragment: DialogFragment) + // Save with Android's download manager. + fun saveWithAndroidDownloadManager(dialogFragment: DialogFragment) + + // Save with Privacy Browser. + fun saveWithPrivacyBrowser(originalUrlString: String, fileNameString: String, dialogFragment: DialogFragment) } override fun onAttach(context: Context) { @@ -90,12 +97,28 @@ class SaveDialog : DialogFragment() { } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + // Get the arguments + val arguments = requireArguments() + // Get the arguments from the bundle. - val originalUrlString = requireArguments().getString(URL_STRING)!! - var fileNameString = requireArguments().getString(FILE_NAME_STRING)!! - val fileSizeString = requireArguments().getString(FILE_SIZE_STRING)!! - val userAgentString = requireArguments().getString(USER_AGENT_STRING)!! - val cookiesEnabled = requireArguments().getBoolean(COOKIES_ENABLED) + val originalUrlString = arguments.getString(URL_STRING)!! + var fileNameString = arguments.getString(FILE_NAME_STRING)!! + val fileSizeString = arguments.getString(FILE_SIZE_STRING)!! + val userAgentString = arguments.getString(USER_AGENT_STRING)!! + val cookiesEnabled = arguments.getBoolean(COOKIES_ENABLED) + + // Get the download provider entry values string array. + val downloadProviderEntryValuesStringArray = resources.getStringArray(R.array.download_provider_entry_values) + + // Get a handle for the shared preferences. + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) + + // Get the preference. + val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) + val downloadProvider = sharedPreferences.getString(getString(R.string.download_provider_key), getString(R.string.download_provider_default_value))!! + + // Determine the download provider. + val privacyBrowserDownloadProvider = downloadProvider == downloadProviderEntryValuesStringArray[0] // Use an alert dialog builder to create the alert dialog. val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) @@ -114,19 +137,16 @@ class SaveDialog : DialogFragment() { // Set the save button listener. dialogBuilder.setPositiveButton(R.string.save) { _: DialogInterface, _: Int -> - // Return the dialog fragment to the parent activity. - saveListener.saveUrl(originalUrlString, fileNameString, this) + // Save the URL with the selected download provider. + if (privacyBrowserDownloadProvider) // Download with Privacy Browser. + saveListener.saveWithPrivacyBrowser(originalUrlString, fileNameString, this) + else // Download with Android's download manager. + saveListener.saveWithAndroidDownloadManager(this) } // Create an alert dialog from the builder. val alertDialog = dialogBuilder.create() - // Get a handle for the shared preferences. - val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) - - // Get the screenshot preference. - val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) - // Disable screenshots if not allowed. if (!allowScreenshots) { alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE) @@ -138,10 +158,19 @@ class SaveDialog : DialogFragment() { // Get handles for the layout items. val urlEditText = alertDialog.findViewById(R.id.url_edittext)!! val fileSizeTextView = alertDialog.findViewById(R.id.file_size_textview)!! + val blobUrlWarningTextView = alertDialog.findViewById(R.id.blob_url_warning_textview)!! + val dataUrlWarningTextView = alertDialog.findViewById(R.id.data_url_warning_textview)!! + val androidDownloadManagerLinearLayout = alertDialog.findViewById(R.id.android_download_manager_linearlayout)!! + val fileNameEditText = alertDialog.findViewById(R.id.file_name_edittext)!! val saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) - // Set the file size text view. + // Display the extra views if using Android's download manager. + if (!privacyBrowserDownloadProvider) + androidDownloadManagerLinearLayout.visibility = View.VISIBLE + + // Populate the views. fileSizeTextView.text = fileSizeString + fileNameEditText.text = fileNameString // Populate the URL edit text according to the type. This must be done before the text change listener is created below so that the file size isn't requested again. if (originalUrlString.startsWith("data:")) { // The URL contains the entire data of an image. @@ -153,47 +182,104 @@ class SaveDialog : DialogFragment() { // Disable the editing of the URL edit text. urlEditText.inputType = InputType.TYPE_NULL + + // Display the warning if using Android's download manager. + if (!privacyBrowserDownloadProvider) { + // Display the data URL warning. + dataUrlWarningTextView.visibility = View.VISIBLE + + // Disable the save button. + saveButton.isEnabled = false + } } else { // The URL contains a reference to the location of the data. // Populate the URL edit text with the full URL. urlEditText.setText(originalUrlString) } - // Update the file size when the URL changes. + // Handle blob URLs. + if (originalUrlString.startsWith("blob:")) { + // Display the blob URL warning. + blobUrlWarningTextView.visibility = View.VISIBLE + + // Disable the save button. + saveButton.isEnabled = false + } + + // Update the UI when the URL changes. urlEditText.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) { + override fun beforeTextChanged(charSequence: CharSequence?, i: Int, i1: Int, i2: Int) { // Do nothing. } - override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) { + override fun onTextChanged(charSequence: CharSequence?, i: Int, i1: Int, i2: Int) { // Do nothing. } - override fun afterTextChanged(editable: Editable) { - // Get the current URL to save. + override fun afterTextChanged(editable: Editable?) { + // Get the contents of the edit texts. val urlToSave = urlEditText.text.toString() + val fileName = fileNameEditText.text.toString() - // Enable the save button if the URL is populated. - saveButton.isEnabled = urlToSave.isNotEmpty() + // Determine if this is a blob URL. + val blobUrl = urlToSave.startsWith("blob:") - CoroutineScope(Dispatchers.Main).launch { - // Create a URL size string. - var fileNameAndSize: Pair + // Set the display status of the blob warning. + if (blobUrl) + blobUrlWarningTextView.visibility = View.VISIBLE + else + blobUrlWarningTextView.visibility = View.GONE - // Get the URL size on the IO thread. - withContext(Dispatchers.IO) { - // Get the updated file name and size. - fileNameAndSize = UrlHelper.getNameAndSize(requireContext(), urlToSave, userAgentString, cookiesEnabled) + // Enable the save button if the edit texts are populated and this isn't a blob URL. + saveButton.isEnabled = urlToSave.isNotEmpty() && fileName.isNotEmpty() && !blobUrl - // Save the updated file name. - fileNameString = fileNameAndSize.first - } + // Determine if this is a data URL. + val dataUrl = urlToSave.startsWith("data:") + + // Only process the URL if it is not a data URL. + if (!dataUrl) { + CoroutineScope(Dispatchers.Main).launch { + // Create a URL size string. + var fileNameAndSize: Pair + + // Get the URL size on the IO thread. + withContext(Dispatchers.IO) { + // Get the updated file name and size. + fileNameAndSize = UrlHelper.getNameAndSize(requireContext(), urlToSave, userAgentString, cookiesEnabled) - // Display the updated URL. - fileSizeTextView.text = fileNameAndSize.second + // Save the updated file name. + fileNameString = fileNameAndSize.first + } + + // Display the updated file size. + fileSizeTextView.text = fileNameAndSize.second + } } } }) + // Update the UI when the file name changes. + fileNameEditText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(charSequence: CharSequence?, p1: Int, p2: Int, p3: Int) { + // Do nothing. + } + + override fun onTextChanged(charSequence: CharSequence?, p1: Int, p2: Int, p3: Int) { + // Do nothing. + } + + override fun afterTextChanged(editable: Editable?) { + // Get the contents of the edit texts. + val urlToSave = urlEditText.text.toString() + val fileName = fileNameEditText.text.toString() + + // Determine if this is a blob URL. + val blobUrl = urlToSave.startsWith("blob:") + + // Enable the save button if the edit texts are populated and this isn't a blob URL (or a data URL using Android's download manager). + saveButton.isEnabled = urlToSave.isNotEmpty() && fileName.isNotEmpty() && !blobUrl && !dataUrlWarningTextView.isVisible + } + }) + // Return the alert dialog. return alertDialog } diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.kt b/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.kt index 61d45ef1..10a4532d 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.kt @@ -73,7 +73,8 @@ class SettingsFragment : PreferenceFragmentCompat() { private lateinit var displayAdditionalAppBarIconsPreference: Preference private lateinit var displayWebpageImagesPreference: Preference private lateinit var domStoragePreference: Preference - private lateinit var downloadWithExternalAppPreference: Preference + private lateinit var downloadProviderEntryValuesStringArray: Array + private lateinit var downloadProviderPreference: Preference private lateinit var easyListPreference: Preference private lateinit var easyPrivacyPreference: Preference private lateinit var fanboyAnnoyanceListPreference: Preference @@ -157,7 +158,7 @@ class SettingsFragment : PreferenceFragmentCompat() { fontSizePreference = findPreference(getString(R.string.font_size_key))!! openIntentsInNewTabPreference = findPreference(getString(R.string.open_intents_in_new_tab_key))!! swipeToRefreshPreference = findPreference(getString(R.string.swipe_to_refresh_key))!! - downloadWithExternalAppPreference = findPreference(getString(R.string.download_with_external_app_key))!! + downloadProviderPreference = findPreference(getString(R.string.download_provider_key))!! scrollAppBarPreference = findPreference(getString(R.string.scroll_app_bar_key))!! bottomAppBarPreference = findPreference(getString(R.string.bottom_app_bar_key))!! displayAdditionalAppBarIconsPreference = findPreference(getString(R.string.display_additional_app_bar_icons_key))!! @@ -280,6 +281,16 @@ class SettingsFragment : PreferenceFragmentCompat() { // Set the font size as the summary text for the preference. fontSizePreference.summary = sharedPreferences.getString(getString(R.string.font_size_key), getString(R.string.font_size_default_value)) + "%" + // Get the download provider entry values string array + downloadProviderEntryValuesStringArray = resources.getStringArray(R.array.download_provider_entry_values) + + // Set the summary text for the download provider preference. + downloadProviderPreference.summary = when (sharedPreferences.getString(getString(R.string.download_provider_key), getString(R.string.download_provider_default_value))) { + downloadProviderEntryValuesStringArray[0] -> getString(R.string.download_with_privacy_browser) // Privacy Browser is selected. + downloadProviderEntryValuesStringArray[1] -> getString(R.string.download_with_android_download_manager) // Android download manager is selected. + else -> getString(R.string.download_with_external_app) // External app is selected. + } + // Get the app theme string arrays. appThemeEntriesStringArray = resources.getStringArray(R.array.app_theme_entries) appThemeEntryValuesStringArray = resources.getStringArray(R.array.app_theme_entry_values) @@ -516,12 +527,6 @@ class SettingsFragment : PreferenceFragmentCompat() { else swipeToRefreshPreference.setIcon(R.drawable.refresh_disabled) - // Set the download with external app icon. - if (sharedPreferences.getBoolean(getString(R.string.download_with_external_app_key), false)) - downloadWithExternalAppPreference.setIcon(R.drawable.download_with_external_app_enabled) - else - downloadWithExternalAppPreference.setIcon(R.drawable.download_with_external_app_disabled) - // Set the scroll app bar icon. if (sharedPreferences.getBoolean(getString(R.string.scroll_app_bar_key), false)) scrollAppBarPreference.setIcon(R.drawable.app_bar_enabled) @@ -1069,12 +1074,13 @@ class SettingsFragment : PreferenceFragmentCompat() { swipeToRefreshPreference.setIcon(R.drawable.refresh_disabled) } - getString(R.string.download_with_external_app_key) -> { - // Update the icon. - if (sharedPreferences.getBoolean(getString(R.string.download_with_external_app_key), false)) - downloadWithExternalAppPreference.setIcon(R.drawable.download_with_external_app_enabled) - else - downloadWithExternalAppPreference.setIcon(R.drawable.download_with_external_app_disabled) + getString(R.string.download_provider_key) -> { + // Set the summary text for the download provider preference. + downloadProviderPreference.summary = when (sharedPreferences.getString(getString(R.string.download_provider_key), getString(R.string.download_provider_default_value))) { + downloadProviderEntryValuesStringArray[0] -> getString(R.string.download_with_privacy_browser) // Privacy Browser is selected. + downloadProviderEntryValuesStringArray[1] -> getString(R.string.download_with_android_download_manager) // Android download manager is selected. + else -> getString(R.string.download_with_external_app) // External app is selected. + } } getString(R.string.scroll_app_bar_key) -> { diff --git a/app/src/main/res/drawable/download.xml b/app/src/main/res/drawable/download.xml index 1a9f35ef..b98ad8c2 100644 --- a/app/src/main/res/drawable/download.xml +++ b/app/src/main/res/drawable/download.xml @@ -10,4 +10,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/download_with_external_app_disabled.xml b/app/src/main/res/drawable/download_with_external_app_disabled.xml index 42438e36..5e731914 100644 --- a/app/src/main/res/drawable/download_with_external_app_disabled.xml +++ b/app/src/main/res/drawable/download_with_external_app_disabled.xml @@ -1,5 +1,5 @@ diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 294e0777..b03539c4 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -659,8 +659,6 @@ Herunterziehen zum Aktualisieren Einige Websites funktionieren nicht, wenn "Herunterziehen zum Aktualisieren" eingeschaltet ist. Mit einer externen App herunterladen - Externe Apps befolgen die Proxy-Einstellungen von Privacy Browser nicht und haben keinen Zugriff auf Cookies - (daher werden Dateien, die von Websites heruntergeladen geladen werden, die eine Anmeldung erfordern, vermutlich nicht funkitionieren). App-Leiste scrollen Scrollt die App-Leiste mit der URL nach oben, wenn die Webseite gescrollt wird. Untere Anwendungs-Leiste diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index a8fc837e..3d250717 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -663,8 +663,6 @@ Deslizar para actualizar Algunas webs no funcionan bien si la opción deslizar para actualizar está habilitada. Descargar con una app externa - Las aplicaciones externas no respetarán la configuración del proxy del Navegador de Privacidad y no tendrán acceso a las cookies - (lo que significa que es poco probable que funcionen los archivos descargados de sitios que requieren un inicio de sesión). Desplazar la barra de aplicaciones Desplazar la barra de aplicaciones desde la parte superior de la pantalla cuando el WebView se desplaza hacia abajo. Barra inferior de la app diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 7d1e7703..b473c0e8 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -660,8 +660,6 @@ Glisser pour rafraîchir Certains sites Web ne fonctionnent pas bien lorsque "Glisser pour rafraîchir" est activé. Téléchargement avec une app externe - Les applications externes ne tiendront pas compte des paramètres proxy de Privacy Browseret n\'auront pas accès aux cookies - (ce qui signifie qu\'il est peu probable que les fichiers téléchargés à partir de sites nécessitant une identification fonctionnent). Défilement barre d\'applications Faites défiler la barre d\'applications en haut de l\'écran lorsque WebView défile vers le bas. Barre d\'application en bas diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 065e357c..ec164bc1 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -3,7 +3,7 @@ Cancella i cookies, il DOM storage, i dati dei moduli, il logcat e la cache di WebView. Cancella completamente le cartelle “app_webview” e “cache”. @@ -662,8 +662,6 @@ Swipe per aggiornare Alcuni siti non funzionano correttamente se questa opzione è abilitata. Scarica con una applicazione esterna - Le applicazioni esterne non rispetteranno le impostazioni proxy di Privacy Browser’s proxy e non accederanno ai cookie - (ovvero è improbabile che un download di file da siti che richiedano il login funzioni). Permetti lo scrolling della barra dell\'applicazione Permette lo scorrere della barra dell\'applicazione dalla parte alta dello schermo quando si effettua lo scrolling. Barra dell\'app in basso diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 5247217a..10999f0a 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1,7 +1,7 @@ @@ -658,8 +658,6 @@ Потянуть для обновления Некоторые веб-сайты могут работать некорректно при включении данной опции. Загрузка во внешнем приложении - Внешние приложения не будут учитывать настройки прокси Privacy Browser и не будут иметь доступа к cookie - (это означает, что файлы, загруженные с сайтов, для которых требуется авторизация, вряд ли будут работать). Прокручивать панель приложения Прокручивает панель приложения вверху экрана при прокрутке WebView вниз. Нижняя панель приложения diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 09ef33e1..cd165b6a 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1,7 +1,7 @@ + Privacy Browser + Android download manager + External app + + Privacy Browser - Privacy Browser’s built-in downloader is simple, but it has the advantage of honoring the proxy and using cookies (if enabled), + as well as being able to save data: URLs. + Android download manager - Android’s download manager does not honor Privacy Browser’s proxy settings, + but it does have access to cookies (meaning that files downloaded from sites that require a login will probably work). + External app - External apps do not honor Privacy Browser’s proxy settings and do not have access to cookies (meaning that it is unlikely that files downloaded from sites that require a login will work). Scroll the app bar Scroll the app bar off the top of the screen when the WebView scrolls down. @@ -771,7 +791,7 @@ display_additional_app_bar_icons display_webpage_images dom_storage - download_with_external_app + download_provider easylist easyprivacy fanboys_annoyance_list @@ -803,6 +823,7 @@ System default PrivacyBrowser/1.0 + Privacy Browser 100 https://www.mojeek.com/ socks://localhost:9050 diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 345ad307..2e106fd5 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -1,7 +1,7 @@