package com.stoutner.privacybrowser.activities;
-import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ClipboardManager;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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 androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
-import androidx.core.content.FileProvider;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
import com.stoutner.privacybrowser.dialogs.SaveWebpageDialog;
import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
-import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog;
import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog;
import com.stoutner.privacybrowser.helpers.BlocklistHelper;
import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
-import com.stoutner.privacybrowser.helpers.FileNameHelper;
import com.stoutner.privacybrowser.helpers.ProxyHelper;
import com.stoutner.privacybrowser.views.NestedScrollWebView;
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;
public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener,
- PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, StoragePermissionDialog.StoragePermissionDialogListener,
- UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
+ PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, UrlHistoryDialog.NavigateHistoryListener,
+ WebViewTabFragment.NewTabListener {
// The executor service handles background tasks. It is accessed from `ViewSourceActivity`.
public static ExecutorService executorService = Executors.newFixedThreadPool(4);
public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
- // Start activity for result request codes. The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`.
- public final static int BROWSE_OPEN_REQUEST_CODE = 0;
- public final static int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 1;
- private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 2;
+ // Define the start activity for result request codes. The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`.
+ private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 0;
+ public final static int BROWSE_OPEN_REQUEST_CODE = 1;
+ public final static int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 2;
// The proxy mode is public static so it can be accessed from `ProxyHelper()`.
// It is also used in `onRestart()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxy()`.
private boolean sanitizeFacebookClickIds;
private boolean sanitizeTwitterAmpRedirects;
- // The file path strings are used in `onSaveWebpage()` and `onRequestPermissionResult()`
- private String openFilePath;
- private String saveWebpageUrl;
- private String saveWebpageFilePath;
-
// Declare the class views.
private FrameLayout rootFrameLayout;
private DrawerLayout drawerLayout;
PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
// Print the document.
- printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
+ printManager.print(getString(R.string.privacy_browser_webpage), printDocumentAdapter, null);
// Consume the event.
return true;
} else if (menuItemId == R.id.save_url) { // Save URL.
// 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(),
+ new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl());
// Consume the event.
return true;
- } else if (menuItemId == R.id.save_archive) { // Save archive.
- // Instantiate the save dialog.
- DialogFragment saveArchiveFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_ARCHIVE, null, null, getString(R.string.webpage_mht), null,
+ } else if (menuItemId == R.id.save_archive) {
+ // Instantiate the save dialog. TODO. Replace the hard coded file name.
+ DialogFragment saveArchiveFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_ARCHIVE, null, null, "Webpage.mht", null,
false);
// Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
saveArchiveFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
- // Consume the event.
return true;
} else if (menuItemId == R.id.save_image) { // Save image.
// Instantiate the save dialog.
- DialogFragment saveImageFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_IMAGE, null, null, getString(R.string.webpage_png), null,
+ DialogFragment saveImageFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_IMAGE, null, null, getString(R.string.webpage_png), null,
false);
// Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
// Add a Save URL entry.
menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
// 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(),
+ new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
currentWebView.getAcceptFirstPartyCookies()).execute(linkUrl);
// Consume the event.
// Add a Save Image entry.
menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
// 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(),
+ new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
currentWebView.getAcceptFirstPartyCookies()).execute(imageUrl);
// Consume the event.
// Add a Save Image entry.
menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
// 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(),
+ new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
currentWebView.getAcceptFirstPartyCookies()).execute(imageUrl);
// Consume the event.
// Add a Save URL entry.
menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
// 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(),
+ new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
currentWebView.getAcceptFirstPartyCookies()).execute(linkUrl);
// Consume the event.
assert dialog != null;
// Get handles for the views in the dialog fragment.
- EditText createFolderNameEditText = dialog.findViewById(R.id.create_folder_name_edittext);
- RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.create_folder_default_icon_radiobutton);
- ImageView folderIconImageView = dialog.findViewById(R.id.create_folder_default_icon);
+ EditText folderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
+ RadioButton defaultIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
+ ImageView defaultIconImageView = dialog.findViewById(R.id.default_icon_imageview);
// Get new folder name string.
- String folderNameString = createFolderNameEditText.getText().toString();
+ String folderNameString = folderNameEditText.getText().toString();
// Create a folder icon bitmap.
Bitmap folderIconBitmap;
// Set the folder icon bitmap according to the dialog.
- if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
+ if (defaultIconRadioButton.isChecked()) { // Use the default folder icon.
// Get the default folder icon drawable.
- Drawable folderIconDrawable = folderIconImageView.getDrawable();
+ Drawable folderIconDrawable = defaultIconImageView.getDrawable();
// Convert the folder icon drawable to a bitmap drawable.
BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
assert dialog != null;
// Get handles for the views from the dialog.
- EditText editFolderNameEditText = dialog.findViewById(R.id.edit_folder_name_edittext);
- RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_current_icon_radiobutton);
- RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_default_icon_radiobutton);
- ImageView defaultFolderIconImageView = dialog.findViewById(R.id.edit_folder_default_icon_imageview);
+ RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton);
+ RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
+ ImageView defaultFolderIconImageView = dialog.findViewById(R.id.default_icon_imageview);
+ EditText editFolderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
// Get the new folder name.
String newFolderNameString = editFolderNameEditText.getText().toString();
// Update the folder icon in the database.
bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
} else { // The folder icon and the name have changed.
- // Get the new folder icon `Bitmap`.
+ // Get the new folder icon bitmap.
Bitmap folderIconBitmap;
if (defaultFolderIconRadioButton.isChecked()) {
// Get the default folder icon drawable.
}
break;
- case BROWSE_SAVE_WEBPAGE_REQUEST_CODE:
+ case BROWSE_OPEN_REQUEST_CODE:
// Don't do anything if the user pressed back from the file picker.
if (resultCode == Activity.RESULT_OK) {
- // Get a handle for the save dialog fragment.
- DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_dialog));
+ // Get a handle for the open dialog fragment.
+ DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open));
// Only update the file name if the dialog still exists.
- if (saveWebpageDialogFragment != null) {
- // Get a handle for the save webpage dialog.
- Dialog saveWebpageDialog = saveWebpageDialogFragment.getDialog();
+ if (openDialogFragment != null) {
+ // Get a handle for the open dialog.
+ Dialog openDialog = openDialogFragment.getDialog();
// Remove the incorrect lint warning below that the dialog might be null.
- assert saveWebpageDialog != null;
+ assert openDialog != null;
// Get a handle for the file name edit text.
- EditText fileNameEditText = saveWebpageDialog.findViewById(R.id.file_name_edittext);
- TextView fileExistsWarningTextView = saveWebpageDialog.findViewById(R.id.file_exists_warning_textview);
-
- // Instantiate the file name helper.
- FileNameHelper fileNameHelper = new FileNameHelper();
+ EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext);
- // Get the file path if it isn't null.
- if (returnedIntent.getData() != null) {
- // Convert the file name URI to a file name path.
- String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData());
+ // Get the file name URI from the intent.
+ Uri fileNameUri = returnedIntent.getData();
- // Set the file name path as the text of the file name edit text.
- fileNameEditText.setText(fileNamePath);
+ // Get the file name string from the URI.
+ String fileNameString = fileNameUri.toString();
- // Move the cursor to the end of the file name edit text.
- fileNameEditText.setSelection(fileNamePath.length());
+ // Set the file name text.
+ fileNameEditText.setText(fileNameString);
- // Hide the file exists warning.
- fileExistsWarningTextView.setVisibility(View.GONE);
- }
+ // Move the cursor to the end of the file name edit text.
+ fileNameEditText.setSelection(fileNameString.length());
}
}
break;
- case BROWSE_OPEN_REQUEST_CODE:
+ case BROWSE_SAVE_WEBPAGE_REQUEST_CODE:
// Don't do anything if the user pressed back from the file picker.
if (resultCode == Activity.RESULT_OK) {
- // Get a handle for the open dialog fragment.
- DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open));
+ // Get a handle for the save dialog fragment.
+ DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_dialog));
// Only update the file name if the dialog still exists.
- if (openDialogFragment != null) {
- // Get a handle for the open dialog.
- Dialog openDialog = openDialogFragment.getDialog();
+ if (saveWebpageDialogFragment != null) {
+ // Get a handle for the save webpage dialog.
+ Dialog saveWebpageDialog = saveWebpageDialogFragment.getDialog();
// Remove the incorrect lint warning below that the dialog might be null.
- assert openDialog != null;
+ assert saveWebpageDialog != null;
// Get a handle for the file name edit text.
- EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext);
+ EditText fileNameEditText = saveWebpageDialog.findViewById(R.id.file_name_edittext);
- // Instantiate the file name helper.
- FileNameHelper fileNameHelper = new FileNameHelper();
+ // Get the file name URI from the intent.
+ Uri fileNameUri = returnedIntent.getData();
- // Get the file path if it isn't null.
- if (returnedIntent.getData() != null) {
- // Convert the file name URI to a file name path.
- String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData());
+ // Get the file name string from the URI.
+ String fileNameString = fileNameUri.toString();
- // Set the file name path as the text of the file name edit text.
- fileNameEditText.setText(fileNamePath);
+ // Set the file name text.
+ fileNameEditText.setText(fileNameString);
- // Move the cursor to the end of the file name edit text.
- fileNameEditText.setSelection(fileNamePath.length());
- }
+ // Move the cursor to the end of the file name edit text.
+ fileNameEditText.setSelection(fileNameString.length());
}
}
break;
// 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.
- openFilePath = fileNameEditText.getText().toString();
+ String openFilePath = fileNameEditText.getText().toString();
// Apply the domain settings. This resets the favorite icon and removes any domain settings.
- applyDomainSettings(currentWebView, "file://" + openFilePath, true, false, false);
+ applyDomainSettings(currentWebView, openFilePath, true, false, false);
- // Check to see if the storage permission is needed.
- if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted.
- // Open the file.
- currentWebView.loadUrl("file://" + openFilePath);
- } else { // The storage permission has not been granted.
- // Get the external private directory file.
- File externalPrivateDirectoryFile = getExternalFilesDir(null);
-
- // Remove the incorrect lint error below that the file might be null.
- assert externalPrivateDirectoryFile != null;
-
- // Get the external private directory string.
- String externalPrivateDirectory = externalPrivateDirectoryFile.toString();
-
- // Check to see if the file path is in the external private directory.
- if (openFilePath.startsWith(externalPrivateDirectory)) { // the file path is in the external private directory.
- // Open the file.
- currentWebView.loadUrl("file://" + openFilePath);
- } 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.
- // Instantiate the storage permission alert dialog.
- DialogFragment storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(StoragePermissionDialog.OPEN);
-
- // Show the storage permission alert dialog. The permission will be requested the the dialog is closed.
- storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission));
- } else { // Show the permission request directly.
- // Request the write external storage permission. The file will be opened when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, StoragePermissionDialog.OPEN);
+ // 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, String originalUrlString, DialogFragment dialogFragment) {
+ public void onSaveWebpage(int saveType, @Nullable String originalUrlString, DialogFragment dialogFragment) {
// Get the dialog.
Dialog dialog = dialogFragment.getDialog();
// Remove the incorrect lint warning below that the dialog might be null.
assert dialog != null;
- // Get a handle for the edit texts.
- EditText dialogUrlEditText = dialog.findViewById(R.id.url_edittext);
+ // Get a handle for the file name edit text.
EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
- // 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 = dialogUrlEditText.getText().toString();
- }
-
// Get the file path from the edit text.
- saveWebpageFilePath = fileNameEditText.getText().toString();
-
- // Check to see if the storage permission is needed.
- if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted.
- //Save the webpage according to the save type.
- switch (saveType) {
- case StoragePermissionDialog.SAVE_URL:
- // Save the URL.
- new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
- break;
-
- case StoragePermissionDialog.SAVE_ARCHIVE:
- // Save the webpage archive.
- saveWebpageArchive(saveWebpageFilePath);
- break;
-
- case StoragePermissionDialog.SAVE_IMAGE:
- // Save the webpage image.
- new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute();
- break;
- }
+ String saveWebpageFilePath = fileNameEditText.getText().toString();
- // Reset the strings.
- saveWebpageUrl = "";
- saveWebpageFilePath = "";
- } else { // The storage permission has not been granted.
- // Get the external private directory file.
- File externalPrivateDirectoryFile = getExternalFilesDir(null);
-
- // Remove the incorrect lint error below that the file might be null.
- assert externalPrivateDirectoryFile != null;
-
- // Get the external private directory string.
- String externalPrivateDirectory = externalPrivateDirectoryFile.toString();
-
- // Check to see if the file path is in the external private directory.
- if (saveWebpageFilePath.startsWith(externalPrivateDirectory)) { // The file path is in the external private directory.
- // Save the webpage according to the save type.
- switch (saveType) {
- case StoragePermissionDialog.SAVE_URL:
- // Save the URL.
- new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
- break;
+ //Save the webpage according to the save type.
+ switch (saveType) {
+ case SaveWebpageDialog.SAVE_URL:
+ // Get a handle for the dialog URL edit text.
+ EditText dialogUrlEditText = dialog.findViewById(R.id.url_edittext);
- case StoragePermissionDialog.SAVE_ARCHIVE:
- // Save the webpage archive.
- saveWebpageArchive(saveWebpageFilePath);
- break;
+ // Define the save webpage URL.
+ String saveWebpageUrl;
- case StoragePermissionDialog.SAVE_IMAGE:
- // Save the webpage image.
- new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute();
- break;
+ // 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 = dialogUrlEditText.getText().toString();
}
- // 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.
- // Instantiate the storage permission alert dialog.
- DialogFragment storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(saveType);
-
- // Show the storage permission alert dialog. The permission will be requested when the dialog is closed.
- storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission));
- } else { // Show the permission request directly.
- // Request the write external storage permission according to the save type. The URL will be saved when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, saveType);
- }
- }
- }
- }
+ // Save the URL.
+ new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
+ break;
- @Override
- public void onCloseStoragePermissionDialog(int requestType) {
- // Request the write external storage permission according to the request type. The file will be opened when it finishes.
- ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestType);
+ 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);
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- //Only process the results if they exist (this method is triggered when a dialog is presented the first time for an app, but no grant results are included).
- if (grantResults.length > 0) {
- switch (requestCode) {
- case StoragePermissionDialog.OPEN:
- // 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.
- // Load the file.
- currentWebView.loadUrl("file://" + openFilePath);
- } else { // The storage permission was not granted.
- // Display an error snackbar.
- Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
- }
- break;
-
- case StoragePermissionDialog.SAVE_URL:
- // 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 raw URL.
- new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
- } else { // The storage permission was not granted.
- // Display an error snackbar.
- Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
- }
- 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(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();
- }
- break;
-
- case StoragePermissionDialog.SAVE_IMAGE:
- // 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 image.
- new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute();
- } else { // The storage permission was not granted.
- // Display an error snackbar.
- Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
- }
- break;
- }
+ // 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);
+ }
- // Reset the strings.
- openFilePath = "";
- saveWebpageUrl = "";
- saveWebpageFilePath = "";
+ // Close the streams.
+ mhtOutputStream.close();
+ temporaryMhtFileInputStream.close();
+
+ // Display a snackbar.
+ Snackbar.make(currentWebView, getString(R.string.file_saved) + " " + saveWebpageFilePath, 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();
+ break;
}
}
@Override
public void onDrawerClosed(@NonNull View drawerView) {
+ // Reset the drawer icon when the drawer is closed. Otherwise, it is an arrow if the drawer is open when the app is restarted.
+ actionBarDrawerToggle.syncState();
}
@Override
}
@Override
- public void navigateHistory(String url, int steps) {
+ public void navigateHistory(@NonNull String url, int steps) {
// Apply the domain settings.
applyDomainSettings(currentWebView, url, false, false, false);
PackageManager packageManager = getPackageManager();
// Check to see if I2P is in the list. This will throw an error and drop to the catch section if it isn't installed.
- packageManager.getPackageInfo("org.torproject.android", 0);
+ packageManager.getPackageInfo("net.i2p.android.router", 0);
} catch (PackageManager.NameNotFoundException exception) { // I2P is not installed.
// Sow the I2P not installed dialog if it is not already displayed.
if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
}
private void addNewTab(String url, boolean moveToTab) {
+ // Clear the focus from the URL edit text, so that it will be populated with the information from the new tab.
+ urlEditText.clearFocus();
+
// Get the new page number. The page numbers are 0 indexed, so the new page number will match the current count.
int newTabNumber = tabLayout.getTabCount();
if (webViewPagerAdapter.deletePage(currentTabNumber, webViewPager)) {
setCurrentWebView(currentTabNumber);
}
-
- // Expand the app bar if it is currently collapsed.
- appBarLayout.setExpanded(true);
- }
-
- private void saveWebpageArchive(String filePath) {
- // Save the webpage archive.
- currentWebView.saveWebArchive(filePath);
-
- // Display a snackbar.
- 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(filePath);
-
- // Declare a file URI variable.
- Uri fileUri;
-
- // Get the URI for the file according to the Android version.
- if (Build.VERSION.SDK_INT >= 24) { // Use a file provider.
- fileUri = FileProvider.getUriForFile(this, getString(R.string.file_provider), file);
- } else { // Get the raw file path URI.
- fileUri = Uri.fromFile(file);
- }
-
- // Get a handle for the content resolver.
- ContentResolver contentResolver = getContentResolver();
-
- // Create an open intent with `ACTION_VIEW`.
- Intent openIntent = new Intent(Intent.ACTION_VIEW);
-
- // Set the URI and the MIME type.
- openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri));
-
- // Allow the app to read the file URI.
- openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- // Show the chooser.
- startActivity(Intent.createChooser(openIntent, getString(R.string.open)));
- });
-
- // Show the snackbar.
- saveWebpageArchiveSnackbar.show();
}
private void clearAndExit() {
// 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.
// Get the file name from the content disposition.
String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
+ // Prevent the dialog from displaying if the app window is not visible.
+ // The download listener continues to function even when the WebView is paused. Attempting to display a dialog in that state leads to a crash.
+ while (!activity.getWindow().isActive()) {
+ try {
+ // The window is not active. Wait 1 second.
+ wait(1000);
+ } catch (InterruptedException e) {
+ // Do nothing.
+ }
+ }
+
// Instantiate the save dialog.
- DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent,
+ DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.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.
// Store the SSL error handler.
nestedScrollWebView.setSslErrorHandler(handler);
+ // Prevent the dialog from displaying if the app window is not visible.
+ // The SSL error handler continues to function even when the WebView is paused. Attempting to display a dialog in that state leads to a crash.
+ while (!activity.getWindow().isActive()) {
+ try {
+ // The window is not active. Wait 1 second.
+ wait(1000);
+ } catch (InterruptedException e) {
+ // Do nothing.
+ }
+ }
+
// Instantiate an SSL certificate error alert dialog.
DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error, nestedScrollWebView.getWebViewFragmentId());