+ // Consume the double-tap.
+ return true;
+ } else { // Do not consume the double-tap because full screen browsing mode is disabled.
+ return false;
+ }
+ }
+ });
+
+ // Pass all touch events on the WebView through the double-tap gesture detector.
+ nestedScrollWebView.setOnTouchListener((View view, MotionEvent event) -> {
+ // Call `performClick()` on the view, which is required for accessibility.
+ view.performClick();
+
+ // Send the event to the gesture detector.
+ return doubleTapGestureDetector.onTouchEvent(event);
+ });
+
+ // Register the WebView for a context menu. This is used to see link targets and download images.
+ registerForContextMenu(nestedScrollWebView);
+
+ // Allow the downloading of files.
+ nestedScrollWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
+ // Check if the download should be processed by an external app.
+ if (downloadWithExternalApp) { // Download with an external app.
+ // Create a download intent. Not specifying the action type will display the maximum number of options.
+ Intent downloadIntent = new Intent();
+
+ // Set the URI and the MIME type. Specifying `text/html` displays a good number of options.
+ downloadIntent.setDataAndType(Uri.parse(url), "text/html");
+
+ // Flag the intent to open in a new task.
+ downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Show the chooser.
+ startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
+ } else { // Download with Android's download manager.
+ // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission has not been granted.
+ // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
+
+ // Store the variables for future use by `onRequestPermissionsResult()`.
+ downloadUrl = url;
+ downloadContentDisposition = contentDisposition;
+ downloadContentLength = contentLength;
+
+ // 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_FILE.
+ DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
+
+ // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
+ downloadLocationPermissionDialogFragment.show(getSupportFragmentManager(), 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_FILE_REQUEST_CODE);
+ }
+ } else { // The storage permission has already been granted.
+ // Get a handle for the download file alert dialog.
+ DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
+
+ // Show the download file alert dialog.
+ downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+ }
+ }
+ });
+
+ // Update the find on page count.
+ nestedScrollWebView.setFindListener(new WebView.FindListener() {
+ // Get a handle for `findOnPageCountTextView`.
+ final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
+
+ @Override
+ public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
+ if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches.
+ // Set `findOnPageCountTextView` to `0/0`.
+ findOnPageCountTextView.setText(R.string.zero_of_zero);
+ } else if (isDoneCounting) { // There are matches.
+ // `activeMatchOrdinal` is zero-based.
+ int activeMatch = activeMatchOrdinal + 1;
+
+ // Build the match string.
+ String matchString = activeMatch + "/" + numberOfMatches;
+
+ // Set `findOnPageCountTextView`.
+ findOnPageCountTextView.setText(matchString);
+ }
+ }
+ });
+
+ // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView.
+ // 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);
+ }
+ });
+
+ // Set the web chrome client.
+ nestedScrollWebView.setWebChromeClient(new WebChromeClient() {
+ // Update the progress bar when a page is loading.
+ @Override
+ public void onProgressChanged(WebView view, int progress) {
+ // Inject the night mode CSS if night mode is enabled.
+ if (nestedScrollWebView.getNightMode()) { // Night mode is enabled.
+ // `background-color: #212121` sets the background to be dark gray. `color: #BDBDBD` sets the text color to be light gray. `box-shadow: none` removes a lower underline on links
+ // used by WordPress. `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color.
+ // `border: none` removes all borders, which can also be used to underline text. `a {color: #1565C0}` sets links to be a dark blue.
+ // `::selection {background: #0D47A1}' sets the text selection highlight color to be a dark blue. `!important` takes precedent over any existing sub-settings.
+ nestedScrollWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
+ "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
+ "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;} ::selection {background: #0D47A1 !important;}'; parent.appendChild(style)})()", value -> {
+ // Initialize a handler to display `mainWebView`.
+ Handler displayWebViewHandler = new Handler();
+
+ // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
+ Runnable displayWebViewRunnable = () -> {
+ // Only display `mainWebView` if the progress bar is gone. This prevents the display of the `WebView` while it is still loading.
+ if (progressBar.getVisibility() == View.GONE) {
+ nestedScrollWebView.setVisibility(View.VISIBLE);
+ }
+ };
+
+ // Display the WebView after 500 milliseconds.
+ displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
+ });
+ } else { // Night mode is disabled.
+ // Display the nested scroll WebView if night mode is disabled.
+ // Because of a race condition between `applyDomainSettings` and `onPageStarted`,
+ // when night mode is set by domain settings the WebView may be hidden even if night mode is not currently enabled.
+ nestedScrollWebView.setVisibility(View.VISIBLE);
+ }
+
+ // Update the progress bar.
+ progressBar.setProgress(progress);
+
+ // Set the visibility of the progress bar.
+ if (progress < 100) {
+ // Show the progress bar.
+ progressBar.setVisibility(View.VISIBLE);
+ } else {
+ // Hide the progress bar.
+ progressBar.setVisibility(View.GONE);
+
+ //Stop the swipe to refresh indicator if it is running
+ swipeRefreshLayout.setRefreshing(false);
+ }
+ }
+
+ // Set the favorite icon when it changes.
+ @Override
+ public void onReceivedIcon(WebView view, Bitmap icon) {
+ // Only update the favorite icon if the website has finished loading.
+ if (progressBar.getVisibility() == View.GONE) {
+ // Store the new favorite icon.
+ nestedScrollWebView.setFavoriteOrDefaultIcon(icon);
+
+ // Get the current page position.
+ int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
+
+ // Get the current tab.
+ TabLayout.Tab tab = tabLayout.getTabAt(currentPosition);
+
+ // Check to see if the tab has been populated.
+ if (tab != null) {
+ // Get the custom view from the tab.
+ View tabView = tab.getCustomView();
+
+ // Check to see if the custom tab view has been populated.
+ if (tabView != null) {
+ // Get the favorite icon image view from the tab.
+ ImageView tabFavoriteIconImageView = tabView.findViewById(R.id.favorite_icon_imageview);
+
+ // Display the favorite icon in the tab.
+ tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
+ }
+ }
+ }
+ }
+
+ // Save a copy of the title when it changes.
+ @Override
+ public void onReceivedTitle(WebView view, String title) {
+ // Get the current page position.
+ int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
+
+ // Get the current tab.
+ TabLayout.Tab tab = tabLayout.getTabAt(currentPosition);
+
+ // Only populate the title text view if the tab has been fully created.
+ if (tab != null) {
+ // Get the custom view from the tab.
+ View tabView = tab.getCustomView();
+
+ // Remove the incorrect warning below that the current tab view might be null.
+ assert tabView != null;
+
+ // Get the title text view from the tab.
+ TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
+
+ // Set the title as the tab text.
+ tabTitleTextView.setText(title);
+ }
+ }
+
+ // Enter full screen video.
+ @Override
+ public void onShowCustomView(View video, CustomViewCallback callback) {
+ // Get a handle for the full screen video frame layout.
+ FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
+
+ // Set the full screen video flag.
+ displayingFullScreenVideo = true;
+
+ // Pause the ad if this is the free flavor.
+ if (BuildConfig.FLAVOR.contentEquals("free")) {
+ // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+ AdHelper.pauseAd(findViewById(R.id.adview));
+ }
+
+ // Hide the keyboard.
+ inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
+
+ // Hide the main content relative layout.
+ mainContentRelativeLayout.setVisibility(View.GONE);
+
+ // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
+ drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+
+ // 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);
+
+ // Disable the sliding drawers.
+ drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+
+ // Add the video view to the full screen video frame layout.
+ fullScreenVideoFrameLayout.addView(video);
+
+ // Show the full screen video frame layout.
+ fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
+ }
+
+ // Exit full screen video.
+ @Override
+ public void onHideCustomView() {
+ // Get a handle for the full screen video frame layout.
+ FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
+
+ // 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")) {
+ 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));
+ }
+ }
+
+ // Upload files.
+ @Override
+ public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
+ // Show the file chooser if the device is running API >= 21.
+ if (Build.VERSION.SDK_INT >= 21) {
+ // Store the file path callback.
+ fileChooserCallback = filePathCallback;
+
+ // 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);
+ }
+ return true;
+ }
+ });
+
+ nestedScrollWebView.setWebViewClient(new WebViewClient() {
+ // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
+ // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ if (url.startsWith("http")) { // Load the URL in Privacy Browser.
+ // Apply the domain settings for the new URL. This doesn't do anything if the domain has not changed.
+ boolean userAgentChanged = applyDomainSettings(nestedScrollWebView, url, true, false);
+
+ // Check if the user agent has changed.
+ if (userAgentChanged) {
+ // Manually load the URL. The changing of the user agent will cause WebView to reload the previous URL.
+ nestedScrollWebView.loadUrl(url, customHeaders);
+
+ // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
+ return true;
+ } else {
+ // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
+ return false;
+ }
+ } else if (url.startsWith("mailto:")) { // Load the email address in an external email program.
+ // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
+ Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
+
+ // Parse the url and set it as the data for the intent.
+ emailIntent.setData(Uri.parse(url));
+
+ // Open the email program in a new task instead of as part of Privacy Browser.
+ emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Make it so.
+ startActivity(emailIntent);
+
+ // Returning true indicates Privacy Browser is handling the URL by creating an intent.
+ return true;
+ } else if (url.startsWith("tel:")) { // Load the phone number in the dialer.
+ // Open the dialer and load the phone number, but wait for the user to place the call.
+ Intent dialIntent = new Intent(Intent.ACTION_DIAL);
+
+ // Add the phone number to the intent.
+ dialIntent.setData(Uri.parse(url));
+
+ // Open the dialer in a new task instead of as part of Privacy Browser.
+ dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Make it so.
+ startActivity(dialIntent);
+
+ // Returning true indicates Privacy Browser is handling the URL by creating an intent.
+ return true;
+ } else { // Load a system chooser to select an app that can handle the URL.
+ // Open an app that can handle the URL.
+ Intent genericIntent = new Intent(Intent.ACTION_VIEW);
+
+ // Add the URL to the intent.
+ genericIntent.setData(Uri.parse(url));
+
+ // List all apps that can handle the URL instead of just opening the first one.
+ genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
+
+ // Open the app in a new task instead of as part of Privacy Browser.
+ genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Start the app or display a snackbar if no app is available to handle the URL.
+ try {
+ startActivity(genericIntent);
+ } catch (ActivityNotFoundException exception) {
+ Snackbar.make(nestedScrollWebView, getString(R.string.unrecognized_url) + " " + url, Snackbar.LENGTH_SHORT).show();
+ }
+
+ // Returning true indicates Privacy Browser is handling the URL by creating an intent.
+ 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) {
+ // Get a handle for the navigation view.
+ NavigationView navigationView = findViewById(R.id.navigationview);
+
+ // Get a handle for the navigation menu.
+ Menu navigationMenu = navigationView.getMenu();
+
+ // Get a handle for the navigation requests menu item. The menu is 0 based.
+ MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6);
+
+ // Create an empty web resource response to be used if the resource request is blocked.
+ WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
+
+ // Reset the whitelist results tracker.
+ String[] whitelistResultStringArray = null;
+
+ // Initialize the third party request tracker.
+ boolean isThirdPartyRequest = false;
+
+ // Get the current URL. `.getUrl()` throws an error because operations on the WebView cannot be made from this thread.
+ String currentBaseDomain = nestedScrollWebView.getCurrentDomainName();
+
+ // Store a copy of the current domain for use in later requests.
+ String currentDomain = currentBaseDomain;
+
+ // Nobody is happy when comparing null strings.
+ if ((currentBaseDomain != null) && (url != null)) {
+ // Convert the request URL to a URI.
+ Uri requestUri = Uri.parse(url);
+
+ // Get the request host name.
+ String requestBaseDomain = requestUri.getHost();
+
+ // Only check for third-party requests if the current base domain is not empty and the request domain is not null.
+ if (!currentBaseDomain.isEmpty() && (requestBaseDomain != null)) {
+ // Determine the current base domain.
+ while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
+ // Remove the first subdomain.
+ currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
+ }
+
+ // Determine the request base domain.
+ while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
+ // Remove the first subdomain.
+ requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
+ }
+
+ // Update the third party request tracker.
+ isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
+ }
+ }
+
+ // Get the current WebView page position.
+ int webViewPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
+
+ // Determine if the WebView is currently displayed.
+ boolean webViewDisplayed = (webViewPagePosition == tabLayout.getSelectedTabPosition());
+
+ // Block third-party requests if enabled.
+ if (isThirdPartyRequest && nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)) {
+ // Add the result to the resource requests.
+ nestedScrollWebView.addResourceRequest(new String[]{BlockListHelper.REQUEST_THIRD_PARTY, url});
+
+ // Increment the blocked requests counters.
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS);
+
+ // 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.block_all_third_party_requests).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " +
+ getString(R.string.block_all_third_party_requests));
+ }
+ });
+ }
+
+ // Return an empty web resource response.
+ return emptyWebResourceResponse;
+ }
+
+ // Check UltraPrivacy if it is enabled.
+ if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY)) {
+ // Check the URL against UltraPrivacy.
+ String[] ultraPrivacyResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy);
+
+ // Process the UltraPrivacy results.
+ if (ultraPrivacyResults[0].equals(BlockListHelper.REQUEST_BLOCKED)) { // The resource request matched UltraPrivacy's blacklist.
+ // Add the result to the resource requests.
+ nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
+ ultraPrivacyResults[5]});
+
+ // Increment the blocked requests counters.
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRA_PRIVACY);
+
+ // 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.ultraprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY) + " - " + getString(R.string.ultraprivacy));
+ }
+ });
+ }
+
+ // The resource request was blocked. Return an empty web resource response.
+ return emptyWebResourceResponse;
+ } else if (ultraPrivacyResults[0].equals(BlockListHelper.REQUEST_ALLOWED)) { // The resource request matched UltraPrivacy's whitelist.
+ // Add a whitelist entry to the resource requests array.
+ nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
+ ultraPrivacyResults[5]});
+
+ // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource.
+ return null;
+ }
+ }
+
+ // Check EasyList if it is enabled.
+ if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST)) {
+ // Check the URL against EasyList.
+ String[] easyListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList);
+
+ // Process the EasyList results.
+ if (easyListResults[0].equals(BlockListHelper.REQUEST_BLOCKED)) { // The resource request matched EasyList's blacklist.
+ // Add the result to the resource requests.
+ nestedScrollWebView.addResourceRequest(new String[] {easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]});
+
+ // Increment the blocked requests counters.
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_LIST);
+
+ // 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.easylist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_LIST) + " - " + getString(R.string.easylist));
+ }
+ });
+ }
+
+ // The resource request was blocked. Return an empty web resource response.
+ return emptyWebResourceResponse;
+ } else if (easyListResults[0].equals(BlockListHelper.REQUEST_ALLOWED)) { // The resource request matched EasyList's whitelist.
+ // Update the whitelist result string array tracker.
+ whitelistResultStringArray = new String[] {easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]};
+ }
+ }
+
+ // Check EasyPrivacy if it is enabled.
+ if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY)) {
+ // Check the URL against EasyPrivacy.
+ String[] easyPrivacyResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyPrivacy);
+
+ // Process the EasyPrivacy results.
+ if (easyPrivacyResults[0].equals(BlockListHelper.REQUEST_BLOCKED)) { // The resource request matched EasyPrivacy's blacklist.
+ // Add the result to the resource requests.
+ nestedScrollWebView.addResourceRequest(new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4],
+ easyPrivacyResults[5]});
+
+ // Increment the blocked requests counters.
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_PRIVACY);
+
+ // 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.easyprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY) + " - " + getString(R.string.easyprivacy));
+ }
+ });
+ }
+
+ // The resource request was blocked. Return an empty web resource response.
+ return emptyWebResourceResponse;
+ } else if (easyPrivacyResults[0].equals(BlockListHelper.REQUEST_ALLOWED)) { // The resource request matched EasyPrivacy's whitelist.
+ // Update the whitelist result string array tracker.
+ whitelistResultStringArray = new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4], easyPrivacyResults[5]};
+ }
+ }
+
+ // Check Fanboy’s Annoyance List if it is enabled.
+ if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)) {
+ // Check the URL against Fanboy's Annoyance List.
+ String[] fanboysAnnoyanceListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList);
+
+ // Process the Fanboy's Annoyance List results.
+ if (fanboysAnnoyanceListResults[0].equals(BlockListHelper.REQUEST_BLOCKED)) { // The resource request matched Fanboy's Annoyance List's blacklist.
+ // Add the result to the resource requests.
+ nestedScrollWebView.addResourceRequest(new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
+ fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]});
+
+ // Increment the blocked requests counters.
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST);
+
+ // 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.fanboys_annoyance_list).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " +
+ getString(R.string.fanboys_annoyance_list));
+ }
+ });
+ }
+
+ // The resource request was blocked. Return an empty web resource response.
+ return emptyWebResourceResponse;
+ } else if (fanboysAnnoyanceListResults[0].equals(BlockListHelper.REQUEST_ALLOWED)){ // The resource request matched Fanboy's Annoyance List's whitelist.
+ // Update the whitelist result string array tracker.
+ whitelistResultStringArray = new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
+ fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]};
+ }
+ } else if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)) { // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
+ // Check the URL against Fanboy's Annoyance List.
+ String[] fanboysSocialListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysSocialList);
+
+ // Process the Fanboy's Social Blocking List results.
+ if (fanboysSocialListResults[0].equals(BlockListHelper.REQUEST_BLOCKED)) { // The resource request matched Fanboy's Social Blocking List's blacklist.
+ // Add the result to the resource requests.
+ nestedScrollWebView.addResourceRequest(new String[] {fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3],
+ fanboysSocialListResults[4], fanboysSocialListResults[5]});
+
+ // Increment the blocked requests counters.
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
+ nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST);
+
+ // 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.fanboys_social_blocking_list).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " +
+ getString(R.string.fanboys_social_blocking_list));
+ }
+ });
+ }
+
+ // The resource request was blocked. Return an empty web resource response.
+ return emptyWebResourceResponse;
+ } else if (fanboysSocialListResults[0].equals(BlockListHelper.REQUEST_ALLOWED)) { // The resource request matched Fanboy's Social Blocking List's whitelist.
+ // Update the whitelist result string array tracker.
+ whitelistResultStringArray = new String[] {fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3],
+ fanboysSocialListResults[4], fanboysSocialListResults[5]};
+ }
+ }
+
+ // Add the request to the log because it hasn't been processed by any of the previous checks.
+ if (whitelistResultStringArray != null) { // The request was processed by a whitelist.
+ nestedScrollWebView.addResourceRequest(whitelistResultStringArray);
+ } else { // The request didn't match any blocklist entry. Log it as a default request.
+ nestedScrollWebView.addResourceRequest(new String[]{BlockListHelper.REQUEST_DEFAULT, url});
+ }
+
+ // The resource request has not been blocked. `return null` loads the requested resource.
+ return null;
+ }
+
+ // Handle HTTP authentication requests.
+ @Override
+ public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
+ // Store the handler.
+ nestedScrollWebView.setHttpAuthHandler(handler);
+
+ // Instantiate an HTTP authentication dialog.
+ DialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm, nestedScrollWebView.getWebViewFragmentId());
+
+ // Show the HTTP authentication dialog.
+ httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication));
+ }
+
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ // Get the preferences.
+ boolean scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
+ boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
+
+ // Get a handler for the app bar layout.
+ AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
+
+ // Set the top padding of the swipe refresh layout according to the app bar scrolling preference.
+ if (scrollAppBar) {
+ // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior.
+ swipeRefreshLayout.setPadding(0, 0, 0, 0);
+
+ // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
+ swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10, defaultProgressViewEndOffset);
+ } else {
+ // Get the app bar layout height. This can't be done in `applyAppSettings()` because the app bar is not yet populated.
+ int appBarHeight = appBarLayout.getHeight();
+
+ // The swipe refresh layout must be manually moved below the app bar layout.
+ swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
+
+ // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
+ swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
+ }
+
+ // Reset the list of resource requests.
+ nestedScrollWebView.clearResourceRequests();
+
+ // Reset the requests counters.
+ nestedScrollWebView.resetRequestsCounters();
+
+ // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
+ if (nestedScrollWebView.getNightMode()) {
+ nestedScrollWebView.setVisibility(View.INVISIBLE);
+ } else {
+ nestedScrollWebView.setVisibility(View.VISIBLE);
+ }
+
+ // Hide the keyboard.
+ inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
+
+ // Check to see if Privacy Browser is waiting on Orbot.
+ if (!waitingForOrbot) { // Process the URL.
+ // Get the current page position.
+ int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
+
+ // Update the URL text bar if the page is currently selected.
+ if (tabLayout.getSelectedTabPosition() == currentPagePosition) {
+ // Clear the focus from the URL edit text.
+ urlEditText.clearFocus();
+
+ // Display the formatted URL text.
+ urlEditText.setText(url);
+
+ // Apply text highlighting to `urlTextBox`.
+ highlightUrlText();
+ }
+
+ // Reset the list of host IP addresses.
+ nestedScrollWebView.clearCurrentIpAddresses();
+
+ // Get a URI for the current URL.
+ Uri currentUri = Uri.parse(url);
+
+ // 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 WebView typically begins loading before the menu items are instantiated.)
+ if (optionsMenu != null) {
+ // Get a handle for the refresh menu item.
+ MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
+
+ // Set the title.
+ refreshMenuItem.setTitle(R.string.stop);
+
+ // Get the app bar and theme preferences.
+ boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
+
+ // If the icon is displayed in the AppBar, set it according to the theme.
+ if (displayAdditionalAppBarIcons) {
+ if (darkTheme) {
+ refreshMenuItem.setIcon(R.drawable.close_dark);
+ } else {
+ refreshMenuItem.setIcon(R.drawable.close_light);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ // Reset the wide view port if it has been turned off by the waiting for Orbot message.
+ if (!waitingForOrbot) {
+ // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`.
+ nestedScrollWebView.getSettings().setUseWideViewPort(url.startsWith("http"));
+ }
+
+ // 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) {
+ CookieManager.getInstance().flush();
+ }
+
+ // Update the Refresh menu item if the options menu has been created.
+ if (optionsMenu != null) {
+ // Get a handle for the refresh menu item.
+ MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
+
+ // Reset the Refresh title.
+ refreshMenuItem.setTitle(R.string.refresh);
+
+ // Get the app bar and theme preferences.
+ boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
+ boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
+
+ // If the icon is displayed in the AppBar, reset it according to the theme.
+ if (displayAdditionalAppBarIcons) {
+ if (darkTheme) {
+ refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
+ } else {
+ refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
+ }
+ }
+ }
+
+ // Clear the cache and history if Incognito Mode is enabled.
+ if (incognitoModeEnabled) {
+ // Clear the cache. `true` includes disk files.
+ nestedScrollWebView.clearCache(true);
+
+ // Clear the back/forward history.
+ nestedScrollWebView.clearHistory();
+
+ // Manually delete cache folders.
+ try {
+ // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
+ // which links to `/data/data/com.stoutner.privacybrowser.standard`.
+ String privateDataDirectoryString = getApplicationInfo().dataDir;
+
+ // Delete the main cache directory.
+ Runtime.getRuntime().exec("rm -rf " + privateDataDirectoryString + "/cache");
+
+ // Delete the secondary `Service Worker` cache directory.
+ // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
+ Runtime.getRuntime().exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
+ } catch (IOException e) {
+ // Do nothing if an error is thrown.
+ }
+ }
+
+ // Update the URL text box and apply domain settings if not waiting on Orbot.
+ if (!waitingForOrbot) {
+ // Get the current page position.
+ int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
+
+ // Check the current website information against any pinned domain information if the current IP addresses have been loaded.
+ if ((nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses()) && nestedScrollWebView.hasCurrentIpAddresses() &&
+ !nestedScrollWebView.ignorePinnedDomainInformation()) {
+ CheckPinnedMismatchHelper.checkPinnedMismatch(getSupportFragmentManager(), nestedScrollWebView);
+ }
+
+ // Update the URL text bar if the page is currently selected and the user is not currently typing in the URL edit text.
+ if ((tabLayout.getSelectedTabPosition() == currentPagePosition) && !urlEditText.hasFocus()) {
+ // Check to see if the URL is `about:blank`.
+ if (nestedScrollWebView.getUrl().equals("about:blank")) { // The WebView is blank.
+ // Display the hint in the URL edit text.
+ urlEditText.setText("");
+
+ // Request focus for the URL text box.
+ urlEditText.requestFocus();
+
+ // Display the keyboard.
+ inputMethodManager.showSoftInput(urlEditText, 0);
+
+ // Hide the WebView, which causes the default background color to be displayed according to the theme.
+ nestedScrollWebView.setVisibility(View.INVISIBLE);
+
+ // Apply the domain settings. This clears any settings from the previous domain.
+ applyDomainSettings(nestedScrollWebView, "", true, false);
+ } 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(nestedScrollWebView.getUrl());
+
+ // Apply text highlighting to the URL.
+ highlightUrlText();
+ }
+ }
+ }
+ }
+
+ // Handle SSL Certificate errors.
+ @Override
+ public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+ // Get the current website SSL certificate.
+ SslCertificate currentWebsiteSslCertificate = error.getCertificate();
+
+ // Extract the individual pieces of information from the current website SSL certificate.
+ String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
+ String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
+ String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
+ String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
+ String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
+ String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
+ Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
+ Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
+
+ // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
+ if (nestedScrollWebView.hasPinnedSslCertificate()) {
+ // Get the pinned SSL certificate.
+ ArrayList<Object> pinnedSslCertificateArrayList = nestedScrollWebView.getPinnedSslCertificate();
+
+ // Extract the arrays from the array list.
+ String[] pinnedSslCertificateStringArray = (String[]) pinnedSslCertificateArrayList.get(0);
+ Date[] pinnedSslCertificateDateArray = (Date[]) pinnedSslCertificateArrayList.get(1);
+
+ // Check if the current SSL certificate matches the pinned certificate.
+ if (currentWebsiteIssuedToCName.equals(pinnedSslCertificateStringArray[0]) && currentWebsiteIssuedToOName.equals(pinnedSslCertificateStringArray[1]) &&
+ currentWebsiteIssuedToUName.equals(pinnedSslCertificateStringArray[2]) && currentWebsiteIssuedByCName.equals(pinnedSslCertificateStringArray[3]) &&
+ currentWebsiteIssuedByOName.equals(pinnedSslCertificateStringArray[4]) && currentWebsiteIssuedByUName.equals(pinnedSslCertificateStringArray[5]) &&
+ currentWebsiteSslStartDate.equals(pinnedSslCertificateDateArray[0]) && currentWebsiteSslEndDate.equals(pinnedSslCertificateDateArray[1])) {
+
+ // An SSL certificate is pinned and matches the current domain certificate. Proceed to the website without displaying an error.
+ handler.proceed();
+ }
+ } else { // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
+ // Store the SSL error handler.
+ nestedScrollWebView.setSslErrorHandler(handler);
+
+ // Instantiate an SSL certificate error alert dialog.
+ DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error, nestedScrollWebView.getWebViewFragmentId());
+
+ // Show the SSL certificate error dialog.
+ sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
+ }
+ }
+ });
+
+ // Check to see if this is the first page.
+ if (pageNumber == 0) {
+ // Set this nested scroll WebView as the current WebView.
+ currentWebView = nestedScrollWebView;
+
+ // Apply the app settings from the shared preferences.
+ applyAppSettings();
+
+ // Load the website if not waiting for Orbot to connect.
+ if (!waitingForOrbot) {
+ // Get the intent that started the app.
+ Intent launchingIntent = getIntent();
+
+ // Get the information from the intent.
+ String launchingIntentAction = launchingIntent.getAction();
+ Uri launchingIntentUriData = launchingIntent.getData();
+
+ // If the intent action is a web search, perform the search.
+ if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
+ // Create an encoded URL string.
+ String encodedUrlString;
+
+ // Sanitize the search input and convert it to a search.
+ try {
+ encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
+ } catch (UnsupportedEncodingException exception) {
+ encodedUrlString = "";
+ }
+
+ // Load the completed search URL.
+ loadUrl(searchURL + encodedUrlString);
+ } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL.
+ // Load the URL from the intent.
+ loadUrl(launchingIntentUriData.toString());
+ } else { // The is no URL in the intent.
+ // Select the homepage based on the proxy through Orbot status.
+ if (proxyThroughOrbot) {
+ // Load the Tor homepage.
+ loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
+ } else {
+ // Load the normal homepage.
+ loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
+ }
+ }
+ }