// 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.
- 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.
- closeOnBack = intent.getBooleanExtra("closeOnBack", false);
+ closeOnBack = intent.getBooleanExtra("close_on_back", false);
// Set the content view.
setContentView(R.layout.domains_coordinatorlayout);
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.
- outState.putBoolean("domainSettingsDisplayed", false);
- outState.putInt("domainSettingsDatabaseId", -1);
+ outState.putBoolean("domain_settings_displayed", false);
+ outState.putInt("domain_settings_database_id", -1);
}
super.onSaveInstanceState(outState);
// 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;
- // 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;
// `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;
// `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 navigationRequestsMenuItem;
+ private MenuItem navigationRequestsMenuItem; // TODO.
// TODO. This could probably be removed.
// The blocklist helper is used in `onCreate()` and `WebViewPagerAdapter`.
// `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;
// `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;
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);
// 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;
// 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();
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.
inFullScreenBrowsingMode = false;
// Initialize the privacy settings variables.
- javaScriptEnabled = false;
firstPartyCookiesEnabled = false;
thirdPartyCookiesEnabled = false;
domStorageEnabled = false;
// 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);
}
// 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);
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());
// 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:
- // 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`.
- 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();
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.
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;
- currentDomainName = "";
+ currentWebView.resetCurrentDomainName();
// Get the current domain
Uri currentUri = Uri.parse(formattedUrlString);
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);
// 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();
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.
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();
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.
- // 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.
- // 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);
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);
+
+ // Add the user agent as an extra to the intent.
+ viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString());
+
+ // Make it so.
startActivity(viewSourceIntent);
return 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.
// 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);
}
@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
- public void onSslErrorProceed() {
+ public void onSslErrorProceed() { // TODO. How to handle this with multiple tabs? There could be multiple errors at once.
sslErrorHandler.proceed();
}
// 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);
}
// `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);
- // 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();
- // 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`.
- 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.
- 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.
- 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);
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.
- domainNameInDatabase = hostName;
+ domainNameInDatabase = newHostName;
// Set the domain settings applied tracker to true.
nestedScrollWebView.setDomainSettingsApplied(true);
}
// 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.
- domainNameInDatabase = "*." + hostName;
+ domainNameInDatabase = "*." + newHostName;
}
// 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));
- 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);
+ // 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);
// 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.
// 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.
- nestedScrollWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); // TODO.
cookieManager.setAcceptCookie(firstPartyCookiesEnabled); //TODO This could be bad.
nestedScrollWebView.getSettings().setDomStorageEnabled(domStorageEnabled); // TODO.
// Only set the user agent if the webpage is not currently loading. Otherwise, changing the user agent on redirects can cause the original website to reload.
// <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.
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:
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 `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.
- nestedScrollWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); // TODO.
cookieManager.setAcceptCookie(firstPartyCookiesEnabled); // TODO.
nestedScrollWebView.getSettings().setDomStorageEnabled(domStorageEnabled); // TODO.
nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
// 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);
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]);
}
-
- // 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 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();
- // 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.
}
// Return the user agent changed status.
- return userAgentChanged;
+ return !nestedScrollWebView.getSettings().getUserAgentString().equals(initialUserAgent);
}
private void applyProxyThroughOrbot(boolean reloadWebsite) {
}
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) {
- domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
+ refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
} 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();
+ }
}
}
@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();
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.
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.LocaleList;
-import android.preference.PreferenceManager;
-import android.text.SpannableStringBuilder;
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.webkit.CookieManager;
import android.widget.EditText;
-import android.widget.ProgressBar;
-import android.widget.TextView;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.asynctasks.GetSource;
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;
// 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;
// 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.
// 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);
// 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);
}
}
}
}
}
-
- // `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
// 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);
}
}
--- /dev/null
+/*
+ * 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
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;
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;
}
+ // 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.