import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
+import android.app.Dialog;
import android.app.DownloadManager;
import android.app.SearchManager;
import android.content.ActivityNotFoundException;
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.webkit.HttpAuthHandler;
import android.webkit.SslErrorHandler;
import android.webkit.ValueCallback;
+import android.webkit.WebBackForwardList;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
+import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage;
import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
+import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
+import com.stoutner.privacybrowser.dialogs.SaveWebpageImageDialog;
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.fragments.WebViewTabFragment;
import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
import com.stoutner.privacybrowser.helpers.CheckPinnedMismatchHelper;
import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
+import com.stoutner.privacybrowser.helpers.FileNameHelper;
import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
import com.stoutner.privacybrowser.views.NestedScrollWebView;
// AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener,
- EditBookmarkFolderDialog.EditBookmarkFolderListener, NavigationView.OnNavigationItemSelectedListener, PopulateBlocklists.PopulateBlocklistsListener, WebViewTabFragment.NewTabListener {
+ EditBookmarkFolderDialog.EditBookmarkFolderListener, NavigationView.OnNavigationItemSelectedListener, PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageImageDialog.SaveWebpageImageListener,
+ StoragePermissionDialog.StoragePermissionDialogListener, UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
// `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
public static String orbotStatus;
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.
+ private final int FILE_UPLOAD_REQUEST_CODE = 0;
+ public final static int BROWSE_SAVE_WEBPAGE_IMAGE_REQUEST_CODE = 1;
// The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
private ArrayList<List<String[]>> easyPrivacy;
private ArrayList<List<String[]>> fanboysAnnoyanceList;
private ArrayList<List<String[]>> fanboysSocialList;
+ private ArrayList<List<String[]>> ultraList;
private ArrayList<List<String[]>> ultraPrivacy;
// `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
// `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
private String downloadImageUrl;
- // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, `onRequestPermissionResult()`, and `initializeWebView()`.
+ // The save website image file path string is used in `onSaveWebpageImage()` and `onRequestPermissionResult()`
+ private String saveWebsiteImageFilePath;
+
+ // The permission result request codes are used in `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, `onRequestPermissionResult()`, `onSaveWebpageImage()`,
+ // `onCloseStoragePermissionDialog()`, and `initializeWebView()`.
private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
+ private final int SAVE_WEBPAGE_IMAGE_REQUEST_CODE = 3;
@Override
// Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
@SuppressLint("ClickableViewAccessibility")
protected void onCreate(Bundle savedInstanceState) {
+ if (Build.VERSION.SDK_INT >= 21) {
+ WebView.enableSlowWholeDocumentDraw();
+ }
+
// Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default.
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
// Set the content view.
setContentView(R.layout.main_framelayout);
+
// Get handles for the views that need to be modified.
DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
Toolbar toolbar = findViewById(R.id.toolbar);
@Override
protected void onNewIntent(Intent intent) {
- // Get the information from the intent.
- String intentAction = intent.getAction();
- Uri intentUriData = intent.getData();
+ // Replace the intent that started the app with this one.
+ setIntent(intent);
- // Determine if this is a web search.
- boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
+ // Process the intent here if Privacy Browser is fully initialized. If the process has been killed by the system while sitting in the background, this will be handled in `initializeWebView()`.
+ if (ultraPrivacy != null) {
+ // Get the information from the intent.
+ String intentAction = intent.getAction();
+ Uri intentUriData = intent.getData();
- // 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 || isWebSearch) {
- // Get the shared preferences.
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+ // Determine if this is a web search.
+ boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
- // Create a URL string.
- String url;
+ // 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 || isWebSearch) {
+ // Get the shared preferences.
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
- // If the intent action is a web search, perform the search.
- if (isWebSearch) {
- // Create an encoded URL string.
- String encodedUrlString;
+ // Create a URL string.
+ String url;
- // Sanitize the search input and convert it to a search.
- try {
- encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
- } catch (UnsupportedEncodingException exception) {
- encodedUrlString = "";
- }
+ // If the intent action is a web search, perform the search.
+ if (isWebSearch) {
+ // Create an encoded URL string.
+ String encodedUrlString;
- // Add the base search URL.
- url = searchURL + encodedUrlString;
- } else { // The intent should contain a URL.
- // Set the intent data as the URL.
- url = intentUriData.toString();
- }
+ // Sanitize the search input and convert it to a search.
+ try {
+ encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
+ } catch (UnsupportedEncodingException exception) {
+ encodedUrlString = "";
+ }
- // Add a new tab if specified in the preferences.
- if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) { // Load the URL in a new tab.
- // Set the loading new intent flag.
- loadingNewIntent = true;
+ // Add the base search URL.
+ url = searchURL + encodedUrlString;
+ } else { // The intent should contain a URL.
+ // Set the intent data as the URL.
+ url = intentUriData.toString();
+ }
- // Add a new tab.
- addNewTab(url);
- } else { // Load the URL in the current tab.
- // Make it so.
- loadUrl(url);
- }
+ // Add a new tab if specified in the preferences.
+ if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) { // Load the URL in a new tab.
+ // Set the loading new intent flag.
+ loadingNewIntent = true;
- // Get a handle for the drawer layout.
- DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+ // Add a new tab.
+ addNewTab(url, true);
+ } else { // Load the URL in the current tab.
+ // Make it so.
+ loadUrl(url);
+ }
- // Close the navigation drawer if it is open.
- if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
- drawerLayout.closeDrawer(GravityCompat.START);
- }
+ // Get a handle for the drawer layout.
+ DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
- // Close the bookmarks drawer if it is open.
- if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
- drawerLayout.closeDrawer(GravityCompat.END);
+ // Close the navigation drawer if it is open.
+ if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
+ drawerLayout.closeDrawer(GravityCompat.START);
+ }
+
+ // Close the bookmarks drawer if it is open.
+ if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
+ drawerLayout.closeDrawer(GravityCompat.END);
+ }
}
}
}
MenuItem easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
MenuItem fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
MenuItem fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
+ MenuItem ultraListMenuItem = menu.findItem(R.id.ultralist);
MenuItem ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
MenuItem blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
// Set the status of the menu item checkboxes.
domStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
saveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData()); // Form data can be removed once the minimum API >= 26.
- easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
- easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
+ easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
+ easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
fanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
fanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
- ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
+ ultraListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
+ ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
wideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
// Initialize the display names for the blocklists with the number of blocked requests.
blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
- easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_LIST) + " - " + getString(R.string.easylist));
- easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY) + " - " + getString(R.string.easyprivacy));
+ easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
+ easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
- ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY) + " - " + getString(R.string.ultraprivacy));
+ ultraListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
+ ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
// Only modify third-party cookies if the API >= 21.
// Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
@SuppressLint("SetJavaScriptEnabled")
public boolean onOptionsItemSelected(MenuItem menuItem) {
- // Reenter full screen browsing mode if it was interrupted by the options menu. <https://redmine.stoutner.com/issues/389>
- if (inFullScreenBrowsingMode) {
- // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
-
- FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
-
- /* 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);
- }
-
// Get the selected menu item ID.
int menuItemId = menuItem.getItemId();
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.add_or_edit_domain:
// Make it so.
startActivity(domainsIntent);
}
+
+ // Consume the event.
return true;
case R.id.toggle_first_party_cookies:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.toggle_third_party_cookies:
// Reload the current WebView.
currentWebView.reload();
} // Else do nothing because SDK < 21.
+
+ // Consume the event.
return true;
case R.id.toggle_dom_storage:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
// Form data can be removed once the minimum API >= 26.
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.clear_cookies:
}
})
.show();
+
+ // Consume the event.
return true;
case R.id.clear_dom_storage:
}
})
.show();
+
+ // Consume the event.
return true;
// Form data can be remove once the minimum API >= 26.
}
})
.show();
+
+ // Consume the event.
return true;
case R.id.easylist:
// Toggle the EasyList status.
- currentWebView.enableBlocklist(NestedScrollWebView.EASY_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
+ currentWebView.enableBlocklist(NestedScrollWebView.EASYLIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
// Update the menu checkbox.
- menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
+ menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.easyprivacy:
// Toggle the EasyPrivacy status.
- currentWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
+ currentWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
// Update the menu checkbox.
- menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
+ menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.fanboys_annoyance_list:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.fanboys_social_blocking_list:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.ultralist:
+ // Toggle the UltraList status.
+ currentWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
+
+ // Update the menu checkbox.
+ menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.ultraprivacy:
// Toggle the UltraPrivacy status.
- currentWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
+ currentWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
// Update the menu checkbox.
- menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
+ menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.block_all_third_party_requests:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.user_agent_privacy_browser:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.user_agent_webview_default:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.user_agent_firefox_on_android:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.user_agent_chrome_on_android:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.user_agent_safari_on_ios:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.user_agent_firefox_on_linux:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.user_agent_chromium_on_linux:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.user_agent_firefox_on_windows:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.user_agent_chrome_on_windows:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.user_agent_edge_on_windows:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.user_agent_internet_explorer_on_windows:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.user_agent_safari_on_macos:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.user_agent_custom:
// Reload the current WebView.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.font_size_twenty_five_percent:
+ // Set the font size.
currentWebView.getSettings().setTextZoom(25);
+
+ // Consume the event.
return true;
case R.id.font_size_fifty_percent:
+ // Set the font size.
currentWebView.getSettings().setTextZoom(50);
+
+ // Consume the event.
return true;
case R.id.font_size_seventy_five_percent:
+ // Set the font size.
currentWebView.getSettings().setTextZoom(75);
+
+ // Consume the event.
return true;
case R.id.font_size_one_hundred_percent:
+ // Set the font size.
currentWebView.getSettings().setTextZoom(100);
+
+ // Consume the event.
return true;
case R.id.font_size_one_hundred_twenty_five_percent:
+ // Set the font size.
currentWebView.getSettings().setTextZoom(125);
+
+ // Consume the event.
return true;
case R.id.font_size_one_hundred_fifty_percent:
+ // Set the font size.
currentWebView.getSettings().setTextZoom(150);
+
+ // Consume the event.
return true;
case R.id.font_size_one_hundred_seventy_five_percent:
+ // Set the font size.
currentWebView.getSettings().setTextZoom(175);
+
+ // Consume the event.
return true;
case R.id.font_size_two_hundred_percent:
+ // Set the font size.
currentWebView.getSettings().setTextZoom(200);
+
+ // Consume the event.
return true;
case R.id.swipe_to_refresh:
// Disable the swipe refresh layout.
swipeRefreshLayout.setEnabled(false);
}
+
+ // Consume the event.
return true;
case R.id.wide_viewport:
// Toggle the viewport.
currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort());
+
+ // Consume the event.
return true;
case R.id.display_images:
// Enable loading of images. Missing images will be loaded without the need for a reload.
currentWebView.getSettings().setLoadsImagesAutomatically(true);
}
+
+ // Consume the event.
return true;
case R.id.night_mode:
// Reload the website.
currentWebView.reload();
+
+ // Consume the event.
return true;
case R.id.find_on_page:
// Display the keyboard. `0` sets no input flags.
inputMethodManager.showSoftInput(findOnPageEditText, 0);
}, 200);
+
+ // Consume the event.
return true;
case R.id.print:
// Print the document.
printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
+
+ // Consume the event.
+ return true;
+
+ case R.id.save_as_image:
+ // Instantiate the save webpage image dialog.
+ DialogFragment saveWebpageImageDialogFragment = new SaveWebpageImageDialog();
+
+ // Show the save webpage image dialog.
+ saveWebpageImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_as_image));
+
+ // Consume the event.
return true;
case R.id.add_to_homescreen:
// Show the create home screen shortcut dialog.
createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
+
+ // Consume the event.
return true;
case R.id.view_source:
// Make it so.
startActivity(viewSourceIntent);
+
+ // Consume the event.
return true;
case R.id.share_url:
// Make it so.
startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
+
+ // Consume the event.
return true;
case R.id.open_with_app:
+ // Open the URL with an outside app.
openWithApp(currentWebView.getUrl());
+
+ // Consume the event.
return true;
case R.id.open_with_browser:
+ // Open the URL with an outside browser.
openWithBrowser(currentWebView.getUrl());
+
+ // Consume the event.
return true;
case R.id.proxy_through_orbot:
// Apply the proxy through Orbot settings.
applyProxyThroughOrbot(true);
+
+ // Consume the event.
return true;
case R.id.refresh:
// Stop the loading of the WebView.
currentWebView.stopLoading();
}
+
+ // Consume the event.
return true;
case R.id.ad_consent:
- // Display the ad consent dialog.
+ // Instantiate the ad consent dialog.
DialogFragment adConsentDialogFragment = new AdConsentDialog();
+
+ // Display the ad consent dialog.
adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
+
+ // Consume the event.
return true;
default:
case R.id.back:
if (currentWebView.canGoBack()) {
+ // Get the current web back forward list.
+ WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
+
+ // Get the previous entry URL.
+ String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
+
// Reset the current domain name so that navigation works if third-party requests are blocked.
currentWebView.resetCurrentDomainName();
- // Set navigating history so that the domain settings are applied when the new URL is loaded.
- currentWebView.setNavigatingHistory(true);
+ // Apply the domain settings.
+ applyDomainSettings(currentWebView, previousUrl, false, false);
// Load the previous website in the history.
currentWebView.goBack();
case R.id.forward:
if (currentWebView.canGoForward()) {
+ // Get the current web back forward list.
+ WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
+
+ // Get the next entry URL.
+ String nextUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() + 1).getUrl();
+
// Reset the current domain name so that navigation works if third-party requests are blocked.
currentWebView.resetCurrentDomainName();
- // Set navigating history so that the domain settings are applied when the new URL is loaded.
- currentWebView.setNavigatingHistory(true);
+ // Apply the domain settings.
+ applyDomainSettings(currentWebView, nextUrl, false, false);
// Load the next website in the history.
currentWebView.goForward();
// Create a string array for the blocklist versions.
String[] blocklistVersions = new String[] {easyList.get(0).get(0)[0], easyPrivacy.get(0).get(0)[0], fanboysAnnoyanceList.get(0).get(0)[0], fanboysSocialList.get(0).get(0)[0],
- ultraPrivacy.get(0).get(0)[0]};
+ ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]};
// Add the blocklist versions to the intent.
aboutIntent.putExtra("blocklist_versions", blocklistVersions);
// Store the hit test result.
final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
- // Create the URL strings.
+ // Define the URL strings.
final String imageUrl;
final String linkUrl;
// Add an Open in New Tab entry.
menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
// Load the link URL in a new tab.
- addNewTab(linkUrl);
- return false;
+ addNewTab(linkUrl, false);
+
+ // Consume the event.
+ return true;
});
// Add an Open with App entry.
menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
openWithApp(linkUrl);
- return false;
+
+ // Consume the event.
+ return true;
});
// Add an Open with Browser entry.
menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
openWithBrowser(linkUrl);
- return false;
+
+ // Consume the event.
+ return true;
});
// Add a Copy URL entry.
// Set the `ClipData` as the clipboard's primary clip.
clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
- return false;
+
+ // Consume the event.
+ return true;
});
// Add a Download URL entry.
downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
}
}
- return false;
+
+ // Consume the event.
+ return true;
});
// Add a Cancel entry, which by default closes the context menu.
// Make it so.
startActivity(emailIntent);
- return false;
+
+ // Consume the event.
+ return true;
});
// Add a Copy Email Address entry.
// Set the `ClipData` as the clipboard's primary clip.
clipboardManager.setPrimaryClip(srcEmailTypeClipData);
- return false;
+
+ // Consume the event.
+ return true;
});
// Add a `Cancel` entry, which by default closes the `ContextMenu`.
menu.add(R.string.cancel);
break;
- // `IMAGE_TYPE` is an image. `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link. Privacy Browser processes them the same.
+ // `IMAGE_TYPE` is an image.
case WebView.HitTestResult.IMAGE_TYPE:
- case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
// Get the image URL.
imageUrl = hitTestResult.getExtra();
menu.setHeaderTitle(imageUrl);
// Add an Open in New Tab entry.
- menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
- // Load the image URL in a new tab.
- addNewTab(imageUrl);
- return false;
+ menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
+ // Load the image in a new tab.
+ addNewTab(imageUrl, false);
+
+ // Consume the event.
+ return true;
});
// Add a View Image entry.
menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
+ // Load the image in the current tab.
loadUrl(imageUrl);
- return false;
+
+ // Consume the event.
+ return true;
});
- // Add a `Download Image` entry.
+ // Add a Download Image entry.
menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
// Check if the download should be processed by an external app.
if (sharedPreferences.getBoolean("download_with_external_app", false)) { // Download with an external app.
// Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
} else { // Show the permission request directly.
- // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
+ // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
}
} else { // The storage permission has already been granted.
downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
}
}
- return false;
+
+ // Consume the event.
+ return true;
});
- // Add a `Copy URL` entry.
- menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
- // Save the image URL in a `ClipData`.
- ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
+ // Add a Copy URL entry.
+ menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
+ // Save the image URL in a clip data.
+ ClipData imageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
- // Set the `ClipData` as the clipboard's primary clip.
- clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
- return false;
+ // Set the clip data as the clipboard's primary clip.
+ clipboardManager.setPrimaryClip(imageTypeClipData);
+
+ // Consume the event.
+ return true;
});
// Add an Open with App entry.
menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
+ // Open the image URL with an external app.
openWithApp(imageUrl);
- return false;
+
+ // Consume the event.
+ return true;
});
// Add an Open with Browser entry.
menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
+ // Open the image URL with an external browser.
openWithBrowser(imageUrl);
- return false;
+
+ // Consume the event.
+ return true;
});
- // Add a `Cancel` entry, which by default closes the `ContextMenu`.
+ // Add a Cancel entry, which by default closes the context menu.
+ menu.add(R.string.cancel);
+ break;
+
+ // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
+ case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
+ // Get the image URL.
+ imageUrl = hitTestResult.getExtra();
+
+ // Instantiate a handler.
+ Handler handler = new Handler();
+
+ // Get a message from the handler.
+ Message message = handler.obtainMessage();
+
+ // Request the image details from the last touched node be returned in the message.
+ currentWebView.requestFocusNodeHref(message);
+
+ // Get the link URL from the message data.
+ linkUrl = message.getData().getString("url");
+
+ // Set the link URL as the title of the context menu.
+ menu.setHeaderTitle(linkUrl);
+
+ // Add an Open in New Tab entry.
+ menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
+ // Load the link URL in a new tab.
+ addNewTab(linkUrl, false);
+
+ // Consume the event.
+ return true;
+ });
+
+ // Add an Open Image in New Tab entry.
+ menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
+ // Load the image in a new tab.
+ addNewTab(imageUrl, false);
+
+ // Consume the event.
+ return true;
+ });
+
+ // Add a View Image entry.
+ menu.add(R.string.view_image).setOnMenuItemClickListener((MenuItem item) -> {
+ // View the image in the current tab.
+ loadUrl(imageUrl);
+
+ // Consume the event.
+ return true;
+ });
+
+ // Add a Download Image entry.
+ menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
+ // Check if the download should be processed by an external app.
+ if (sharedPreferences.getBoolean("download_with_external_app", false)) { // Download with an external app.
+ openUrlWithExternalApp(imageUrl);
+ } else { // Download with Android's download manager.
+ // Check to see if the storage permission has already been granted.
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
+ // Store the image URL for use by `onRequestPermissionResult()`.
+ downloadImageUrl = imageUrl;
+
+ // Show a dialog if the user has previously denied the permission.
+ if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
+ // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
+ DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
+
+ // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
+ downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
+ } else { // Show the permission request directly.
+ // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
+ }
+ } else { // The storage permission has already been granted.
+ // Get a handle for the download image alert dialog.
+ DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+
+ // Show the download image alert dialog.
+ downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
+ }
+ }
+
+ // Consume the event.
+ return true;
+ });
+
+ // Add a Copy URL entry.
+ menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
+ // Save the link URL in a clip data.
+ ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
+
+ // Set the clip data as the clipboard's primary clip.
+ clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
+
+ // Consume the event.
+ return true;
+ });
+
+ // Add an Open with App entry.
+ menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
+ // Open the link URL with an external app.
+ openWithApp(linkUrl);
+
+ // Consume the event.
+ return true;
+ });
+
+ // Add an Open with Browser entry.
+ menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
+ // Open the link URL with an external browser.
+ openWithBrowser(linkUrl);
+
+ // Consume the event.
+ return true;
+ });
+
+ // Add a cancel entry, which by default closes the context menu.
menu.add(R.string.cancel);
break;
}
// Reset the image URL variable.
downloadImageUrl = "";
break;
+
+ case SAVE_WEBPAGE_IMAGE_REQUEST_CODE:
+ // Check to see if the storage permission was granted. If the dialog was canceled the grant result will be empty.
+ if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted.
+ // Save the webpage image.
+ new SaveWebpageImage(this, currentWebView).execute(saveWebsiteImageFilePath);
+ } else { // The storage permission was not granted.
+ // Display an error snackbar.
+ Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
+ }
+
+ // Reset the save website image file path.
+ saveWebsiteImageFilePath = "";
+ break;
}
}
}
}
- // Override `onBackPressed` to handle the navigation drawer and and the WebView.
+ // Override `onBackPressed` to handle the navigation drawer and and the WebViews.
@Override
public void onBackPressed() {
// Get a handle for the drawer layout and the tab layout.
// Load the new folder.
loadBookmarksFolder();
}
+ } else if (displayingFullScreenVideo) { // A full screen video is shown.
+ // Get a handle for the layouts.
+ FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+ RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
+ FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
+
+ // 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) {
+ // Get handles for the views.
+ LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
+ ActionBar actionBar = getSupportActionBar();
+
+ // Remove the incorrect lint warning below that the action bar might be null.
+ assert actionBar != null;
+
+ // 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")) {
+ AdHelper.hideAd(findViewById(R.id.adview));
+ }
+
+ // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+ /* 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);
+
+ // Add the translucent status flag.
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ }
+
+ // Reload the ad for the free flavor if not in full screen mode.
+ if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
+ // Reload the ad.
+ AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
+ }
} 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();
+
+ // Get the previous entry URL.
+ String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
+
// Reset the current domain name so that navigation works if third-party requests are blocked.
currentWebView.resetCurrentDomainName();
- // Set navigating history so that the domain settings are applied when the new URL is loaded.
- currentWebView.setNavigatingHistory(true);
+ // Apply the domain settings.
+ applyDomainSettings(currentWebView, previousUrl, false, false);
// Go back.
currentWebView.goBack();
}
}
- // Process the results of an upload file chooser. Currently there is only one `startActivityForResult` in this activity, so the request code, used to differentiate them, is ignored.
+ // Process the results of a file browse.
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
- // File uploads only work on API >= 21.
- if (Build.VERSION.SDK_INT >= 21) {
- // Pass the file to the WebView.
- fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
+ // Run the commands that correlate to the specified request code.
+ switch (requestCode) {
+ case FILE_UPLOAD_REQUEST_CODE:
+ // File uploads only work on API >= 21.
+ if (Build.VERSION.SDK_INT >= 21) {
+ // Pass the file to the WebView.
+ fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
+ }
+ break;
+
+ case BROWSE_SAVE_WEBPAGE_IMAGE_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 saveWebpageImageDialogFragment= (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_as_image));
+
+ // Only update the file name if the dialog still exists.
+ if (saveWebpageImageDialogFragment != null) {
+ // Get a handle for the save webpage image dialog.
+ Dialog saveWebpageImageDialog = saveWebpageImageDialogFragment.getDialog();
+
+ // Get a handle for the file name edit text.
+ EditText fileNameEditText = saveWebpageImageDialog.findViewById(R.id.file_name_edittext);
+
+ // Instantiate the file name helper.
+ FileNameHelper fileNameHelper = new FileNameHelper();
+
+ // Convert the file name URI to a file name path.
+ String fileNamePath = fileNameHelper.convertUriToFileNamePath(data.getData());
+
+ // Set the file name path as the text of the file name edit text.
+ fileNameEditText.setText(fileNamePath);
+ }
+ }
+ break;
}
}
inputMethodManager.hideSoftInputFromWindow(toolbar.getWindowToken(), 0);
}
+ @Override
+ public void onSaveWebpageImage(DialogFragment dialogFragment) {
+ // Get a handle for the file name edit text.
+ EditText fileNameEditText = dialogFragment.getDialog().findViewById(R.id.file_name_edittext);
+
+ // Get the file path string.
+ saveWebsiteImageFilePath = 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 image.
+ new SaveWebpageImage(this, currentWebView).execute(saveWebsiteImageFilePath);
+ } 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 (saveWebsiteImageFilePath.startsWith(externalPrivateDirectory)) { // The file path is in the external private directory.
+ // Save the webpage image.
+ new SaveWebpageImage(this, currentWebView).execute(saveWebsiteImageFilePath);
+ } 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 = new StoragePermissionDialog();
+
+ // 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. The webpage image will be saved when it finishes.
+ ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, SAVE_WEBPAGE_IMAGE_REQUEST_CODE);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onCloseStoragePermissionDialog() {
+ // Request the write external storage permission. The webpage image will be saved when it finishes.
+ ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, SAVE_WEBPAGE_IMAGE_REQUEST_CODE);
+ }
+
private void applyAppSettings() {
// Initialize the app if this is the first run. This is done here instead of in `onCreate()` to shorten the time that an unthemed background is displayed on app startup.
if (webViewDefaultUserAgent == null) {
bareWebView.destroy();
}
+ @Override
+ public void navigateHistory(String url, int steps) {
+ // Reset the current domain name so that navigation works if third-party requests are blocked.
+ currentWebView.resetCurrentDomainName();
+
+ // Apply the domain settings.
+ applyDomainSettings(currentWebView, url, false, false);
+
+ // Load the history entry.
+ currentWebView.goBackOrForward(steps);
+ }
+
+ @Override
+ public void pinnedErrorGoBack() {
+ // Get the current web back forward list.
+ WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
+
+ // Get the previous entry URL.
+ String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
+
+ // Reset the current domain name so that navigation works if third-party requests are blocked.
+ currentWebView.resetCurrentDomainName();
+
+ // Apply the domain settings.
+ applyDomainSettings(currentWebView, previousUrl, false, false);
+
+ // Go back.
+ currentWebView.goBack();
+ }
+
// `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled.
@SuppressLint("SetJavaScriptEnabled")
private boolean applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetTab, boolean reloadWebsite) {
// Store a copy of the current user agent to track changes for the return boolean.
String initialUserAgent = nestedScrollWebView.getSettings().getUserAgentString();
+ // Store the current URL.
+ nestedScrollWebView.setCurrentUrl(url);
+
// Parse the URL into a URI.
Uri uri = Uri.parse(url);
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);
- nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_LIST,
+ nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST,
currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
- nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY,
+ nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY,
currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST,
currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST,
currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
- nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY,
+ nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ULTRALIST)) == 1);
+ nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY,
currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS,
currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
boolean defaultThirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", 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.EASY_LIST, sharedPreferences.getBoolean("easylist", true));
- nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, sharedPreferences.getBoolean("easyprivacy", true));
+ nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST, sharedPreferences.getBoolean("easylist", true));
+ nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, sharedPreferences.getBoolean("easyprivacy", true));
nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean("fanboys_annoyance_list", true));
nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean("fanboys_social_blocking_list", true));
- nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY, sharedPreferences.getBoolean("ultraprivacy", true));
+ nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, sharedPreferences.getBoolean("ultralist", true));
+ nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, sharedPreferences.getBoolean("ultraprivacy", true));
nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false));
nestedScrollWebView.setNightMode(sharedPreferences.getBoolean("night_mode", false));
// Flag the intent to open in a new task.
openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // Show the chooser.
- startActivity(openWithAppIntent);
+ try {
+ // Show the chooser.
+ startActivity(openWithAppIntent);
+ } catch (ActivityNotFoundException exception) {
+ // Show a snackbar with the error.
+ Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show();
+ }
}
private void openWithBrowser(String url) {
// Flag the intent to open in a new task.
openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // Show the chooser.
- startActivity(openWithBrowserIntent);
+ try {
+ // Show the chooser.
+ startActivity(openWithBrowserIntent);
+ } catch (ActivityNotFoundException exception) {
+ // Show a snackbar with the error.
+ Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show();
+ }
}
private String sanitizeUrl(String url) {
if (url.contains("&fbclid=")) {
url = url.substring(0, url.indexOf("&fbclid="));
}
+
+ // Remove `?fbadid=`.
+ if (url.contains("?fbadid=")) {
+ url = url.substring(0, url.indexOf("?fbadid="));
+ }
+
+ // Remove `&fbadid=`.
+ if (url.contains("&fbadid=")) {
+ url = url.substring(0, url.indexOf("&fbadid="));
+ }
}
// Sanitize Twitter AMP redirects.
easyPrivacy = combinedBlocklists.get(1);
fanboysAnnoyanceList = combinedBlocklists.get(2);
fanboysSocialList = combinedBlocklists.get(3);
- ultraPrivacy = combinedBlocklists.get(4);
+ ultraList = combinedBlocklists.get(4);
+ ultraPrivacy = combinedBlocklists.get(5);
// Add the first tab.
- addNewTab("");
+ addNewTab("", true);
}
public void addTab(View view) {
// Add a new tab with a blank URL.
- addNewTab("");
+ addNewTab("", true);
}
- private void addNewTab(String url) {
+ private void addNewTab(String url, boolean moveToTab) {
// Sanitize the URL.
url = sanitizeUrl(url);
newTab.setCustomView(R.layout.tab_custom_view);
// Add the new WebView page.
- webViewPagerAdapter.addPage(newTabNumber, webViewPager, url);
+ webViewPagerAdapter.addPage(newTabNumber, webViewPager, url, moveToTab);
}
public void closeTab(View view) {
}
});
- // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView.
+ // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView. Also reinforce full screen browsing mode.
// Once the minimum API >= 23 this can be replaced with `nestedScrollWebView.setOnScrollChangeListener()`.
nestedScrollWebView.getViewTreeObserver().addOnScrollChangedListener(() -> {
if (nestedScrollWebView.getSwipeToRefresh()) {
// Only enable swipe to refresh if the WebView is scrolled to the top.
swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
}
+
+ // Reinforce the system UI visibility flags if in full screen browsing mode.
+ // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
+ if (inFullScreenBrowsingMode) {
+ /* 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);
+ }
});
// Set the web chrome client.
// Show the full screen video frame layout.
fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
+
+ // Disable the screen timeout while the video is playing. YouTube does this automatically, but not all other videos do.
+ fullScreenVideoFrameLayout.setKeepScreenOn(true);
}
// Exit full screen video.
// Get a handle for the full screen video frame layout.
FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
+ // Re-enable the screen timeout.
+ fullScreenVideoFrameLayout.setKeepScreenOn(false);
+
// Unset the full screen video flag.
displayingFullScreenVideo = false;
// Create an intent to open a chooser based ont the file chooser parameters.
Intent fileChooserIntent = fileChooserParams.createIntent();
- // Open the file chooser. Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
- startActivityForResult(fileChooserIntent, 0);
+ // Open the file chooser.
+ startActivityForResult(fileChooserIntent, FILE_UPLOAD_REQUEST_CODE);
}
return true;
}
// Check requests against the block lists. The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
+ // Check to see if the resource request is for the main URL.
+ if (url.equals(nestedScrollWebView.getCurrentUrl())) {
+ // `return null` loads the resource request, which should never be blocked if it is the main URL.
+ return null;
+ }
+
+ // Wait until the blocklists have been populated. When Privacy Browser is being resumed after having the process killed in the background it will try to load the URLs immediately.
+ while (ultraPrivacy == null) {
+ // The wait must be synchronized, which only lets one thread run on it at a time, or `java.lang.IllegalMonitorStateException` is thrown.
+ synchronized (this) {
+ try {
+ // Check to see if the blocklists have been populated after 100 ms.
+ wait(100);
+ } catch (InterruptedException exception) {
+ // Do nothing.
+ }
+ }
+ }
+
// Sanitize the URL.
url = sanitizeUrl(url);
return emptyWebResourceResponse;
}
+ // Check UltraList if it is enabled.
+ if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)) {
+ // Check the URL against UltraList.
+ String[] ultraListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraList);
+
+ // Process the UltraList results.
+ if (ultraListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched UltraLists's blacklist.
+ // Add the result to the resource requests.
+ nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]});
+
+ // Increment the blocked requests counters.
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRALIST);
+
+ // Update the titles of the blocklist menu items if the WebView is currently displayed.
+ if (webViewDisplayed) {
+ // Updating the UI must be run from the UI thread.
+ activity.runOnUiThread(() -> {
+ // Update the menu item titles.
+ navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+
+ // Update the options menu if it has been populated.
+ if (optionsMenu != null) {
+ optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+ optionsMenu.findItem(R.id.ultralist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
+ }
+ });
+ }
+
+ // The resource request was blocked. Return an empty web resource response.
+ return emptyWebResourceResponse;
+ } else if (ultraListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched UltraList's whitelist.
+ // Add a whitelist entry to the resource requests array.
+ nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]});
+
+ // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource.
+ return null;
+ }
+ }
+
// Check UltraPrivacy if it is enabled.
- if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY)) {
+ if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)) {
// Check the URL against UltraPrivacy.
String[] ultraPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy);
// Increment the blocked requests counters.
nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
- nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRA_PRIVACY);
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRAPRIVACY);
// Update the titles of the blocklist menu items if the WebView is currently displayed.
if (webViewDisplayed) {
// Update the options menu if it has been populated.
if (optionsMenu != null) {
optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
- optionsMenu.findItem(R.id.ultraprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY) + " - " + getString(R.string.ultraprivacy));
+ optionsMenu.findItem(R.id.ultraprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
}
});
}
}
// Check EasyList if it is enabled.
- if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST)) {
+ if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)) {
// Check the URL against EasyList.
String[] easyListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList);
// Increment the blocked requests counters.
nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
- nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_LIST);
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASYLIST);
// Update the titles of the blocklist menu items if the WebView is currently displayed.
if (webViewDisplayed) {
// Update the options menu if it has been populated.
if (optionsMenu != null) {
optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
- optionsMenu.findItem(R.id.easylist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_LIST) + " - " + getString(R.string.easylist));
+ optionsMenu.findItem(R.id.easylist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
}
});
}
}
// Check EasyPrivacy if it is enabled.
- if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY)) {
+ if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)) {
// Check the URL against EasyPrivacy.
String[] easyPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyPrivacy);
// Increment the blocked requests counters.
nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
- nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_PRIVACY);
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASYPRIVACY);
// Update the titles of the blocklist menu items if the WebView is currently displayed.
if (webViewDisplayed) {
// Update the options menu if it has been populated.
if (optionsMenu != null) {
optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
- optionsMenu.findItem(R.id.easyprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY) + " - " + getString(R.string.easyprivacy));
+ optionsMenu.findItem(R.id.easyprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
}
});
}
// Get the IP addresses for the host.
new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost());
- // Apply any custom domain settings if the URL was loaded by navigating history.
- if (nestedScrollWebView.getNavigatingHistory()) {
- // Reset navigating history.
- nestedScrollWebView.setNavigatingHistory(false);
-
- // Apply the domain settings.
- boolean userAgentChanged = applyDomainSettings(nestedScrollWebView, url, true, false);
-
- // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded.
- if (userAgentChanged) {
- loadUrl(url);
- }
- }
-
// Replace Refresh with Stop if the options menu has been created. (The first WebView typically begins loading before the menu items are instantiated.)
if (optionsMenu != null) {
// Get a handle for the refresh menu item.
tabTitleTextView.setText(R.string.new_tab);
}
} else { // The WebView has loaded a webpage.
- // Display the final URL. Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly.
- urlEditText.setText(currentUrl);
+ // Update the URL edit text if it is not currently being edited.
+ if (!urlEditText.hasFocus()) {
+ // Sanitize the current URL. This removes unwanted URL elements that were added by redirects, so that they won't be included if the URL is shared.
+ String sanitizedUrl = sanitizeUrl(currentUrl);
+
+ // Display the final URL. Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly.
+ urlEditText.setText(sanitizedUrl);
- // Apply text highlighting to the URL.
- highlightUrlText();
+ // Apply text highlighting to the URL.
+ highlightUrlText();
+ }
// Only populate the title text view if the tab has been fully created.
if (tab != null) {