X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FMainWebViewActivity.java;h=fd357a6a4ed478550916b88ddc639026720590e0;hp=1ec5a020037e08504db735b8d43fc6ecd1c00184;hb=9d5e4c56326502b6b74e8f3e463275f5c1e176cc;hpb=16ccee9956383ad3a38b09f7a4f7e9aeee92cd42 diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index 1ec5a020..fd357a6a 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -36,7 +36,6 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -46,7 +45,6 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.net.http.SslCertificate; import android.net.http.SslError; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Environment; @@ -103,39 +101,40 @@ import androidx.core.content.ContextCompat; import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.viewpager.widget.ViewPager; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.navigation.NavigationView; import com.google.android.material.snackbar.Snackbar; - import com.google.android.material.tabs.TabLayout; + import com.stoutner.privacybrowser.BuildConfig; import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter; +import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses; import com.stoutner.privacybrowser.dialogs.AdConsentDialog; import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog; import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog; import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog; +import com.stoutner.privacybrowser.dialogs.DownloadFileDialog; import com.stoutner.privacybrowser.dialogs.DownloadImageDialog; import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog; import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog; import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog; import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog; import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog; +import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog; import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog; import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog; import com.stoutner.privacybrowser.fragments.WebViewTabFragment; import com.stoutner.privacybrowser.helpers.AdHelper; import com.stoutner.privacybrowser.helpers.BlockListHelper; import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; +import com.stoutner.privacybrowser.helpers.CheckPinnedMismatchHelper; import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper; import com.stoutner.privacybrowser.helpers.OrbotProxyHelper; -import com.stoutner.privacybrowser.dialogs.DownloadFileDialog; -import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog; import com.stoutner.privacybrowser.views.NestedScrollWebView; import java.io.ByteArrayInputStream; @@ -143,22 +142,20 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.lang.ref.WeakReference; -import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; -import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +// TODO. The swipe refresh indicator needs to be enabled/disabled when switching tabs. + // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21. public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener, @@ -171,36 +168,34 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `allowScreenshots` is public static so it can be accessed from everywhere. It is also used in `onCreate()`. public static boolean allowScreenshots; - // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`, - // `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `EditBookmarkDatabaseViewDialog`, and `ViewSslCertificateDialog`. It is also used in `onCreate()`, - // `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcut()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`. + // `favoriteIconBitmap` is public static so it can be accessed from `BookmarksActivity`, `BookmarksDatabaseViewActivity`, `CreateBookmarkFolderDialog`, + // `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `EditBookmarkDatabaseViewDialog`, and `ViewSslCertificateDialog`. It is also used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, + // `onCreateHomeScreenShortcut()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`. public static Bitmap favoriteIconBitmap; // `favoriteIconDefaultBitmap` public static so it can be accessed from `PinnedMismatchDialog`. It is also used in `onCreate()` and `applyDomainSettings`. public static Bitmap favoriteIconDefaultBitmap; - // `formattedUrlString` is public static so it can be accessed from `AddDomainDialog`, `BookmarksActivity`, `DomainSettingsFragment`, `CreateBookmarkDialog`, - // and `PinnedMismatchDialog`. + // TODO Remove. + // `formattedUrlString` is public static so it can be accessed from `AddDomainDialog`, `BookmarksActivity`, `DomainSettingsFragment`, and `PinnedMismatchDialog`. // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`. public static String formattedUrlString; - // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedMismatchDialog`, and `ViewSslCertificateDialog`. - // It is also used in `onCreate()` and `checkPinnedMismatch()`. - public static SslCertificate sslCertificate; - - // `currentHostIpAddresses` is public static so it can be accessed from `DomainSettingsFragment` and `ViewSslCertificateDialog`. - // It is also used in `onCreate()` and `GetHostIpAddresses()`. - public static String currentHostIpAddresses; + // TODO. We are going to have to move this to the NestedScrollWebView. + // The URL loading tracker is public static so it can be accessed from `GetHostIpAddresses`. + // It is also used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, `applyDomainSettings()`, and `GetHostIpAddresses`. + public static boolean urlIsLoading; // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`. public static String orbotStatus; - // `webViewTitle` is public static so it can be accessed from `CreateBookmarkDialog`. It is also used in `onCreate()`. - public static String webViewTitle; - + // TODO. // `appliedUserAgentString` is public static so it can be accessed from `ViewSourceActivity`. It is also used in `applyDomainSettings()`. public static String appliedUserAgentString; + // The WebView pager adapter is accessed from `PinnedMismatchDialog`. It is also used in `onCreate()`, `onResume()`, and `addTab()`. + public static WebViewPagerAdapter webViewPagerAdapter; + // `reloadOnRestart` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()` public static boolean reloadOnRestart; @@ -210,60 +205,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`. public static boolean restartFromBookmarksActivity; - // The block list versions are public static so they can be accessed from `AboutTabFragment`. They are also used in `onCreate()`. + // The blocklist versions are public static so they can be accessed from `AboutTabFragment`. They are also used in `onCreate()`. public static String easyListVersion; public static String easyPrivacyVersion; public static String fanboysAnnoyanceVersion; public static String fanboysSocialVersion; public static String ultraPrivacyVersion; - // The request items are public static so they can be accessed by `BlockListHelper`, `RequestsArrayAdapter`, and `ViewRequestsDialog`. They are also used in `onCreate()` and `onPrepareOptionsMenu()`. - public static List resourceRequests; - public static String[] whiteListResultStringArray; - private int blockedRequests; - private int easyListBlockedRequests; - private int easyPrivacyBlockedRequests; - private int fanboysAnnoyanceListBlockedRequests; - private int fanboysSocialBlockingListBlockedRequests; - private int ultraPrivacyBlockedRequests; - private int thirdPartyBlockedRequests; - - public final static int REQUEST_DISPOSITION = 0; - public final static int REQUEST_URL = 1; - public final static int REQUEST_BLOCKLIST = 2; - public final static int REQUEST_SUBLIST = 3; - public final static int REQUEST_BLOCKLIST_ENTRIES = 4; - public final static int REQUEST_BLOCKLIST_ORIGINAL_ENTRY = 5; - - public final static int REQUEST_DEFAULT = 0; - public final static int REQUEST_ALLOWED = 1; - public final static int REQUEST_THIRD_PARTY = 2; - public final static int REQUEST_BLOCKED = 3; - - public final static int MAIN_WHITELIST = 1; - public final static int FINAL_WHITELIST = 2; - public final static int DOMAIN_WHITELIST = 3; - public final static int DOMAIN_INITIAL_WHITELIST = 4; - public final static int DOMAIN_FINAL_WHITELIST = 5; - public final static int THIRD_PARTY_WHITELIST = 6; - public final static int THIRD_PARTY_DOMAIN_WHITELIST = 7; - public final static int THIRD_PARTY_DOMAIN_INITIAL_WHITELIST = 8; - - public final static int MAIN_BLACKLIST = 9; - public final static int INITIAL_BLACKLIST = 10; - public final static int FINAL_BLACKLIST = 11; - public final static int DOMAIN_BLACKLIST = 12; - public final static int DOMAIN_INITIAL_BLACKLIST = 13; - public final static int DOMAIN_FINAL_BLACKLIST = 14; - public final static int DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 15; - public final static int THIRD_PARTY_BLACKLIST = 16; - public final static int THIRD_PARTY_INITIAL_BLACKLIST = 17; - public final static int THIRD_PARTY_DOMAIN_BLACKLIST = 18; - public final static int THIRD_PARTY_DOMAIN_INITIAL_BLACKLIST = 19; - public final static int THIRD_PARTY_REGULAR_EXPRESSION_BLACKLIST = 20; - public final static int THIRD_PARTY_DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 21; - public final static int REGULAR_EXPRESSION_BLACKLIST = 22; - // `blockAllThirdPartyRequests` is public static so it can be accessed from `RequestsActivity`. // It is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()` public static boolean blockAllThirdPartyRequests; @@ -272,20 +220,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`. public static String currentBookmarksFolder; - // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedMismatchDialog`. It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. - public static int domainSettingsDatabaseId; - - // The pinned variables are public static so they can be accessed from `PinnedMismatchDialog`. They are also used in `onCreate()`, `applyDomainSettings()`, and `checkPinnedMismatch()`. - public static String pinnedSslIssuedToCName; - public static String pinnedSslIssuedToOName; - public static String pinnedSslIssuedToUName; - public static String pinnedSslIssuedByCName; - public static String pinnedSslIssuedByOName; - public static String pinnedSslIssuedByUName; - public static Date pinnedSslStartDate; - public static Date pinnedSslEndDate; - public static String pinnedHostIpAddresses; - // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`. public final static int UNRECOGNIZED_USER_AGENT = -1; public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1; @@ -296,33 +230,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook - // `urlIsLoading` is used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, `applyDomainSettings()`, and `GetHostIpAddresses`. - private static boolean urlIsLoading; - - // `gettingIpAddresses` is used in `onCreate() and `GetHostIpAddresses`. - private static boolean gettingIpAddresses; - - // `pinnedDomainSslCertificate` is used in `onCreate()`, `applyDomainSettings()`, and `checkPinnedMismatch()`. - private static boolean pinnedSslCertificate; - - // `pinnedIpAddress` is used in `applyDomainSettings()` and `checkPinnedMismatch()`. - private static boolean pinnedIpAddresses; - - // `ignorePinnedDomainInformation` is used in `onSslMismatchProceed()`, `applyDomainSettings()`, and `checkPinnedMismatch()`. - private static boolean ignorePinnedDomainInformation; - - // The fragment manager is initialized in `onCreate()` and accessed from the static `checkPinnedMismatch()`. - private static FragmentManager fragmentManager; - - - // A handle for the activity is set in `onCreate()` and accessed in `WebViewPagerAdapter`. - private Activity activity; - // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`. private boolean navigatingHistory; // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, - // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `applyProxyThroughOrbot()`. + // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxyThroughOrbot()`, and `applyDomainSettings()`. private NestedScrollWebView currentWebView; // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`. @@ -349,6 +261,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `saveFormDataEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. It can be removed once the minimum API >= 26. private boolean saveFormDataEnabled; + // TODO Move to NestedScrollWebView. // `nightMode` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. private boolean nightMode; @@ -364,14 +277,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `refreshMenuItem` is used in `onCreate()` and `onCreateOptionsMenu()`. private MenuItem refreshMenuItem; - // The WebView pager adapter is used in `onCreate()`, `onResume()`, and `addTab()`. - private WebViewPagerAdapter webViewPagerAdapter; - // The navigation requests menu item is used in `onCreate()` and accessed from `WebViewPagerAdapter`. private MenuItem navigationRequestsMenuItem; + // TODO. This could probably be removed. // The blocklist helper is used in `onCreate()` and `WebViewPagerAdapter`. - BlockListHelper blockListHelper; + private BlockListHelper blockListHelper; // The blocklists are populated in `onCreate()` and accessed from `WebViewPagerAdapter`. private ArrayList> easyList; @@ -441,9 +352,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`. private boolean waitingForOrbot; - // `domainSettingsApplied` is used in `prepareOptionsMenu()` and `applyDomainSettings()`. - private boolean domainSettingsApplied; - // `domainSettingsJavaScriptEnabled` is used in `onOptionsItemSelected()` and `applyDomainSettings()`. private Boolean domainSettingsJavaScriptEnabled; @@ -462,9 +370,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`. private ActionBarDrawerToggle actionBarDrawerToggle; - // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`. - private EditText urlTextBox; - // The color spans are used in `onCreate()` and `highlightUrlText()`. private ForegroundColorSpan redColorSpan; private ForegroundColorSpan initialGrayColorSpan; @@ -554,10 +459,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the content view. setContentView(R.layout.main_framelayout); - // Get handles for views, resources, and managers. - activity = this; - Resources resources = getResources(); - fragmentManager = getSupportFragmentManager(); + // Get handles for the input method manager and toolbar. inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); Toolbar toolbar = findViewById(R.id.toolbar); @@ -568,28 +470,28 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // This is needed to get rid of the Android Studio warning that the action bar might be null. assert actionBar != null; - // Add the custom `url_app_bar` layout, which shows the favorite icon and the URL text bar. + // Add the custom layout, which shows the URL text bar. actionBar.setCustomView(R.layout.url_app_bar); actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); // Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23. - redColorSpan = new ForegroundColorSpan(resources.getColor(R.color.red_a700)); - initialGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500)); - finalGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500)); + redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700)); + initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500)); + finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500)); // Get a handle for `urlTextBox`. - urlTextBox = findViewById(R.id.url_edittext); + EditText urlEditText = findViewById(R.id.url_edittext); // Remove the formatting from `urlTextBar` when the user is editing the text. - urlTextBox.setOnFocusChangeListener((View v, boolean hasFocus) -> { + urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> { if (hasFocus) { // The user is editing the URL text box. // Remove the highlighting. - urlTextBox.getText().removeSpan(redColorSpan); - urlTextBox.getText().removeSpan(initialGrayColorSpan); - urlTextBox.getText().removeSpan(finalGrayColorSpan); + 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. - urlTextBox.setSelection(0); + urlEditText.setSelection(0); // Reapply the highlighting. highlightUrlText(); @@ -597,7 +499,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook }); // Set the go button on the keyboard to load the URL in `urlTextBox`. - urlTextBox.setOnKeyListener((View v, int keyCode, KeyEvent event) -> { + 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. @@ -643,9 +545,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Instantiate the block list helper. blockListHelper = new BlockListHelper(); - // Initialize the list of resource requests. - resourceRequests = new ArrayList<>(); - // Parse the block lists. easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt"); easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt"); @@ -686,7 +585,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook navigationRequestsMenuItem = navigationMenu.getItem(6); // Initialize the web view pager adapter. - webViewPagerAdapter = new WebViewPagerAdapter(fragmentManager); + webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager()); // Set the pager adapter on the web view pager. webViewPager.setAdapter(webViewPagerAdapter); @@ -703,15 +602,44 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onPageSelected(int position) { - // TODO. Consider using an array of the WebViews. - // Get the current WebView fragment. Instantiate item returns the current item if it already exists. - Fragment webViewFragment = (Fragment) webViewPagerAdapter.instantiateItem(webViewPager, position); + // Get the WebView tab fragment. + WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(position); + + // Get the fragment view. + View fragmentView = webViewTabFragment.getView(); - // Remove the lint error below that the WebView fragment might be null. - assert webViewFragment.getView() != null; + // Remove the incorrect lint warning below that the fragment view might be null. + assert fragmentView != null; // Store the current WebView. - currentWebView = webViewFragment.getView().findViewById(R.id.nestedscroll_webview); + currentWebView = fragmentView.findViewById(R.id.nestedscroll_webview); + + // Store the current formatted URL string. + formattedUrlString = currentWebView.getUrl(); + + // Clear the focus from the URL text box. + urlEditText.clearFocus(); + + // Hide the soft keyboard. + inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0); + + // Display the current URL in the URL text box. + urlEditText.setText(formattedUrlString); + + // Highlight the URL text. + highlightUrlText(); + + // Set the background to indicate the domain settings status. + if (currentWebView.getDomainSettingsApplied()) { + // Set a green background on `urlTextBox` to indicate that custom domain settings are being used. The deprecated `.getDrawable()` must be used until the minimum API >= 21. + if (darkTheme) { + urlEditText.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue)); + } else { + urlEditText.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green)); + } + } else { + urlEditText.setBackgroundDrawable(getResources().getDrawable(R.color.transparent)); + } // Select the corresponding tab if it does not match the currently selected page. This will happen if the page was scrolled via swiping in the view pager. if (tabLayout.getSelectedTabPosition() != position) { @@ -748,31 +676,36 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onTabReselected(TabLayout.Tab tab) { // Instantiate the View SSL Certificate dialog. - DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificateDialog(); + DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId()); // Display the View SSL Certificate dialog. viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate)); } }); - // Add the first tab. - webViewPagerAdapter.addPage(); + // Add the first tab. (It doesn't matter what view is passed. That is just required as part of the ImageView `onClick()` syntax). + addTab(webViewPager); // Set the bookmarks drawer resources according to the theme. This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget. + // The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21 and and `getResources().getColor()` must be used until the minimum API >= 23. if (darkTheme) { - launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_dark)); - createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_dark)); - createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_dark)); - bookmarksListView.setBackgroundColor(resources.getColor(R.color.gray_850)); + launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_dark)); + createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_dark)); + createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_dark)); + bookmarksListView.setBackgroundColor(getResources().getColor(R.color.gray_850)); } else { - launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_light)); - createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_light)); - createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_light)); - bookmarksListView.setBackgroundColor(resources.getColor(R.color.white)); + launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_light)); + createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_light)); + createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_light)); + bookmarksListView.setBackgroundColor(getResources().getColor(R.color.white)); } // Set the launch bookmarks activity FAB to launch the bookmarks activity. launchBookmarksActivityFab.setOnClickListener(v -> { + // Store the current WebView url and title in the bookmarks activity. + BookmarksActivity.currentWebViewUrl = currentWebView.getUrl(); + BookmarksActivity.currentWebViewTitle = currentWebView.getTitle(); + // Create an intent to launch the bookmarks activity. Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class); @@ -787,14 +720,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook createBookmarkFolderFab.setOnClickListener(v -> { // Show the create bookmark folder dialog and name the instance `@string/create_folder`. DialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog(); - createBookmarkFolderDialog.show(fragmentManager, resources.getString(R.string.create_folder)); + createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder)); }); // Set the create new bookmark FAB to display an alert dialog. createBookmarkFab.setOnClickListener(view -> { - // Show the create bookmark dialog and name the instance `@string/create_bookmark`. - DialogFragment createBookmarkDialog = new CreateBookmarkDialog(); - createBookmarkDialog.show(fragmentManager, resources.getString(R.string.create_bookmark)); + // Instantiate the create bookmark dialog. + DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), favoriteIconBitmap); + + // 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`. @@ -819,7 +754,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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. `0` indicates no additional flags. + // Hide the soft keyboard. inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0); // Consume the event. @@ -897,11 +832,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`. DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId); - editBookmarkFolderDialog.show(fragmentManager, resources.getString(R.string.edit_folder)); + editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder)); } else { // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`. DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId); - editBookmarkDialog.show(fragmentManager, resources.getString(R.string.edit_bookmark)); + editBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.edit_bookmark)); } // Consume the event. @@ -909,11 +844,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook }); // Get the status bar pixel size. - int statusBarResourceId = resources.getIdentifier("status_bar_height", "dimen", "android"); - int statusBarPixelSize = resources.getDimensionPixelSize(statusBarResourceId); + int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); + int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId); // Get the resource density. - float screenDensity = resources.getDisplayMetrics().density; + float screenDensity = getResources().getDisplayMetrics().density; // Calculate the drawer header padding. This is used to move the text in the drawer headers below any cutouts. drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity); @@ -956,13 +891,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook navigationBackMenuItem.setEnabled(currentWebView.canGoBack()); navigationForwardMenuItem.setEnabled(currentWebView.canGoForward()); navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward())); - navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests); + 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. - urlTextBox.clearFocus(); + urlEditText.clearFocus(); currentWebView.clearFocus(); } } @@ -998,11 +933,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook saveFormDataEnabled = false; // Form data can be removed once the minimum API >= 26. nightMode = false; + // 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. - // TODO webViewDefaultUserAgent = mainWebView.getSettings().getUserAgentString(); + webViewDefaultUserAgent = bareWebView.getSettings().getUserAgentString(); - // Initialize the WebView title. - webViewTitle = getString(R.string.no_title); + // Destroy the bare WebView. + bareWebView.destroy(); // Initialize the favorite icon bitmap. `ContextCompat` must be used until API >= 21. Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world); @@ -1017,7 +958,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Initialize the user agent array adapter and string array. userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item); - userAgentDataArray = resources.getStringArray(R.array.user_agent_data); + userAgentDataArray = getResources().getStringArray(R.array.user_agent_data); // Get the intent that started the app. Intent launchingIntent = getIntent(); @@ -1111,7 +1052,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook sendBroadcast(orbotIntent); } - // Apply the app settings if returning from the Settings activity.. + // Apply the app settings if returning from the Settings activity. if (reapplyAppSettingsOnRestart) { // Apply the app settings. applyAppSettings(); @@ -1119,8 +1060,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the webpage if displaying of images has been disabled in the Settings activity. if (reloadOnRestart) { // Reload the WebViews. - // TODO - currentWebView.reload(); + 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 reload 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); + + // Reload the WebView. This doesn't seem to work if for WebViews that aren't visible. + nestedScrollWebView.reload(); + } + } // Reset `reloadOnRestartBoolean`. reloadOnRestart = false; @@ -1130,10 +1085,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook reapplyAppSettingsOnRestart = false; } + // TODO apply to all the tabs. // Apply the domain settings if returning from the Domains activity. if (reapplyDomainSettingsOnRestart) { // Reapply the domain settings. - applyDomainSettings(formattedUrlString, false, true); + applyDomainSettings(currentWebView, formattedUrlString, false, true); // Reset `reapplyDomainSettingsOnRestart`. reapplyDomainSettingsOnRestart = false; @@ -1173,11 +1129,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Run the default commands. super.onResume(); - // Resume JavaScript (if enabled). - // TODO mainWebView.resumeTimers(); + 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 resume the WebViews if they exist (they won't when the app is first created). + if (fragmentView != null) { + // Get the nested scroll WebView from the tab fragment. + NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - // Resume `mainWebView`. - // TODO mainWebView.onResume(); + // Resume the nested scroll WebView JavaScript timers. + nestedScrollWebView.resumeTimers(); + + // Resume the nested scroll WebView. + nestedScrollWebView.onResume(); + } + } // Display a message to the user if waiting for Orbot. if (waitingForOrbot && !orbotStatus.equals("ON")) { @@ -1214,13 +1184,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Run the default commands. super.onPause(); - // Pause `mainWebView`. - // TODO - currentWebView.onPause(); + 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(); - // Stop all JavaScript. - // TODO - currentWebView.pauseTimers(); + // Only pause the WebViews if they exist (they won't when the app is first created). + if (fragmentView != null) { + // Get the nested scroll WebView from the tab fragment. + NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); + + // Pause the nested scroll WebView. + nestedScrollWebView.onPause(); + + // Pause the nested scroll WebView JavaScript timers. + nestedScrollWebView.pauseTimers(); + } + } // Pause the ad or it will continue to consume resources in the background on the free flavor. if (BuildConfig.FLAVOR.contentEquals("free")) { @@ -1335,11 +1317,37 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode); MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot); - // Set the text for the domain menu item. - if (domainSettingsApplied) { - addOrEditDomain.setTitle(R.string.edit_domain_settings); - } else { - addOrEditDomain.setTitle(R.string.add_domain_settings); + // Initialize the current user agent string and the font size. + String currentUserAgent = getString(R.string.user_agent_privacy_browser); + int fontSize = 100; + + // Set items that require the current web view to be populated. It will be null when the program is first opened, as `onPrepareOptionsMenu()` is called before the first WebView is initialized. + if (currentWebView != null) { + // Set the add or edit domain text. + if (currentWebView.getDomainSettingsApplied()) { + addOrEditDomain.setTitle(R.string.edit_domain_settings); + } else { + addOrEditDomain.setTitle(R.string.add_domain_settings); + } + + // Get the current user agent from the WebView. + currentUserAgent = currentWebView.getSettings().getUserAgentString(); + + // Get the current font size from the + fontSize = currentWebView.getSettings().getTextZoom(); + + // Set the status of the display images menu item. + displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically()); + + // Initialize the display names for the blocklists with the number of blocked requests. + blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); + easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_LIST_BLOCKED_REQUESTS) + " - " + getString(R.string.easylist)); + easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY_BLOCKED_REQUESTS) + " - " + getString(R.string.easyprivacy)); + fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS) + " - " + getString(R.string.fanboys_annoyance_list)); + fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS) + " - " + + getString(R.string.fanboys_social_blocking_list)); + ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY_BLOCKED_REQUESTS) + " - " + getString(R.string.ultraprivacy)); + blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_BLOCKED_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests)); } // Set the status of the menu item checkboxes. @@ -1354,7 +1362,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook ultraPrivacyMenuItem.setChecked(ultraPrivacyEnabled); blockAllThirdPartyRequestsMenuItem.setChecked(blockAllThirdPartyRequests); swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled()); - // TODO displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically()); nightModeMenuItem.setChecked(nightMode); proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot); @@ -1399,19 +1406,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Disable Fanboy's Social Blocking List if Fanboy's Annoyance List is checked. fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled); - // Initialize the display names for the blocklists with the number of blocked requests. - blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + blockedRequests); - easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist)); - easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy)); - fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list)); - fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list)); - ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy)); - blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests)); - - // Get the current user agent. - // TODO String currentUserAgent = mainWebView.getSettings().getUserAgentString(); - String currentUserAgent = ""; - // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants. if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser. menu.findItem(R.id.user_agent_privacy_browser).setChecked(true); @@ -1441,9 +1435,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook menu.findItem(R.id.user_agent_custom).setChecked(true); } - // Initialize font size variables. - // TODO int fontSize = mainWebView.getSettings().getTextZoom(); - int fontSize = 100; + // Instantiate the font size title and the selected font size menu item. String fontSizeTitle; MenuItem selectedFontSizeMenuItem; @@ -1558,16 +1550,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; case R.id.add_or_edit_domain: - if (domainSettingsApplied) { // Edit the current domain settings. + if (currentWebView.getDomainSettingsApplied()) { // Edit the current domain settings. // Reapply the domain settings on returning to `MainWebViewActivity`. reapplyDomainSettingsOnRestart = true; currentDomainName = ""; + // TODO. Move these to `putExtra`. The certificate can be stored as strings. + // Store the current SSL certificate and IP addresses in the domains activity. + DomainsActivity.currentSslCertificate = currentWebView.getCertificate(); + DomainsActivity.currentIpAddresses = currentWebView.getCurrentIpAddresses(); + // Create an intent to launch the domains activity. Intent domainsIntent = new Intent(this, DomainsActivity.class); // Put extra information instructing the domains activity to directly load the current domain and close on back instead of returning to the domains list. - domainsIntent.putExtra("loadDomain", domainSettingsDatabaseId); + domainsIntent.putExtra("loadDomain", currentWebView.getDomainSettingsDatabaseId()); domainsIntent.putExtra("closeOnBack", true); // Make it so. @@ -1587,6 +1584,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Create the domain and store the database ID. int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain); + // TODO. Move these to `putExtra`. The certificate can be stored as strings. + // Store the current SSL certificate and IP addresses in the domains activity. + DomainsActivity.currentSslCertificate = currentWebView.getCertificate(); + DomainsActivity.currentIpAddresses = currentWebView.getCurrentIpAddresses(); + // Create an intent to launch the domains activity. Intent domainsIntent = new Intent(this, DomainsActivity.class); @@ -2043,7 +2045,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook if (nightMode) { // Night mode is enabled. Enable JavaScript. // Update the global variable. javaScriptEnabled = true; - } else if (domainSettingsApplied) { // Night mode is disabled and domain settings are applied. Set JavaScript according to the domain settings. + } else if (currentWebView.getDomainSettingsApplied()) { // Night mode is disabled and domain settings are applied. Set JavaScript according to the domain settings. // Get the JavaScript preference that was stored the last time domain settings were loaded. javaScriptEnabled = domainSettingsJavaScriptEnabled; } else { // Night mode is disabled and domain settings are not applied. Set JavaScript according to the global preference. @@ -2094,7 +2096,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case R.id.share_url: // Setup the share string. - String shareString = webViewTitle + " – " + urlTextBox.getText().toString(); + String shareString = currentWebView.getTitle() + " – " + formattedUrlString; // Create the share intent. Intent shareIntent = new Intent(Intent.ACTION_SEND); @@ -2181,7 +2183,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the current tab number. int currentTabNumber = tabLayout.getSelectedTabPosition(); - // Delete the tab and page. + // Delete the current tab. + tabLayout.removeTabAt(currentTabNumber); + + // Delete the current page. webViewPagerAdapter.deletePage(currentTabNumber); break; @@ -2268,9 +2273,23 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Clear the cache. if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) { - // Clear the cache. - // TODO - currentWebView.clearCache(true); + // Clear the cache from 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 clear the cache if the WebView exists. + if (fragmentView != null) { + // Get the nested scroll WebView from the tab fragment. + NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); + + // Clear the cache for this WebView. + nestedScrollWebView.clearCache(true); + } + } // Manually delete the cache directories. try { @@ -2289,24 +2308,36 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Clear SSL certificate preferences. - // TODO - currentWebView.clearSslPreferences(); + // Wipe out 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 wipe out the WebView if it exists. + if (fragmentView != null) { + // Get the nested scroll WebView from the tab fragment. + NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); + + // Clear SSL certificate preferences for this WebView. + nestedScrollWebView.clearSslPreferences(); + + // Clear the back/forward history for this WebView. + nestedScrollWebView.clearHistory(); - // Clear the back/forward history. - // TODO - currentWebView.clearHistory(); + // Destroy the internal state of `mainWebView`. + nestedScrollWebView.destroy(); + } + } - // Clear `formattedUrlString`. + // Clear the formatted URL string. formattedUrlString = null; - // Clear `customHeaders`. + // Clear the custom headers. customHeaders.clear(); - // Destroy the internal state of `mainWebView`. - // TODO - currentWebView.destroy(); - // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache. // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`. if (clearEverything) { @@ -2372,8 +2403,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook break; case R.id.requests: - // Launch the requests activity. + // Populate the resource requests. + RequestsActivity.resourceRequests = currentWebView.getResourceRequests(); + + // Create an intent to launch the Requests activity. Intent requestsIntent = new Intent(this, RequestsActivity.class); + + // Make it so. startActivity(requestsIntent); break; @@ -2392,6 +2428,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook reapplyDomainSettingsOnRestart = true; currentDomainName = ""; + // TODO. Move these to `putExtra`. The certificate can be stored as strings. + // Store the current SSL certificate and IP addresses in the domains activity. + DomainsActivity.currentSslCertificate = currentWebView.getCertificate(); + DomainsActivity.currentIpAddresses = currentWebView.getCurrentIpAddresses(); + // Launch the domains activity. Intent domainsIntent = new Intent(this, DomainsActivity.class); startActivity(domainsIntent); @@ -3183,13 +3224,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @Override - public void onPinnedMismatchBack() { + public void onPinnedMismatchBack() { // TODO. Move this logic to the dialog. if (currentWebView.canGoBack()) { // There is a back page in the history. // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled. - formattedUrlString = ""; + formattedUrlString = ""; // TODO. // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded. - navigatingHistory = true; + navigatingHistory = true; // TODO. // Go back. currentWebView.goBack(); @@ -3200,9 +3241,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @Override - public void onPinnedMismatchProceed() { + public void onPinnedMismatchProceed() { // TODO. Move this logic to the dialog. // Do not check the pinned information for this domain again until the domain changes. - ignorePinnedDomainInformation = true; + currentWebView.setIgnorePinnedDomainInformation(true); } @Override @@ -3270,8 +3311,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } private void loadUrlFromTextBox() { + // Get a handle for the URL edit text. + EditText urlEditText = findViewById(R.id.url_edittext); + // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string. - String unformattedUrlString = urlTextBox.getText().toString().trim(); + String unformattedUrlString = urlEditText.getText().toString().trim(); // Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search. if (unformattedUrlString.startsWith("content://")) { @@ -3330,8 +3374,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook formattedUrlString = searchURL + encodedUrlString; } - // Clear the focus from the URL text box. Otherwise, proximate typing in the box will retain the colorized formatting instead of being reset during refocus. - urlTextBox.clearFocus(); + // Clear the focus from the URL edit text. Otherwise, proximate typing in the box will retain the colorized formatting instead of being reset during refocus. + urlEditText.clearFocus(); // Make it so. loadUrl(formattedUrlString); @@ -3342,7 +3386,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook formattedUrlString = url; // Apply the domain settings. - applyDomainSettings(url, true, false); + applyDomainSettings(currentWebView, url, true, false); // If loading a website, set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website. urlIsLoading = !url.equals(""); @@ -3411,8 +3455,23 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook customHeaders.remove("DNT"); } - // Set the app bar scrolling. - currentWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true)); + // 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(sharedPreferences.getBoolean("scroll_app_bar", true)); + } + } // Update the full screen browsing mode settings. if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode. @@ -3449,7 +3508,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show the banner ad in the free flavor. if (BuildConfig.FLAVOR.contentEquals("free")) { // Initialize the ads. If this isn't the first run, `loadAd()` will be automatically called instead. - AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), fragmentManager, getString(R.string.google_app_id), getString(R.string.ad_unit_id)); + AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getSupportFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id)); } // Remove the `SYSTEM_UI` flags from the root frame layout. @@ -3464,12 +3523,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled. // The deprecated `.getDrawable()` must be used until the minimum API >= 21. @SuppressWarnings("deprecation") - private boolean applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) { + private boolean applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetFavoriteIcon, boolean reloadWebsite) { // Get a handle for the URL edit text. EditText urlEditText = findViewById(R.id.url_edittext); // Get the current user agent. - String initialUserAgent = currentWebView.getSettings().getUserAgentString(); + String initialUserAgent = nestedScrollWebView.getSettings().getUserAgentString(); // Initialize a variable to track if the user agent changes. boolean userAgentChanged = false; @@ -3486,10 +3545,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // If either `hostName` or `currentDomainName` are `null`, run the options for loading a new domain name. // The lint suggestion to simplify the `if` statement is incorrect, because `hostName.equals(currentDomainName)` can produce a `null object reference.` //noinspection SimplifiableIfStatement - if ((hostName == null) || (currentDomainName == null)) { + if ((hostName == null) || (currentDomainName == null)) { // TODO. loadingNewDomainName = true; } else { // Determine if `hostName` equals `currentDomainName`. - loadingNewDomainName = !hostName.equals(currentDomainName); + loadingNewDomainName = !hostName.equals(currentDomainName); // TODO. } // Strings don't like to be null. @@ -3500,21 +3559,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Only apply the domain settings if a new domain is being loaded. This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc. if (loadingNewDomainName) { // Set the new `hostname` as the `currentDomainName`. - currentDomainName = hostName; + currentDomainName = hostName; // TODO. // Reset the ignoring of pinned domain information. - ignorePinnedDomainInformation = false; + nestedScrollWebView.setIgnorePinnedDomainInformation(false); + + // Clear any pinned SSL certificate or IP addresses. + nestedScrollWebView.clearPinnedSslCertificate(); + nestedScrollWebView.clearPinnedIpAddresses(); // Reset the favorite icon if specified. if (resetFavoriteIcon) { // Store the favorite icon bitmap. - favoriteIconBitmap = favoriteIconDefaultBitmap; + favoriteIconBitmap = favoriteIconDefaultBitmap; // TODO. // Get a handle for the tab layout. TabLayout tabLayout = findViewById(R.id.tablayout); // Get the current tab. - TabLayout.Tab currentTab = tabLayout.getTabAt(tabLayout.getSelectedTabPosition()); + TabLayout.Tab currentTab = tabLayout.getTabAt(tabLayout.getSelectedTabPosition()); // TODO. We need to get the tab for this WebView, which might not be the current tab. // Remove the warning below that the current tab might be null. assert currentTab != null; @@ -3559,21 +3622,26 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Close `domainNameCursor. domainNameCursor.close(); - // Initialize variables to track if domain settings will be applied and, if so, under which name. - domainSettingsApplied = false; + // Initialize the domain name in database variable. String domainNameInDatabase = null; - // Check the hostname. - if (domainSettingsSet.contains(hostName)) { - domainSettingsApplied = true; + // Check the hostname against the domain settings set. + if (domainSettingsSet.contains(hostName)) { // The hostname is contained in the domain settings set. + // Record the domain name in the database. domainNameInDatabase = hostName; + + // 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 (!domainSettingsApplied && hostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name. + while (!nestedScrollWebView.getDomainSettingsApplied() && hostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name. if (domainSettingsSet.contains("*." + hostName)) { // Check the host name prepended by `*.`. - // Apply the domain settings. - domainSettingsApplied = true; + // Set the domain settings applied tracker to true. + nestedScrollWebView.setDomainSettingsApplied(true); // Store the applied domain names as it appears in the database. domainNameInDatabase = "*." + hostName; @@ -3590,131 +3658,147 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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)); - defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)); + defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)); // TODO. boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true); - nightMode = sharedPreferences.getBoolean("night_mode", false); + nightMode = sharedPreferences.getBoolean("night_mode", false); // TODO. boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true); - if (domainSettingsApplied) { // The url has custom domain settings. + if (nestedScrollWebView.getDomainSettingsApplied()) { // The url has custom domain settings. // Get a cursor for the current host and move it to the first position. Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase); currentHostDomainSettingsCursor.moveToFirst(); // Get the settings from the cursor. - domainSettingsDatabaseId = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID))); - javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1); - firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1); - thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1); - domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1); + nestedScrollWebView.setDomainSettingsDatabaseId(currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID))); + javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1); // TODO. + firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1); // TODO. + thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1); // TODO. + domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1); // TODO. // Form data can be removed once the minimum API >= 26. - saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1); - easyListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1); - easyPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1); - fanboysAnnoyanceListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1); - fanboysSocialBlockingListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1); - ultraPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1); - blockAllThirdPartyRequests = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1); + saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1); // TODO. + easyListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1); // TODO. + easyPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1); // TODO. + fanboysAnnoyanceListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1); // TODO. + fanboysSocialBlockingListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1); // TODO. + ultraPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1); // TODO. + blockAllThirdPartyRequests = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1); // TODO. String userAgentName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT)); int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE)); int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH)); int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE)); int displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES)); - pinnedSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1); - pinnedSslIssuedToCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)); - pinnedSslIssuedToOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)); - pinnedSslIssuedToUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)); - pinnedSslIssuedByCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)); - pinnedSslIssuedByOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)); - pinnedSslIssuedByUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)); - pinnedIpAddresses = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1); - pinnedHostIpAddresses = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES)); + boolean pinnedSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1); + String pinnedSslIssuedToCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)); + String pinnedSslIssuedToOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)); + String pinnedSslIssuedToUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)); + String pinnedSslIssuedByCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)); + String pinnedSslIssuedByOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)); + String pinnedSslIssuedByUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)); + boolean pinnedIpAddresses = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1); + String pinnedHostIpAddresses = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES)); + + // Create the pinned SSL date variables. + Date pinnedSslStartDate; + Date pinnedSslEndDate; + + // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0 because creating a new Date results in an error if the input is 0. + if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) { + pinnedSslStartDate = null; + } else { + pinnedSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE))); + } + + // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0 because creating a new Date results in an error if the input is 0. + if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) { + pinnedSslEndDate = null; + } else { + pinnedSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE))); + } + + // If there is a pinned SSL certificate, store it in the WebView. + if (pinnedSslCertificate) { + nestedScrollWebView.setPinnedSslCertificate(pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName, + pinnedSslStartDate, pinnedSslEndDate); + } + + // If there is a pinned IP address, store it in the WebView. + if (pinnedIpAddresses) { + nestedScrollWebView.setPinnedIpAddresses(pinnedHostIpAddresses); + } // Set `nightMode` according to `nightModeInt`. If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used. switch (nightModeInt) { case DomainsDatabaseHelper.NIGHT_MODE_ENABLED: - nightMode = true; + nightMode = true; // TODO. break; case DomainsDatabaseHelper.NIGHT_MODE_DISABLED: - nightMode = false; + nightMode = false; // TODO. break; } + // TODO. // Store the domain JavaScript status. This is used by the options menu night mode toggle. domainSettingsJavaScriptEnabled = javaScriptEnabled; // Enable JavaScript if night mode is enabled. if (nightMode) { - javaScriptEnabled = true; - } - - // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0. - if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) { - pinnedSslStartDate = null; - } else { - pinnedSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE))); - } - - // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0. - if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) { - pinnedSslEndDate = null; - } else { - pinnedSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE))); + javaScriptEnabled = true; // TODO. } // Close `currentHostDomainSettingsCursor`. currentHostDomainSettingsCursor.close(); // Apply the domain settings. - currentWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); - cookieManager.setAcceptCookie(firstPartyCookiesEnabled); - currentWebView.getSettings().setDomStorageEnabled(domStorageEnabled); + nestedScrollWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); // TODO. + cookieManager.setAcceptCookie(firstPartyCookiesEnabled); //TODO This could be bad. + nestedScrollWebView.getSettings().setDomStorageEnabled(domStorageEnabled); // TODO. // Apply the form data setting if the API < 26. if (Build.VERSION.SDK_INT < 26) { - currentWebView.getSettings().setSaveFormData(saveFormDataEnabled); + nestedScrollWebView.getSettings().setSaveFormData(saveFormDataEnabled); } // Apply the font size. if (fontSize == 0) { // Apply the default font size. - currentWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); + nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); } else { // Apply the specified font size. - currentWebView.getSettings().setTextZoom(fontSize); + nestedScrollWebView.getSettings().setTextZoom(fontSize); } // Set third-party cookies status if API >= 21. if (Build.VERSION.SDK_INT >= 21) { - cookieManager.setAcceptThirdPartyCookies(currentWebView, thirdPartyCookiesEnabled); + cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, thirdPartyCookiesEnabled); // TODO. } // Only set the user agent if the webpage is not currently loading. Otherwise, changing the user agent on redirects can cause the original website to reload. // - if (!urlIsLoading) { + if (!urlIsLoading) { // TODO. We need to track this by WebView. // Set the user agent. if (userAgentName.equals(getString(R.string.system_default_user_agent))) { // Use the system default user agent. // Get the array position of the default user agent name. - int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName); + int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName); // TODO Could this be local. // Set the user agent according to the system default. switch (defaultUserAgentArrayPosition) { case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list. // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names. - currentWebView.getSettings().setUserAgentString(defaultUserAgentName); + nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName); break; case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT: // Set the user agent to `""`, which uses the default value. - currentWebView.getSettings().setUserAgentString(""); + nestedScrollWebView.getSettings().setUserAgentString(""); break; case SETTINGS_CUSTOM_USER_AGENT: // Set the custom user agent. - currentWebView.getSettings().setUserAgentString(defaultCustomUserAgentString); + nestedScrollWebView.getSettings().setUserAgentString(defaultCustomUserAgentString); break; default: // Get the user agent string from the user agent data array - currentWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]); + nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]); } } else { // Set the user agent according to the stored name. // Get the array position of the user agent name. @@ -3722,29 +3806,29 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook switch (userAgentArrayPosition) { case UNRECOGNIZED_USER_AGENT: // The user agent name contains a custom user agent. - currentWebView.getSettings().setUserAgentString(userAgentName); + nestedScrollWebView.getSettings().setUserAgentString(userAgentName); break; case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT: // Set the user agent to `""`, which uses the default value. - currentWebView.getSettings().setUserAgentString(""); + nestedScrollWebView.getSettings().setUserAgentString(""); break; default: // Get the user agent string from the user agent data array. - currentWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]); + nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]); } } // Store the applied user agent string, which is used in the View Source activity. - appliedUserAgentString = currentWebView.getSettings().getUserAgentString(); + appliedUserAgentString = nestedScrollWebView.getSettings().getUserAgentString(); // TODO. // Update the user agent change tracker. - userAgentChanged = !appliedUserAgentString.equals(initialUserAgent); + userAgentChanged = !appliedUserAgentString.equals(initialUserAgent); // TODO. } // Set swipe to refresh. - switch (swipeToRefreshInt) { + switch (swipeToRefreshInt) { // TODO. This needs to be set and updated by tab. case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT: // Set swipe to refresh according to the default. swipeRefreshLayout.setEnabled(defaultSwipeToRefresh); @@ -3763,19 +3847,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the loading of webpage images. switch (displayWebpageImagesInt) { case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT: - currentWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages); + nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages); break; case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED: - currentWebView.getSettings().setLoadsImagesAutomatically(true); + nestedScrollWebView.getSettings().setLoadsImagesAutomatically(true); break; case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED: - currentWebView.getSettings().setLoadsImagesAutomatically(false); + nestedScrollWebView.getSettings().setLoadsImagesAutomatically(false); break; } - // Set a green background on `urlTextBox` to indicate that custom domain settings are being used. We have to use the deprecated `.getDrawable()` until the minimum API >= 21. + // Set a green background on URL edit text to indicate that custom domain settings are being used. The deprecated `.getDrawable()` must be used until the minimum API >= 21. if (darkTheme) { urlEditText.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue)); } else { @@ -3783,28 +3867,28 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } else { // The new URL does not have custom domain settings. Load the defaults. // Store the values from `sharedPreferences` in variables. - javaScriptEnabled = sharedPreferences.getBoolean("javascript", false); - firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies", false); - thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false); - domStorageEnabled = sharedPreferences.getBoolean("dom_storage", false); - saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26. - easyListEnabled = sharedPreferences.getBoolean("easylist", true); - easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true); - fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboys_annoyance_list", true); - fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboys_social_blocking_list", true); - ultraPrivacyEnabled = sharedPreferences.getBoolean("ultraprivacy", true); - blockAllThirdPartyRequests = sharedPreferences.getBoolean("block_all_third_party_requests", false); + javaScriptEnabled = sharedPreferences.getBoolean("javascript", false); // TODO. + firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies", false); // TODO. + thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false); // TODO. + domStorageEnabled = sharedPreferences.getBoolean("dom_storage", false); // TODO. + saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26. // TODO. + easyListEnabled = sharedPreferences.getBoolean("easylist", true); // TODO. + easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true); // TODO. + fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboys_annoyance_list", true); // TODO. + fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboys_social_blocking_list", true); // TODO. + ultraPrivacyEnabled = sharedPreferences.getBoolean("ultraprivacy", true); // TODO. + blockAllThirdPartyRequests = sharedPreferences.getBoolean("block_all_third_party_requests", false); // TODO. // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`. if (nightMode) { - javaScriptEnabled = true; + javaScriptEnabled = true; // TODO. } // Apply the default settings. - currentWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); - cookieManager.setAcceptCookie(firstPartyCookiesEnabled); - currentWebView.getSettings().setDomStorageEnabled(domStorageEnabled); - currentWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); + nestedScrollWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); // TODO. + cookieManager.setAcceptCookie(firstPartyCookiesEnabled); // TODO. + nestedScrollWebView.getSettings().setDomStorageEnabled(domStorageEnabled); // TODO. + nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); swipeRefreshLayout.setEnabled(defaultSwipeToRefresh); // Apply the form data setting if the API < 26. @@ -3813,27 +3897,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Reset the pinned variables. - domainSettingsDatabaseId = -1; - pinnedSslCertificate = false; - pinnedSslIssuedToCName = ""; - pinnedSslIssuedToOName = ""; - pinnedSslIssuedToUName = ""; - pinnedSslIssuedByCName = ""; - pinnedSslIssuedByOName = ""; - pinnedSslIssuedByUName = ""; - pinnedSslStartDate = null; - pinnedSslEndDate = null; - pinnedIpAddresses = false; - pinnedHostIpAddresses = ""; + nestedScrollWebView.setDomainSettingsDatabaseId(-1); // Set third-party cookies status if API >= 21. if (Build.VERSION.SDK_INT >= 21) { - cookieManager.setAcceptThirdPartyCookies(currentWebView, thirdPartyCookiesEnabled); + cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, thirdPartyCookiesEnabled); } // Only set the user agent if the webpage is not currently loading. Otherwise, changing the user agent on redirects can cause the original website to reload. // - if (!urlIsLoading) { + if (!urlIsLoading) { // TODO. // Get the array position of the user agent name. int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName); @@ -3841,50 +3914,50 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook switch (userAgentArrayPosition) { case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list. // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names. - currentWebView.getSettings().setUserAgentString(defaultUserAgentName); + nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName); break; case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT: // Set the user agent to `""`, which uses the default value. - currentWebView.getSettings().setUserAgentString(""); + nestedScrollWebView.getSettings().setUserAgentString(""); break; case SETTINGS_CUSTOM_USER_AGENT: // Set the custom user agent. - currentWebView.getSettings().setUserAgentString(defaultCustomUserAgentString); + nestedScrollWebView.getSettings().setUserAgentString(defaultCustomUserAgentString); // TODO. break; default: // Get the user agent string from the user agent data array - currentWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]); + nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]); } // Store the applied user agent string, which is used in the View Source activity. - appliedUserAgentString = currentWebView.getSettings().getUserAgentString(); + appliedUserAgentString = nestedScrollWebView.getSettings().getUserAgentString(); // TODO. // Update the user agent change tracker. - userAgentChanged = !appliedUserAgentString.equals(initialUserAgent); + userAgentChanged = !appliedUserAgentString.equals(initialUserAgent); // TODO. } // Set the loading of webpage images. - currentWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages); + nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages); - // Set a transparent background on `urlTextBox`. The deprecated `.getDrawable()` must be used until the minimum API >= 21. + // Set a transparent background on URL edit text. The deprecated `.getDrawable()` must be used until the minimum API >= 21. urlEditText.setBackgroundDrawable(getResources().getDrawable(R.color.transparent)); } // Close the domains database helper. domainsDatabaseHelper.close(); - // Update the privacy icons, but only if `mainMenu` has already been populated. - if (mainMenu != null) { + // Update the privacy icons, but only if the options menu has already been populated. + if (mainMenu != null) { // TODO. Consider renaming this to optionsMenu. updatePrivacyIcons(true); } } // Reload the website if returning from the Domains activity. if (reloadWebsite) { - currentWebView.reload(); + nestedScrollWebView.reload(); } // Return the user agent changed status. @@ -3979,9 +4052,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reset `waitingForOrbot. waitingForOrbot = false; - // Reload the website if requested. + // Reload the WebViews if requested. if (reloadWebsite) { - currentWebView.reload(); + // Reload the WebViews. + 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 reload 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); + + // Reload the WebView. + nestedScrollWebView.reload(); + } + } } } } @@ -4058,18 +4147,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } private void highlightUrlText() { + // Get a handle for the URL edit text. + EditText urlEditText = findViewById(R.id.url_edittext); + // Only highlight the URL text if the box is not currently selected. - if (!urlTextBox.hasFocus()) { + if (!urlEditText.hasFocus()) { // Get the URL string. - String urlString = urlTextBox.getText().toString(); + String urlString = urlEditText.getText().toString(); // Highlight the URL according to the protocol. if (urlString.startsWith("file://")) { // This is a file URL. // De-emphasize only the protocol. - urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + urlEditText.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); } else if (urlString.startsWith("content://")) { // De-emphasize only the protocol. - urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + urlEditText.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE); } else { // This is a web URL. // Get the index of the `/` immediately after the domain name. int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2)); @@ -4094,25 +4186,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Markup the beginning of the URL. if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted. - urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + urlEditText.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); // De-emphasize subdomains. if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name. - urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + urlEditText.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); } } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted. if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name. // De-emphasize the protocol and the additional subdomains. - urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + urlEditText.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); } else { // There is only one subdomain in the domain name. // De-emphasize only the protocol. - urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + urlEditText.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE); } } // De-emphasize the text after the domain name. if (endOfDomainName > 0) { - urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + urlEditText.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); } } } @@ -4197,298 +4289,50 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook startActivity(openWithBrowserIntent); } - private static void checkPinnedMismatch() { - if ((pinnedSslCertificate || pinnedIpAddresses) && !ignorePinnedDomainInformation) { - // 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 pinnedSslStartDateString = ""; - String pinnedSslEndDateString = ""; - - // Convert the `Dates` to `Strings` if they are not `null`. - if (currentWebsiteSslStartDate != null) { - currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString(); - } - - if (currentWebsiteSslEndDate != null) { - currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString(); - } - - if (pinnedSslStartDate != null) { - pinnedSslStartDateString = pinnedSslStartDate.toString(); - } - - if (pinnedSslEndDate != null) { - pinnedSslEndDateString = pinnedSslEndDate.toString(); - } - - // Check to see if the pinned information matches the current information. - if ((pinnedIpAddresses && !currentHostIpAddresses.equals(pinnedHostIpAddresses)) || (pinnedSslCertificate && (!currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) || - !currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) || !currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) || - !currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) || !currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) || - !currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) || !currentWebsiteSslStartDateString.equals(pinnedSslStartDateString) || - !currentWebsiteSslEndDateString.equals(pinnedSslEndDateString)))) { - - // Get a handle for the pinned mismatch alert dialog. - DialogFragment pinnedMismatchDialogFragment = PinnedMismatchDialog.displayDialog(pinnedSslCertificate, pinnedIpAddresses); - - // Show the pinned mismatch alert dialog. - pinnedMismatchDialogFragment.show(fragmentManager, "Pinned Mismatch"); - } - } - } - - // This must run asynchronously because it involves a network request. `String` declares the parameters. `Void` does not declare progress units. `String` contains the results. - private static class GetHostIpAddresses extends AsyncTask { - // The weak references are used to determine if the activity have disappeared while the AsyncTask is running. - private final WeakReference activityWeakReference; - - GetHostIpAddresses(Activity activity) { - // Populate the weak references. - activityWeakReference = new WeakReference<>(activity); - } - - // `onPreExecute()` operates on the UI thread. - @Override - protected void onPreExecute() { - // Get a handle for the activity. - Activity activity = activityWeakReference.get(); - - // Abort if the activity is gone. - if ((activity == null) || activity.isFinishing()) { - return; - } - - // Set the getting IP addresses tracker. - gettingIpAddresses = true; - } - - - @Override - protected String doInBackground(String... domainName) { - // Get a handle for the activity. - Activity activity = activityWeakReference.get(); - - // Abort if the activity is gone. - if ((activity == null) || activity.isFinishing()) { - // Return an empty spannable string builder. - return ""; - } - - // Initialize an IP address string builder. - StringBuilder ipAddresses = new StringBuilder(); - - // Get an array with the IP addresses for the host. - try { - // Get an array with all the IP addresses for the domain. - InetAddress[] inetAddressesArray = InetAddress.getAllByName(domainName[0]); - - // Add each IP address to the string builder. - for (InetAddress inetAddress : inetAddressesArray) { - if (ipAddresses.length() == 0) { // This is the first IP address. - // Add the IP address to the string builder. - ipAddresses.append(inetAddress.getHostAddress()); - } else { // This is not the first IP address. - // Add a line break to the string builder first. - ipAddresses.append("\n"); - - // Add the IP address to the string builder. - ipAddresses.append(inetAddress.getHostAddress()); - } - } - } catch (UnknownHostException exception) { - // Do nothing. - } - - // Return the string. - return ipAddresses.toString(); - } - - // `onPostExecute()` operates on the UI thread. - @Override - protected void onPostExecute(String ipAddresses) { - // Get a handle for the activity. - Activity activity = activityWeakReference.get(); - - // Abort if the activity is gone. - if ((activity == null) || activity.isFinishing()) { - return; - } - - // Store the IP addresses. - currentHostIpAddresses = ipAddresses; - - if (!urlIsLoading) { - checkPinnedMismatch(); - } - - // Reset the getting IP addresses tracker. - gettingIpAddresses = false; - } - } - - private class WebViewPagerAdapter extends FragmentPagerAdapter { - // The WebView fragments list contains all the WebViews. - private LinkedList webViewFragmentsList = new LinkedList<>(); - - // Define the constructor. - private WebViewPagerAdapter(FragmentManager fragmentManager){ - // Run the default commands. - super(fragmentManager); - } - - @Override - public int getCount() { - // Return the number of pages. - return webViewFragmentsList.size(); - } - - @Override - public int getItemPosition(@NonNull Object object) { - //noinspection SuspiciousMethodCalls - if (webViewFragmentsList.contains(object)) { - // The tab has not been deleted. - return POSITION_UNCHANGED; - } else { - // The tab has been deleted. - return POSITION_NONE; - } - } - - @Override - public Fragment getItem(int pageNumber) { - // Get a WebView for a particular page. Page numbers are 0 indexed. - return webViewFragmentsList.get(pageNumber); - } - - private void addPage() { - // Add a new page. The pages and tabs are 0 indexed, so the size of the current list equals the number of the next page. - webViewFragmentsList.add(WebViewTabFragment.createTab(webViewFragmentsList.size())); - - // Update the view pager. - notifyDataSetChanged(); - } - - private void deletePage(int pageNumber) { - // Get a handle for the tab layout. - TabLayout tabLayout = findViewById(R.id.tablayout); - - // TODO always move to the next tab if possible. - // Select a tab that is not being deleted. - if (pageNumber == 0) { // The first tab is being deleted. - // Get a handle for the second tab. The tabs are 0 indexed. - TabLayout.Tab secondTab = tabLayout.getTabAt(1); - - // Remove the incorrect lint warning below that the second tab might be null. - assert secondTab != null; + public void addTab(View view) { + // Get a handle for the tab layout. + TabLayout tabLayout = findViewById(R.id.tablayout); - // Select the second tab. - secondTab.select(); - } else { // The first tab is not being deleted. - // Get a handle for the previous tab. - TabLayout.Tab previousTab = tabLayout.getTabAt(pageNumber - 1); + // Get the new page number. The page numbers are 0 indexed, so the new page number will match the current count. + int newTabNumber = tabLayout.getTabCount(); - // Remove the incorrect lint warning below tha the previous tab might be null. - assert previousTab != null; + // Add a new tab. + tabLayout.addTab(tabLayout.newTab()); - // Select the previous tab. - previousTab.select(); - } + // Get the new tab. + TabLayout.Tab newTab = tabLayout.getTabAt(newTabNumber); - // Delete the page. - webViewFragmentsList.remove(pageNumber); - - // Delete the tab. - tabLayout.removeTabAt(pageNumber); + // Remove the lint warning below that the current tab might be null. + assert newTab != null; - // Update the view pager. - notifyDataSetChanged(); - } - } + // Set a custom view on the current tab. + newTab.setCustomView(R.layout.custom_tab_view); - public void addTab(View view) { // Add the new WebView page. - webViewPagerAdapter.addPage(); + webViewPagerAdapter.addPage(newTabNumber); - // Get a handle for the tab layout. - TabLayout tabLayout = findViewById(R.id.tablayout); - - // Get a handle for the new tab. The tabs are 0 indexed. - TabLayout.Tab newTab = tabLayout.getTabAt(tabLayout.getTabCount() - 1); - - // Remove the incorrect warning below that the new tab might be null. - assert newTab != null; - - // Move the tab layout to the new tab. - newTab.select(); + if (newTabNumber > 0) { + // Move to the new tab. + newTab.select(); + } } @Override - public void initializeWebView(int tabNumber, ProgressBar progressBar, NestedScrollWebView nestedScrollWebView) { + public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar) { // Get handles for the activity views. - final FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout); - final DrawerLayout drawerLayout = findViewById(R.id.drawerlayout); - final RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout); - final ActionBar actionBar = getSupportActionBar(); - final TabLayout tabLayout = findViewById(R.id.tablayout); - final SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); + FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout); + DrawerLayout drawerLayout = findViewById(R.id.drawerlayout); + RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout); + ActionBar actionBar = getSupportActionBar(); + EditText urlEditText = findViewById(R.id.url_edittext); + TabLayout tabLayout = findViewById(R.id.tablayout); + SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); // Remove the incorrect lint warnings below that the some of the views might be null. assert actionBar != null; - // TODO. Still doesn't work right. - // Create the tab if it doesn't already exist. - try { - TabLayout.Tab tab = tabLayout.getTabAt(tabNumber); - - assert tab != null; - - tab.getCustomView(); - } catch (Exception exception) { - tabLayout.addTab(tabLayout.newTab()); - } - - // Get the current tab. - TabLayout.Tab currentTab = tabLayout.getTabAt(tabNumber); - - // Remove the lint warning below that the current tab might be null. - assert currentTab != null; - - // Set a custom view on the current tab. - currentTab.setCustomView(R.layout.custom_tab_view); - - // Get the custom view from the tab. - View currentTabView = currentTab.getCustomView(); - - // Remove the incorrect warning below that the current tab view might be null. - assert currentTabView != null; - - // Get the current views from the tab. - ImageView tabFavoriteIconImageView = currentTabView.findViewById(R.id.favorite_icon_imageview); - TextView tabTitleTextView = currentTabView.findViewById(R.id.title_textview); + // Get a handle for the activity + Activity activity = this; // Get a handle for the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); @@ -4496,6 +4340,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the relevant preferences. boolean downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false); + // Set the app bar scrolling. + nestedScrollWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true)); + // Allow pinch to zoom. nestedScrollWebView.getSettings().setBuiltInZoomControls(true); @@ -4611,22 +4458,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook downloadContentLength = contentLength; // Show a dialog if the user has previously denied the permission. - if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. + 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(fragmentManager, getString(R.string.download_location)); + 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(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE); + 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(fragmentManager, getString(R.string.download)); + downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); } } }); @@ -4713,9 +4560,27 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Only update the favorite icon if the website has finished loading. if (progressBar.getVisibility() == View.GONE) { // Save a copy of the favorite icon. - // TODO. We need to save and access the icons for each tab. favoriteIconBitmap = icon; + // Get the current page position. + int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); + + // Get the current tab. + TabLayout.Tab tab = tabLayout.getTabAt(currentPosition); + + // Remove the lint warning below that the current tab might be null. + assert 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 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)); } } @@ -4723,12 +4588,26 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Save a copy of the title when it changes. @Override public void onReceivedTitle(WebView view, String title) { - // Save a copy of the title. - // TODO. Replace `webViewTitle` with `currentWebView.getTitle()`. - webViewTitle = title; + // Get the current page position. + int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); + + // Get the current tab. + TabLayout.Tab tab = tabLayout.getTabAt(currentPosition); + + // Remove the lint warning below that the current tab might be null. + assert 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(webViewTitle); + tabTitleTextView.setText(title); } // Enter full screen video. @@ -4859,7 +4738,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook formattedUrlString = ""; // Apply the domain settings for the new URL. `applyDomainSettings` doesn't do anything if the domain has not changed. - boolean userAgentChanged = applyDomainSettings(url, true, false); + boolean userAgentChanged = applyDomainSettings(nestedScrollWebView, url, true, false); // Check if the user agent has changed. if (userAgentChanged) { @@ -4935,7 +4814,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes())); // Reset the whitelist results tracker. - whiteListResultStringArray = null; + String[] whitelistResultStringArray = null; // Initialize the third party request tracker. boolean isThirdPartyRequest = false; @@ -4975,21 +4854,32 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } + // 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 && blockAllThirdPartyRequests) { + // Add the result to the resource requests. + nestedScrollWebView.addResourceRequest(new String[]{BlockListHelper.REQUEST_THIRD_PARTY, url}); + // Increment the blocked requests counters. - blockedRequests++; - thirdPartyBlockedRequests++; - - // Update the titles of the blocklist menu items. This must be run from the UI thread. - activity.runOnUiThread(() -> { - navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests); - blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests); - blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests)); - }); + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.THIRD_PARTY_BLOCKED_REQUESTS); - // Add the request to the log. - resourceRequests.add(new String[]{String.valueOf(REQUEST_THIRD_PARTY), url}); + // 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)); + blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); + blockAllThirdPartyRequestsMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_BLOCKED_REQUESTS) + " - " + + getString(R.string.block_all_third_party_requests)); + }); + } // Return an empty web resource response. return emptyWebResourceResponse; @@ -4997,26 +4887,36 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Check UltraPrivacy if it is enabled. if (ultraPrivacyEnabled) { - if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, ultraPrivacy)) { - // Increment the blocked requests counters. - blockedRequests++; - ultraPrivacyBlockedRequests++; + // Check the URL against UltraPrivacy. + String[] ultraPrivacyResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy); - // Update the titles of the blocklist menu items. This must be run from the UI thread. - activity.runOnUiThread(() -> { - navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests); - blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests); - ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.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_BLOCKED_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)); + blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); + ultraPrivacyMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY_BLOCKED_REQUESTS) + " - " + getString(R.string.ultraprivacy)); + }); + } // The resource request was blocked. Return an empty web resource response. return emptyWebResourceResponse; - } - - // If the whitelist result is not null, the request has been allowed by UltraPrivacy. - if (whiteListResultStringArray != null) { + } else if (ultraPrivacyResults[0].equals(BlockListHelper.REQUEST_ALLOWED)) { // The resource request matched UltraPrivacy's whitelist. // Add a whitelist entry to the resource requests array. - resourceRequests.add(whiteListResultStringArray); + 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; @@ -5025,94 +4925,145 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Check EasyList if it is enabled. if (easyListEnabled) { - if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyList)) { - // Increment the blocked requests counters. - blockedRequests++; - easyListBlockedRequests++; + // Check the URL against EasyList. + String[] easyListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList); - // Update the titles of the blocklist menu items. This must be run from the UI thread. - activity.runOnUiThread(() -> { - navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests); - blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests); - easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.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]}); - // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition). - whiteListResultStringArray = null; + // Increment the blocked requests counters. + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_LIST_BLOCKED_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)); + blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); + easyListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_LIST_BLOCKED_REQUESTS) + " - " + 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 (easyPrivacyEnabled) { - if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyPrivacy)) { - // Increment the blocked requests counters. - blockedRequests++; - easyPrivacyBlockedRequests++; + // Check the URL against EasyPrivacy. + String[] easyPrivacyResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyPrivacy); - // Update the titles of the blocklist menu items. This must be run from the UI thread. - activity.runOnUiThread(() -> { - navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests); - blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests); - easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.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[5], + easyPrivacyResults[5]}); - // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition). - whiteListResultStringArray = null; + // Increment the blocked requests counters. + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_PRIVACY_BLOCKED_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)); + blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); + easyPrivacyMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY_BLOCKED_REQUESTS) + " - " + 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 (fanboysAnnoyanceListEnabled) { - if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList)) { - // Increment the blocked requests counters. - blockedRequests++; - fanboysAnnoyanceListBlockedRequests++; + // Check the URL against Fanboy's Annoyance List. + String[] fanboysAnnoyanceListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList); - // Update the titles of the blocklist menu items. This must be run from the UI thread. - activity.runOnUiThread(() -> { - navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests); - blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests); - fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list)); - }); + // 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]}); - // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition). - whiteListResultStringArray = null; + // Increment the blocked requests counters. + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST_BLOCKED_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)); + blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); + fanboysAnnoyanceListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS) + " - " + + 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 (fanboysSocialBlockingListEnabled) { // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled. - if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysSocialList)) { - // Increment the blocked requests counters. - blockedRequests++; - fanboysSocialBlockingListBlockedRequests++; + // Check the URL against Fanboy's Annoyance List. + String[] fanboysSocialListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysSocialList); - // Update the titles of the blocklist menu items. This must be run from the UI thread. - activity.runOnUiThread(() -> { - navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests); - blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests); - fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list)); - }); + // 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]}); - // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition). - whiteListResultStringArray = null; + // Increment the blocked requests counters. + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_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)); + blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); + fanboysSocialBlockingListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS) + " - " + + 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. - resourceRequests.add(whiteListResultStringArray); + 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. - resourceRequests.add(new String[]{String.valueOf(REQUEST_DEFAULT), url}); + nestedScrollWebView.addResourceRequest(new String[]{BlockListHelper.REQUEST_DEFAULT, url}); } // The resource request has not been blocked. `return null` loads the requested resource. @@ -5127,30 +5078,26 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Display the HTTP authentication dialog. DialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm); - httpAuthenticationDialogFragment.show(fragmentManager, getString(R.string.http_authentication)); + 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) { // 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. // This is also used to determine when to check for pinned mismatches. urlIsLoading = true; - // Reset the list of host IP addresses. - currentHostIpAddresses = ""; - // Reset the list of resource requests. - resourceRequests.clear(); + nestedScrollWebView.clearResourceRequests(); // Initialize the counters for requests blocked by each blocklist. - blockedRequests = 0; - easyListBlockedRequests = 0; - easyPrivacyBlockedRequests = 0; - fanboysAnnoyanceListBlockedRequests = 0; - fanboysSocialBlockingListBlockedRequests = 0; - ultraPrivacyBlockedRequests = 0; - thirdPartyBlockedRequests = 0; + nestedScrollWebView.resetRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); + nestedScrollWebView.resetRequestsCount(NestedScrollWebView.EASY_LIST_BLOCKED_REQUESTS); + nestedScrollWebView.resetRequestsCount(NestedScrollWebView.EASY_PRIVACY_BLOCKED_REQUESTS); + nestedScrollWebView.resetRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS); + nestedScrollWebView.resetRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS); + nestedScrollWebView.resetRequestsCount(NestedScrollWebView.ULTRA_PRIVACY_BLOCKED_REQUESTS); + nestedScrollWebView.resetRequestsCount(NestedScrollWebView.THIRD_PARTY_BLOCKED_REQUESTS); // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied. if (nightMode) { @@ -5166,7 +5113,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook formattedUrlString = url; // Display the formatted URL text. - urlTextBox.setText(formattedUrlString); + urlEditText.setText(formattedUrlString); // Apply text highlighting to `urlTextBox`. highlightUrlText(); @@ -5174,13 +5121,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get a URI for the current URL. Uri currentUri = Uri.parse(formattedUrlString); + // Reset the list of host IP addresses. + nestedScrollWebView.clearCurrentIpAddresses(); + // Get the IP addresses for the host. - new GetHostIpAddresses(activity).execute(currentUri.getHost()); + new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost()); // Apply any custom domain settings if the URL was loaded by navigating history. if (navigatingHistory) { // Apply the domain settings. - boolean userAgentChanged = applyDomainSettings(url, true, false); + boolean userAgentChanged = applyDomainSettings(nestedScrollWebView, url, true, false); // Reset `navigatingHistory`. navigatingHistory = false; @@ -5266,36 +5216,34 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set `formattedUrlString` to `""`. formattedUrlString = ""; - urlTextBox.setText(formattedUrlString); + urlEditText.setText(formattedUrlString); // Request focus for `urlTextBox`. - urlTextBox.requestFocus(); + urlEditText.requestFocus(); // Display the keyboard. - inputMethodManager.showSoftInput(urlTextBox, 0); + inputMethodManager.showSoftInput(urlEditText, 0); // Apply the domain settings. This clears any settings from the previous domain. - applyDomainSettings(formattedUrlString, true, false); + applyDomainSettings(nestedScrollWebView, formattedUrlString, true, false); } else { // `WebView` has loaded a webpage. // Set the formatted URL string. Getting the URL from the WebView instead of using the one provided by `onPageFinished` makes websites like YouTube function correctly. formattedUrlString = nestedScrollWebView.getUrl(); // Only update the URL text box if the user is not typing in it. - if (!urlTextBox.hasFocus()) { + if (!urlEditText.hasFocus()) { // Display the formatted URL text. - urlTextBox.setText(formattedUrlString); + urlEditText.setText(formattedUrlString); // Apply text highlighting to `urlTextBox`. highlightUrlText(); } } - // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedMismatchDialog`. - sslCertificate = nestedScrollWebView.getCertificate(); - // Check the current website information against any pinned domain information if the current IP addresses have been loaded. - if (!gettingIpAddresses) { - checkPinnedMismatch(); + if ((nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses()) && nestedScrollWebView.hasCurrentIpAddresses() && + !nestedScrollWebView.ignorePinnedDomainInformation()) { + CheckPinnedMismatchHelper.checkPinnedMismatch(getSupportFragmentManager(), nestedScrollWebView); } } @@ -5320,27 +5268,36 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate(); // Proceed to the website if the current SSL website certificate matches the pinned domain certificate. - if (pinnedSslCertificate && - currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) && currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) && - currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) && currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) && - currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) && currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) && - currentWebsiteSslStartDate.equals(pinnedSslStartDate) && currentWebsiteSslEndDate.equals(pinnedSslEndDate)) { - - // An SSL certificate is pinned and matches the current domain certificate. Proceed to the website without displaying an error. - handler.proceed(); + if (nestedScrollWebView.hasPinnedSslCertificate()) { + // Get the pinned SSL certificate. + ArrayList 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 `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`. - sslErrorHandler = handler; + sslErrorHandler = handler; // TODO. We need to pass this in instead of using a static variable. Because multiple could be displayed at once from different tabs. // Display the SSL error `AlertDialog`. DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error); - sslCertificateErrorDialogFragment.show(fragmentManager, getString(R.string.ssl_certificate_error)); + sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error)); } } }); - // Check to see if this is the first tab. - if (tabNumber == 0) { + // Check to see if this is the first page. + if (pageNumber == 0) { // Set this nested scroll WebView as the current WebView. currentWebView = nestedScrollWebView;