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.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
+import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.print.PrintDocumentAdapter;
import android.print.PrintManager;
+import android.provider.DocumentsContract;
import android.text.Editable;
import android.text.Spanned;
import android.text.TextWatcher;
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 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;
private MenuItem navigationRequestsMenuItem;
private MenuItem optionsPrivacyMenuItem;
private MenuItem optionsRefreshMenuItem;
- private MenuItem optionsFirstPartyCookiesMenuItem;
- private MenuItem optionsThirdPartyCookiesMenuItem;
+ private MenuItem optionsCookiesMenuItem;
private MenuItem optionsDomStorageMenuItem;
private MenuItem optionsSaveFormDataMenuItem;
private MenuItem optionsClearDataMenuItem;
// Only process the URI if it contains data or it is a web search. If the user pressed the desktop icon after the app was already running the URI will be null.
if (intentUriData != null || intentStringExtra != null || isWebSearch) {
+ // Exit the full screen video if it is displayed.
+ if (displayingFullScreenVideo) {
+ // Exit full screen video mode.
+ exitFullScreenVideo();
+
+ // Reload the current WebView. Otherwise, it can display entirely black.
+ currentWebView.reload();
+ }
+
// Get the shared preferences.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
// Get handles for the class menu items.
optionsPrivacyMenuItem = menu.findItem(R.id.javascript);
optionsRefreshMenuItem = menu.findItem(R.id.refresh);
- optionsFirstPartyCookiesMenuItem = menu.findItem(R.id.first_party_cookies);
- optionsThirdPartyCookiesMenuItem = menu.findItem(R.id.third_party_cookies);
+ optionsCookiesMenuItem = menu.findItem(R.id.cookies);
optionsDomStorageMenuItem = menu.findItem(R.id.dom_storage);
optionsSaveFormDataMenuItem = menu.findItem(R.id.save_form_data); // Form data can be removed once the minimum API >= 26.
optionsClearDataMenuItem = menu.findItem(R.id.clear_data);
// Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
updatePrivacyIcons(false);
- // Only display third-party cookies if API >= 21
- optionsThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
-
// Only display the form data menu items if the API < 26.
optionsSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
optionsClearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
if (displayAdditionalAppBarIcons) {
optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
bookmarksMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- optionsFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ optionsCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
} else { //Do not display the additional icons.
optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
bookmarksMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
- optionsFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ optionsCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
}
// Replace Refresh with Stop if a URL is already loading.
optionsUltraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
optionsBlockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
- // Only modify third-party cookies if the API >= 21.
- if (Build.VERSION.SDK_INT >= 21) {
- // Set the status of the third-party cookies checkbox.
- optionsThirdPartyCookiesMenuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
-
- // Enable third-party cookies if first-party cookies are enabled.
- optionsThirdPartyCookiesMenuItem.setEnabled(cookieManager.acceptCookie());
- }
-
// Enable DOM Storage if JavaScript is enabled.
optionsDomStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
}
}
- // Set the checked status of the first party cookies menu item.
- optionsFirstPartyCookiesMenuItem.setChecked(cookieManager.acceptCookie());
+ // Set the cookies menu item checked status.
+ optionsCookiesMenuItem.setChecked(cookieManager.acceptCookie());
// Enable Clear Cookies if there are any.
optionsClearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
// Consume the event.
return true;
- } else if (menuItemId == R.id.first_party_cookies) { // First-party cookies.
+ } else if (menuItemId == R.id.cookies) { // Cookies.
// Switch the first-party cookie status.
cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
- // Store the first-party cookie status.
- currentWebView.setAcceptFirstPartyCookies(cookieManager.acceptCookie());
+ // Store the cookie status.
+ currentWebView.setAcceptCookies(cookieManager.acceptCookie());
// Update the menu checkbox.
menuItem.setChecked(cookieManager.acceptCookie());
updatePrivacyIcons(true);
// Display a snackbar.
- if (cookieManager.acceptCookie()) { // First-party cookies are enabled.
- Snackbar.make(webViewPager, R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
+ if (cookieManager.acceptCookie()) { // Cookies are enabled.
+ Snackbar.make(webViewPager, R.string.cookies_enabled, Snackbar.LENGTH_SHORT).show();
} else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is still enabled.
- Snackbar.make(webViewPager, R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
+ Snackbar.make(webViewPager, R.string.cookies_disabled, Snackbar.LENGTH_SHORT).show();
} else { // Privacy mode.
Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
}
// Reload the current WebView.
currentWebView.reload();
- // Consume the event.
- return true;
- } else if (menuItemId == R.id.third_party_cookies) { // Third-party cookies.
- // Only act if the API >= 21. Otherwise, there are no third-party cookie controls.
- if (Build.VERSION.SDK_INT >= 21) {
- // Toggle the status of thirdPartyCookiesEnabled.
- cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView));
-
- // Update the menu checkbox.
- menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
-
- // Display a snackbar.
- if (cookieManager.acceptThirdPartyCookies(currentWebView)) {
- Snackbar.make(webViewPager, R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
- } else {
- Snackbar.make(webViewPager, R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
- }
-
- // Reload the current WebView.
- currentWebView.reload();
- }
-
// Consume the event.
return true;
} else if (menuItemId == R.id.dom_storage) { // DOM storage.
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(),
- currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl());
+ new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+ currentWebView.getAcceptCookies()).execute(currentWebView.getCurrentUrl());
// Consume the event.
return true;
- } else if (menuItemId == R.id.save_archive) { // Save archive.
+ } else if (menuItemId == R.id.save_archive) {
// Instantiate the save dialog.
- DialogFragment saveArchiveFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_ARCHIVE, null, null, getString(R.string.webpage_mht), 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.
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, 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.
// Make it so.
startActivity(requestsIntent);
} else if (menuItemId == R.id.downloads) { // Downloads.
- // Launch the system Download Manager.
- Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
+ // Try the default system download manager.
+ try {
+ // Launch the default system Download Manager.
+ Intent defaultDownloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
- // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
- downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ // Launch as a new task so that the download manager and Privacy Browser show as separate windows in the recent tasks list.
+ defaultDownloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // Make it so.
- startActivity(downloadManagerIntent);
+ // Make it so.
+ startActivity(defaultDownloadManagerIntent);
+ } catch (Exception defaultDownloadManagerException) {
+ // Try a generic file manager.
+ try {
+ // Create a generic file manager intent.
+ Intent genericFileManagerIntent = new Intent(Intent.ACTION_VIEW);
+
+ // Open the download directory.
+ genericFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), DocumentsContract.Document.MIME_TYPE_DIR);
+
+ // Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
+ genericFileManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Make it so.
+ startActivity(genericFileManagerIntent);
+ } catch (Exception genericFileManagerException) {
+ // Try an alternate file manager.
+ try {
+ // Create an alternate file manager intent.
+ Intent alternateFileManagerIntent = new Intent(Intent.ACTION_VIEW);
+
+ // Open the download directory.
+ alternateFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), "resource/folder");
+
+ // Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
+ alternateFileManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Open the alternate file manager.
+ startActivity(alternateFileManagerIntent);
+ } catch (Exception alternateFileManagerException) {
+ // Display a snackbar.
+ Snackbar.make(currentWebView, R.string.no_file_manager_detected, Snackbar.LENGTH_INDEFINITE).show();
+ }
+ }
+ }
} else if (menuItemId == R.id.domains) { // Domains.
// Set the flag to reapply the domain settings on restart when returning from Domain Settings.
reapplyDomainSettingsOnRestart = true;
// 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(),
- currentWebView.getAcceptFirstPartyCookies()).execute(linkUrl);
+ new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+ currentWebView.getAcceptCookies()).execute(linkUrl);
// Consume the event.
return true;
// 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(),
- currentWebView.getAcceptFirstPartyCookies()).execute(imageUrl);
+ new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+ currentWebView.getAcceptCookies()).execute(imageUrl);
// Consume the event.
return true;
// 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(),
- currentWebView.getAcceptFirstPartyCookies()).execute(imageUrl);
+ new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+ currentWebView.getAcceptCookies()).execute(imageUrl);
// Consume the event.
return true;
// 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(),
- currentWebView.getAcceptFirstPartyCookies()).execute(linkUrl);
+ new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+ currentWebView.getAcceptCookies()).execute(linkUrl);
// Consume the event.
return true;
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.
// close the bookmarks drawer.
drawerLayout.closeDrawer(GravityCompat.END);
} else if (displayingFullScreenVideo) { // A full screen video is shown.
- // Re-enable the screen timeout.
- fullScreenVideoFrameLayout.setKeepScreenOn(false);
-
- // Unset the full screen video flag.
- displayingFullScreenVideo = false;
-
- // Remove all the views from the full screen video frame layout.
- fullScreenVideoFrameLayout.removeAllViews();
-
- // Hide the full screen video frame layout.
- fullScreenVideoFrameLayout.setVisibility(View.GONE);
-
- // Enable the sliding drawers.
- drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
-
- // Show the main content relative layout.
- mainContentRelativeLayout.setVisibility(View.VISIBLE);
-
- // Apply the appropriate full screen mode flags.
- if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
- // Hide the banner ad in the free flavor.
- if (BuildConfig.FLAVOR.contentEquals("free")) {
- // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load.
- View adView = findViewById(R.id.adview);
-
- // Hide the banner ad.
- AdHelper.hideAd(adView);
- }
-
- /* Hide the system bars.
- * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
- * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
- * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
- * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
- */
- rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-
- // Reload the website if the app bar is hidden. Otherwise, there is some bug in Android that causes the WebView to be entirely black.
- if (hideAppBar) {
- // Reload the WebView.
- currentWebView.reload();
- }
- } else { // Switch to normal viewing mode.
- // Remove the `SYSTEM_UI` flags from the root frame layout.
- rootFrameLayout.setSystemUiVisibility(0);
- }
-
- // Reload the ad for the free flavor if not in full screen mode.
- if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
- // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load.
- View adView = findViewById(R.id.adview);
-
- // Reload the ad. `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23.
- AdHelper.loadAd(adView, getApplicationContext(), this, getString(R.string.ad_unit_id));
- }
+ // Exit the full screen video.
+ exitFullScreenVideo();
} else if (currentWebView.canGoBack()) { // There is at least one item in the current WebView history.
// Get the current web back forward list.
WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
}
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, @NonNull 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.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.getAcceptCookies()).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) + " " + 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();
+ break;
}
}
-
+
+ // Remove the warning that `OnTouchListener()` needs to override `performClick()`, as the only purpose of setting the `OnTouchListener()` is to make it do nothing.
+ @SuppressLint("ClickableViewAccessibility")
private void initializeApp() {
// Get a handle for the input method.
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
// Get handles for views that need to be modified.
+ LinearLayout bookmarksHeaderLinearLayout = findViewById(R.id.bookmarks_header_linearlayout);
ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
}
});
+ // Set a touch listener on the bookmarks header linear layout so that touches don't pass through to the button underneath.
+ bookmarksHeaderLinearLayout.setOnTouchListener((view, motionEvent) -> {
+ // Consume the touch.
+ return true;
+ });
+
// Set the launch bookmarks activity FAB to launch the bookmarks activity.
launchBookmarksActivityFab.setOnClickListener(v -> {
// Get a copy of the favorite icon bitmap.
// Find out if the selected bookmark is a folder.
boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
- if (isFolder) {
+ // Check to see if the bookmark is a folder.
+ if (isFolder) { // The bookmark is a folder.
// Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
// Show the edit folder bookmark dialog.
editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder));
- } else {
+ } else { // The bookmark is not a folder.
// Get the bookmark cursor for this ID.
Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
// Load the bookmark in a new tab but do not switch to the tab or close the drawer.
addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)), false);
+
+ // Display a snackbar.
+ Snackbar.make(currentWebView, R.string.bookmark_opened_in_background, Snackbar.LENGTH_SHORT).show();
}
// Consume the event.
@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
// Store the values from the shared preferences in variables.
incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
- boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true);
sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true);
sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true);
// Apply the proxy.
applyProxy(false);
- // Set Do Not Track status.
- if (doNotTrackEnabled) {
- customHeaders.put("DNT", "1");
- } else {
- customHeaders.remove("DNT");
- }
-
// Get the current layout parameters. Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
}
@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);
// Get the settings from the cursor.
nestedScrollWebView.setDomainSettingsDatabaseId(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
nestedScrollWebView.getSettings().setJavaScriptEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
- nestedScrollWebView.setAcceptFirstPartyCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
- boolean domainThirdPartyCookiesEnabled = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
+ nestedScrollWebView.setAcceptCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.COOKIES)) == 1);
nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
// Form data can be removed once the minimum API >= 26.
boolean saveFormData = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
}
// Apply the cookie domain settings.
- cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies());
-
- // Set third-party cookies status if API >= 21.
- if (Build.VERSION.SDK_INT >= 21) {
- cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, domainThirdPartyCookiesEnabled);
- }
+ cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptCookies());
// Apply the form data setting if the API < 26.
if (Build.VERSION.SDK_INT < 26) {
} else { // The new URL does not have custom domain settings. Load the defaults.
// Store the values from the shared preferences.
nestedScrollWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false));
- nestedScrollWebView.setAcceptFirstPartyCookies(sharedPreferences.getBoolean("first_party_cookies", false));
- boolean defaultThirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
+ nestedScrollWebView.setAcceptCookies(sharedPreferences.getBoolean(getString(R.string.cookies_key), false));
nestedScrollWebView.getSettings().setDomStorageEnabled(sharedPreferences.getBoolean("dom_storage", false));
boolean saveFormData = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26.
nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST, sharedPreferences.getBoolean("easylist", true));
nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, sharedPreferences.getBoolean("ultraprivacy", true));
nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false));
- // Apply the default first-party cookie setting.
- cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies());
+ // Apply the default cookie setting.
+ cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptCookies());
// Apply the default font size setting.
try {
// Reset the pinned variables.
nestedScrollWebView.setDomainSettingsDatabaseId(-1);
- // Set third-party cookies status if API >= 21.
- if (Build.VERSION.SDK_INT >= 21) {
- cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, defaultThirdPartyCookiesEnabled);
- }
-
// Get the array position of the user agent name.
int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
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) {
// Update the privacy icon.
if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is enabled.
optionsPrivacyMenuItem.setIcon(R.drawable.javascript_enabled);
- } else if (currentWebView.getAcceptFirstPartyCookies()) { // JavaScript is disabled but cookies are enabled.
+ } else if (currentWebView.getAcceptCookies()) { // JavaScript is disabled but cookies are enabled.
optionsPrivacyMenuItem.setIcon(R.drawable.warning);
} else { // All the dangerous features are disabled.
optionsPrivacyMenuItem.setIcon(R.drawable.privacy_mode);
// Get the current theme status.
int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
- // Update the first-party cookies icon.
- if (currentWebView.getAcceptFirstPartyCookies()) { // First-party cookies are enabled.
- optionsFirstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
- } else { // First-party cookies are disabled.
+ // Update the cookies icon.
+ if (currentWebView.getAcceptCookies()) { // Cookies are enabled.
+ optionsCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
+ } else { // Cookies are disabled.
if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
- optionsFirstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_day);
+ optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled_day);
} else {
- optionsFirstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_night);
+ optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled_night);
}
}
// Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
- // Populate the bookmarks cursor adapter. `this` specifies the `Context`. `false` disables `autoRequery`.
+ // Populate the bookmarks cursor adapter.
bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
- // Inflate the individual item layout. `false` does not attach it to the root.
+ // Inflate the individual item layout.
return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
}
}
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);
+ private void exitFullScreenVideo() {
+ // Re-enable the screen timeout.
+ fullScreenVideoFrameLayout.setKeepScreenOn(false);
- // Display a snackbar.
- Snackbar saveWebpageArchiveSnackbar = Snackbar.make(currentWebView, getString(R.string.file_saved) + " " + filePath, Snackbar.LENGTH_SHORT);
+ // Unset the full screen video flag.
+ displayingFullScreenVideo = false;
- // 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);
+ // Remove all the views from the full screen video frame layout.
+ fullScreenVideoFrameLayout.removeAllViews();
- // Declare a file URI variable.
- Uri fileUri;
+ // Hide the full screen video frame layout.
+ fullScreenVideoFrameLayout.setVisibility(View.GONE);
- // 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);
- }
+ // Enable the sliding drawers.
+ drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
- // Get a handle for the content resolver.
- ContentResolver contentResolver = getContentResolver();
+ // Show the main content relative layout.
+ mainContentRelativeLayout.setVisibility(View.VISIBLE);
- // Create an open intent with `ACTION_VIEW`.
- Intent openIntent = new Intent(Intent.ACTION_VIEW);
+ // Apply the appropriate full screen mode flags.
+ if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
+ // Hide the app bar if specified.
+ if (hideAppBar) {
+ // Hide the tab linear layout.
+ tabsLinearLayout.setVisibility(View.GONE);
- // Set the URI and the MIME type.
- openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri));
+ // Hide the action bar.
+ actionBar.hide();
+ }
- // Allow the app to read the file URI.
- openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ // Hide the banner ad in the free flavor.
+ if (BuildConfig.FLAVOR.contentEquals("free")) {
+ // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load.
+ View adView = findViewById(R.id.adview);
- // Show the chooser.
- startActivity(Intent.createChooser(openIntent, getString(R.string.open)));
- });
+ // Hide the banner ad.
+ AdHelper.hideAd(adView);
+ }
- // Show the snackbar.
- saveWebpageArchiveSnackbar.show();
+ /* Hide the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
+ rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ } else { // Switch to normal viewing mode.
+ // Remove the `SYSTEM_UI` flags from the root frame layout.
+ rootFrameLayout.setSystemUiVisibility(0);
+ }
+
+ // Reload the ad for the free flavor if not in full screen mode.
+ if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
+ // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load.
+ View adView = findViewById(R.id.adview);
+
+ // Reload the ad.
+ AdHelper.loadAd(adView, this, this, getString(R.string.ad_unit_id));
+ }
}
private void clearAndExit() {
// Get a handle for the cookie manager.
CookieManager cookieManager = CookieManager.getInstance();
- // Set the first-party cookie status.
- cookieManager.setAcceptCookie(currentWebView.getAcceptFirstPartyCookies());
+ // Set the cookie status.
+ cookieManager.setAcceptCookie(currentWebView.getAcceptCookies());
// Update the privacy icons. `true` redraws the icons in the app bar.
updatePrivacyIcons(true);
// 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,
- nestedScrollWebView.getAcceptFirstPartyCookies());
+ DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent,
+ nestedScrollWebView.getAcceptCookies());
// 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));
// Exit full screen video.
@Override
public void onHideCustomView() {
- // Re-enable the screen timeout.
- fullScreenVideoFrameLayout.setKeepScreenOn(false);
-
- // Unset the full screen video flag.
- displayingFullScreenVideo = false;
-
- // Remove all the views from the full screen video frame layout.
- fullScreenVideoFrameLayout.removeAllViews();
-
- // Hide the full screen video frame layout.
- fullScreenVideoFrameLayout.setVisibility(View.GONE);
-
- // Enable the sliding drawers.
- drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
-
- // Show the main content relative layout.
- mainContentRelativeLayout.setVisibility(View.VISIBLE);
-
- // Apply the appropriate full screen mode flags.
- if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
- // Hide the app bar if specified.
- if (hideAppBar) {
- // Hide the tab linear layout.
- tabsLinearLayout.setVisibility(View.GONE);
-
- // Hide the action bar.
- actionBar.hide();
- }
-
- // Hide the banner ad in the free flavor.
- if (BuildConfig.FLAVOR.contentEquals("free")) {
- // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load.
- View adView = findViewById(R.id.adview);
-
- // Hide the banner ad.
- AdHelper.hideAd(adView);
- }
-
- /* Hide the system bars.
- * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
- * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
- * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
- * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
- */
- rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
- } else { // Switch to normal viewing mode.
- // Remove the `SYSTEM_UI` flags from the root frame layout.
- rootFrameLayout.setSystemUiVisibility(0);
- }
-
- // Reload the ad for the free flavor if not in full screen mode.
- if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
- // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load.
- View adView = findViewById(R.id.adview);
-
- // Reload the ad. `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23.
- AdHelper.loadAd(adView, getApplicationContext(), activity, getString(R.string.ad_unit_id));
- }
+ // Exit the full screen video.
+ exitFullScreenVideo();
}
// Upload files.
@Override
public void onPageFinished(WebView view, String url) {
// Flush any cookies to persistent storage. The cookie manager has become very lazy about flushing cookies in recent versions.
- if (nestedScrollWebView.getAcceptFirstPartyCookies() && Build.VERSION.SDK_INT >= 21) {
+ if (nestedScrollWebView.getAcceptCookies() && Build.VERSION.SDK_INT >= 21) {
CookieManager.getInstance().flush();
}
// 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());