]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt
Expand the options for selecting a download provider. https://redmine.stoutner.com...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.kt
index 3e6acce2a0820758051297c0d18bfbee828abaaf..782116a9c64053a60cd07a8b1711994df208316e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015-2023 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2015-2024 Soren Stoutner <soren@stoutner.com>.
  *
  * Download cookie code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
  *
@@ -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.
@@ -644,9 +645,9 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
             drawerLayout.visibility = View.GONE
 
             // Initialize the WebView state adapter.
-            webViewStateAdapter = WebViewStateAdapter(this)
+            webViewStateAdapter = WebViewStateAdapter(this, bottomAppBar)
 
-            // Set the pager adapter on the web view pager.
+            // Set the WebView pager adapter.
             webViewViewPager2.adapter = webViewStateAdapter
 
             // Store up to 100 tabs in memory.
@@ -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)
 
@@ -2114,7 +2115,6 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                     // Add the extra information to the intent.
                     domainsIntent.putExtra(LOAD_DOMAIN, currentWebView!!.domainSettingsDatabaseId)
                     domainsIntent.putExtra(CLOSE_ON_BACK, true)
-                    domainsIntent.putExtra(CURRENT_URL, currentWebView!!.url)
                     domainsIntent.putExtra(CURRENT_IP_ADDRESSES, currentWebView!!.currentIpAddresses)
 
                     // Get the current certificate.
@@ -2221,7 +2221,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
 
                     // Set the font size integer.
                     val fontSizeInt = if (textZoomInt == defaultFontSizeString.toInt())  // The current system default is used, which is encoded as a zoom of `0`.
-                        0
+                        SYSTEM_DEFAULT
                     else  // A custom font size is used.
                         textZoomInt
 
@@ -2271,7 +2271,6 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                     // Add the extra information to the intent.
                     domainsIntent.putExtra(LOAD_DOMAIN, newDomainDatabaseId)
                     domainsIntent.putExtra(CLOSE_ON_BACK, true)
-                    domainsIntent.putExtra(CURRENT_URL, currentWebView!!.url)
                     domainsIntent.putExtra(CURRENT_IP_ADDRESSES, currentWebView!!.currentIpAddresses)
 
                     // Get the current certificate.
@@ -2488,7 +2487,6 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                 val domainsIntent = Intent(this, DomainsActivity::class.java)
 
                 // Add the extra information to the intent.
-                domainsIntent.putExtra(CURRENT_URL, currentWebView!!.url)
                 domainsIntent.putExtra(CURRENT_IP_ADDRESSES, currentWebView!!.currentIpAddresses)
 
                 // Get the current certificate.
@@ -2663,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)
 
@@ -2747,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)
                     }
@@ -2871,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)
 
@@ -2916,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)
 
@@ -3007,17 +3005,21 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         // Clear the focus from the URL edit text, so that it will be populated with the information from the new tab.
         urlEditText.clearFocus()
 
-        // Get the new tab position.
-        val newTabPosition = if (adjacent)  // The new tab position is immediately to the right of the current tab position.
-            tabLayout.selectedTabPosition + 1
-        else  // The new tab position is at the end.  The tab positions are 0 indexed, so the new page number will match the current count.
-            tabLayout.tabCount
+        // Add the new tab after the tab layout has quiesced.
+        // Otherwise, there can be problems when restoring a large number of tabs and processing a new intent at the same time.  <https://redmine.stoutner.com/issues/1136>
+        tabLayout.post {
+            // Get the new tab position.
+            val newTabPosition = if (adjacent)  // The new tab position is immediately to the right of the current tab position.
+                tabLayout.selectedTabPosition + 1
+            else  // The new tab position is at the end.  The tab positions are 0 indexed, so the new page number will match the current count.
+                tabLayout.tabCount
 
-        // Add the new WebView page.
-        webViewStateAdapter!!.addPage(newTabPosition, urlString)
+            // Add the new WebView page.
+            webViewStateAdapter!!.addPage(newTabPosition, urlString)
 
-        // Add the new tab.
-        addNewTab(newTabPosition, moveToTab)
+            // Add the new tab.
+            addNewTab(newTabPosition, moveToTab)
+        }
     }
 
     private fun addNewTab(newTabPosition: Int, moveToTab: Boolean) {
@@ -3085,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)
@@ -3103,8 +3104,11 @@ 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)
-        scrollAppBar = sharedPreferences.getBoolean(getString(R.string.scroll_app_bar_key), true)
+        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) {
@@ -4173,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
@@ -4969,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.
@@ -4986,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 {
@@ -6229,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<EditText>(R.id.url_edittext)
+        val downloadDirectoryRadioGroup = dialog.findViewById<RadioGroup>(R.id.download_directory_radiogroup)
+        val dialogFileNameEditText = dialog.findViewById<EditText>(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 <soren@stoutner.com>.
+        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.
@@ -6241,7 +6299,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
             // Get a handle for the dialog URL edit text.
             val dialogUrlEditText = dialog.findViewById<EditText>(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()
         }