Allow saving of `data:` URLs. https://redmine.stoutner.com/issues/596
authorSoren Stoutner <soren@stoutner.com>
Mon, 12 Oct 2020 20:25:04 +0000 (13:25 -0700)
committerSoren Stoutner <soren@stoutner.com>
Mon, 12 Oct 2020 20:25:04 +0000 (13:25 -0700)
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/asynctasks/PrepareSaveDialog.java
app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveUrl.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java

index cdb2c9fb105b737c6f4931df2091689a020d3948..cbf7c1e82e4720370c1fd09dba52242aa9648103 100644 (file)
@@ -2143,7 +2143,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
     @Override
     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
-        // Store the hit test result.
+        // Get the hit test result.
         final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
 
         // Define the URL strings.
@@ -2231,8 +2231,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Get the image URL.
                 imageUrl = hitTestResult.getExtra();
 
-                // Set the image URL as the title of the context menu.
-                menu.setHeaderTitle(imageUrl);
+                // Remove the incorrect lint warning below that the image URL might be null.
+                assert imageUrl != null;
+
+                // Set the context menu title.
+                if (imageUrl.startsWith("data:")) {  // The image data is contained in within the URL, making it exceedingly long.
+                    // Truncate the image URL before making it the title.
+                    menu.setHeaderTitle(imageUrl.substring(0, 100));
+                } else {  // The image URL does not contain the full image data.
+                    // Set the image URL as the title of the context menu.
+                    menu.setHeaderTitle(imageUrl);
+                }
 
                 // Add an Open in New Tab entry.
                 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
@@ -3031,7 +3040,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     @Override
-    public void onSaveWebpage(int saveType, DialogFragment dialogFragment) {
+    public void onSaveWebpage(int saveType, String originalUrlString, DialogFragment dialogFragment) {
         // Get the dialog.
         Dialog dialog = dialogFragment.getDialog();
 
@@ -3042,8 +3051,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         EditText urlEditText = dialog.findViewById(R.id.url_edittext);
         EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
 
-        // Get the strings from the edit texts.
-        saveWebpageUrl = urlEditText.getText().toString();
+        // Store the URL.
+        if ((originalUrlString != null) && originalUrlString.startsWith("data:")) {
+            // Save the original URL.
+            saveWebpageUrl = originalUrlString;
+        } else {
+            // Get the URL from the edit text, which may have been modified.
+            saveWebpageUrl = urlEditText.getText().toString();
+        }
+
+        // Get the file path from the edit text.
         saveWebpageFilePath = fileNameEditText.getText().toString();
 
         // Check to see if the storage permission is needed.
@@ -3057,7 +3074,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 case StoragePermissionDialog.SAVE_ARCHIVE:
                     // Save the webpage archive.
-                    saveWebpageArchive();
+                    saveWebpageArchive(saveWebpageFilePath);
                     break;
 
                 case StoragePermissionDialog.SAVE_IMAGE:
@@ -3065,6 +3082,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute();
                     break;
             }
+
+            // Reset the strings.
+            saveWebpageUrl = "";
+            saveWebpageFilePath = "";
         } else {  // The storage permission has not been granted.
             // Get the external private directory file.
             File externalPrivateDirectoryFile = getExternalFilesDir(null);
@@ -3086,7 +3107,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                     case StoragePermissionDialog.SAVE_ARCHIVE:
                         // Save the webpage archive.
-                        saveWebpageArchive();
+                        saveWebpageArchive(saveWebpageFilePath);
                         break;
 
                     case StoragePermissionDialog.SAVE_IMAGE:
@@ -3094,6 +3115,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute();
                         break;
                 }
+
+                // Reset the strings.
+                saveWebpageUrl = "";
+                saveWebpageFilePath = "";
             } else {  // The file path is in a public directory.
                 // Check if the user has previously denied the storage permission.
                 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
@@ -3131,9 +3156,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         // Display an error snackbar.
                         Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
                     }
-
-                    // Reset the open file path.
-                    openFilePath = "";
                     break;
 
                 case StoragePermissionDialog.SAVE_URL:
@@ -3145,24 +3167,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         // Display an error snackbar.
                         Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
                     }
-
-                    // Reset the save strings.
-                    saveWebpageUrl = "";
-                    saveWebpageFilePath = "";
                     break;
 
                 case StoragePermissionDialog.SAVE_ARCHIVE:
                     // Check to see if the storage permission was granted.  If the dialog was canceled the grant results will be empty.
                     if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {  // The storage permission was granted.
                         // Save the webpage archive.
-                        saveWebpageArchive();
+                        saveWebpageArchive(saveWebpageFilePath);
                     } else {  // The storage permission was not granted.
                         // Display an error snackbar.
                         Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
                     }
-
-                    // Reset the save webpage file path.
-                    saveWebpageFilePath = "";
                     break;
 
                 case StoragePermissionDialog.SAVE_IMAGE:
@@ -3174,11 +3189,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         // Display an error snackbar.
                         Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
                     }
-
-                    // Reset the save webpage file path.
-                    saveWebpageFilePath = "";
                     break;
             }
+
+            // Reset the strings.
+            openFilePath = "";
+            saveWebpageUrl = "";
+            saveWebpageFilePath = "";
         }
     }
 
@@ -4866,17 +4883,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         appBarLayout.setExpanded(true);
     }
 
-    private void saveWebpageArchive() {
+    private void saveWebpageArchive(String filePath) {
         // Save the webpage archive.
-        currentWebView.saveWebArchive(saveWebpageFilePath);
+        currentWebView.saveWebArchive(filePath);
 
         // Display a snackbar.
-        Snackbar saveWebpageArchiveSnackbar = Snackbar.make(currentWebView, getString(R.string.file_saved) + "  " + saveWebpageFilePath, Snackbar.LENGTH_SHORT);
+        Snackbar saveWebpageArchiveSnackbar = Snackbar.make(currentWebView, getString(R.string.file_saved) + "  " + filePath, Snackbar.LENGTH_SHORT);
 
         // Add an open option to the snackbar.
         saveWebpageArchiveSnackbar.setAction(R.string.open, (View view) -> {
             // Get a file for the file name string.
-            File file = new File(saveWebpageFilePath);
+            File file = new File(filePath);
 
             // Declare a file URI variable.
             Uri fileUri;
@@ -4906,9 +4923,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Show the snackbar.
         saveWebpageArchiveSnackbar.show();
-
-        // Reset the save Webpage file path.
-        saveWebpageFilePath = "";
     }
 
     private void clearAndExit() {
index 641bbe07da626092faa0987833ab6979f70bc38f..9e8b1fd83b39d4e366afdbdc10e1582a3fe6eba6 100644 (file)
@@ -79,87 +79,106 @@ public class PrepareSaveDialog extends AsyncTask<String, Void, String[]> {
         // Get the URL string.
         urlString = urlToSave[0];
 
-        // Define the file name string.
+        // Define the strings.
+        String formattedFileSize;
         String fileNameString;
 
-        // Initialize the formatted file size string.
-        String formattedFileSize = context.getString(R.string.unknown_size);
+        // Populate the file size and name strings.
+        if (urlString.startsWith("data:")) {  // The URL contains the entire data of an image.
+            // Remove `data:` from the beginning of the URL.
+            String urlWithoutData = urlString.substring(5);
 
-        // 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);
+            // Get the URL MIME type, which end with a `;`.
+            String urlMimeType = urlWithoutData.substring(0, urlWithoutData.indexOf(";"));
 
-            // Instantiate the proxy helper.
-            ProxyHelper proxyHelper = new ProxyHelper();
+            // Get the Base64 data, which begins after a `,`.
+            String base64DataString = urlWithoutData.substring(urlWithoutData.indexOf(",") + 1);
 
-            // Get the current proxy.
-            Proxy proxy = proxyHelper.getCurrentProxy(context);
+            // Calculate the file size of the data URL.  Each Base64 character represents 6 bits.
+            formattedFileSize = NumberFormat.getInstance().format(base64DataString.length() * 3 / 4) + " " + context.getString(R.string.bytes);
 
-            // Open a connection to the URL.  No data is actually sent at this point.
-            HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
+            // Set the file name according to the MIME type.
+            fileNameString = context.getString(R.string.file) + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(urlMimeType);
+        } else {  // The URL refers to the location of the data.
+            // Initialize the formatted file size string.
+            formattedFileSize = context.getString(R.string.unknown_size);
 
-            // Add the user agent to the header property.
-            httpUrlConnection.setRequestProperty("User-Agent", userAgent);
+            // 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);
 
-            // Add the cookies if they are enabled.
-            if (cookiesEnabled) {
-                // Get the cookies for the current domain.
-                String cookiesString = CookieManager.getInstance().getCookie(url.toString());
+                // Instantiate the proxy helper.
+                ProxyHelper proxyHelper = new ProxyHelper();
 
-                // only add the cookies if they are not null.
-                if (cookiesString != null) {
-                    // Add the cookies to the header property.
-                    httpUrlConnection.setRequestProperty("Cookie", cookiesString);
-                }
-            }
+                // Get the current proxy.
+                Proxy proxy = proxyHelper.getCurrentProxy(context);
 
-            // 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, null);
-                } else {  // The response code is not an error message.
-                    // Get the headers.
-                    String contentLengthString = httpUrlConnection.getHeaderField("Content-Length");
-                    String contentDispositionString = httpUrlConnection.getHeaderField("Content-Disposition");
-                    String contentTypeString = httpUrlConnection.getContentType();
-
-                    // Remove anything after the MIME type in the content type string.
-                    if (contentTypeString.contains(";")) {
-                        // Remove everything beginning with the `;`.
-                        contentTypeString = contentTypeString.substring(0, contentTypeString.indexOf(";"));
-                    }
+                // Open a connection to the URL.  No data is actually sent at this point.
+                HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
 
-                    // 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);
+                // Add the user agent to the header property.
+                httpUrlConnection.setRequestProperty("User-Agent", userAgent);
 
-                        // Format the file size.
-                        formattedFileSize = NumberFormat.getInstance().format(fileSize) + " " + context.getString(R.string.bytes);
+                // 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);
                     }
+                }
 
-                    // Get the file name string from the content disposition.
-                    fileNameString = getFileNameFromHeaders(context, contentDispositionString, contentTypeString, urlString);
+                // 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, null);
+                    } else {  // The response code is not an error message.
+                        // Get the headers.
+                        String contentLengthString = httpUrlConnection.getHeaderField("Content-Length");
+                        String contentDispositionString = httpUrlConnection.getHeaderField("Content-Disposition");
+                        String contentTypeString = httpUrlConnection.getContentType();
+
+                        // Remove anything after the MIME type in the content type string.
+                        if (contentTypeString.contains(";")) {
+                            // Remove everything beginning with the `;`.
+                            contentTypeString = contentTypeString.substring(0, contentTypeString.indexOf(";"));
+                        }
+
+                        // 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 = getFileNameFromHeaders(context, contentDispositionString, contentTypeString, urlString);
+                    }
+                } finally {
+                    // Disconnect the HTTP URL connection.
+                    httpUrlConnection.disconnect();
                 }
-            } 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);
+            } 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, null);
+                // Set the file name according to the URL.
+                fileNameString = getFileNameFromUrl(context, urlString, null);
+            }
         }
 
         // Return the formatted file size and name as a string array.
index ad200216751887d0004e67dcab866f6505804918..10a956091a6586e12b2e869ead094677d348d8bb 100644 (file)
@@ -26,6 +26,7 @@ import android.content.Intent;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
+import android.util.Base64;
 import android.view.View;
 import android.webkit.CookieManager;
 import android.webkit.MimeTypeMap;
@@ -109,120 +110,134 @@ public class SaveUrl extends AsyncTask<String, Long, String> {
         // Define a save disposition string.
         String saveDisposition = SUCCESS;
 
-        // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
         try {
-            // Get the URL from the calling activity.
-            URL url = new URL(urlToSave[0]);
+            // Get the file.
+            File file = new File(filePathString);
 
-            // Instantiate the proxy helper.
-            ProxyHelper proxyHelper = new ProxyHelper();
+            // Delete the file if it exists.
+            if (file.exists()) {
+                //noinspection ResultOfMethodCallIgnored
+                file.delete();
+            }
 
-            // Get the current proxy.
-            Proxy proxy = proxyHelper.getCurrentProxy(context);
+            // Create a new file.
+            //noinspection ResultOfMethodCallIgnored
+            file.createNewFile();
 
-            // Open a connection to the URL.  No data is actually sent at this point.
-            HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
+            // Create an output file stream.
+            OutputStream fileOutputStream = new FileOutputStream(file);
 
-            // Add the user agent to the header property.
-            httpUrlConnection.setRequestProperty("User-Agent", userAgent);
+            // Save the URL.
+            if (urlToSave[0].startsWith("data:")) {  // The URL contains the entire data of an image.
+                // Get the Base64 data, which begins after a `,`.
+                String base64DataString = urlToSave[0].substring(urlToSave[0].indexOf(",") + 1);
 
-            // Add the cookies if they are enabled.
-            if (cookiesEnabled) {
-                // Get the cookies for the current domain.
-                String cookiesString = CookieManager.getInstance().getCookie(url.toString());
+                // Decode the Base64 string to a byte array.
+                byte[] base64DecodedDataByteArray = Base64.decode(base64DataString, Base64.DEFAULT);
 
-                // Only add the cookies if they are not null.
-                if (cookiesString != null) {
-                    // Add the cookies to the header property.
-                    httpUrlConnection.setRequestProperty("Cookie", cookiesString);
-                }
-            }
+                // Write the Base64 byte array to the file output stream.
+                fileOutputStream.write(base64DecodedDataByteArray);
 
-            // 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 content length header, which causes the connection to the server to be made.
-                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 an long.
-                    fileSize = Long.parseLong(contentLengthString);
-                } else {  // The content length is null.
-                    // Set the file size to be `-1`.
-                    fileSize = -1;
-                }
+                // Close the file output stream.
+                fileOutputStream.close();
+            } else {  // The URL points to the data location on the internet.
+                // Get the URL from the calling activity.
+                URL url = new URL(urlToSave[0]);
+
+                // Instantiate the proxy helper.
+                ProxyHelper proxyHelper = new ProxyHelper();
+
+                // Get the current proxy.
+                Proxy proxy = proxyHelper.getCurrentProxy(context);
 
-                // Get the response body stream.
-                InputStream inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
+                // Open a connection to the URL.  No data is actually sent at this point.
+                HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
 
-                // Get the file.
-                File file = new File(filePathString);
+                // Add the user agent to the header property.
+                httpUrlConnection.setRequestProperty("User-Agent", userAgent);
 
-                // Delete the file if it exists.
-                if (file.exists()) {
-                    //noinspection ResultOfMethodCallIgnored
-                    file.delete();
+                // 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);
+                    }
                 }
 
-                // Create a new file.
-                //noinspection ResultOfMethodCallIgnored
-                file.createNewFile();
+                // 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 content length header, which causes the connection to the server to be made.
+                    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 an long.
+                        fileSize = Long.parseLong(contentLengthString);
+                    } else {  // The content length is null.
+                        // Set the file size to be `-1`.
+                        fileSize = -1;
+                    }
 
-                // Create an output file stream.
-                OutputStream outputStream = new FileOutputStream(file);
+                    // Get the response body stream.
+                    InputStream inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
 
-                // Initialize the conversion buffer byte array.
-                byte[] conversionBufferByteArray = new byte[1024];
+                    // Initialize the conversion buffer byte array.
+                    byte[] conversionBufferByteArray = new byte[1024];
 
-                // Initialize the downloaded kilobytes counter.
-                long downloadedKilobytesCounter = 0;
+                    // Initialize the downloaded kilobytes counter.
+                    long downloadedKilobytesCounter = 0;
 
-                // Define the buffer length variable.
-                int bufferLength;
+                    // Define the buffer length variable.
+                    int bufferLength;
 
-                // Attempt to read data from the input stream and store it in the output stream.  Also store the amount of data read in the buffer length variable.
-                while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) {  // Proceed while the amount of data stored in the buffer in > 0.
-                    // Write the contents of the conversion buffer to the output stream.
-                    outputStream.write(conversionBufferByteArray, 0, bufferLength);
+                    // Attempt to read data from the input stream and store it in the output stream.  Also store the amount of data read in the buffer length variable.
+                    while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) {  // Proceed while the amount of data stored in the buffer in > 0.
+                        // Write the contents of the conversion buffer to the file output stream.
+                        fileOutputStream.write(conversionBufferByteArray, 0, bufferLength);
 
-                    // Update the file download progress snackbar.
-                    if (fileSize == -1) {  // The file size is unknown.
-                        // Negatively update the downloaded kilobytes counter.
-                        downloadedKilobytesCounter = downloadedKilobytesCounter - bufferLength;
+                        // Update the file download progress snackbar.
+                        if (fileSize == -1) {  // The file size is unknown.
+                            // Negatively update the downloaded kilobytes counter.
+                            downloadedKilobytesCounter = downloadedKilobytesCounter - bufferLength;
 
-                        publishProgress(downloadedKilobytesCounter);
-                    } else {  // The file size is known.
-                        // Update the downloaded kilobytes counter.
-                        downloadedKilobytesCounter = downloadedKilobytesCounter + bufferLength;
+                            publishProgress(downloadedKilobytesCounter);
+                        } else {  // The file size is known.
+                            // Update the downloaded kilobytes counter.
+                            downloadedKilobytesCounter = downloadedKilobytesCounter + bufferLength;
 
-                        // Calculate the download percentage.
-                        long downloadPercentage = (downloadedKilobytesCounter * 100) / fileSize;
+                            // Calculate the download percentage.
+                            long downloadPercentage = (downloadedKilobytesCounter * 100) / fileSize;
 
-                        // Update the download percentage.
-                        publishProgress(downloadPercentage);
+                            // Update the download percentage.
+                            publishProgress(downloadPercentage);
+                        }
                     }
-                }
 
-                // Close the input stream.
-                inputStream.close();
+                    // Close the input stream.
+                    inputStream.close();
 
-                // Close the output stream.
-                outputStream.close();
+                    // Close the file output stream.
+                    fileOutputStream.close();
 
-                // Create a media scanner intent, which adds items like pictures to Android's recent file list.
-                Intent mediaScannerIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+                    // Create a media scanner intent, which adds items like pictures to Android's recent file list.
+                    Intent mediaScannerIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
 
-                // Add the URI to the media scanner intent.
-                mediaScannerIntent.setData(Uri.fromFile(file));
+                    // Add the URI to the media scanner intent.
+                    mediaScannerIntent.setData(Uri.fromFile(file));
 
-                // Make it so.
-                activity.sendBroadcast(mediaScannerIntent);
-            } finally {
-                // Disconnect the HTTP URL connection.
-                httpUrlConnection.disconnect();
+                    // Make it so.
+                    activity.sendBroadcast(mediaScannerIntent);
+                } finally {
+                    // Disconnect the HTTP URL connection.
+                    httpUrlConnection.disconnect();
+                }
             }
         } catch (Exception exception) {
             // Store the error in the save disposition string.
index 02576db0b9e86b5fd23ff9b11e85165fb48beae8..57d9baa219777afa489a6991dc5df0a29bf26930 100644 (file)
@@ -35,6 +35,7 @@ import android.os.Bundle;
 import android.os.Environment;
 import android.provider.DocumentsContract;
 import android.text.Editable;
+import android.text.InputType;
 import android.text.TextWatcher;
 import android.view.View;
 import android.view.WindowManager;
@@ -62,7 +63,7 @@ public class SaveWebpageDialog extends DialogFragment {
 
     // The public interface is used to send information back to the parent activity.
     public interface SaveWebpageListener {
-        void onSaveWebpage(int saveType, DialogFragment dialogFragment);
+        void onSaveWebpage(int saveType, String originalUrlString, DialogFragment dialogFragment);
     }
 
     // Define the get URL size AsyncTask.  This allows previous instances of the task to be cancelled if a new one is run.
@@ -185,7 +186,7 @@ public class SaveWebpageDialog extends DialogFragment {
         // Set the save button listener.
         dialogBuilder.setPositiveButton(R.string.save, (DialogInterface dialog, int which) -> {
             // Return the dialog fragment to the parent activity.
-            saveWebpageListener.onSaveWebpage(saveType, this);
+            saveWebpageListener.onSaveWebpage(saveType, urlString, this);
         });
 
         // Create an alert dialog from the builder.
@@ -232,8 +233,23 @@ public class SaveWebpageDialog extends DialogFragment {
 
         // Modify the layout based on the save type.
         if (saveType == StoragePermissionDialog.SAVE_URL) {  // A URL is being saved.
-            // Populate the URL edit text.  This must be done before the text change listener is created below so that the file size isn't requested again.
-            urlEditText.setText(urlString);
+            // Remove the incorrect lint error below that the URL string might be null.
+            assert urlString != null;
+
+            // Populate the URL edit text according to the type.  This must be done before the text change listener is created below so that the file size isn't requested again.
+            if (urlString.startsWith("data:")) {  // The URL contains the entire data of an image.
+                // Get a substring of the data URL with the first 100 characters.  Otherwise, the user interface will freeze while trying to layout the edit text.
+                String urlSubstring = urlString.substring(0, 100) + "…";
+
+                // Populate the URL edit text with the truncated URL.
+                urlEditText.setText(urlSubstring);
+
+                // Disable the editing of the URL edit text.
+                urlEditText.setInputType(InputType.TYPE_NULL);
+            } else {  // The URL contains a reference to the location of the data.
+                // Populate the URL edit text with the full URL.
+                urlEditText.setText(urlString);
+            }
 
             // Update the file size and the status of the save button when the URL changes.
             urlEditText.addTextChangedListener(new TextWatcher() {