+ // 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(mainWebView, 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.
+ @SuppressWarnings("deprecation")
+ @Override
+ public WebResourceResponse shouldInterceptRequest(WebView view, String url){
+ // 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()));
+
+ // Check EasyList if it is enabled.
+ if (easyListEnabled) {
+ if (blockListHelper.isBlocked(formattedUrlString, url, easyList)) {
+ // The resource request was blocked. Return an empty web resource response.
+ return emptyWebResourceResponse;
+ }
+ }
+
+ // Check EasyPrivacy if it is enabled.
+ if (easyPrivacyEnabled) {
+ if (blockListHelper.isBlocked(formattedUrlString, url, easyPrivacy)) {
+ // The resource request was blocked. Return an empty web resource response.
+ return emptyWebResourceResponse;
+ }
+ }
+
+ // Check Fanboy’s Annoyance List if it is enabled.
+ if (fanboysAnnoyanceListEnabled) {
+ if (blockListHelper.isBlocked(formattedUrlString, url, fanboyAnnoyance)) {
+ // The resource request was blocked. Return an empty web resource response.
+ return emptyWebResourceResponse;
+ }
+ } else if (fanboysSocialBlockingListEnabled){ // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
+ if (blockListHelper.isBlocked(formattedUrlString, url, fanboySocial)) {
+ // The resource request was blocked. Return an empty web resource response.
+ return emptyWebResourceResponse;
+ }
+ }
+
+ // 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 `handler` so it can be accessed from `onHttpAuthenticationCancel()` and `onHttpAuthenticationProceed()`.
+ httpAuthHandler = handler;
+
+ // Display the HTTP authentication dialog.
+ AppCompatDialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm);
+ httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication));
+ }
+
+ // Update the URL in urlTextBox when the page starts to load.
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {// If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
+ if (nightMode) {
+ mainWebView.setVisibility(View.INVISIBLE);
+ }
+
+ // Hide the keyboard. `0` indicates no additional flags.
+ inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
+
+ // Check to see if we are waiting on Orbot.
+ if (!waitingForOrbot) { // We are not waiting on Orbot, so we need to process the URL.
+ // We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
+ formattedUrlString = url;
+
+ // Display the formatted URL text.
+ urlTextBox.setText(formattedUrlString);
+
+ // Apply text highlighting to `urlTextBox`.
+ highlightUrlText();
+
+ // Apply any custom domain settings if the URL was loaded by navigating history.
+ if (navigatingHistory) {
+ applyDomainSettings(url, true, false);
+ }
+
+ // Set `urlIsLoading` to `true`, so that redirects while loading do not trigger changes in the user agent, which forces another reload of the existing page.
+ urlIsLoading = true;
+ }
+ }
+
+ // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load.
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ // Flush any cookies to persistent storage. `CookieManager` has become very lazy about flushing cookies in recent versions.
+ if (firstPartyCookiesEnabled && Build.VERSION.SDK_INT >= 21) {
+ cookieManager.flush();
+ }
+
+ // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes.
+ urlIsLoading = false;
+
+ // Clear the cache and history if Incognito Mode is enabled.
+ if (incognitoModeEnabled) {
+ // Clear the cache. `true` includes disk files.
+ mainWebView.clearCache(true);
+
+ // Clear the back/forward history.
+ mainWebView.clearHistory();
+
+ // Manually delete cache folders.
+ try {
+ // Delete the main cache directory.
+ privacyBrowserRuntime.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.
+ privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
+ } catch (IOException e) {
+ // Do nothing if an error is thrown.
+ }
+ }
+
+ // Update `urlTextBox` and apply domain settings if not waiting on Orbot.
+ if (!waitingForOrbot) {
+ // Check to see if `WebView` has set `url` to be `about:blank`.
+ if (url.equals("about:blank")) { // `WebView` is blank, so `formattedUrlString` should be `""` and `urlTextBox` should display a hint.
+ // Set `formattedUrlString` to `""`.
+ formattedUrlString = "";
+
+ urlTextBox.setText(formattedUrlString);
+
+ // Request focus for `urlTextBox`.
+ urlTextBox.requestFocus();
+
+ // Display the keyboard.
+ inputMethodManager.showSoftInput(urlTextBox, 0);
+
+ // Apply the domain settings. This clears any settings from the previous domain.
+ applyDomainSettings(formattedUrlString, true, false);
+ } else { // `WebView` has loaded a webpage.
+ // Set `formattedUrlString`.
+ formattedUrlString = url;
+
+ // Only update `urlTextBox` if the user is not typing in it.
+ if (!urlTextBox.hasFocus()) {
+ // Display the formatted URL text.
+ urlTextBox.setText(formattedUrlString);
+
+ // Apply text highlighting to `urlTextBox`.
+ highlightUrlText();
+ }
+ }
+
+ // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedSslCertificateMismatchDialog`.
+ sslCertificate = mainWebView.getCertificate();
+
+ // Check the current website SSL certificate against the pinned SSL certificate if there is a pinned SSL certificate the user has not chosen to ignore it for this session.
+ if (pinnedDomainSslCertificate && !ignorePinnedSslCertificate) {
+ // Initialize the current SSL certificate variables.
+ String currentWebsiteIssuedToCName = "";
+ String currentWebsiteIssuedToOName = "";
+ String currentWebsiteIssuedToUName = "";
+ String currentWebsiteIssuedByCName = "";
+ String currentWebsiteIssuedByOName = "";
+ String currentWebsiteIssuedByUName = "";
+ Date currentWebsiteSslStartDate = null;
+ Date currentWebsiteSslEndDate = null;
+
+
+ // Extract the individual pieces of information from the current website SSL certificate if it is not null.
+ if (sslCertificate != null) {
+ currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName();
+ currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName();
+ currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName();
+ currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName();
+ currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName();
+ currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName();
+ currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate();
+ currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate();
+ }
+
+ // Initialize `String` variables to store the SSL certificate dates. `Strings` are needed to compare the values below, which doesn't work with `Dates` if they are `null`.
+ String currentWebsiteSslStartDateString = "";
+ String currentWebsiteSslEndDateString = "";
+ String pinnedDomainSslStartDateString = "";
+ String pinnedDomainSslEndDateString = "";
+
+ // Convert the `Dates` to `Strings` if they are not `null`.
+ if (currentWebsiteSslStartDate != null) {
+ currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString();
+ }
+
+ if (currentWebsiteSslEndDate != null) {
+ currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString();
+ }
+
+ if (pinnedDomainSslStartDate != null) {
+ pinnedDomainSslStartDateString = pinnedDomainSslStartDate.toString();
+ }
+
+ if (pinnedDomainSslEndDate != null) {
+ pinnedDomainSslEndDateString = pinnedDomainSslEndDate.toString();
+ }
+
+ // Check to see if the pinned SSL certificate matches the current website certificate.
+ if (!currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) || !currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) ||
+ !currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) || !currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) ||
+ !currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) || !currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) ||
+ !currentWebsiteSslStartDateString.equals(pinnedDomainSslStartDateString) || !currentWebsiteSslEndDateString.equals(pinnedDomainSslEndDateString)) {
+ // The pinned SSL certificate doesn't match the current domain certificate.
+ //Display the pinned SSL certificate mismatch `AlertDialog`.
+ AppCompatDialogFragment pinnedSslCertificateMismatchDialogFragment = new PinnedSslCertificateMismatchDialog();
+ pinnedSslCertificateMismatchDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_mismatch));
+ }
+ }
+ }
+ }
+
+ // 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 (pinnedDomainSslCertificate &&
+ currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) && currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) &&
+ currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) && currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) &&
+ currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) && currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) &&
+ currentWebsiteSslStartDate.equals(pinnedDomainSslStartDate) && currentWebsiteSslEndDate.equals(pinnedDomainSslEndDate)) {
+ // 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 `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
+ sslErrorHandler = handler;
+
+ // Display the SSL error `AlertDialog`.
+ AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
+ sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
+ }
+ }
+ });
+
+ // Load the website if not waiting for Orbot to connect.
+ if (!waitingForOrbot) {
+ loadUrl(formattedUrlString);
+ }
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
+ setIntent(intent);
+
+ // Check to see if the intent contains a new URL.
+ if (intent.getData() != null) {
+ // Get the intent data and convert it to a string.
+ final Uri intentUriData = intent.getData();
+ formattedUrlString = intentUriData.toString();
+
+ // Load the website.
+ loadUrl(formattedUrlString);
+
+ // 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);
+ }
+
+ // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
+ mainWebView.requestFocus();
+ }
+ }
+
+ @Override
+ public void onRestart() {
+ // Run the default commands.
+ super.onRestart();
+
+ // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
+ if (proxyThroughOrbot) {
+ // Request Orbot to start. If Orbot is already running no hard will be caused by this request.
+ Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
+
+ // Send the intent to the Orbot package.
+ orbotIntent.setPackage("org.torproject.android");
+
+ // Make it so.
+ sendBroadcast(orbotIntent);
+ }
+
+ // Apply the app settings if returning from the Settings activity..
+ if (reapplyAppSettingsOnRestart) {
+ // Apply the app settings.
+ applyAppSettings();
+
+ // Reload the webpage if displaying of images has been disabled in the Settings activity.
+ if (reloadOnRestart) {
+ // Reload `mainWebView`.
+ mainWebView.reload();
+
+ // Reset `reloadOnRestartBoolean`.
+ reloadOnRestart = false;
+ }
+
+ // Reset the return from settings flag.
+ reapplyAppSettingsOnRestart = false;
+ }
+
+ // Apply the domain settings if returning from the Domains activity.
+ if (reapplyDomainSettingsOnRestart) {
+ // Reapply the domain settings.
+ applyDomainSettings(formattedUrlString, false, true);
+
+ // Reset `reapplyDomainSettingsOnRestart`.
+ reapplyDomainSettingsOnRestart = false;
+ }
+
+ // Load the URL on restart to apply changes to night mode.
+ if (loadUrlOnRestart) {
+ // Load the current `formattedUrlString`.
+ loadUrl(formattedUrlString);
+
+ // Reset `loadUrlOnRestart.
+ loadUrlOnRestart = false;
+ }
+
+ // Update the bookmarks drawer if returning from the Bookmarks activity.
+ if (restartFromBookmarksActivity) {
+ // Close the bookmarks drawer.
+ drawerLayout.closeDrawer(GravityCompat.END);
+
+ // Reload the bookmarks drawer.
+ loadBookmarksFolder();
+
+ // Reset `restartFromBookmarksActivity`.
+ restartFromBookmarksActivity = false;
+ }
+
+ // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated.
+ updatePrivacyIcons(true);
+ }
+
+ // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
+ @Override
+ public void onResume() {
+ // Run the default commands.
+ super.onResume();
+
+ // Resume JavaScript (if enabled).
+ mainWebView.resumeTimers();
+
+ // Resume `mainWebView`.
+ mainWebView.onResume();
+
+ // Resume the adView for 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.resumeAd(findViewById(R.id.adview));
+ }
+ }
+
+ @Override
+ public void onPause() {
+ // Pause `mainWebView`.
+ mainWebView.onPause();
+
+ // Stop all JavaScript.
+ mainWebView.pauseTimers();
+
+ // Pause the adView or it will continue to consume resources in the background on 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));
+ }
+
+ super.onPause();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {