]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/commitdiff
Track URL loading by tab.
authorSoren Stoutner <soren@stoutner.com>
Thu, 4 Apr 2019 02:26:52 +0000 (19:26 -0700)
committerSoren Stoutner <soren@stoutner.com>
Thu, 4 Apr 2019 02:26:52 +0000 (19:26 -0700)
app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java
app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetHostIpAddresses.java
app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.java

index 3f3b942889bb51267bc19a009798e5405fc37eee..26fbd91c8037def37ebfff2f221c23ba21412334 100644 (file)
@@ -136,18 +136,18 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
         // Extract the values from `savedInstanceState` if it is not `null`.
         if (savedInstanceState != null) {
             restartAfterRotate = true;
         // Extract the values from `savedInstanceState` if it is not `null`.
         if (savedInstanceState != null) {
             restartAfterRotate = true;
-            domainSettingsDisplayedBeforeRotate = savedInstanceState.getBoolean("domainSettingsDisplayed");
-            domainSettingsDatabaseIdBeforeRotate = savedInstanceState.getInt("domainSettingsDatabaseId");
+            domainSettingsDisplayedBeforeRotate = savedInstanceState.getBoolean("domain_settings_displayed");
+            domainSettingsDatabaseIdBeforeRotate = savedInstanceState.getInt("domain_settings_database_id");
         }
 
         // Get the launching intent
         Intent intent = getIntent();
 
         // Extract the domain to load if there is one.  `-1` is the default value.
         }
 
         // Get the launching intent
         Intent intent = getIntent();
 
         // Extract the domain to load if there is one.  `-1` is the default value.
-        goDirectlyToDatabaseId = intent.getIntExtra("loadDomain", -1);
+        goDirectlyToDatabaseId = intent.getIntExtra("load_domain", -1);
 
         // Get the status of close-on-back, which is true when the domains activity is called from the options menu.
 
         // Get the status of close-on-back, which is true when the domains activity is called from the options menu.
-        closeOnBack = intent.getBooleanExtra("closeOnBack", false);
+        closeOnBack = intent.getBooleanExtra("close_on_back", false);
 
         // Set the content view.
         setContentView(R.layout.domains_coordinatorlayout);
 
         // Set the content view.
         setContentView(R.layout.domains_coordinatorlayout);
@@ -536,11 +536,11 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
             saveDomainSettings(coordinatorLayout, resources);
 
             // Store `DomainSettingsDisplayed`.
             saveDomainSettings(coordinatorLayout, resources);
 
             // Store `DomainSettingsDisplayed`.
-            outState.putBoolean("domainSettingsDisplayed", true);
-            outState.putInt("domainSettingsDatabaseId", DomainSettingsFragment.databaseId);
+            outState.putBoolean("domain_settings_displayed", true);
+            outState.putInt("domain_settings_database_id", DomainSettingsFragment.databaseId);
         } else {  // `DomainSettingsFragment` is not displayed.
         } else {  // `DomainSettingsFragment` is not displayed.
-            outState.putBoolean("domainSettingsDisplayed", false);
-            outState.putInt("domainSettingsDatabaseId", -1);
+            outState.putBoolean("domain_settings_displayed", false);
+            outState.putInt("domain_settings_database_id", -1);
         }
 
         super.onSaveInstanceState(outState);
         }
 
         super.onSaveInstanceState(outState);
index fd357a6a4ed478550916b88ddc639026720590e0..e0a310c8f63dd734eb228e095d949be66a1b03e3 100644 (file)
@@ -181,18 +181,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`.
     public static String formattedUrlString;
 
     // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`.
     public static String formattedUrlString;
 
-    // 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;
 
     // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`.  It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
     public static String orbotStatus;
 
-    // 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;
 
     // The WebView pager adapter is accessed from `PinnedMismatchDialog`.  It is also used in `onCreate()`, `onResume()`, and `addTab()`.
     public static WebViewPagerAdapter webViewPagerAdapter;
 
@@ -246,9 +237,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
     private final Map<String, String> customHeaders = new HashMap<>();
 
     // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
     private final Map<String, String> customHeaders = new HashMap<>();
 
-    // `javaScriptEnabled` is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
-    private boolean javaScriptEnabled;
-
     // `firstPartyCookiesEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyDomainSettings()`.
     private boolean firstPartyCookiesEnabled;
 
     // `firstPartyCookiesEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyDomainSettings()`.
     private boolean firstPartyCookiesEnabled;
 
@@ -271,14 +259,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `searchURL` is used in `loadURLFromTextBox()` and `applyProxyThroughOrbot()`.
     private String searchURL;
 
     // `searchURL` is used in `loadURLFromTextBox()` and `applyProxyThroughOrbot()`.
     private String searchURL;
 
-    // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
-    private Menu mainMenu;
+    // The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()` and `updatePrivacyIcons()`.
+    private Menu optionsMenu;
 
 
-    // `refreshMenuItem` is used in `onCreate()` and `onCreateOptionsMenu()`.
+    // The refresh menu item is set in `onCreateOptionsMenu()` and accessed from `initializeWebView()`.
+    // It must be this way because `initializeWebView()` runs before the menu is created but doesn't actually modify the menu until later.
     private MenuItem refreshMenuItem;
 
     // The navigation requests menu item is used in `onCreate()` and accessed from `WebViewPagerAdapter`.
     private MenuItem refreshMenuItem;
 
     // The navigation requests menu item is used in `onCreate()` and accessed from `WebViewPagerAdapter`.
-    private MenuItem navigationRequestsMenuItem;
+    private MenuItem navigationRequestsMenuItem;  // TODO.
 
     // TODO.  This could probably be removed.
     // The blocklist helper is used in `onCreate()` and `WebViewPagerAdapter`.
 
     // TODO.  This could probably be removed.
     // The blocklist helper is used in `onCreate()` and `WebViewPagerAdapter`.
@@ -310,9 +299,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
     private String webViewDefaultUserAgent;
 
     // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
     private String webViewDefaultUserAgent;
 
-    // `defaultCustomUserAgentString` is used in `onPrepareOptionsMenu()` and `applyDomainSettings()`.
-    private String defaultCustomUserAgentString;
-
     // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
     private Runtime privacyBrowserRuntime;
 
     // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
     private Runtime privacyBrowserRuntime;
 
@@ -343,9 +329,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`.
     private boolean downloadWithExternalApp;
 
     // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`.
     private boolean downloadWithExternalApp;
 
-    // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
-    private String currentDomainName;
-
     // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
     private BroadcastReceiver orbotStatusBroadcastReceiver;
 
     // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
     private BroadcastReceiver orbotStatusBroadcastReceiver;
 
@@ -428,11 +411,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
 
     @Override
     private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
 
     @Override
-    // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.  The whole premise of Privacy Browser is built around an understanding of these dangers.
-    // Also, remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
-    @SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
-    // Remove Android Studio's warning about deprecations.  The deprecated `getColor()` must be used until API >= 23.
-    @SuppressWarnings("deprecation")
+    // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
+    @SuppressLint("ClickableViewAccessibility")
     protected void onCreate(Bundle savedInstanceState) {
         // Get a handle for the shared preferences.
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
     protected void onCreate(Bundle savedInstanceState) {
         // Get a handle for the shared preferences.
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
@@ -516,8 +496,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Set `waitingForOrbotHTMLString`.
         waitingForOrbotHtmlString = "<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>";
 
         // Set `waitingForOrbotHTMLString`.
         waitingForOrbotHtmlString = "<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>";
 
-        // Initialize `currentDomainName`, `orbotStatus`, and `waitingForOrbot`.
-        currentDomainName = "";
+        // Initialize the Orbot status and the waiting for Orbot trackers.
         orbotStatus = "unknown";
         waitingForOrbot = false;
 
         orbotStatus = "unknown";
         waitingForOrbot = false;
 
@@ -614,6 +593,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Store the current WebView.
                 currentWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
 
                 // Store the current WebView.
                 currentWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
 
+                // Update the privacy icons.  `true` redraws the icons in the app bar.
+                updatePrivacyIcons(true);
+
                 // Store the current formatted URL string.
                 formattedUrlString = currentWebView.getUrl();
 
                 // Store the current formatted URL string.
                 formattedUrlString = currentWebView.getUrl();
 
@@ -638,7 +620,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         urlEditText.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
                     }
                 } else {
                         urlEditText.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
                     }
                 } else {
-                    urlEditText.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
+                    urlEditText.setBackground(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.
                 }
 
                 // 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.
@@ -926,7 +908,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         inFullScreenBrowsingMode = false;
 
         // Initialize the privacy settings variables.
         inFullScreenBrowsingMode = false;
 
         // Initialize the privacy settings variables.
-        javaScriptEnabled = false;
         firstPartyCookiesEnabled = false;
         thirdPartyCookiesEnabled = false;
         domStorageEnabled = false;
         firstPartyCookiesEnabled = false;
         thirdPartyCookiesEnabled = false;
         domStorageEnabled = false;
@@ -1229,8 +1210,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Inflate the menu.  This adds items to the action bar if it is present.
         getMenuInflater().inflate(R.menu.webview_options_menu, menu);
 
         // Inflate the menu.  This adds items to the action bar if it is present.
         getMenuInflater().inflate(R.menu.webview_options_menu, menu);
 
-        // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
-        mainMenu = menu;
+        // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
+        optionsMenu = menu;
 
         // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
         updatePrivacyIcons(false);
 
         // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
         updatePrivacyIcons(false);
@@ -1279,7 +1260,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
 
         // Replace Refresh with Stop if a URL is already loading.
         }
 
         // Replace Refresh with Stop if a URL is already loading.
-        if (urlIsLoading) {
+        if (currentWebView != null && currentWebView.getProgress() != 100) {
             // Set the title.
             refreshMenuItem.setTitle(R.string.stop);
 
             // Set the title.
             refreshMenuItem.setTitle(R.string.stop);
 
@@ -1369,7 +1350,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
 
         // Enable DOM Storage if JavaScript is enabled.
         toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
 
         // Enable DOM Storage if JavaScript is enabled.
-        toggleDomStorageMenuItem.setEnabled(javaScriptEnabled);
+        toggleDomStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
 
         // Enable Clear Cookies if there are any.
         clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
 
         // Enable Clear Cookies if there are any.
         clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
@@ -1524,20 +1505,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Get the selected menu item ID.
         int menuItemId = menuItem.getItemId();
 
         // Get the selected menu item ID.
         int menuItemId = menuItem.getItemId();
 
+        // Get a handle for the shared preferences.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+
         // Run the commands that correlate to the selected menu item.
         switch (menuItemId) {
             case R.id.toggle_javascript:
         // Run the commands that correlate to the selected menu item.
         switch (menuItemId) {
             case R.id.toggle_javascript:
-                // Switch the status of javaScriptEnabled.
-                javaScriptEnabled = !javaScriptEnabled;
-
-                // Apply the new JavaScript status.
-                currentWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
+                // Toggle the JavaScript status.
+                currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
 
                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
                 updatePrivacyIcons(true);
 
                 // Display a `Snackbar`.
 
                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
                 updatePrivacyIcons(true);
 
                 // Display a `Snackbar`.
-                if (javaScriptEnabled) {  // JavaScrip is enabled.
+                if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScrip is enabled.
                     Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
                 } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled, but first-party cookies are enabled.
                     Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
                     Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
                 } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled, but first-party cookies are enabled.
                     Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
@@ -1553,7 +1534,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 if (currentWebView.getDomainSettingsApplied()) {  // Edit the current domain settings.
                     // Reapply the domain settings on returning to `MainWebViewActivity`.
                     reapplyDomainSettingsOnRestart = true;
                 if (currentWebView.getDomainSettingsApplied()) {  // Edit the current domain settings.
                     // Reapply the domain settings on returning to `MainWebViewActivity`.
                     reapplyDomainSettingsOnRestart = true;
-                    currentDomainName = "";
+                    currentWebView.resetCurrentDomainName();
 
                     // TODO.  Move these to `putExtra`.  The certificate can be stored as strings.
                     // Store the current SSL certificate and IP addresses in the domains activity.
 
                     // TODO.  Move these to `putExtra`.  The certificate can be stored as strings.
                     // Store the current SSL certificate and IP addresses in the domains activity.
@@ -1564,15 +1545,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     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.
                     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", currentWebView.getDomainSettingsDatabaseId());
-                    domainsIntent.putExtra("closeOnBack", true);
+                    domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
+                    domainsIntent.putExtra("close_on_back", true);
 
                     // Make it so.
                     startActivity(domainsIntent);
                 } else {  // Add a new domain.
                     // Apply the new domain settings on returning to `MainWebViewActivity`.
                     reapplyDomainSettingsOnRestart = true;
 
                     // Make it so.
                     startActivity(domainsIntent);
                 } else {  // Add a new domain.
                     // Apply the new domain settings on returning to `MainWebViewActivity`.
                     reapplyDomainSettingsOnRestart = true;
-                    currentDomainName = "";
+                    currentWebView.resetCurrentDomainName();
 
                     // Get the current domain
                     Uri currentUri = Uri.parse(formattedUrlString);
 
                     // Get the current domain
                     Uri currentUri = Uri.parse(formattedUrlString);
@@ -1593,8 +1574,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     Intent domainsIntent = new Intent(this, DomainsActivity.class);
 
                     // Put extra information instructing the domains activity to directly load the new domain and close on back instead of returning to the domains list.
                     Intent domainsIntent = new Intent(this, DomainsActivity.class);
 
                     // Put extra information instructing the domains activity to directly load the new domain and close on back instead of returning to the domains list.
-                    domainsIntent.putExtra("loadDomain", newDomainDatabaseId);
-                    domainsIntent.putExtra("closeOnBack", true);
+                    domainsIntent.putExtra("load_domain", newDomainDatabaseId);
+                    domainsIntent.putExtra("close_on_back", true);
 
                     // Make it so.
                     startActivity(domainsIntent);
 
                     // Make it so.
                     startActivity(domainsIntent);
@@ -1617,7 +1598,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Display a `Snackbar`.
                 if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
                     Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
                 // Display a `Snackbar`.
                 if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
                     Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
-                } else if (javaScriptEnabled) {  // JavaScript is still enabled.
+                } else if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is still enabled.
                     Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
                 } else {  // Privacy mode.
                     Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
                     Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
                 } else {  // Privacy mode.
                     Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
@@ -1840,7 +1821,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 menuItem.setChecked(fanboysAnnoyanceListEnabled);
 
                 // Update the staus of Fanboy's Social Blocking List.
                 menuItem.setChecked(fanboysAnnoyanceListEnabled);
 
                 // Update the staus of Fanboy's Social Blocking List.
-                MenuItem fanboysSocialBlockingListMenuItem = mainMenu.findItem(R.id.fanboys_social_blocking_list);
+                MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list);
                 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
 
                 // Reload the current WebView.
                 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
 
                 // Reload the current WebView.
@@ -1978,7 +1959,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             case R.id.user_agent_custom:
                 // Update the user agent.
 
             case R.id.user_agent_custom:
                 // Update the user agent.
-                currentWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
+                currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
 
                 // Reload the current WebView.
                 currentWebView.reload();
 
                 // Reload the current WebView.
                 currentWebView.reload();
@@ -2042,23 +2023,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 nightMode = !nightMode;
 
                 // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
                 nightMode = !nightMode;
 
                 // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
-                if (nightMode) {  // Night mode is enabled.  Enable JavaScript.
-                    // Update the global variable.
-                    javaScriptEnabled = true;
+                if (nightMode) {  // Night mode is enabled, which requires JavaScript.
+                    // Enable JavaScript.
+                    currentWebView.getSettings().setJavaScriptEnabled(true);
                 } else if (currentWebView.getDomainSettingsApplied()) {  // 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;
+                    // Apply the JavaScript preference that was stored the last time domain settings were loaded.
+                    currentWebView.getSettings().setJavaScriptEnabled(domainSettingsJavaScriptEnabled);
                 } else {  // Night mode is disabled and domain settings are not applied.  Set JavaScript according to the global preference.
                 } else {  // Night mode is disabled and domain settings are not applied.  Set JavaScript according to the global preference.
-                    // Get a handle for the shared preference.
-                    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
-
-                    // Get the JavaScript preference.
-                    javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
+                    // Apply the JavaScript preference.
+                    currentWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false));
                 }
 
                 }
 
-                // Apply the JavaScript setting to the WebView.
-                currentWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
-
                 // Update the privacy icons.
                 updatePrivacyIcons(false);
 
                 // Update the privacy icons.
                 updatePrivacyIcons(false);
 
@@ -2089,8 +2064,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 return true;
 
             case R.id.view_source:
                 return true;
 
             case R.id.view_source:
-                // Launch the View Source activity.
+                // Create an intent to launch the view source activity.
                 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
                 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
+
+                // Add the user agent as an extra to the intent.
+                viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString());
+
+                // Make it so.
                 startActivity(viewSourceIntent);
                 return true;
 
                 startActivity(viewSourceIntent);
                 return true;
 
@@ -2426,7 +2406,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             case R.id.domains:
                 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
                 reapplyDomainSettingsOnRestart = true;
             case R.id.domains:
                 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
                 reapplyDomainSettingsOnRestart = true;
-                currentDomainName = "";
+                currentWebView.resetCurrentDomainName();  // TODO.  Do this for all tabs.
 
                 // TODO.  Move these to `putExtra`.  The certificate can be stored as strings.
                 // Store the current SSL certificate and IP addresses in the domains activity.
 
                 // TODO.  Move these to `putExtra`.  The certificate can be stored as strings.
                 // Store the current SSL certificate and IP addresses in the domains activity.
@@ -2444,7 +2424,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Set the flag to reapply the domain settings on restart when returning from Settings.
                 reapplyDomainSettingsOnRestart = true;
 
                 // Set the flag to reapply the domain settings on restart when returning from Settings.
                 reapplyDomainSettingsOnRestart = true;
-                currentDomainName = "";
+                currentWebView.resetCurrentDomainName();  // TODO.  Do this for all tabs.
 
                 // Launch the settings activity.
                 Intent settingsIntent = new Intent(this, SettingsActivity.class);
 
                 // Launch the settings activity.
                 Intent settingsIntent = new Intent(this, SettingsActivity.class);
@@ -3214,12 +3194,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     @Override
     }
 
     @Override
-    public void onSslErrorCancel() {
+    public void onSslErrorCancel() {  // TODO.  How to handle this with multiple tabs?  There could be multiple errors at once.
         sslErrorHandler.cancel();
     }
 
     @Override
         sslErrorHandler.cancel();
     }
 
     @Override
-    public void onSslErrorProceed() {
+    public void onSslErrorProceed() {  // TODO.  How to handle this with multiple tabs?  There could be multiple errors at once.
         sslErrorHandler.proceed();
     }
 
         sslErrorHandler.proceed();
     }
 
@@ -3388,9 +3368,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Apply the domain settings.
         applyDomainSettings(currentWebView, url, true, false);
 
         // Apply the domain settings.
         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("");
-
         // Load the URL.
         currentWebView.loadUrl(url, customHeaders);
     }
         // Load the URL.
         currentWebView.loadUrl(url, customHeaders);
     }
@@ -3521,45 +3498,29 @@ 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.
 
 
     // `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")
+    @SuppressLint("SetJavaScriptEnabled")
     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);
 
     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.
+        // Store a copy of the current user agent to track changes for the return boolean.
         String initialUserAgent = nestedScrollWebView.getSettings().getUserAgentString();
 
         String initialUserAgent = nestedScrollWebView.getSettings().getUserAgentString();
 
-        // Initialize a variable to track if the user agent changes.
-        boolean userAgentChanged = false;
-
         // Parse the URL into a URI.
         Uri uri = Uri.parse(url);
 
         // Extract the domain from `uri`.
         // Parse the URL into a URI.
         Uri uri = Uri.parse(url);
 
         // Extract the domain from `uri`.
-        String hostName = uri.getHost();
-
-        // Initialize `loadingNewDomainName`.
-        boolean loadingNewDomainName;
-
-        // 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)) {  // TODO.
-            loadingNewDomainName = true;
-        } else {  // Determine if `hostName` equals `currentDomainName`.
-            loadingNewDomainName = !hostName.equals(currentDomainName);  // TODO.
-        }
+        String newHostName = uri.getHost();
 
         // Strings don't like to be null.
 
         // Strings don't like to be null.
-        if (hostName == null) {
-            hostName = "";
+        if (newHostName == null) {
+            newHostName = "";
         }
 
         // 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.
         }
 
         // 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;  // TODO.
+        if (!nestedScrollWebView.getCurrentDomainName().equals(newHostName)) {
+            // Set the new host name as the current domain name.
+            nestedScrollWebView.setCurrentDomainName(newHostName);
 
             // Reset the ignoring of pinned domain information.
             nestedScrollWebView.setIgnorePinnedDomainInformation(false);
 
             // Reset the ignoring of pinned domain information.
             nestedScrollWebView.setIgnorePinnedDomainInformation(false);
@@ -3626,9 +3587,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             String domainNameInDatabase = null;
 
             // Check the hostname against the domain settings set.
             String domainNameInDatabase = null;
 
             // Check the hostname against the domain settings set.
-            if (domainSettingsSet.contains(hostName)) {  // The hostname is contained in the domain settings set.
+            if (domainSettingsSet.contains(newHostName)) {  // The hostname is contained in the domain settings set.
                 // Record the domain name in the database.
                 // Record the domain name in the database.
-                domainNameInDatabase = hostName;
+                domainNameInDatabase = newHostName;
 
                 // Set the domain settings applied tracker to true.
                 nestedScrollWebView.setDomainSettingsApplied(true);
 
                 // Set the domain settings applied tracker to true.
                 nestedScrollWebView.setDomainSettingsApplied(true);
@@ -3638,31 +3599,33 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
 
             // Check all the subdomains of the host name against wildcard domains in the domain cursor.
             }
 
             // Check all the subdomains of the host name against wildcard domains in the domain cursor.
-            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 `*.`.
+            while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) {  // Stop checking if domain settings are already applied or there are no more `.` in the host name.
+                if (domainSettingsSet.contains("*." + newHostName)) {  // Check the host name prepended by `*.`.
                     // Set the domain settings applied tracker to true.
                     nestedScrollWebView.setDomainSettingsApplied(true);
 
                     // Store the applied domain names as it appears in the database.
                     // Set the domain settings applied tracker to true.
                     nestedScrollWebView.setDomainSettingsApplied(true);
 
                     // Store the applied domain names as it appears in the database.
-                    domainNameInDatabase = "*." + hostName;
+                    domainNameInDatabase = "*." + newHostName;
                 }
 
                 // Strip out the lowest subdomain of of the host name.
                 }
 
                 // Strip out the lowest subdomain of of the host name.
-                hostName = hostName.substring(hostName.indexOf(".") + 1);
+                newHostName = newHostName.substring(newHostName.indexOf(".") + 1);
             }
 
 
             }
 
 
-            // Get a handle for the shared preference.
+            // Get a handle for the shared preferences.
             SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
             // Store the general preference information.
             String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
             String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
             SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
             // Store the general preference information.
             String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
             String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
-            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);  // TODO.
             boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
 
             boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
             nightMode = sharedPreferences.getBoolean("night_mode", false);  // TODO.
             boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
 
+            // Declare the JavaScript tracker.
+            boolean javaScriptEnabled;
+
             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);
             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);
@@ -3670,7 +3633,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Get the settings from the cursor.
                 nestedScrollWebView.setDomainSettingsDatabaseId(currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
 
                 // Get the settings from the cursor.
                 nestedScrollWebView.setDomainSettingsDatabaseId(currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
-                javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);  // TODO.
+                javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);  // TODO.  Rename to domainSettingsJavaScriptEnabled after the global variable is removed.
                 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.
                 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.
@@ -3743,14 +3706,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Enable JavaScript if night mode is enabled.
                 if (nightMode) {
 
                 // Enable JavaScript if night mode is enabled.
                 if (nightMode) {
-                    javaScriptEnabled = true;  // TODO.
+                    // Enable JavaScript.
+                    nestedScrollWebView.getSettings().setJavaScriptEnabled(true);
+                } else {
+                    // Set JavaScript according to the domain settings.
+                    nestedScrollWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
                 }
 
                 // Close `currentHostDomainSettingsCursor`.
                 currentHostDomainSettingsCursor.close();
 
                 // Apply the domain settings.
                 }
 
                 // Close `currentHostDomainSettingsCursor`.
                 currentHostDomainSettingsCursor.close();
 
                 // Apply the domain settings.
-                nestedScrollWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);  // TODO.
                 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);  //TODO  This could be bad.
                 nestedScrollWebView.getSettings().setDomStorageEnabled(domStorageEnabled);  // TODO.
 
                 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);  //TODO  This could be bad.
                 nestedScrollWebView.getSettings().setDomStorageEnabled(domStorageEnabled);  // TODO.
 
@@ -3773,7 +3739,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // 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.
                 // <https://redmine.stoutner.com/issues/160>
 
                 // 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.
                 // <https://redmine.stoutner.com/issues/160>
-                if (!urlIsLoading) {  // TODO.  We need to track this by WebView.
+                if (nestedScrollWebView.getProgress() == 100) {  // A URL is not loading.
                     // 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.
                     // 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.
@@ -3792,8 +3758,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                 break;
 
                             case SETTINGS_CUSTOM_USER_AGENT:
                                 break;
 
                             case SETTINGS_CUSTOM_USER_AGENT:
-                                // Set the custom user agent.
-                                nestedScrollWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
+                                // Set the default custom user agent.
+                                nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
                                 break;
 
                             default:
                                 break;
 
                             default:
@@ -3819,12 +3785,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                 nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
                         }
                     }
                                 nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
                         }
                     }
-
-                    // Store the applied user agent string, which is used in the View Source activity.
-                    appliedUserAgentString = nestedScrollWebView.getSettings().getUserAgentString();  // TODO.
-
-                    // Update the user agent change tracker.
-                    userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);  // TODO.
                 }
 
                 // Set swipe to refresh.
                 }
 
                 // Set swipe to refresh.
@@ -3881,11 +3841,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
                 if (nightMode) {
 
                 // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
                 if (nightMode) {
-                    javaScriptEnabled = true;  // TODO.
+                    // Enable JavaScript.
+                    nestedScrollWebView.getSettings().setJavaScriptEnabled(true);
+                } else {
+                    // Set JavaScript according to the domain settings.
+                    nestedScrollWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
                 }
 
                 // Apply the default settings.
                 }
 
                 // Apply the default settings.
-                nestedScrollWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);  // TODO.
                 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);  // TODO.
                 nestedScrollWebView.getSettings().setDomStorageEnabled(domStorageEnabled);  // TODO.
                 nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
                 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);  // TODO.
                 nestedScrollWebView.getSettings().setDomStorageEnabled(domStorageEnabled);  // TODO.
                 nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
@@ -3906,7 +3869,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // 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.
                 // <https://redmine.stoutner.com/issues/160>
 
                 // 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.
                 // <https://redmine.stoutner.com/issues/160>
-                if (!urlIsLoading) {  // TODO.
+                if (nestedScrollWebView.getProgress() == 100) {  // A URL is not loading.
                     // Get the array position of the user agent name.
                     int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
 
                     // Get the array position of the user agent name.
                     int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
 
@@ -3923,36 +3886,28 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             break;
 
                         case SETTINGS_CUSTOM_USER_AGENT:
                             break;
 
                         case SETTINGS_CUSTOM_USER_AGENT:
-                            // Set the custom user agent.
-                            nestedScrollWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);  // TODO.
+                            // Set the default custom user agent.
+                            nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
                             break;
 
                         default:
                             // Get the user agent string from the user agent data array
                             nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
                     }
                             break;
 
                         default:
                             // Get the user agent string from the user agent data array
                             nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
                     }
-
-                    // Store the applied user agent string, which is used in the View Source activity.
-                    appliedUserAgentString = nestedScrollWebView.getSettings().getUserAgentString();  // TODO.
-
-                    // Update the user agent change tracker.
-                    userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);  // TODO.
                 }
 
                 // Set the loading of webpage images.
                 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
 
                 }
 
                 // Set the loading of webpage images.
                 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
 
-                // 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));
+                // Set a transparent background on URL edit text.  The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21.
+                urlEditText.setBackground(getResources().getDrawable(R.color.transparent));
             }
 
             // Close the domains database helper.
             domainsDatabaseHelper.close();
 
             }
 
             // Close the domains database helper.
             domainsDatabaseHelper.close();
 
-            // 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);
-            }
+            // Update the privacy icons.
+            updatePrivacyIcons(true);
         }
 
         // Reload the website if returning from the Domains activity.
         }
 
         // Reload the website if returning from the Domains activity.
@@ -3961,7 +3916,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
 
         // Return the user agent changed status.
         }
 
         // Return the user agent changed status.
-        return userAgentChanged;
+        return !nestedScrollWebView.getSettings().getUserAgentString().equals(initialUserAgent);
     }
 
     private void applyProxyThroughOrbot(boolean reloadWebsite) {
     }
 
     private void applyProxyThroughOrbot(boolean reloadWebsite) {
@@ -4076,59 +4031,62 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
     }
 
     private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
-        // Get handles for the menu items.
-        MenuItem privacyMenuItem = mainMenu.findItem(R.id.toggle_javascript);
-        MenuItem firstPartyCookiesMenuItem = mainMenu.findItem(R.id.toggle_first_party_cookies);
-        MenuItem domStorageMenuItem = mainMenu.findItem(R.id.toggle_dom_storage);
-        MenuItem refreshMenuItem = mainMenu.findItem(R.id.refresh);
-
-        // Update the privacy icon.
-        if (javaScriptEnabled) {  // JavaScript is enabled.
-            privacyMenuItem.setIcon(R.drawable.javascript_enabled);
-        } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled but cookies are enabled.
-            privacyMenuItem.setIcon(R.drawable.warning);
-        } else {  // All the dangerous features are disabled.
-            privacyMenuItem.setIcon(R.drawable.privacy_mode);
-        }
+        // Only update the privacy icons if the options menu has already been populated.
+        if (optionsMenu != null) {
+            // Get handles for the menu items.
+            MenuItem privacyMenuItem = optionsMenu.findItem(R.id.toggle_javascript);
+            MenuItem firstPartyCookiesMenuItem = optionsMenu.findItem(R.id.toggle_first_party_cookies);
+            MenuItem domStorageMenuItem = optionsMenu.findItem(R.id.toggle_dom_storage);
+            MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
+
+            // Update the privacy icon.
+            if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is enabled.
+                privacyMenuItem.setIcon(R.drawable.javascript_enabled);
+            } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled but cookies are enabled.
+                privacyMenuItem.setIcon(R.drawable.warning);
+            } else {  // All the dangerous features are disabled.
+                privacyMenuItem.setIcon(R.drawable.privacy_mode);
+            }
 
 
-        // Update the first-party cookies icon.
-        if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
-            firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
-        } else {  // First-party cookies are disabled.
-            if (darkTheme) {
-                firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
-            } else {
-                firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
+            // Update the first-party cookies icon.
+            if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
+                firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
+            } else {  // First-party cookies are disabled.
+                if (darkTheme) {
+                    firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
+                } else {
+                    firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
+                }
             }
             }
-        }
 
 
-        // Update the DOM storage icon.
-        if (javaScriptEnabled && domStorageEnabled) {  // Both JavaScript and DOM storage are enabled.
-            domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
-        } else if (javaScriptEnabled) {  // JavaScript is enabled but DOM storage is disabled.
-            if (darkTheme) {
-                domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
-            } else {
-                domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
+            // Update the DOM storage icon.
+            if (currentWebView.getSettings().getJavaScriptEnabled() && domStorageEnabled) {  // Both JavaScript and DOM storage are enabled.
+                domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
+            } else if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is enabled but DOM storage is disabled.
+                if (darkTheme) {
+                    domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
+                } else {
+                    domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
+                }
+            } else {  // JavaScript is disabled, so DOM storage is ghosted.
+                if (darkTheme) {
+                    domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
+                } else {
+                    domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
+                }
             }
             }
-        } else {  // JavaScript is disabled, so DOM storage is ghosted.
+
+            // Update the refresh icon.
             if (darkTheme) {
             if (darkTheme) {
-                domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
+                refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
             } else {
             } else {
-                domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
+                refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
             }
             }
-        }
 
 
-        // Update the refresh icon.
-        if (darkTheme) {
-            refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
-        } else {
-            refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
-        }
-
-        // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
-        if (runInvalidateOptionsMenu) {
-            invalidateOptionsMenu();
+            // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
+            if (runInvalidateOptionsMenu) {
+                invalidateOptionsMenu();
+            }
         }
     }
 
         }
     }
 
@@ -5083,10 +5041,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             @Override
             public void onPageStarted(WebView view, String url, Bitmap favicon) {
 
             @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 resource requests.
                 nestedScrollWebView.clearResourceRequests();
 
                 // Reset the list of resource requests.
                 nestedScrollWebView.clearResourceRequests();
 
@@ -5246,9 +5200,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         CheckPinnedMismatchHelper.checkPinnedMismatch(getSupportFragmentManager(), nestedScrollWebView);
                     }
                 }
                         CheckPinnedMismatchHelper.checkPinnedMismatch(getSupportFragmentManager(), nestedScrollWebView);
                     }
                 }
-
-                // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes.  It is also used to determine when to check for pinned mismatches.
-                urlIsLoading = false;
             }
 
             // Handle SSL Certificate errors.
             }
 
             // Handle SSL Certificate errors.
index 052750b88576c8f989c1ba69c27168d727e4eef7..bcb94c18ef74a1776c7cc59fcfd1d90fd4cf3b0b 100644 (file)
@@ -22,27 +22,17 @@ package com.stoutner.privacybrowser.activities;
 import android.app.Activity;
 import android.app.DialogFragment;
 import android.content.Context;
 import android.app.Activity;
 import android.app.DialogFragment;
 import android.content.Context;
-import android.content.SharedPreferences;
-import android.graphics.Typeface;
-import android.os.AsyncTask;
-import android.os.Build;
+import android.content.Intent;
 import android.os.Bundle;
 import android.os.Bundle;
-import android.os.LocaleList;
-import android.preference.PreferenceManager;
-import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.style.ForegroundColorSpan;
 import android.text.Spanned;
 import android.text.style.ForegroundColorSpan;
-import android.text.style.StyleSpan;
 import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.inputmethod.InputMethodManager;
 import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.inputmethod.InputMethodManager;
-import android.webkit.CookieManager;
 import android.widget.EditText;
 import android.widget.EditText;
-import android.widget.ProgressBar;
-import android.widget.TextView;
 
 import androidx.appcompat.app.ActionBar;
 import androidx.appcompat.app.AppCompatActivity;
 
 import androidx.appcompat.app.ActionBar;
 import androidx.appcompat.app.AppCompatActivity;
@@ -51,17 +41,9 @@ import androidx.core.app.NavUtils;
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 
 import com.stoutner.privacybrowser.R;
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 
 import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.asynctasks.GetSource;
 import com.stoutner.privacybrowser.dialogs.AboutViewSourceDialog;
 
 import com.stoutner.privacybrowser.dialogs.AboutViewSourceDialog;
 
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.Locale;
-
 public class ViewSourceActivity extends AppCompatActivity {
     // `activity` is used in `onCreate()` and `goBack()`.
     private Activity activity;
 public class ViewSourceActivity extends AppCompatActivity {
     // `activity` is used in `onCreate()` and `goBack()`.
     private Activity activity;
@@ -88,6 +70,12 @@ public class ViewSourceActivity extends AppCompatActivity {
         // Run the default commands.
         super.onCreate(savedInstanceState);
 
         // Run the default commands.
         super.onCreate(savedInstanceState);
 
+        // Get the launching intent
+        Intent intent = getIntent();
+
+        // Get the user agent.
+        String userAgent = intent.getStringExtra("user_agent");
+
         // Store a handle for the current activity.
         activity = this;
 
         // Store a handle for the current activity.
         activity = this;
 
@@ -165,7 +153,7 @@ public class ViewSourceActivity extends AppCompatActivity {
 
                 // Get new source data for the current URL if it beings with `http`.
                 if (url.startsWith("http")) {
 
                 // Get new source data for the current URL if it beings with `http`.
                 if (url.startsWith("http")) {
-                    new GetSource(this).execute(url);
+                    new GetSource(this, userAgent).execute(url);
                 }
 
                 // Consume the key press.
                 }
 
                 // Consume the key press.
@@ -184,7 +172,7 @@ public class ViewSourceActivity extends AppCompatActivity {
 
             // Get new source data for the URL if it begins with `http`.
             if (url.startsWith("http")) {
 
             // Get new source data for the URL if it begins with `http`.
             if (url.startsWith("http")) {
-                new GetSource(this).execute(url);
+                new GetSource(this, userAgent).execute(url);
             } else {
                 // Stop the refresh animation.
                 swipeRefreshLayout.setRefreshing(false);
             } else {
                 // Stop the refresh animation.
                 swipeRefreshLayout.setRefreshing(false);
@@ -201,7 +189,7 @@ public class ViewSourceActivity extends AppCompatActivity {
 
         // Get the source using an AsyncTask if the URL begins with `http`.
         if (formattedUrlString.startsWith("http")) {
 
         // Get the source using an AsyncTask if the URL begins with `http`.
         if (formattedUrlString.startsWith("http")) {
-            new GetSource(this).execute(formattedUrlString);
+            new GetSource(this, userAgent).execute(formattedUrlString);
         }
     }
 
         }
     }
 
@@ -291,407 +279,4 @@ public class ViewSourceActivity extends AppCompatActivity {
             }
         }
     }
             }
         }
     }
-
-    // `String` declares the parameters.  `Void` does not declare progress units.  `SpannableStringBuilder[]` contains the results.
-    private static class GetSource extends AsyncTask<String, Void, SpannableStringBuilder[]> {
-        // Create a weak reference to the calling activity.
-        private WeakReference<Activity> activityWeakReference;
-
-        // Populate the weak reference to the calling activity.
-        GetSource(Activity activity) {
-            activityWeakReference = new WeakReference<>(activity);
-        }
-
-        // `onPreExecute()` operates on the UI thread.
-        @Override
-        protected void onPreExecute() {
-            // Get a handle for the activity.
-            Activity viewSourceActivity = activityWeakReference.get();
-
-            // Abort if the activity is gone.
-            if ((viewSourceActivity == null) || viewSourceActivity.isFinishing()) {
-                return;
-            }
-
-            // Get a handle for the progress bar.
-            ProgressBar progressBar = viewSourceActivity.findViewById(R.id.progress_bar);
-
-            // Make the progress bar visible.
-            progressBar.setVisibility(View.VISIBLE);
-
-            // Set the progress bar to be indeterminate.
-            progressBar.setIndeterminate(true);
-        }
-
-        @Override
-        protected SpannableStringBuilder[] doInBackground(String... formattedUrlString) {
-            // Initialize the response body String.
-            SpannableStringBuilder requestHeadersBuilder = new SpannableStringBuilder();
-            SpannableStringBuilder responseMessageBuilder = new SpannableStringBuilder();
-            SpannableStringBuilder responseHeadersBuilder = new SpannableStringBuilder();
-            SpannableStringBuilder responseBodyBuilder = new SpannableStringBuilder();
-
-            // Get a handle for the activity.
-            Activity activity = activityWeakReference.get();
-
-            // Abort if the activity is gone.
-            if ((activity == null) || activity.isFinishing()) {
-                return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
-            }
-
-            // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
-            try {
-                // Get the current URL from the main activity.
-                URL url = new URL(formattedUrlString[0]);
-
-                // Open a connection to the URL.  No data is actually sent at this point.
-                HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection();
-
-                // Instantiate the variables necessary to build the request headers.
-                requestHeadersBuilder = new SpannableStringBuilder();
-                int oldRequestHeadersBuilderLength;
-                int newRequestHeadersBuilderLength;
-
-
-                // Set the `Host` header property.
-                httpUrlConnection.setRequestProperty("Host", url.getHost());
-
-                // Add the `Host` header to the string builder and format the text.
-                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                    requestHeadersBuilder.append("Host", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                } else {  // Older versions not so much.
-                    oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.append("Host");
-                    newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                }
-                requestHeadersBuilder.append(":  ");
-                requestHeadersBuilder.append(url.getHost());
-
-
-                // Set the `Connection` header property.
-                httpUrlConnection.setRequestProperty("Connection", "keep-alive");
-
-                // Add the `Connection` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"));
-                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                    requestHeadersBuilder.append("Connection", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                } else {  // Older versions not so much.
-                    oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.append("Connection");
-                    newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                }
-                requestHeadersBuilder.append(":  keep-alive");
-
-
-                // Get the current `User-Agent` string.
-                String userAgentString = MainWebViewActivity.appliedUserAgentString;
-
-                // Set the `User-Agent` header property.
-                httpUrlConnection.setRequestProperty("User-Agent", userAgentString);
-
-                // Add the `User-Agent` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"));
-                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                    requestHeadersBuilder.append("User-Agent", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                } else {  // Older versions not so much.
-                    oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.append("User-Agent");
-                    newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                }
-                requestHeadersBuilder.append(":  ");
-                requestHeadersBuilder.append(userAgentString);
-
-
-                // Set the `Upgrade-Insecure-Requests` header property.
-                httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1");
-
-                // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"));
-                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                    requestHeadersBuilder.append("Upgrade-Insecure-Requests", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                } else {  // Older versions not so much.
-                    oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.append("Upgrade-Insecure_Requests");
-                    newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                }
-                requestHeadersBuilder.append(":  1");
-
-
-                // Set the `x-requested-with` header property.
-                httpUrlConnection.setRequestProperty("x-requested-with", "");
-
-                // Add the `x-requested-with` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"));
-                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                    requestHeadersBuilder.append("x-requested-with", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                } else {  // Older versions not so much.
-                    oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.append("x-requested-with");
-                    newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                }
-                requestHeadersBuilder.append(":  ");
-
-
-                // Get a handle for the shared preferences.
-                SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext());
-
-                // Only populate `Do Not Track` if it is enabled.
-                if (sharedPreferences.getBoolean("do_not_track", false)) {
-                    // Set the `dnt` header property.
-                    httpUrlConnection.setRequestProperty("dnt", "1");
-
-                    // Add the `dnt` header to the string builder and format the text.
-                    requestHeadersBuilder.append(System.getProperty("line.separator"));
-                    if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                        requestHeadersBuilder.append("dnt", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                    } else {  // Older versions not so much.
-                        oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                        requestHeadersBuilder.append("dnt");
-                        newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                        requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                    }
-                    requestHeadersBuilder.append(":  1");
-                }
-
-
-                // Set the `Accept` header property.
-                httpUrlConnection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
-
-                // Add the `Accept` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"));
-                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                    requestHeadersBuilder.append("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                } else {  // Older versions not so much.
-                    oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.append("Accept");
-                    newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                }
-                requestHeadersBuilder.append(":  ");
-                requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
-
-
-                // Instantiate a locale string.
-                String localeString;
-
-                // Populate the locale string.
-                if (Build.VERSION.SDK_INT >= 24) {  // SDK >= 24 has a list of locales.
-                    // Get the list of locales.
-                    LocaleList localeList = activity.getResources().getConfiguration().getLocales();
-
-                    // Initialize a string builder to extract the locales from the list.
-                    StringBuilder localesStringBuilder = new StringBuilder();
-
-                    // Initialize a `q` value, which is used by `WebView` to indicate the order of importance of the languages.
-                    int q = 10;
-
-                    // Populate the string builder with the contents of the locales list.
-                    for (int i = 0; i < localeList.size(); i++) {
-                        // Append a comma if there is already an item in the string builder.
-                        if (i > 0) {
-                            localesStringBuilder.append(",");
-                        }
-
-                        // Get the indicated locale from the list.
-                        localesStringBuilder.append(localeList.get(i));
-
-                        // If not the first locale, append `;q=0.i`, which drops by .1 for each removal from the main locale.
-                        if (q < 10) {
-                            localesStringBuilder.append(";q=0.");
-                            localesStringBuilder.append(q);
-                        }
-
-                        // Decrement `q`.
-                        q--;
-                    }
-
-                    // Store the populated string builder in the locale string.
-                    localeString = localesStringBuilder.toString();
-                } else {  // SDK < 24 only has a primary locale.
-                    // Store the locale in the locale string.
-                    localeString = Locale.getDefault().toString();
-                }
-
-                // Set the `Accept-Language` header property.
-                httpUrlConnection.setRequestProperty("Accept-Language", localeString);
-
-                // Add the `Accept-Language` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"));
-                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                    requestHeadersBuilder.append("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                } else {  // Older versions not so much.
-                    oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.append("Accept-Language");
-                    newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                }
-                requestHeadersBuilder.append(":  ");
-                requestHeadersBuilder.append(localeString);
-
-
-                // Get the cookies for the current domain.
-                String cookiesString = CookieManager.getInstance().getCookie(url.toString());
-
-                // Only process the cookies if they are not null.
-                if (cookiesString != null) {
-                    // Set the `Cookie` header property.
-                    httpUrlConnection.setRequestProperty("Cookie", cookiesString);
-
-                    // Add the `Cookie` header to the string builder and format the text.
-                    requestHeadersBuilder.append(System.getProperty("line.separator"));
-                    if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                        requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                    } else {  // Older versions not so much.
-                        oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                        requestHeadersBuilder.append("Cookie");
-                        newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                        requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                    }
-                    requestHeadersBuilder.append(":  ");
-                    requestHeadersBuilder.append(cookiesString);
-                }
-
-
-                // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default.  If the property is manually set, than `HttpUrlConnection` does not process the decoding.
-                // Add the `Accept-Encoding` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"));
-                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                    requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                } else {  // Older versions not so much.
-                    oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.append("Accept-Encoding");
-                    newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                }
-                requestHeadersBuilder.append(":  gzip");
-
-
-                // The actual network request is in a `try` bracket so that `disconnect()` is run in the `finally` section even if an error is encountered in the main block.
-                try {
-                    // Initialize the string builders.
-                    responseMessageBuilder = new SpannableStringBuilder();
-                    responseHeadersBuilder = new SpannableStringBuilder();
-
-                    // Get the response code, which causes the connection to the server to be made.
-                    int responseCode = httpUrlConnection.getResponseCode();
-
-                    // Populate the response message string builder.
-                    if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                        responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                    } else {  // Older versions not so much.
-                        responseMessageBuilder.append(String.valueOf(responseCode));
-                        int newLength = responseMessageBuilder.length();
-                        responseMessageBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                    }
-                    responseMessageBuilder.append(":  ");
-                    responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
-
-                    // Initialize the iteration variable.
-                    int i = 0;
-
-                    // Iterate through the received header fields.
-                    while (httpUrlConnection.getHeaderField(i) != null) {
-                        // Add a new line if there is already information in the string builder.
-                        if (i > 0) {
-                            responseHeadersBuilder.append(System.getProperty("line.separator"));
-                        }
-
-                        // Add the header to the string builder and format the text.
-                        if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                            responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                        } else {  // Older versions not so much.
-                            int oldLength = responseHeadersBuilder.length();
-                            responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i));
-                            int newLength = responseHeadersBuilder.length();
-                            responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldLength + 1, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                        }
-                        responseHeadersBuilder.append(":  ");
-                        responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
-
-                        // Increment the iteration variable.
-                        i++;
-                    }
-
-                    // Instantiate an input stream for the response body.
-                    InputStream inputStream;
-
-                    // Get the correct input stream based on the response code.
-                    if (responseCode == 404) {  // Get the error stream.
-                        inputStream = new BufferedInputStream(httpUrlConnection.getErrorStream());
-                    } else {  // Get the response body stream.
-                        inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
-                    }
-
-                    // Initialize the byte array output stream and the conversion buffer byte array.
-                    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-                    byte[] conversionBufferByteArray = new byte[1024];
-
-                    // Instantiate the variable to track the buffer length.
-                    int bufferLength;
-
-                    try {
-                        // Attempt to read data from the input stream and store it in the conversion buffer byte array.  Also store the amount of data transferred in the buffer length variable.
-                        while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) {  // Proceed while the amount of data stored in the buffer is > 0.
-                            // Write the contents of the conversion buffer to the byte array output stream.
-                            byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength);
-                        }
-                    } catch (IOException e) {
-                        e.printStackTrace();
-                    }
-
-                    // Close the input stream.
-                    inputStream.close();
-
-                    // Populate the response body string with the contents of the byte array output stream.
-                    responseBodyBuilder.append(byteArrayOutputStream.toString());
-                } finally {
-                    // Disconnect `httpUrlConnection`.
-                    httpUrlConnection.disconnect();
-                }
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-
-            // Return the response body string as the result.
-            return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
-        }
-
-        // `onPostExecute()` operates on the UI thread.
-        @Override
-        protected void onPostExecute(SpannableStringBuilder[] viewSourceStringArray){
-            // Get a handle for the activity.
-            Activity activity = activityWeakReference.get();
-
-            // Abort if the activity is gone.
-            if ((activity == null) || activity.isFinishing()) {
-                return;
-            }
-
-            // Get handles for the text views.
-            TextView requestHeadersTextView = activity.findViewById(R.id.request_headers);
-            TextView responseMessageTextView = activity.findViewById(R.id.response_message);
-            TextView responseHeadersTextView = activity.findViewById(R.id.response_headers);
-            TextView responseBodyTextView = activity.findViewById(R.id.response_body);
-            ProgressBar progressBar = activity.findViewById(R.id.progress_bar);
-            SwipeRefreshLayout swipeRefreshLayout = activity.findViewById(R.id.view_source_swiperefreshlayout);
-
-            // Populate the text views.  This can take a long time, and freeze the user interface, if the response body is particularly large.
-            requestHeadersTextView.setText(viewSourceStringArray[0]);
-            responseMessageTextView.setText(viewSourceStringArray[1]);
-            responseHeadersTextView.setText(viewSourceStringArray[2]);
-            responseBodyTextView.setText(viewSourceStringArray[3]);
-
-            // Hide the progress bar.
-            progressBar.setIndeterminate(false);
-            progressBar.setVisibility(View.GONE);
-
-            //Stop the swipe to refresh indicator if it is running
-            swipeRefreshLayout.setRefreshing(false);
-        }
-    }
 }
\ No newline at end of file
 }
\ No newline at end of file
index 3c7f37cfbb65bf0086e7a017d3a9a9fe9ee01011..66111de7c351185668e5ffbcc71082beac11f118 100644 (file)
@@ -104,8 +104,8 @@ public class GetHostIpAddresses extends AsyncTask<String, Void, String> {
         // Store the IP addresses.
         nestedScrollWebView.setCurrentIpAddresses(ipAddresses);
 
         // Store the IP addresses.
         nestedScrollWebView.setCurrentIpAddresses(ipAddresses);
 
-        //TODO.  Move `urlIsLoading` to the WebView.
-        if (!MainWebViewActivity.urlIsLoading && !nestedScrollWebView.ignorePinnedDomainInformation() && (nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses())) {
+        // Checked for pinned mismatches if the WebView is not loading a URL, pinned information is not ignored, and there is pinned information.
+        if ((nestedScrollWebView.getProgress() == 100) && !nestedScrollWebView.ignorePinnedDomainInformation() && (nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses())) {
             CheckPinnedMismatchHelper.checkPinnedMismatch(fragmentManager, nestedScrollWebView);
         }
     }
             CheckPinnedMismatchHelper.checkPinnedMismatch(fragmentManager, nestedScrollWebView);
         }
     }
diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java
new file mode 100644 (file)
index 0000000..680fb7f
--- /dev/null
@@ -0,0 +1,454 @@
+/*
+ * Copyright Â© 2017-2019 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.asynctasks;
+
+import android.app.Activity;
+import android.content.SharedPreferences;
+import android.graphics.Typeface;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.LocaleList;
+import android.preference.PreferenceManager;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.StyleSpan;
+import android.view.View;
+import android.webkit.CookieManager;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+
+import com.stoutner.privacybrowser.R;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Locale;
+
+// This must run asynchronously because it involves a network request.  `String` declares the parameters.  `Void` does not declare progress units.  `SpannableStringBuilder[]` contains the results.
+public class GetSource extends AsyncTask<String, Void, SpannableStringBuilder[]> {
+    // Declare a weak reference to the calling activity.
+    private WeakReference<Activity> activityWeakReference;
+
+    // Store the user agent.
+    private String userAgent;
+
+    public GetSource(Activity activity, String userAgent) {
+        // Populate the weak reference to the calling activity.
+        activityWeakReference = new WeakReference<>(activity);
+
+        // Store the user agent.
+        this.userAgent = userAgent;
+    }
+
+    // `onPreExecute()` operates on the UI thread.
+    @Override
+    protected void onPreExecute() {
+        // Get a handle for the activity.
+        Activity viewSourceActivity = activityWeakReference.get();
+
+        // Abort if the activity is gone.
+        if ((viewSourceActivity == null) || viewSourceActivity.isFinishing()) {
+            return;
+        }
+
+        // Get a handle for the progress bar.
+        ProgressBar progressBar = viewSourceActivity.findViewById(R.id.progress_bar);
+
+        // Make the progress bar visible.
+        progressBar.setVisibility(View.VISIBLE);
+
+        // Set the progress bar to be indeterminate.
+        progressBar.setIndeterminate(true);
+    }
+
+    @Override
+    protected SpannableStringBuilder[] doInBackground(String... formattedUrlString) {
+        // Initialize the response body String.
+        SpannableStringBuilder requestHeadersBuilder = new SpannableStringBuilder();
+        SpannableStringBuilder responseMessageBuilder = new SpannableStringBuilder();
+        SpannableStringBuilder responseHeadersBuilder = new SpannableStringBuilder();
+        SpannableStringBuilder responseBodyBuilder = new SpannableStringBuilder();
+
+        // Get a handle for the activity.
+        Activity activity = activityWeakReference.get();
+
+        // Abort if the activity is gone.
+        if ((activity == null) || activity.isFinishing()) {
+            return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
+        }
+
+        // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
+        try {
+            // Get the current URL from the main activity.
+            URL url = new URL(formattedUrlString[0]);
+
+            // Open a connection to the URL.  No data is actually sent at this point.
+            HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection();
+
+            // Instantiate the variables necessary to build the request headers.
+            requestHeadersBuilder = new SpannableStringBuilder();
+            int oldRequestHeadersBuilderLength;
+            int newRequestHeadersBuilderLength;
+
+
+            // Set the `Host` header property.
+            httpUrlConnection.setRequestProperty("Host", url.getHost());
+
+            // Add the `Host` header to the string builder and format the text.
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("Host", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("Host");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  ");
+            requestHeadersBuilder.append(url.getHost());
+
+
+            // Set the `Connection` header property.
+            httpUrlConnection.setRequestProperty("Connection", "keep-alive");
+
+            // Add the `Connection` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("Connection", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("Connection");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  keep-alive");
+
+
+            // Set the `User-Agent` header property.
+            httpUrlConnection.setRequestProperty("User-Agent", userAgent);
+
+            // Add the `User-Agent` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("User-Agent", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("User-Agent");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  ");
+            requestHeadersBuilder.append(userAgent);
+
+
+            // Set the `Upgrade-Insecure-Requests` header property.
+            httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1");
+
+            // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("Upgrade-Insecure-Requests", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("Upgrade-Insecure_Requests");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  1");
+
+
+            // Set the `x-requested-with` header property.
+            httpUrlConnection.setRequestProperty("x-requested-with", "");
+
+            // Add the `x-requested-with` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("x-requested-with", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("x-requested-with");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  ");
+
+
+            // Get a handle for the shared preferences.
+            SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext());
+
+            // Only populate `Do Not Track` if it is enabled.
+            if (sharedPreferences.getBoolean("do_not_track", false)) {
+                // Set the `dnt` header property.
+                httpUrlConnection.setRequestProperty("dnt", "1");
+
+                // Add the `dnt` header to the string builder and format the text.
+                requestHeadersBuilder.append(System.getProperty("line.separator"));
+                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                    requestHeadersBuilder.append("dnt", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                } else {  // Older versions not so much.
+                    oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                    requestHeadersBuilder.append("dnt");
+                    newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                    requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+                requestHeadersBuilder.append(":  1");
+            }
+
+
+            // Set the `Accept` header property.
+            httpUrlConnection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
+
+            // Add the `Accept` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("Accept");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  ");
+            requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
+
+
+            // Instantiate a locale string.
+            String localeString;
+
+            // Populate the locale string.
+            if (Build.VERSION.SDK_INT >= 24) {  // SDK >= 24 has a list of locales.
+                // Get the list of locales.
+                LocaleList localeList = activity.getResources().getConfiguration().getLocales();
+
+                // Initialize a string builder to extract the locales from the list.
+                StringBuilder localesStringBuilder = new StringBuilder();
+
+                // Initialize a `q` value, which is used by `WebView` to indicate the order of importance of the languages.
+                int q = 10;
+
+                // Populate the string builder with the contents of the locales list.
+                for (int i = 0; i < localeList.size(); i++) {
+                    // Append a comma if there is already an item in the string builder.
+                    if (i > 0) {
+                        localesStringBuilder.append(",");
+                    }
+
+                    // Get the indicated locale from the list.
+                    localesStringBuilder.append(localeList.get(i));
+
+                    // If not the first locale, append `;q=0.i`, which drops by .1 for each removal from the main locale.
+                    if (q < 10) {
+                        localesStringBuilder.append(";q=0.");
+                        localesStringBuilder.append(q);
+                    }
+
+                    // Decrement `q`.
+                    q--;
+                }
+
+                // Store the populated string builder in the locale string.
+                localeString = localesStringBuilder.toString();
+            } else {  // SDK < 24 only has a primary locale.
+                // Store the locale in the locale string.
+                localeString = Locale.getDefault().toString();
+            }
+
+            // Set the `Accept-Language` header property.
+            httpUrlConnection.setRequestProperty("Accept-Language", localeString);
+
+            // Add the `Accept-Language` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("Accept-Language");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  ");
+            requestHeadersBuilder.append(localeString);
+
+
+            // Get the cookies for the current domain.
+            String cookiesString = CookieManager.getInstance().getCookie(url.toString());
+
+            // Only process the cookies if they are not null.
+            if (cookiesString != null) {
+                // Set the `Cookie` header property.
+                httpUrlConnection.setRequestProperty("Cookie", cookiesString);
+
+                // Add the `Cookie` header to the string builder and format the text.
+                requestHeadersBuilder.append(System.getProperty("line.separator"));
+                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                    requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                } else {  // Older versions not so much.
+                    oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                    requestHeadersBuilder.append("Cookie");
+                    newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                    requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+                requestHeadersBuilder.append(":  ");
+                requestHeadersBuilder.append(cookiesString);
+            }
+
+
+            // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default.  If the property is manually set, than `HttpUrlConnection` does not process the decoding.
+            // Add the `Accept-Encoding` header to the string builder and format the text.
+            requestHeadersBuilder.append(System.getProperty("line.separator"));
+            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {  // Older versions not so much.
+                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.append("Accept-Encoding");
+                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            requestHeadersBuilder.append(":  gzip");
+
+
+            // The actual network request is in a `try` bracket so that `disconnect()` is run in the `finally` section even if an error is encountered in the main block.
+            try {
+                // Initialize the string builders.
+                responseMessageBuilder = new SpannableStringBuilder();
+                responseHeadersBuilder = new SpannableStringBuilder();
+
+                // Get the response code, which causes the connection to the server to be made.
+                int responseCode = httpUrlConnection.getResponseCode();
+
+                // Populate the response message string builder.
+                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                    responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                } else {  // Older versions not so much.
+                    responseMessageBuilder.append(String.valueOf(responseCode));
+                    int newLength = responseMessageBuilder.length();
+                    responseMessageBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+                responseMessageBuilder.append(":  ");
+                responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
+
+                // Initialize the iteration variable.
+                int i = 0;
+
+                // Iterate through the received header fields.
+                while (httpUrlConnection.getHeaderField(i) != null) {
+                    // Add a new line if there is already information in the string builder.
+                    if (i > 0) {
+                        responseHeadersBuilder.append(System.getProperty("line.separator"));
+                    }
+
+                    // Add the header to the string builder and format the text.
+                    if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                        responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    } else {  // Older versions not so much.
+                        int oldLength = responseHeadersBuilder.length();
+                        responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i));
+                        int newLength = responseHeadersBuilder.length();
+                        responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldLength + 1, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    }
+                    responseHeadersBuilder.append(":  ");
+                    responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
+
+                    // Increment the iteration variable.
+                    i++;
+                }
+
+                // Instantiate an input stream for the response body.
+                InputStream inputStream;
+
+                // Get the correct input stream based on the response code.
+                if (responseCode == 404) {  // Get the error stream.
+                    inputStream = new BufferedInputStream(httpUrlConnection.getErrorStream());
+                } else {  // Get the response body stream.
+                    inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
+                }
+
+                // Initialize the byte array output stream and the conversion buffer byte array.
+                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+                byte[] conversionBufferByteArray = new byte[1024];
+
+                // Instantiate the variable to track the buffer length.
+                int bufferLength;
+
+                try {
+                    // Attempt to read data from the input stream and store it in the conversion buffer byte array.  Also store the amount of data transferred in the buffer length variable.
+                    while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) {  // Proceed while the amount of data stored in the buffer is > 0.
+                        // Write the contents of the conversion buffer to the byte array output stream.
+                        byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength);
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+
+                // Close the input stream.
+                inputStream.close();
+
+                // Populate the response body string with the contents of the byte array output stream.
+                responseBodyBuilder.append(byteArrayOutputStream.toString());
+            } finally {
+                // Disconnect `httpUrlConnection`.
+                httpUrlConnection.disconnect();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        // Return the response body string as the result.
+        return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
+    }
+
+    // `onPostExecute()` operates on the UI thread.
+    @Override
+    protected void onPostExecute(SpannableStringBuilder[] viewSourceStringArray){
+        // Get a handle for the activity.
+        Activity activity = activityWeakReference.get();
+
+        // Abort if the activity is gone.
+        if ((activity == null) || activity.isFinishing()) {
+            return;
+        }
+
+        // Get handles for the text views.
+        TextView requestHeadersTextView = activity.findViewById(R.id.request_headers);
+        TextView responseMessageTextView = activity.findViewById(R.id.response_message);
+        TextView responseHeadersTextView = activity.findViewById(R.id.response_headers);
+        TextView responseBodyTextView = activity.findViewById(R.id.response_body);
+        ProgressBar progressBar = activity.findViewById(R.id.progress_bar);
+        SwipeRefreshLayout swipeRefreshLayout = activity.findViewById(R.id.view_source_swiperefreshlayout);
+
+        // Populate the text views.  This can take a long time, and freeze the user interface, if the response body is particularly large.
+        requestHeadersTextView.setText(viewSourceStringArray[0]);
+        responseMessageTextView.setText(viewSourceStringArray[1]);
+        responseHeadersTextView.setText(viewSourceStringArray[2]);
+        responseBodyTextView.setText(viewSourceStringArray[3]);
+
+        // Hide the progress bar.
+        progressBar.setIndeterminate(false);
+        progressBar.setVisibility(View.GONE);
+
+        //Stop the swipe to refresh indicator if it is running
+        swipeRefreshLayout.setRefreshing(false);
+    }
+}
\ No newline at end of file
index c7e5ade3dee5a2a45dfe5b970bac98c6c10e9a70..b6708a40a2558e72a71c4cc6e8dff94f34370b82 100644 (file)
@@ -24,6 +24,7 @@ import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.webkit.WebView;
 
 import android.view.MotionEvent;
 import android.webkit.WebView;
 
+import androidx.annotation.NonNull;
 import androidx.core.view.NestedScrollingChild2;
 import androidx.core.view.NestedScrollingChildHelper;
 import androidx.core.view.ViewCompat;
 import androidx.core.view.NestedScrollingChild2;
 import androidx.core.view.NestedScrollingChildHelper;
 import androidx.core.view.ViewCompat;
@@ -49,6 +50,9 @@ public class NestedScrollWebView extends WebView implements NestedScrollingChild
     private boolean domainSettingsApplied;
     private int domainSettingsDatabaseId;
 
     private boolean domainSettingsApplied;
     private int domainSettingsDatabaseId;
 
+    // Keep track of when the domain name changes so that domain settings can be reapplied.  This should never be null.
+    private String currentDomainName = "";
+
     // Track the resource requests.
     private ArrayList<String[]> resourceRequests = new ArrayList<>();
     private int blockedRequests;
     // Track the resource requests.
     private ArrayList<String[]> resourceRequests = new ArrayList<>();
     private int blockedRequests;
@@ -151,6 +155,23 @@ public class NestedScrollWebView extends WebView implements NestedScrollingChild
     }
 
 
     }
 
 
+    // Current domain name.  To function well when called, the domain name should never be allowed to be null.
+    public void setCurrentDomainName(@NonNull String domainName) {
+        // Store the current domain name.
+        currentDomainName = domainName;
+    }
+
+    public void resetCurrentDomainName() {
+        // Reset the current domain name.
+        currentDomainName = "";
+    }
+
+    public String getCurrentDomainName() {
+        // Return the current domain name.
+        return currentDomainName;
+    }
+
+
     // Resource requests.
     public void addResourceRequest(String[] resourceRequest) {
         // Add the resource request to the list.
     // Resource requests.
     public void addResourceRequest(String[] resourceRequest) {
         // Add the resource request to the list.