import android.webkit.WebViewClient;
import android.webkit.WebViewDatabase;
import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
import android.widget.CursorAdapter;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
// Consume the event.
return true;
- } else if (menuItemId == R.id.save_image) { // Save image.
+ } else if (menuItemId == R.id.save_archive) {
// Instantiate the save dialog.
- DialogFragment saveImageFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_IMAGE, null, null, getString(R.string.webpage_png), null,
+ DialogFragment saveArchiveFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_ARCHIVE, currentWebView.getCurrentUrl(), null, null, null,
false);
// Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
- saveImageFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
+ saveArchiveFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
- // Consume the event.
return true;
- } else if (menuItemId == R.id.save_archive) {
- /* TODO.
- try {
- // Create an MHT file.
- File mhtFile = File.createTempFile("mht_file", ".mht", getCacheDir());
-
- // Populate the MHT file.
- currentWebView.saveWebArchive(mhtFile.toString());
-
- // Check the file length.
- Log.i("MHT", "MHT file size: " + mhtFile.length());
- } catch (Exception exception){
- Log.i("MHT", "MHT exception: " + exception.toString());
- }
+ } else if (menuItemId == R.id.save_image) { // Save image.
+ // Instantiate the save dialog.
+ DialogFragment saveImageFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_IMAGE, currentWebView.getCurrentUrl(), null, null, null,
+ false);
- */
+ // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
+ saveImageFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
+ // Consume the event.
return true;
} else if (menuItemId == R.id.add_to_homescreen) { // Add to homescreen.
// Instantiate the create home screen shortcut dialog.
// Remove the incorrect lint warning below that the dialog might be null.
assert dialog != null;
- // Get a handle for the file name edit text.
+ // Get handles for the views.
EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
+ CheckBox mhtCheckBox = dialog.findViewById(R.id.mht_checkbox);
// Get the file path string.
String openFilePath = fileNameEditText.getText().toString();
// Apply the domain settings. This resets the favorite icon and removes any domain settings.
applyDomainSettings(currentWebView, openFilePath, true, false, false);
- // Open the file.
- currentWebView.loadUrl(openFilePath);
+ // Open the file according to the type.
+ if (mhtCheckBox.isChecked()) { // Force opening of an MHT file.
+ try {
+ // Get the MHT file input stream.
+ InputStream mhtFileInputStream = getContentResolver().openInputStream(Uri.parse(openFilePath));
+
+ // Create a temporary MHT file.
+ File temporaryMhtFile = File.createTempFile("temporary_mht_file", ".mht", getCacheDir());
+
+ // Get a file output stream for the temporary MHT file.
+ FileOutputStream temporaryMhtFileOutputStream = new FileOutputStream(temporaryMhtFile);
+
+ // Create a transfer byte array.
+ byte[] transferByteArray = new byte[1024];
+
+ // Create an integer to track the number of bytes read.
+ int bytesRead;
+
+ // Copy the temporary MHT file input stream to the MHT output stream.
+ while ((bytesRead = mhtFileInputStream.read(transferByteArray)) > 0) {
+ temporaryMhtFileOutputStream.write(transferByteArray, 0, bytesRead);
+ }
+
+ // Flush the temporary MHT file output stream.
+ temporaryMhtFileOutputStream.flush();
+
+ // Close the streams.
+ temporaryMhtFileOutputStream.close();
+ mhtFileInputStream.close();
+
+ // Load the temporary MHT file.
+ currentWebView.loadUrl(temporaryMhtFile.toString());
+ } catch (Exception exception) {
+ // Display a snackbar.
+ Snackbar.make(currentWebView, getString(R.string.error) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
+ }
+ } else { // Let the WebView handle opening of the file.
+ // Open the file.
+ currentWebView.loadUrl(openFilePath);
+ }
}
@Override
- public void onSaveWebpage(int saveType, @Nullable String originalUrlString, DialogFragment dialogFragment) {
+ public void onSaveWebpage(int saveType, @NonNull String originalUrlString, DialogFragment dialogFragment) {
// Get the dialog.
Dialog dialog = dialogFragment.getDialog();
String saveWebpageUrl;
// Store the URL.
- if ((originalUrlString != null) && originalUrlString.startsWith("data:")) {
+ if (originalUrlString.startsWith("data:")) {
// Save the original URL.
saveWebpageUrl = originalUrlString;
} else {
new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
break;
+ case SaveWebpageDialog.SAVE_ARCHIVE:
+ try {
+ // Create a temporary MHT file.
+ File temporaryMhtFile = File.createTempFile("temporary_mht_file", ".mht", getCacheDir());
+
+ // Save the temporary MHT file.
+ currentWebView.saveWebArchive(temporaryMhtFile.toString(), false, callbackValue -> {
+ if (callbackValue != null) { // The temporary MHT file was saved successfully.
+ try {
+ // Create a temporary MHT file input stream.
+ FileInputStream temporaryMhtFileInputStream = new FileInputStream(temporaryMhtFile);
+
+ // Get an output stream for the save webpage file path.
+ OutputStream mhtOutputStream = getContentResolver().openOutputStream(Uri.parse(saveWebpageFilePath));
+
+ // Create a transfer byte array.
+ byte[] transferByteArray = new byte[1024];
+
+ // Create an integer to track the number of bytes read.
+ int bytesRead;
+
+ // Copy the temporary MHT file input stream to the MHT output stream.
+ while ((bytesRead = temporaryMhtFileInputStream.read(transferByteArray)) > 0) {
+ mhtOutputStream.write(transferByteArray, 0, bytesRead);
+ }
+
+ // Close the streams.
+ mhtOutputStream.close();
+ temporaryMhtFileInputStream.close();
+
+ // Display a snackbar.
+ Snackbar.make(currentWebView, getString(R.string.file_saved) + " " + currentWebView.getCurrentUrl(), Snackbar.LENGTH_SHORT).show();
+ } catch (Exception exception) {
+ // Display a snackbar with the exception.
+ Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
+ } finally {
+ // Delete the temporary MHT file.
+ //noinspection ResultOfMethodCallIgnored
+ temporaryMhtFile.delete();
+ }
+ } else { // There was an unspecified error while saving the temporary MHT file.
+ // Display an error snackbar.
+ Snackbar.make(currentWebView, getString(R.string.error_saving_file), Snackbar.LENGTH_INDEFINITE).show();
+ }
+ });
+ } catch (IOException ioException) {
+ // Display a snackbar with the IO exception.
+ Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + ioException.toString(), Snackbar.LENGTH_INDEFINITE).show();
+ }
+ break;
+
case SaveWebpageDialog.SAVE_IMAGE:
// Save the webpage image.
new SaveWebpageImage(this, saveWebpageFilePath, currentWebView).execute();
// Explicitly disable geolocation.
nestedScrollWebView.getSettings().setGeolocationEnabled(false);
+ // Allow loading of file:// URLs. This is necessary for opening MHT web archives, which are copies into a temporary cache location.
+ nestedScrollWebView.getSettings().setAllowFileAccess(true);
+
// Create a double-tap gesture detector to toggle full-screen mode.
GestureDetector doubleTapGestureDetector = new GestureDetector(getApplicationContext(), new GestureDetector.SimpleOnGestureListener() {
// Override `onDoubleTap()`. All other events are handled using the default settings.