Use the Content-Disposition header to get file names for downloads. https://redmine...
authorSoren Stoutner <soren@stoutner.com>
Fri, 10 Apr 2020 21:18:57 +0000 (14:18 -0700)
committerSoren Stoutner <soren@stoutner.com>
Fri, 10 Apr 2020 21:18:57 +0000 (14:18 -0700)
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetUrlSize.java
app/src/main/java/com/stoutner/privacybrowser/asynctasks/PrepareSaveDialog.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.java
app/src/main/java/com/stoutner/privacybrowser/helpers/ProxyHelper.java

index 4fc10372eab493e85799eb831b7400a63aa2ecc4..42b2065685e945e9865596d7f30a3bdff8692ee4 100644 (file)
@@ -117,6 +117,7 @@ import com.stoutner.privacybrowser.R;
 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
 import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
 import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
+import com.stoutner.privacybrowser.asynctasks.PrepareSaveDialog;
 import com.stoutner.privacybrowser.asynctasks.SaveUrl;
 import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage;
 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
@@ -155,6 +156,7 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
+import java.text.NumberFormat;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
@@ -1606,34 +1608,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 return true;
 
             case R.id.save_url:
-                // Instantiate the save dialog.
-                DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, currentWebView.getCurrentUrl(), currentWebView.getSettings().getUserAgentString(),
-                        currentWebView.getAcceptFirstPartyCookies());
-
-                // Show the save dialog.  It must be named `save_dialog` so that the file picked can update the file name.
-                saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
+                // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
+                new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+                        currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl());
 
                 // Consume the event.
                 return true;
 
             case R.id.save_as_archive:
-                // Instantiate the save webpage archive dialog.
-                DialogFragment saveWebpageArchiveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_AS_ARCHIVE, currentWebView.getCurrentUrl(), currentWebView.getSettings().getUserAgentString(),
-                        currentWebView.getAcceptFirstPartyCookies());
-
-                // Show the save webpage archive dialog.  It must be named `save_dialog` so that the file picked can update the file name.
-                saveWebpageArchiveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
+                // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
+                new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_AS_ARCHIVE, currentWebView.getSettings().getUserAgentString(),
+                        currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl());
 
                 // Consume the event.
                 return true;
 
             case R.id.save_as_image:
-                // Instantiate the save webpage image dialog.  It must be named `save_webpage` so that the file picked can update the file name.
-                DialogFragment saveWebpageImageDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_AS_IMAGE, currentWebView.getCurrentUrl(), currentWebView.getSettings().getUserAgentString(),
-                        currentWebView.getAcceptFirstPartyCookies());
-
-                // Show the save webpage image dialog.  It must be named `save_dialog` so that the file picked can update the file name.
-                saveWebpageImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
+                // Prepare the save dialog.  The dialog will be displayed once the file size adn the content disposition have been acquired.
+                new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_AS_IMAGE, currentWebView.getSettings().getUserAgentString(),
+                        currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl());
 
                 // Consume the event.
                 return true;
@@ -2120,12 +2113,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a Save URL entry.
                 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Instantiate the save dialog.
-                    DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, linkUrl, currentWebView.getSettings().getUserAgentString(),
-                            currentWebView.getAcceptFirstPartyCookies());
-
-                    // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
-                    saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
+                    // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
+                    new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+                            currentWebView.getAcceptFirstPartyCookies()).execute(linkUrl);
 
                     // Consume the event.
                     return true;
@@ -2181,12 +2171,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a Save Image entry.
                 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
-                   // Instantiate the save dialog.
-                   DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, imageUrl, currentWebView.getSettings().getUserAgentString(),
-                           currentWebView.getAcceptFirstPartyCookies());
-
-                   // Show the save dialog.  It must be named `save_dialog` so that the file picked can update the file name.
-                    saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
+                   // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
+                    new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+                            currentWebView.getAcceptFirstPartyCookies()).execute(imageUrl);
 
                     // Consume the event.
                     return true;
@@ -2284,12 +2271,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a Save Image entry.
                 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Instantiate the save  dialog.
-                    DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, imageUrl, currentWebView.getSettings().getUserAgentString(),
-                            currentWebView.getAcceptFirstPartyCookies());
-
-                    // Show the save raw dialog.  It must be named `save_dialog` so that the file picked can update the file name.
-                    saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
+                    // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
+                    new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+                            currentWebView.getAcceptFirstPartyCookies()).execute(imageUrl);
 
                     // Consume the event.
                     return true;
@@ -2309,12 +2293,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a Save URL entry.
                 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Instantiate the save dialog.
-                    DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, linkUrl, currentWebView.getSettings().getUserAgentString(),
-                            currentWebView.getAcceptFirstPartyCookies());
-
-                    // Show the save raw dialog.  It must be named `save_dialog` so that the file picked can update the file name.
-                    saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
+                    // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
+                    new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+                            currentWebView.getAcceptFirstPartyCookies()).execute(linkUrl);
 
                     // Consume the event.
                     return true;
@@ -5228,8 +5209,24 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Allow the downloading of files.
         nestedScrollWebView.setDownloadListener((String downloadUrl, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
+            // Define a formatted file size string.
+            String formattedFileSizeString;
+
+            // Process the content length if it contains data.
+            if (contentLength > 0) {  // The content length is greater than 0.
+                // Format the content length as a string.
+                formattedFileSizeString = NumberFormat.getInstance().format(contentLength) + " " + getString(R.string.bytes);
+            } else {  // The content length is not greater than 0.
+                // Set the formatted file size string to be `unknown size`.
+                formattedFileSizeString = getString(R.string.unknown_size);
+            }
+
+            // Get the file name from the content disposition.
+            String fileNameString = PrepareSaveDialog.getFileNameFromContentDisposition(this, contentDisposition, downloadUrl);
+
             // Instantiate the save dialog.
-            DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, downloadUrl, userAgent, nestedScrollWebView.getAcceptFirstPartyCookies());
+            DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent,
+                    nestedScrollWebView.getAcceptFirstPartyCookies());
 
             // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
             saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
index 74968df9508599744504dec070bf568297ef992c..5f0020d0403c9e994e68e448586385207d3465c6 100644 (file)
@@ -35,7 +35,7 @@ import java.net.URL;
 import java.text.NumberFormat;
 
 public class GetUrlSize extends AsyncTask<String, Void, String> {
-    // Define a weak reference for the calling context and fragment.
+    // Define weak references for the calling context and alert dialog.
     private WeakReference<Context> contextWeakReference;
     private WeakReference<AlertDialog> alertDialogWeakReference;
 
@@ -45,7 +45,7 @@ public class GetUrlSize extends AsyncTask<String, Void, String> {
 
     // The public constructor.
     public GetUrlSize(Context context, AlertDialog alertDialog, String userAgent, boolean cookiesEnabled) {
-        // Populate the week references to the calling activity and fragment.
+        // Populate the week references for the context and alert dialog.
         contextWeakReference = new WeakReference<>(context);
         alertDialogWeakReference = new WeakReference<>(alertDialog);
 
@@ -68,7 +68,7 @@ public class GetUrlSize extends AsyncTask<String, Void, String> {
         // Initialize the formatted file size string.
         String formattedFileSize = context.getString(R.string.unknown_size);
 
-        // Because everything relating to requesting data from a webserver can throw errors, the entire section much catch `IOExceptions`.
+        // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch exceptions.
         try {
             // Get the URL from the calling fragment.
             URL url = new URL(urlToSave[0]);
@@ -87,7 +87,7 @@ public class GetUrlSize extends AsyncTask<String, Void, String> {
 
             // Add the cookies if they are enabled.
             if (cookiesEnabled) {
-                // Ge the cookies for the current domain.
+                // Get the cookies for the current domain.
                 String cookiesString = CookieManager.getInstance().getCookie(url.toString());
 
                 // Only add the cookies if they are not null.
@@ -108,7 +108,7 @@ public class GetUrlSize extends AsyncTask<String, Void, String> {
                     return formattedFileSize;
                 }
 
-                // Get the status code.
+                // Get the status code.  This initiates a network connection.
                 int responseCode = httpUrlConnection.getResponseCode();
 
                 // Exit if the task has been cancelled.
@@ -128,13 +128,10 @@ public class GetUrlSize extends AsyncTask<String, Void, String> {
                     // Get the content length header.
                     String contentLengthString = httpUrlConnection.getHeaderField("Content-Length");
 
-                    // Define the file size long.
-                    long fileSize;
-
-                    // Make sure the content length isn't null.
-                    if (contentLengthString != null) {  // The content length isn't null.
-                        // Convert the content length to a long.
-                        fileSize = Long.parseLong(contentLengthString);
+                    // Only process the content length string if it isn't null.
+                    if (contentLengthString != null) {
+                        // Convert the content length string to a long.
+                        long fileSize = Long.parseLong(contentLengthString);
 
                         // Format the file size.
                         formattedFileSize = NumberFormat.getInstance().format(fileSize) + " " + context.getString(R.string.bytes);
@@ -156,7 +153,7 @@ public class GetUrlSize extends AsyncTask<String, Void, String> {
     // `onPostExecute()` operates on the UI thread.
     @Override
     protected void onPostExecute(String fileSize) {
-        // Get a handle for the context and alert dialog.
+        // Get a handle for the alert dialog.
         AlertDialog alertDialog = alertDialogWeakReference.get();
 
         // Abort if the alert dialog is gone.
diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/PrepareSaveDialog.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/PrepareSaveDialog.java
new file mode 100644 (file)
index 0000000..9b1dcd0
--- /dev/null
@@ -0,0 +1,239 @@
+/*
+ * Copyright © 2020 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.asynctasks;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.webkit.CookieManager;
+
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentManager;
+
+import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.dialogs.SaveDialog;
+import com.stoutner.privacybrowser.helpers.ProxyHelper;
+
+import java.lang.ref.WeakReference;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.URL;
+import java.text.NumberFormat;
+
+public class PrepareSaveDialog extends AsyncTask<String, Void, String[]> {
+    // Define weak references.
+    private WeakReference<Activity> activityWeakReference;
+    private WeakReference<Context> contextWeakReference;
+    private WeakReference<FragmentManager> fragmentManagerWeakReference;
+
+    // Define the class variables.
+    private int saveType;
+    private String userAgent;
+    private boolean cookiesEnabled;
+    private String urlString;
+
+    // The public constructor.
+    public PrepareSaveDialog(Activity activity, Context context, FragmentManager fragmentManager, int saveType, String userAgent, boolean cookiesEnabled) {
+        // Populate the weak references.
+        activityWeakReference = new WeakReference<>(activity);
+        contextWeakReference = new WeakReference<>(context);
+        fragmentManagerWeakReference = new WeakReference<>(fragmentManager);
+
+        // Store the class variables.
+        this.saveType = saveType;
+        this.userAgent = userAgent;
+        this.cookiesEnabled = cookiesEnabled;
+    }
+
+    @Override
+    protected String[] doInBackground(String... urlToSave) {
+        // Get a handle for the activity and context.
+        Activity activity = activityWeakReference.get();
+        Context context = contextWeakReference.get();
+
+        // Abort if the activity is gone.
+        if (activity == null || activity.isFinishing()) {
+            // Return a null string array.
+            return null;
+        }
+
+        // Get the URL string.
+        urlString = urlToSave[0];
+
+        // Define the file name string.
+        String fileNameString;
+
+        // Initialize the formatted file size string.
+        String formattedFileSize = context.getString(R.string.unknown_size);
+
+        // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch exceptions.
+        try {
+            // Convert the URL string to a URL.
+            URL url = new URL(urlString);
+
+            // Instantiate the proxy helper.
+            ProxyHelper proxyHelper = new ProxyHelper();
+
+            // Get the current proxy.
+            Proxy proxy = proxyHelper.getCurrentProxy(context);
+
+            // Open a connection to the URL.  No data is actually sent at this point.
+            HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
+
+            // Add the user agent to the header property.
+            httpUrlConnection.setRequestProperty("User-Agent", userAgent);
+
+            // Add the cookies if they are enabled.
+            if (cookiesEnabled) {
+                // Get the cookies for the current domain.
+                String cookiesString = CookieManager.getInstance().getCookie(url.toString());
+
+                // only add the cookies if they are not null.
+                if (cookiesString != null) {
+                    // Add the cookies to the header property.
+                    httpUrlConnection.setRequestProperty("Cookie", cookiesString);
+                }
+            }
+
+            // The actual network request is in a `try` bracket so that `disconnect()` is run in the `finally` section even if an error is encountered in the main block.
+            try {
+                // Get the status code.  This initiates a network connection.
+                int responseCode = httpUrlConnection.getResponseCode();
+
+                // Check the response code.
+                if (responseCode >= 400) {  // The response code is an error message.
+                    // Set the formatted file size to indicate a bad URL.
+                    formattedFileSize = context.getString(R.string.invalid_url);
+
+                    // Set the file name according to the URL.
+                    fileNameString = getFileNameFromUrl(context, urlString);
+                } else {  // The response code is not an error message.
+                    // Get the content length and disposition headers.
+                    String contentLengthString = httpUrlConnection.getHeaderField("Content-Length");
+                    String contentDispositionString = httpUrlConnection.getHeaderField("Content-Disposition");
+
+                    // Only process the content length string if it isn't null.
+                    if (contentLengthString != null) {
+                        // Convert the content length string to a long.
+                        long fileSize = Long.parseLong(contentLengthString);
+
+                        // Format the file size.
+                        formattedFileSize = NumberFormat.getInstance().format(fileSize) + " " + context.getString(R.string.bytes);
+                    }
+
+                    // Get the file name string from the content disposition.
+                    fileNameString = getFileNameFromContentDisposition(context, contentDispositionString, urlString);
+                }
+            } finally {
+                // Disconnect the HTTP URL connection.
+                httpUrlConnection.disconnect();
+            }
+        } catch (Exception exception) {
+            // Set the formatted file size to indicate a bad URL.
+            formattedFileSize = context.getString(R.string.invalid_url);
+
+            // Set the file name according to the URL.
+            fileNameString = getFileNameFromUrl(context, urlString);
+        }
+
+        // Return the formatted file size and name as a string array.
+        return new String[] {formattedFileSize, fileNameString};
+    }
+
+    // `onPostExecute()` operates on the UI thread.
+    @Override
+    protected void onPostExecute(String[] fileStringArray) {
+        // Get a handle for the activity and the fragment manager.
+        Activity activity = activityWeakReference.get();
+        FragmentManager fragmentManager = fragmentManagerWeakReference.get();
+
+        // Abort if the activity is gone.
+        if (activity == null || activity.isFinishing()) {
+            // Exit.
+            return;
+        }
+
+        // Instantiate the save dialog.
+        DialogFragment saveDialogFragment = SaveDialog.saveUrl(saveType, urlString, fileStringArray[0], fileStringArray[1], userAgent, cookiesEnabled);
+
+        // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
+        saveDialogFragment.show(fragmentManager, activity.getString(R.string.save_dialog));
+    }
+
+    // Content dispositions can contain other text besides the file name, and they can be in any order.
+    // Elements are separated by semicolons.  Sometimes the file names are contained in quotes.
+    public static String getFileNameFromContentDisposition(Context context, String contentDispositionString, String urlString) {
+        // Define a file name string.
+        String fileNameString;
+
+        // Only process the content disposition string if it isn't null.
+        if (contentDispositionString != null) {  // The content disposition is not null.
+            // Check to see if the content disposition contains a file name.
+            if (contentDispositionString.contains("filename=")) {  // The content disposition contains a filename.
+                // Get the part of the content disposition after `filename=`.
+                fileNameString = contentDispositionString.substring(contentDispositionString.indexOf("filename=") + 9);
+
+                // Remove any `;` and anything after it.  This removes any entries after the filename.
+                if (fileNameString.contains(";")) {
+                    // Remove the first `;` and everything after it.
+                    fileNameString = fileNameString.substring(0, fileNameString.indexOf(";") - 1);
+                }
+
+                // Remove any `"` at the beginning of the string.
+                if (fileNameString.startsWith("\"")) {
+                    // Remove the first character.
+                    fileNameString = fileNameString.substring(1);
+                }
+
+                // Remove any `"` at the end of the string.
+                if (fileNameString.endsWith("\"")) {
+                    // Remove the last character.
+                    fileNameString = fileNameString.substring(0, fileNameString.length() - 1);
+                }
+            } else {  // The content disposition does not contain a filename.
+                // Get the file name string from the URL.
+                fileNameString = getFileNameFromUrl(context, urlString);
+            }
+        } else {  // The content disposition is null.
+            // Get the file name string from the URL.
+            fileNameString = getFileNameFromUrl(context, urlString);
+        }
+
+        // Return the file name string.
+        return fileNameString;
+    }
+
+    private static String getFileNameFromUrl(Context context, String urlString) {
+        // Convert the URL string to a URI.
+        Uri uri = Uri.parse(urlString);
+
+        // Get the last path segment.
+        String lastPathSegment = uri.getLastPathSegment();
+
+        // Use a default file name if the last path segment is null.
+        if (lastPathSegment == null) {
+            lastPathSegment = context.getString(R.string.file);
+        }
+
+        // Return the last path segment as the file name.
+        return lastPathSegment;
+    }
+}
\ No newline at end of file
index 9b599b531ede15447a445ffcd0f61804efc3d6bd..19ff43ce8f372fe0a0f1a1ca56f5de40bca05845 100644 (file)
@@ -29,7 +29,6 @@ import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
-import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
@@ -76,14 +75,16 @@ public class SaveDialog extends DialogFragment {
         saveWebpageListener = (SaveWebpageListener) context;
     }
 
-    public static SaveDialog saveUrl(int saveType, String url, String userAgent, boolean cookiesEnabled) {
+    public static SaveDialog saveUrl(int saveType, String urlString, String fileSizeString, String contentDispositionFileNameString, String userAgentString, boolean cookiesEnabled) {
         // Create an arguments bundle.
         Bundle argumentsBundle = new Bundle();
 
         // Store the arguments in the bundle.
         argumentsBundle.putInt("save_type", saveType);
-        argumentsBundle.putString("url", url);
-        argumentsBundle.putString("user_agent", userAgent);
+        argumentsBundle.putString("url_string", urlString);
+        argumentsBundle.putString("file_size_string", fileSizeString);
+        argumentsBundle.putString("content_disposition_file_name_string", contentDispositionFileNameString);
+        argumentsBundle.putString("user_agent_string", userAgentString);
         argumentsBundle.putBoolean("cookies_enabled", cookiesEnabled);
 
         // Create a new instance of the save webpage dialog.
@@ -109,8 +110,10 @@ public class SaveDialog extends DialogFragment {
 
         // Get the arguments from the bundle.
         int saveType = arguments.getInt("save_type");
-        String url = arguments.getString("url");
-        String userAgent = arguments.getString("user_agent");
+        String urlString = arguments.getString("url_string");
+        String fileSizeString = arguments.getString("file_size_string");
+        String contentDispositionFileNameString = arguments.getString("content_disposition_file_name_string");
+        String userAgentString = arguments.getString("user_agent_string");
         boolean cookiesEnabled = arguments.getBoolean("cookies_enabled");
 
         // Get a handle for the activity and the context.
@@ -220,9 +223,51 @@ public class SaveDialog extends DialogFragment {
         TextView storagePermissionTextView = alertDialog.findViewById(R.id.storage_permission_textview);
         Button saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
 
+        // Set the file size text view.
+        fileSizeTextView.setText(fileSizeString);
+
+        // Create a file name string.
+        String fileName = "";
+
+        // Set the file name according to the type.
+        switch (saveType) {
+            case StoragePermissionDialog.SAVE_URL:
+                // Use the file name from the content disposition.
+                fileName = contentDispositionFileNameString;
+                break;
+
+            case StoragePermissionDialog.SAVE_AS_ARCHIVE:
+                // Use an archive name ending in `.mht`.
+                fileName = getString(R.string.webpage_mht);
+                break;
+
+            case StoragePermissionDialog.SAVE_AS_IMAGE:
+                // Use a file name ending in `.png`.
+                fileName = getString(R.string.webpage_png);
+                break;
+        }
+
+        // Save the file name as the default file name.  This must be final to be used in the lambda below.
+        final String defaultFileName = fileName;
+
+        // Instantiate the download location helper.
+        DownloadLocationHelper downloadLocationHelper = new DownloadLocationHelper();
+
+        // Get the default file path.
+        String defaultFilePath = downloadLocationHelper.getDownloadLocation(context) + "/" + defaultFileName;
+
+        // Populate the file name edit text.  This must be done before the text change listener is created below so that the file size isn't requested again.
+        fileNameEditText.setText(defaultFilePath);
+
+        // Move the cursor to the end of the default file path.
+        fileNameEditText.setSelection(defaultFilePath.length());
+
         // Modify the layout based on the save type.
         if (saveType == StoragePermissionDialog.SAVE_URL) {  // A URL is being saved.
-            // Update the status of the save button whe the URL changes.
+            // Populate the URL edit text.
+            urlEditText.setText(urlString);
+
+            // Update the file size and the status of the save button when the URL changes.
             urlEditText.addTextChangedListener(new TextWatcher() {
                 @Override
                 public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
@@ -248,15 +293,12 @@ public class SaveDialog extends DialogFragment {
                     fileSizeTextView.setText("");
 
                     // Get the file size for the current URL.
-                    getUrlSize = new GetUrlSize(context, alertDialog, userAgent, cookiesEnabled).execute(urlToSave);
+                    getUrlSize = new GetUrlSize(context, alertDialog, userAgentString, cookiesEnabled).execute(urlToSave);
 
                     // Enable the save button if the URL and file name are populated.
                     saveButton.setEnabled(!urlToSave.isEmpty() && !fileNameEditText.getText().toString().isEmpty());
                 }
             });
-
-            // Populate the URL edit text.
-            urlEditText.setText(url);
         } else {  // An archive or an image is being saved.
             // Hide the URL edit text and the file size text view.
             urlEditText.setVisibility(View.GONE);
@@ -303,56 +345,6 @@ public class SaveDialog extends DialogFragment {
             }
         });
 
-        // Create a file name string.
-        String fileName = "";
-
-        // Set the file name according to the type.
-        switch (saveType) {
-            case StoragePermissionDialog.SAVE_URL:
-                // Convert the URL to a URI.
-                Uri uri = Uri.parse(url);
-
-                // Get the last path segment.
-                String lastPathSegment = uri.getLastPathSegment();
-
-                // Use a default file name if the last path segment is null.
-                if (lastPathSegment == null) {
-                    lastPathSegment = getString(R.string.file);
-                }
-
-                // Use the last path segment as the file name.
-                fileName = lastPathSegment;
-                break;
-
-            case StoragePermissionDialog.SAVE_AS_ARCHIVE:
-                fileName = getString(R.string.webpage_mht);
-                break;
-
-            case StoragePermissionDialog.SAVE_AS_IMAGE:
-                fileName = getString(R.string.webpage_png);
-                break;
-        }
-
-        // Save the file name as the default file name.  This must be final to be used in the lambda below.
-        final String defaultFileName = fileName;
-
-        // Instantiate the download location helper.
-        DownloadLocationHelper downloadLocationHelper = new DownloadLocationHelper();
-
-        // Get the default file path.
-        String defaultFilePath = downloadLocationHelper.getDownloadLocation(context) + "/" + defaultFileName;
-
-        // Hide the storage permission text view if the permission has already been granted.
-        if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
-            storagePermissionTextView.setVisibility(View.GONE);
-        }
-
-        // Populate the file name edit text.
-        fileNameEditText.setText(defaultFilePath);
-
-        // Move the cursor to the end of the default file path.
-        fileNameEditText.setSelection(defaultFilePath.length());
-
         // Handle clicks on the browse button.
         browseButton.setOnClickListener((View view) -> {
             // Create the file picker intent.
@@ -376,6 +368,11 @@ public class SaveDialog extends DialogFragment {
             activity.startActivityForResult(browseIntent, MainWebViewActivity.BROWSE_SAVE_WEBPAGE_REQUEST_CODE);
         });
 
+        // Hide the storage permission text view if the permission has already been granted.
+        if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
+            storagePermissionTextView.setVisibility(View.GONE);
+        }
+
         // Return the alert dialog.
         return alertDialog;
     }
index b978a02d48b77759cf291f47f6306c9ffcb49596..f30d71a8f42ba4f184ffcd8ba2c54564f42c60c2 100644 (file)
@@ -245,29 +245,29 @@ public class ProxyHelper {
         // Define a proxy variable.
         Proxy proxy;
 
-        // Set the proxy according to the current proxy mode
+        // Get the proxy according to the current proxy mode.
         switch (MainWebViewActivity.proxyMode) {
             case (ProxyHelper.TOR):
                 if (Build.VERSION.SDK_INT >= 21) {
-                    // Set the socket address to be localhost port 9050.
+                    // Use localhost port 9050 as the socket address.
                     SocketAddress torSocketAddress = new InetSocketAddress("localhost", 9050);
 
-                    // Set a SOCKS proxy.
+                    // Create a SOCKS proxy.
                     proxy = new Proxy(Proxy.Type.SOCKS, torSocketAddress);
                 } else {
-                    // Set the socket address to be localhost port 8118.
+                    // Use localhost port 8118 as the socket address.
                     SocketAddress oldTorSocketAddress = new InetSocketAddress("localhost", 8118);
 
-                    // Set an HTTP proxy.
+                    // Create an HTTP proxy.
                     proxy = new Proxy(Proxy.Type.HTTP, oldTorSocketAddress);
                 }
                 break;
 
             case (ProxyHelper.I2P):
-                // Set the socket address to be localhost port 4444.
+                // Use localhost port 4444 as the socket address.
                 SocketAddress i2pSocketAddress = new InetSocketAddress("localhost", 4444);
 
-                // Set an HTTP proxy.
+                // Create an HTTP proxy.
                 proxy = new Proxy(Proxy.Type.HTTP, i2pSocketAddress);
                 break;
 
@@ -283,18 +283,18 @@ public class ProxyHelper {
                     // Convert the custom proxy URL string to a URI.
                     Uri customProxyUri = Uri.parse(customProxyUrlString);
 
-                    // Set the socket address.
+                    // Get the custom socket address.
                     SocketAddress customSocketAddress = new InetSocketAddress(customProxyUri.getHost(), customProxyUri.getPort());
 
                     // Get the custom proxy scheme.
                     String customProxyScheme = customProxyUri.getScheme();
 
-                    // Set the proxy according to the scheme.
+                    // Create a proxy according to the scheme.
                     if ((customProxyScheme != null) && customProxyScheme.startsWith("socks")) {  // A SOCKS proxy is specified.
-                        // Set a SOCKS proxy.
+                        // Create a SOCKS proxy.
                         proxy = new Proxy(Proxy.Type.SOCKS, customSocketAddress);
                     } else {  // A SOCKS proxy is not specified.
-                        // Set an HTTP proxy.
+                        // Create an HTTP proxy.
                         proxy = new Proxy(Proxy.Type.HTTP, customSocketAddress);
                     }
                 } catch (Exception exception) {  // The custom proxy cannot be parsed.
@@ -304,10 +304,9 @@ public class ProxyHelper {
                 break;
 
             default:  // No proxy is in use.
-                // Set a direct proxy.
+                // Create a direct proxy.
                 proxy = Proxy.NO_PROXY;
                 break;
-
         }
 
         // Return the proxy.