From f52255e6eeb1a6be9f190e733563cc37b5d1f2b5 Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Fri, 10 Apr 2020 14:18:57 -0700 Subject: [PATCH] Use the Content-Disposition header to get file names for downloads. https://redmine.stoutner.com/issues/547 --- .../activities/MainWebViewActivity.java | 83 +++--- .../privacybrowser/asynctasks/GetUrlSize.java | 23 +- .../asynctasks/PrepareSaveDialog.java | 239 ++++++++++++++++++ .../privacybrowser/dialogs/SaveDialog.java | 119 +++++---- .../privacybrowser/helpers/ProxyHelper.java | 25 +- 5 files changed, 359 insertions(+), 130 deletions(-) create mode 100644 app/src/main/java/com/stoutner/privacybrowser/asynctasks/PrepareSaveDialog.java diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index 4fc10372..42b20656 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -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)); diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetUrlSize.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetUrlSize.java index 74968df9..5f0020d0 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetUrlSize.java +++ b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetUrlSize.java @@ -35,7 +35,7 @@ import java.net.URL; import java.text.NumberFormat; public class GetUrlSize extends AsyncTask { - // Define a weak reference for the calling context and fragment. + // Define weak references for the calling context and alert dialog. private WeakReference contextWeakReference; private WeakReference alertDialogWeakReference; @@ -45,7 +45,7 @@ public class GetUrlSize extends AsyncTask { // 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 { // 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 { // 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 { 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 { // 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 { // `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 index 00000000..9b1dcd09 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/PrepareSaveDialog.java @@ -0,0 +1,239 @@ +/* + * Copyright © 2020 Soren Stoutner . + * + * This file is part of 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 . + */ + +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 { + // Define weak references. + private WeakReference activityWeakReference; + private WeakReference contextWeakReference; + private WeakReference 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 diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.java index 9b599b53..19ff43ce 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.java @@ -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; } diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/ProxyHelper.java b/app/src/main/java/com/stoutner/privacybrowser/helpers/ProxyHelper.java index b978a02d..f30d71a8 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/helpers/ProxyHelper.java +++ b/app/src/main/java/com/stoutner/privacybrowser/helpers/ProxyHelper.java @@ -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. -- 2.43.0