+ // Create an integer to track the number of bytes read.
+ int bytesRead;
+
+ // Copy the temporary MHT file input stream to the MHT output stream.
+ while ((bytesRead = temporaryMhtFileInputStream.read(transferByteArray)) > 0) {
+ mhtOutputStream.write(transferByteArray, 0, bytesRead);
+ }
+
+ // Close the streams.
+ mhtOutputStream.close();
+ temporaryMhtFileInputStream.close();
+
+ // Display a snackbar.
+ Snackbar.make(currentWebView, getString(R.string.file_saved) + " " + currentWebView.getCurrentUrl(), Snackbar.LENGTH_SHORT).show();
+ } catch (Exception exception) {
+ // Display a snackbar with the exception.
+ Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
+ } finally {
+ // Delete the temporary MHT file.
+ //noinspection ResultOfMethodCallIgnored
+ temporaryMhtFile.delete();
+ }
+ } else { // There was an unspecified error while saving the temporary MHT file.
+ // Display an error snackbar.
+ Snackbar.make(currentWebView, getString(R.string.error_saving_file), Snackbar.LENGTH_INDEFINITE).show();
+ }
+ });
+ } catch (IOException ioException) {
+ // Display a snackbar with the IO exception.
+ Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + ioException.toString(), Snackbar.LENGTH_INDEFINITE).show();
+ }
+ break;
+
+ case SaveWebpageDialog.SAVE_IMAGE:
+ // Save the webpage image.
+ new SaveWebpageImage(this, saveWebpageFilePath, currentWebView).execute();
+ break;
+ }
+ }
+
+ // Remove the warning that `OnTouchListener()` needs to override `performClick()`, as the only purpose of setting the `OnTouchListener()` is to make it do nothing.
+ @SuppressLint("ClickableViewAccessibility")
+ private void initializeApp() {
+ // Get a handle for the input method.
+ InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ // Remove the lint warning below that the input method manager might be null.
+ assert inputMethodManager != null;
+
+ // Initialize the gray foreground color spans for highlighting the URLs. The deprecated `getResources()` must be used until API >= 23.
+ initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
+ finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
+
+ // Get the current theme status.
+ int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+
+ // Set the red color span according to the theme.
+ if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+ redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
+ } else {
+ redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_900));
+ }
+
+ // Remove the formatting from the URL edit text when the user is editing the text.
+ urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
+ if (hasFocus) { // The user is editing the URL text box.
+ // Remove the highlighting.
+ urlEditText.getText().removeSpan(redColorSpan);
+ urlEditText.getText().removeSpan(initialGrayColorSpan);
+ urlEditText.getText().removeSpan(finalGrayColorSpan);
+ } else { // The user has stopped editing the URL text box.
+ // Move to the beginning of the string.
+ urlEditText.setSelection(0);
+
+ // Reapply the highlighting.
+ highlightUrlText();
+ }
+ });
+
+ // Set the go button on the keyboard to load the URL in `urlTextBox`.
+ urlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
+ // If the event is a key-down event on the `enter` button, load the URL.
+ if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
+ // Load the URL into the mainWebView and consume the event.
+ loadUrlFromTextBox();
+
+ // If the enter key was pressed, consume the event.
+ return true;
+ } else {
+ // If any other key was pressed, do not consume the event.
+ return false;
+ }
+ });
+
+ // Create an Orbot status broadcast receiver.
+ orbotStatusBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Store the content of the status message in `orbotStatus`.
+ orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
+
+ // If Privacy Browser is waiting on the proxy, load the website now that Orbot is connected.
+ if ((orbotStatus != null) && orbotStatus.equals("ON") && waitingForProxy) {
+ // Reset the waiting for proxy status.
+ waitingForProxy = false;
+
+ // Get a handle for the waiting for proxy dialog.
+ DialogFragment waitingForProxyDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.waiting_for_proxy_dialog));
+
+ // Dismiss the waiting for proxy dialog if it is displayed.
+ if (waitingForProxyDialogFragment != null) {
+ waitingForProxyDialogFragment.dismiss();
+ }
+
+ // Reload existing URLs and load any URLs that are waiting for the proxy.
+ for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+ // Get the WebView tab fragment.
+ WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
+
+ // Get the fragment view.
+ View fragmentView = webViewTabFragment.getView();
+
+ // Only process the WebViews if they exist.
+ if (fragmentView != null) {
+ // Get the nested scroll WebView from the tab fragment.
+ NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+
+ // Get the waiting for proxy URL string.
+ String waitingForProxyUrlString = nestedScrollWebView.getWaitingForProxyUrlString();
+
+ // Load the pending URL if it exists.
+ if (!waitingForProxyUrlString.isEmpty()) { // A URL is waiting to be loaded.
+ // Load the URL.
+ loadUrl(nestedScrollWebView, waitingForProxyUrlString);
+
+ // Reset the waiting for proxy URL string.
+ nestedScrollWebView.resetWaitingForProxyUrlString();
+ } else { // No URL is waiting to be loaded.
+ // Reload the existing URL.
+ nestedScrollWebView.reload();
+ }
+ }
+ }
+ }
+ }
+ };
+
+ // Register the Orbot status broadcast receiver on `this` context.
+ this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
+
+ // Get handles for views that need to be modified.
+ LinearLayout bookmarksHeaderLinearLayout = findViewById(R.id.bookmarks_header_linearlayout);
+ ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
+ FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
+ FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
+ FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
+ EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
+
+ // Update the web view pager every time a tab is modified.
+ webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ // Close the find on page bar if it is open.
+ closeFindOnPage(null);
+
+ // Set the current WebView.
+ setCurrentWebView(position);
+
+ // Select the corresponding tab if it does not match the currently selected page. This will happen if the page was scrolled by creating a new tab.
+ if (tabLayout.getSelectedTabPosition() != position) {
+ // Wait until the new tab has been created.
+ tabLayout.post(() -> {
+ // Get a handle for the tab.
+ TabLayout.Tab tab = tabLayout.getTabAt(position);
+
+ // Assert that the tab is not null.
+ assert tab != null;
+
+ // Select the tab.
+ tab.select();
+ });
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ // Do nothing.
+ }
+ });
+
+ // Display the View SSL Certificate dialog when the currently selected tab is reselected.
+ tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
+ @Override
+ public void onTabSelected(TabLayout.Tab tab) {
+ // Select the same page in the view pager.
+ webViewPager.setCurrentItem(tab.getPosition());
+ }
+
+ @Override
+ public void onTabUnselected(TabLayout.Tab tab) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onTabReselected(TabLayout.Tab tab) {
+ // Instantiate the View SSL Certificate dialog.
+ DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId());
+
+ // Display the View SSL Certificate dialog.
+ viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
+ }
+ });
+
+ // Set a touch listener on the bookmarks header linear layout so that touches don't pass through to the button underneath.
+ bookmarksHeaderLinearLayout.setOnTouchListener((view, motionEvent) -> {
+ // Consume the touch.
+ return true;
+ });
+
+ // Set the launch bookmarks activity FAB to launch the bookmarks activity.
+ launchBookmarksActivityFab.setOnClickListener(v -> {
+ // Get a copy of the favorite icon bitmap.
+ Bitmap favoriteIconBitmap = currentWebView.getFavoriteOrDefaultIcon();
+
+ // Create a favorite icon byte array output stream.
+ ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
+
+ // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
+ favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
+
+ // Convert the favorite icon byte array stream to a byte array.
+ byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
+
+ // Create an intent to launch the bookmarks activity.
+ Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
+
+ // Add the extra information to the intent.
+ bookmarksIntent.putExtra("current_url", currentWebView.getUrl());
+ bookmarksIntent.putExtra("current_title", currentWebView.getTitle());
+ bookmarksIntent.putExtra("current_folder", currentBookmarksFolder);
+ bookmarksIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
+
+ // Make it so.
+ startActivity(bookmarksIntent);
+ });
+
+ // Set the create new bookmark folder FAB to display an alert dialog.
+ createBookmarkFolderFab.setOnClickListener(v -> {
+ // Create a create bookmark folder dialog.
+ DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteOrDefaultIcon());
+
+ // Show the create bookmark folder dialog.
+ createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
+ });
+
+ // Set the create new bookmark FAB to display an alert dialog.
+ createBookmarkFab.setOnClickListener(view -> {
+ // Instantiate the create bookmark dialog.
+ DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteOrDefaultIcon());
+
+ // Display the create bookmark dialog.
+ createBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.create_bookmark));
+ });
+
+ // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
+ findOnPageEditText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // Do nothing.
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ // Search for the text in the WebView if it is not null. Sometimes on resume after a period of non-use the WebView will be null.
+ if (currentWebView != null) {
+ currentWebView.findAllAsync(findOnPageEditText.getText().toString());
+ }
+ }
+ });
+
+ // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
+ findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
+ if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed.
+ // Hide the soft keyboard.
+ inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
+
+ // Consume the event.
+ return true;
+ } else { // A different key was pressed.
+ // Do not consume the event.
+ return false;
+ }
+ });
+
+ // Implement swipe to refresh.
+ swipeRefreshLayout.setOnRefreshListener(() -> currentWebView.reload());
+
+ // Store the default progress view offsets for use later in `initializeWebView()`.
+ defaultProgressViewStartOffset = swipeRefreshLayout.getProgressViewStartOffset();
+ defaultProgressViewEndOffset = swipeRefreshLayout.getProgressViewEndOffset();
+
+ // Set the refresh color scheme according to the theme.
+ if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+ swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
+ } else {
+ swipeRefreshLayout.setColorSchemeResources(R.color.violet_500);
+ }
+
+ // Initialize a color background typed value.
+ TypedValue colorBackgroundTypedValue = new TypedValue();
+
+ // Get the color background from the theme.
+ getTheme().resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true);
+
+ // Get the color background int from the typed value.
+ int colorBackgroundInt = colorBackgroundTypedValue.data;
+
+ // Set the swipe refresh background color.
+ swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt);
+
+ // The drawer titles identify the drawer layouts in accessibility mode.
+ drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
+ drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
+
+ // Initialize the bookmarks database helper. The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
+ bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
+
+ // Initialize `currentBookmarksFolder`. `""` is the home folder in the database.
+ currentBookmarksFolder = "";
+
+ // Load the home folder, which is `""` in the database.
+ loadBookmarksFolder();
+
+ bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
+ // Convert the id from long to int to match the format of the bookmarks database.
+ int databaseId = (int) id;
+
+ // Get the bookmark cursor for this ID.
+ Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
+
+ // Move the bookmark cursor to the first row.
+ bookmarkCursor.moveToFirst();
+
+ // Act upon the bookmark according to the type.
+ if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
+ // Store the new folder name in `currentBookmarksFolder`.
+ currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
+
+ // Load the new folder.
+ loadBookmarksFolder();
+ } else { // The selected bookmark is not a folder.
+ // Load the bookmark URL.
+ loadUrl(currentWebView, bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
+
+ // Close the bookmarks drawer.
+ drawerLayout.closeDrawer(GravityCompat.END);
+ }
+
+ // Close the `Cursor`.
+ bookmarkCursor.close();
+ });
+
+ bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
+ // Convert the database ID from `long` to `int`.
+ int databaseId = (int) id;
+
+ // Find out if the selected bookmark is a folder.
+ boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
+
+ // Check to see if the bookmark is a folder.
+ if (isFolder) { // The bookmark is a folder.
+ // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
+ oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
+
+ // Instantiate the edit folder bookmark dialog.
+ DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
+
+ // Show the edit folder bookmark dialog.
+ editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder));
+ } else { // The bookmark is not a folder.
+ // Get the bookmark cursor for this ID.
+ Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
+
+ // Move the bookmark cursor to the first row.
+ bookmarkCursor.moveToFirst();
+
+ // Load the bookmark in a new tab but do not switch to the tab or close the drawer.
+ addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)), false);
+
+ // Display a snackbar.
+ Snackbar.make(currentWebView, R.string.bookmark_opened_in_background, Snackbar.LENGTH_SHORT).show();
+ }
+
+ // Consume the event.
+ return true;
+ });
+
+ // The drawer listener is used to update the navigation menu.
+ drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
+ @Override
+ public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
+ }
+
+ @Override
+ public void onDrawerOpened(@NonNull View drawerView) {
+ }
+
+ @Override
+ public void onDrawerClosed(@NonNull View drawerView) {
+ // Reset the drawer icon when the drawer is closed. Otherwise, it is an arrow if the drawer is open when the app is restarted.
+ actionBarDrawerToggle.syncState();
+ }
+
+ @Override
+ public void onDrawerStateChanged(int newState) {
+ if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing.
+ // Update the navigation menu items if the WebView is not null.
+ if (currentWebView != null) {
+ navigationBackMenuItem.setEnabled(currentWebView.canGoBack());
+ navigationForwardMenuItem.setEnabled(currentWebView.canGoForward());
+ navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward()));
+ navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+
+ // Hide the keyboard (if displayed).
+ inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
+ }
+
+ // Clear the focus from from the URL text box and the WebView. This removes any text selection markers and context menus, which otherwise draw above the open drawers.
+ urlEditText.clearFocus();
+ currentWebView.clearFocus();
+ }
+ }
+ });
+
+ // Replace the header that `WebView` creates for `X-Requested-With` with a null value. The default value is the application ID (com.stoutner.privacybrowser.standard).
+ customHeaders.put("X-Requested-With", "");
+
+ // Inflate a bare WebView to get the default user agent. It is not used to render content on the screen.
+ @SuppressLint("InflateParams") View webViewLayout = getLayoutInflater().inflate(R.layout.bare_webview, null, false);
+
+ // Get a handle for the WebView.
+ WebView bareWebView = webViewLayout.findViewById(R.id.bare_webview);
+
+ // Store the default user agent.
+ webViewDefaultUserAgent = bareWebView.getSettings().getUserAgentString();
+
+ // Destroy the bare WebView.
+ bareWebView.destroy();
+ }
+
+ private void applyAppSettings() {
+ // Get a handle for the shared preferences.
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+
+ // Store the values from the shared preferences in variables.
+ incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
+ sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true);
+ sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true);
+ sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true);
+ proxyMode = sharedPreferences.getString("proxy", getString(R.string.proxy_default_value));
+ fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
+ downloadWithExternalApp = sharedPreferences.getBoolean(getString(R.string.download_with_external_app_key), false);
+ hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
+ scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
+
+ // Apply the saved proxy mode if the app has been restarted.
+ if (savedProxyMode != null) {
+ // Apply the saved proxy mode.
+ proxyMode = savedProxyMode;
+
+ // Reset the saved proxy mode.
+ savedProxyMode = null;
+ }
+
+ // Get the search string.
+ String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
+
+ // Set the search string.
+ if (searchString.equals("Custom URL")) { // A custom search string is used.
+ searchURL = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
+ } else { // A custom search string is not used.
+ searchURL = searchString;
+ }
+
+ // Apply the proxy.
+ applyProxy(false);
+
+ // Get the current layout parameters. Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
+ CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+ AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
+ AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
+ AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
+
+ // Add the scrolling behavior to the layout parameters.
+ if (scrollAppBar) {
+ // Enable scrolling of the app bar.
+ swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
+ toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+ findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+ tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+ } else {
+ // Disable scrolling of the app bar.
+ swipeRefreshLayoutParams.setBehavior(null);
+ toolbarLayoutParams.setScrollFlags(0);
+ findOnPageLayoutParams.setScrollFlags(0);
+ tabsLayoutParams.setScrollFlags(0);
+
+ // Expand the app bar if it is currently collapsed.
+ appBarLayout.setExpanded(true);
+ }
+
+ // Apply the modified layout parameters.
+ swipeRefreshLayout.setLayoutParams(swipeRefreshLayoutParams);
+ toolbar.setLayoutParams(toolbarLayoutParams);
+ findOnPageLinearLayout.setLayoutParams(findOnPageLayoutParams);
+ tabsLinearLayout.setLayoutParams(tabsLayoutParams);
+
+ // Set the app bar scrolling for each WebView.
+ for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+ // Get the WebView tab fragment.
+ WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
+
+ // Get the fragment view.
+ View fragmentView = webViewTabFragment.getView();
+
+ // Only modify the WebViews if they exist.
+ if (fragmentView != null) {
+ // Get the nested scroll WebView from the tab fragment.
+ NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+
+ // Set the app bar scrolling.
+ nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
+ }
+ }
+
+ // Update the full screen browsing mode settings.
+ if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
+ // Update the visibility of the app bar, which might have changed in the settings.
+ if (hideAppBar) {
+ // Hide the tab linear layout.
+ tabsLinearLayout.setVisibility(View.GONE);
+
+ // Hide the action bar.
+ actionBar.hide();
+ } else {
+ // Show the tab linear layout.
+ tabsLinearLayout.setVisibility(View.VISIBLE);
+
+ // Show the action bar.
+ actionBar.show();
+ }
+
+ // Hide the banner ad in the free flavor.
+ if (BuildConfig.FLAVOR.contentEquals("free")) {
+ // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load.
+ View adView = findViewById(R.id.adview);
+
+ // Hide the banner ad.
+ AdHelper.hideAd(adView);
+ }
+
+ /* Hide the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
+ rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ } else { // Privacy Browser is not in full screen browsing mode.
+ // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled.
+ inFullScreenBrowsingMode = false;
+
+ // Show the tab linear layout.
+ tabsLinearLayout.setVisibility(View.VISIBLE);
+
+ // Show the action bar.
+ actionBar.show();
+
+ // Show the banner ad in the free flavor.
+ if (BuildConfig.FLAVOR.contentEquals("free")) {
+ // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load.
+ View adView = findViewById(R.id.adview);
+
+ // Initialize the ads. If this isn't the first run, `loadAd()` will be automatically called instead.
+ // `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23.
+ AdHelper.initializeAds(adView, getApplicationContext(), this, getSupportFragmentManager(), getString(R.string.ad_unit_id));
+ }
+
+ // Remove the `SYSTEM_UI` flags from the root frame layout.
+ rootFrameLayout.setSystemUiVisibility(0);
+ }
+ }
+
+ @Override
+ public void navigateHistory(@NonNull String url, int steps) {
+ // Apply the domain settings.
+ applyDomainSettings(currentWebView, url, false, 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();
+
+ // Apply the domain settings.
+ applyDomainSettings(currentWebView, previousUrl, false, 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 void applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetTab, boolean reloadWebsite, boolean loadUrl) {
+ // Store the current URL.
+ nestedScrollWebView.setCurrentUrl(url);
+
+ // Parse the URL into a URI.
+ Uri uri = Uri.parse(url);
+
+ // Extract the domain from `uri`.
+ String newHostName = uri.getHost();
+
+ // Strings don't like to be null.
+ if (newHostName == null) {
+ newHostName = "";
+ }
+
+ // Apply the domain settings if a new domain is being loaded or if the new domain is blank. This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc.
+ if (!nestedScrollWebView.getCurrentDomainName().equals(newHostName) || newHostName.equals("")) {
+ // Set the new host name as the current domain name.
+ nestedScrollWebView.setCurrentDomainName(newHostName);
+
+ // Reset the ignoring of pinned domain information.
+ nestedScrollWebView.setIgnorePinnedDomainInformation(false);
+
+ // Clear any pinned SSL certificate or IP addresses.
+ nestedScrollWebView.clearPinnedSslCertificate();
+ nestedScrollWebView.clearPinnedIpAddresses();
+
+ // Reset the favorite icon if specified.
+ if (resetTab) {
+ // Initialize the favorite icon.
+ nestedScrollWebView.initializeFavoriteIcon();
+
+ // Get the current page position.
+ int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
+
+ // Get the corresponding tab.
+ TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
+
+ // Update the tab if it isn't null, which sometimes happens when restarting from the background.
+ if (tab != null) {
+ // Get the tab custom view.
+ View tabCustomView = tab.getCustomView();
+
+ // Remove the warning below that the tab custom view might be null.
+ assert tabCustomView != null;
+
+ // Get the tab views.
+ ImageView tabFavoriteIconImageView = tabCustomView.findViewById(R.id.favorite_icon_imageview);
+ TextView tabTitleTextView = tabCustomView.findViewById(R.id.title_textview);
+
+ // Set the default favorite icon as the favorite icon for this tab.
+ tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteOrDefaultIcon(), 64, 64, true));
+
+ // Set the loading title text.
+ tabTitleTextView.setText(R.string.loading);
+ }
+ }
+
+ // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
+ DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
+
+ // Get a full cursor from `domainsDatabaseHelper`.
+ Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
+
+ // Initialize `domainSettingsSet`.
+ Set<String> domainSettingsSet = new HashSet<>();
+
+ // Get the domain name column index.
+ int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
+
+ // Populate `domainSettingsSet`.
+ for (int i = 0; i < domainNameCursor.getCount(); i++) {
+ // Move `domainsCursor` to the current row.
+ domainNameCursor.moveToPosition(i);
+
+ // Store the domain name in `domainSettingsSet`.
+ domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
+ }
+
+ // Close `domainNameCursor.
+ domainNameCursor.close();
+
+ // Initialize the domain name in database variable.
+ String domainNameInDatabase = null;
+
+ // Check the hostname against the domain settings set.
+ if (domainSettingsSet.contains(newHostName)) { // The hostname is contained in the domain settings set.
+ // Record the domain name in the database.
+ domainNameInDatabase = newHostName;
+
+ // Set the domain settings applied tracker to true.
+ nestedScrollWebView.setDomainSettingsApplied(true);
+ } else { // The hostname is not contained in the domain settings set.
+ // Set the domain settings applied tracker to false.
+ nestedScrollWebView.setDomainSettingsApplied(false);
+ }
+
+ // Check all the subdomains of the host name against wildcard domains in the domain cursor.
+ while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name.
+ if (domainSettingsSet.contains("*." + newHostName)) { // Check the host name prepended by `*.`.
+ // Set the domain settings applied tracker to true.
+ nestedScrollWebView.setDomainSettingsApplied(true);
+
+ // Store the applied domain names as it appears in the database.
+ domainNameInDatabase = "*." + newHostName;
+ }
+
+ // Strip out the lowest subdomain of of the host name.
+ newHostName = newHostName.substring(newHostName.indexOf(".") + 1);
+ }
+
+
+ // Get a handle for the shared preferences.
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+
+ // Store the general preference information.
+ String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
+ String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
+ boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
+ String webViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value));
+ boolean wideViewport = sharedPreferences.getBoolean("wide_viewport", true);
+ boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);