+ // 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.
+ // Also ignore if changes in the user agent causes an error while navigating history.
+ if (pinnedDomainSslCertificate && !ignorePinnedSslCertificate && navigatingHistory) {
+ // 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));
+ }
+ }
+ });
+
+ // 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 = "";
+ }
+
+ // Add the base search URL.
+ formattedUrlString = searchURL + encodedUrlString;
+ } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL.
+ // Set the formatted URL string.
+ formattedUrlString = launchingIntentUriData.toString();
+ }
+
+ // 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);
+
+ // Get the information from the intent.
+ String intentAction = intent.getAction();
+ Uri intentUriData = intent.getData();
+
+ // If the intent action is a web search, perform the search.
+ if ((intentAction != null) && intentAction.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(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
+ } catch (UnsupportedEncodingException exception) {
+ encodedUrlString = "";
+ }
+
+ // Add the base search URL.
+ formattedUrlString = searchURL + encodedUrlString;
+ } else if (intentUriData != null){ // Check to see if the intent contains a new URL.
+ // Set the formatted URL string.
+ formattedUrlString = intentUriData.toString();
+ }
+
+ // Load the URL.
+ 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")) {
+ // Resume the ad.
+ AdHelper.resumeAd(findViewById(R.id.adview));
+ }
+
+ // Display a message to the user if waiting for Orbot.
+ if (waitingForOrbot && !orbotStatus.equals("ON")) {
+ // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
+ mainWebView.getSettings().setUseWideViewPort(false);
+
+ // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
+ mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
+ }
+
+ if (displayingFullScreenVideo) {
+ // Remove the translucent overlays.
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+ // 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);
+
+ /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * 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.
+ */
+ rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ // Run the default commands.
+ super.onPause();
+
+ // Pause `mainWebView`.
+ mainWebView.onPause();
+
+ // Stop all JavaScript.
+ mainWebView.pauseTimers();
+
+ // Pause the ad or it will continue to consume resources in the background on the free flavor.
+ if (BuildConfig.FLAVOR.contentEquals("free")) {
+ // Pause the ad.
+ AdHelper.pauseAd(findViewById(R.id.adview));
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ // Unregister the Orbot status broadcast receiver.
+ this.unregisterReceiver(orbotStatusBroadcastReceiver);
+
+ // Close the bookmarks cursor and database.
+ bookmarksCursor.close();
+ bookmarksDatabaseHelper.close();
+
+ // Run the default commands.
+ super.onDestroy();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.webview_options_menu, menu);
+
+ // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
+ mainMenu = menu;
+
+ // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
+ updatePrivacyIcons(false);
+
+ // Get handles for the menu items.
+ MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
+ MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
+ MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
+ MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
+ MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
+ refreshMenuItem = menu.findItem(R.id.refresh);
+ blocklistsMenuItem = menu.findItem(R.id.blocklists);
+ easyListMenuItem = menu.findItem(R.id.easylist);
+ easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
+ fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
+ fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
+ ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
+ blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
+ MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
+
+ // Only display third-party cookies if API >= 21
+ toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
+
+ // Only display the form data menu items if the API < 26.
+ toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
+ clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
+
+ // Only show Ad Consent if this is the free flavor.
+ adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
+
+ // Get the shared preference values.