import com.stoutner.privacybrowser.R;
import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
+import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
// AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener,
- EditBookmarkFolderDialog.EditBookmarkFolderListener, NavigationView.OnNavigationItemSelectedListener, WebViewTabFragment.NewTabListener {
+ EditBookmarkFolderDialog.EditBookmarkFolderListener, NavigationView.OnNavigationItemSelectedListener, PopulateBlocklists.PopulateBlocklistsListener, WebViewTabFragment.NewTabListener {
// `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
public static String orbotStatus;
// The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()`, `updatePrivacyIcons()`, and `initializeWebView()`.
private Menu optionsMenu;
- // The blocklists are populated in `onCreate()` and accessed from `initializeWebView()`.
+ // The blocklists are populated in `finishedPopulatingBlocklists()` and accessed from `initializeWebView()`.
private ArrayList<List<String[]>> easyList;
private ArrayList<List<String[]>> easyPrivacy;
private ArrayList<List<String[]>> fanboysAnnoyanceList;
// The swipe refresh layout top padding is used when exiting full screen browsing mode. It is used in an inner class in `initializeWebView()`.
private int swipeRefreshLayoutPaddingTop;
+ // The URL sanitizers are set in `applyAppSettings()` and used in `sanitizeUrl()`.
+ private boolean sanitizeGoogleAnalytics;
+ private boolean sanitizeFacebookClickIds;
+ private boolean sanitizeTwitterAmpRedirects;
+
// The download strings are used in `onCreate()`, `onRequestPermissionResult()` and `initializeWebView()`.
private String downloadUrl;
private String downloadContentDisposition;
// Register `orbotStatusBroadcastReceiver` on `this` context.
this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
- // Instantiate the blocklist helper.
- BlockListHelper blockListHelper = new BlockListHelper();
-
- // Parse the block lists.
- easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
- easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
- fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
- fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
- ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
-
// Get handles for views that need to be modified.
DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
NavigationView navigationView = findViewById(R.id.navigationview);
// Create a handler to select the tab.
Handler selectTabHandler = new Handler();
- // Create a runnable select the new tab.
+ // Create a runnable to select the tab.
Runnable selectTabRunnable = () -> {
// Get a handle for the tab.
TabLayout.Tab tab = tabLayout.getTabAt(position);
tab.select();
};
- // Select the tab layout after 100 milliseconds, which leaves enough time for a new tab to be created.
- selectTabHandler.postDelayed(selectTabRunnable, 100);
+ // Select the tab layout after 150 milliseconds, which leaves enough time for a new tab to be inflated.
+ selectTabHandler.postDelayed(selectTabRunnable, 150);
}
}
}
});
- // Add the first tab.
- addTab(null);
-
// Set the bookmarks drawer resources according to the theme. This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget.
// The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21 and and `getResources().getColor()` must be used until the minimum API >= 23.
if (darkTheme) {
// Destroy the bare WebView.
bareWebView.destroy();
+
+ // Populate the blocklists.
+ new PopulateBlocklists(this, this).execute();
}
@Override
// Get the shared preferences.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
- // Add a new tab if specified in the preferences.
- if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {
- // Set the loading new intent flag.
- loadingNewIntent = true;
-
- // Add a new tab.
- addTab(null);
- }
-
// Create a URL string.
String url;
url = intentUriData.toString();
}
- // Load the URL.
- loadUrl(url);
+ // Add a new tab if specified in the preferences.
+ if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) { // Load the URL in a new tab.
+ // Set the loading new intent flag.
+ loadingNewIntent = true;
+
+ // Add a new tab.
+ addNewTab(url);
+ } else { // Load the URL in the current tab.
+ // Make it so.
+ loadUrl(url);
+ }
// Get a handle for the drawer layout.
DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
drawerLayout.closeDrawer(GravityCompat.END);
}
-
- // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
- currentWebView.requestFocus();
}
}
MenuItem blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
+ MenuItem wideViewportMenuItem = menu.findItem(R.id.wide_viewport);
MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
+ wideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
nightModeMenuItem.setChecked(currentWebView.getNightMode());
// Update the swipe refresh layout.
if (currentWebView.getSwipeToRefresh()) { // Swipe to refresh is enabled.
- if (Build.VERSION.SDK_INT >= 23) { // For API >= 23, the status of the scroll refresh listener is continuously updated by the on scroll change listener.
- // Only enable the swipe refresh layout if the WebView is scrolled to the top.
- swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
- } else { // For API < 23, the swipe refresh layout is always enabled.
- // Enable the swipe refresh layout.
- swipeRefreshLayout.setEnabled(true);
- }
+ // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes.
+ swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
} else { // Swipe to refresh is disabled.
// Disable the swipe refresh layout.
swipeRefreshLayout.setEnabled(false);
}
return true;
+ case R.id.wide_viewport:
+ // Toggle the viewport.
+ currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort());
+ return true;
+
case R.id.display_images:
if (currentWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically.
// Disable loading of images.
}, 200);
return true;
+ case R.id.print:
+ // Get a print manager instance.
+ PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
+
+ // Remove the lint error below that print manager might be null.
+ assert printManager != null;
+
+ // Create a print document adapter from the current WebView.
+ PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
+
+ // Print the document.
+ printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
+ return true;
+
+ case R.id.add_to_homescreen:
+ // Instantiate the create home screen shortcut dialog.
+ DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
+ currentWebView.getFavoriteOrDefaultIcon());
+
+ // Show the create home screen shortcut dialog.
+ createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
+ return true;
+
case R.id.view_source:
// Create an intent to launch the view source activity.
Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
return true;
- case R.id.print:
- // Get a print manager instance.
- PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
-
- // Remove the lint error below that print manager might be null.
- assert printManager != null;
-
- // Create a print document adapter from the current WebView.
- PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
-
- // Print the document.
- printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
- return true;
-
case R.id.open_with_app:
openWithApp(currentWebView.getUrl());
return true;
openWithBrowser(currentWebView.getUrl());
return true;
- case R.id.add_to_homescreen:
- // Instantiate the create home screen shortcut dialog.
- DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
- currentWebView.getFavoriteOrDefaultIcon());
-
- // Show the create home screen shortcut dialog.
- createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
- return true;
-
case R.id.proxy_through_orbot:
// Toggle the proxy through Orbot variable.
proxyThroughOrbot = !proxyThroughOrbot;
// Add an Open in New Tab entry.
menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
- // Add a new tab.
- addTab(null);
-
- // Load the URL.
- loadUrl(linkUrl);
+ // Load the link URL in a new tab.
+ addNewTab(linkUrl);
return false;
});
// Add an Open in New Tab entry.
menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
- // Add a new tab.
- addTab(null);
-
- // Load the URL.
- loadUrl(imageUrl);
+ // Load the image URL in a new tab.
+ addNewTab(imageUrl);
return false;
});
}
private void loadUrl(String url) {
+ // Sanitize the URL.
+ url = sanitizeUrl(url);
+
// Apply the domain settings.
applyDomainSettings(currentWebView, url, true, false);
assert inputMethodManager != null;
// Hide the keyboard.
- inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
+ inputMethodManager.hideSoftInputFromWindow(toolbar.getWindowToken(), 0);
}
private void applyAppSettings() {
// Store the values from the shared preferences in variables.
incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
+ sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true);
+ sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true);
+ sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true);
proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
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 (!nestedScrollWebView.getCurrentDomainName().equals(newHostName)) {
+ // Apply the domain settings if a new domain is being loaded or if the new domain is blank. This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc.
+ if (!nestedScrollWebView.getCurrentDomainName().equals(newHostName) || newHostName.equals("")) {
// Set the new host name as the current domain name.
nestedScrollWebView.setCurrentDomainName(newHostName);
String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
- boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
+ boolean wideViewport = sharedPreferences.getBoolean("wide_viewport", true);
+ boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
// Get a handle for the cookie manager.
CookieManager cookieManager = CookieManager.getInstance();
int fontSize = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
int swipeToRefreshInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
int nightModeInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
+ int wideViewportInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.WIDE_VIEWPORT));
int displayWebpageImagesInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
boolean pinnedSslCertificate = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
String pinnedSslIssuedToCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
// Set night mode according to the night mode int.
switch (nightModeInt) {
- case DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT:
+ case DomainsDatabaseHelper.SYSTEM_DEFAULT:
// Set night mode according to the current default.
nestedScrollWebView.setNightMode(sharedPreferences.getBoolean("night_mode", false));
break;
- case DomainsDatabaseHelper.NIGHT_MODE_ENABLED:
+ case DomainsDatabaseHelper.ENABLED:
// Enable night mode.
nestedScrollWebView.setNightMode(true);
break;
- case DomainsDatabaseHelper.NIGHT_MODE_DISABLED:
+ case DomainsDatabaseHelper.DISABLED:
// Disable night mode.
nestedScrollWebView.setNightMode(false);
break;
nestedScrollWebView.getSettings().setTextZoom(fontSize);
}
- // 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 (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.
- int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
-
- // Set the user agent according to the system default.
- switch (defaultUserAgentArrayPosition) {
- case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
- // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
- nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName);
- break;
-
- case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
- // Set the user agent to `""`, which uses the default value.
- nestedScrollWebView.getSettings().setUserAgentString("");
- break;
-
- case SETTINGS_CUSTOM_USER_AGENT:
- // 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[defaultUserAgentArrayPosition]);
- }
- } else { // Set the user agent according to the stored name.
- // Get the array position of the user agent name.
- int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
-
- switch (userAgentArrayPosition) {
- case UNRECOGNIZED_USER_AGENT: // The user agent name contains a custom user agent.
- nestedScrollWebView.getSettings().setUserAgentString(userAgentName);
- break;
-
- case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
- // Set the user agent to `""`, which uses the default value.
- nestedScrollWebView.getSettings().setUserAgentString("");
- break;
-
- default:
- // Get the user agent string from the user agent data array.
- nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
- }
+ // Set the user agent.
+ if (userAgentName.equals(getString(R.string.system_default_user_agent))) { // Use the system default user agent.
+ // Get the array position of the default user agent name.
+ int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
+
+ // Set the user agent according to the system default.
+ switch (defaultUserAgentArrayPosition) {
+ case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
+ // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
+ nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName);
+ break;
+
+ case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
+ // Set the user agent to `""`, which uses the default value.
+ nestedScrollWebView.getSettings().setUserAgentString("");
+ break;
+
+ case SETTINGS_CUSTOM_USER_AGENT:
+ // 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[defaultUserAgentArrayPosition]);
+ }
+ } else { // Set the user agent according to the stored name.
+ // Get the array position of the user agent name.
+ int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
+
+ switch (userAgentArrayPosition) {
+ case UNRECOGNIZED_USER_AGENT: // The user agent name contains a custom user agent.
+ nestedScrollWebView.getSettings().setUserAgentString(userAgentName);
+ break;
+
+ case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
+ // Set the user agent to `""`, which uses the default value.
+ nestedScrollWebView.getSettings().setUserAgentString("");
+ break;
+
+ default:
+ // Get the user agent string from the user agent data array.
+ nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
}
}
// Set swipe to refresh.
switch (swipeToRefreshInt) {
- case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT:
+ case DomainsDatabaseHelper.SYSTEM_DEFAULT:
// Store the swipe to refresh status in the nested scroll WebView.
nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh);
swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
break;
- case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED:
+ case DomainsDatabaseHelper.ENABLED:
// Store the swipe to refresh status in the nested scroll WebView.
nestedScrollWebView.setSwipeToRefresh(true);
swipeRefreshLayout.setEnabled(true);
break;
- case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED:
+ case DomainsDatabaseHelper.DISABLED:
// Store the swipe to refresh status in the nested scroll WebView.
nestedScrollWebView.setSwipeToRefresh(false);
swipeRefreshLayout.setEnabled(false);
}
+ // Set the viewport.
+ switch (wideViewportInt) {
+ case DomainsDatabaseHelper.SYSTEM_DEFAULT:
+ nestedScrollWebView.getSettings().setUseWideViewPort(wideViewport);
+ break;
+
+ case DomainsDatabaseHelper.ENABLED:
+ nestedScrollWebView.getSettings().setUseWideViewPort(true);
+ break;
+
+ case DomainsDatabaseHelper.DISABLED:
+ nestedScrollWebView.getSettings().setUseWideViewPort(false);
+ break;
+ }
+
// Set the loading of webpage images.
switch (displayWebpageImagesInt) {
- case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
+ case DomainsDatabaseHelper.SYSTEM_DEFAULT:
nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
break;
- case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
+ case DomainsDatabaseHelper.ENABLED:
nestedScrollWebView.getSettings().setLoadsImagesAutomatically(true);
break;
- case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
+ case DomainsDatabaseHelper.DISABLED:
nestedScrollWebView.getSettings().setLoadsImagesAutomatically(false);
break;
}
cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, defaultThirdPartyCookiesEnabled);
}
- // 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 (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);
- // Set the user agent.
- switch (userAgentArrayPosition) {
- case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
- // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
- nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName);
- break;
+ // Set the user agent.
+ switch (userAgentArrayPosition) {
+ case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
+ // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
+ nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName);
+ break;
- case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
- // Set the user agent to `""`, which uses the default value.
- nestedScrollWebView.getSettings().setUserAgentString("");
- break;
+ case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
+ // Set the user agent to `""`, which uses the default value.
+ nestedScrollWebView.getSettings().setUserAgentString("");
+ break;
- case SETTINGS_CUSTOM_USER_AGENT:
- // Set the default custom user agent.
- nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
- break;
+ case SETTINGS_CUSTOM_USER_AGENT:
+ // 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]);
- }
+ default:
+ // Get the user agent string from the user agent data array
+ nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
}
+ // Set the viewport.
+ nestedScrollWebView.getSettings().setUseWideViewPort(wideViewport);
+
// Set the loading of webpage images.
nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
startActivity(openWithBrowserIntent);
}
+ private String sanitizeUrl(String url) {
+ // Sanitize Google Analytics.
+ if (sanitizeGoogleAnalytics) {
+ // Remove `?utm_`.
+ if (url.contains("?utm_")) {
+ url = url.substring(0, url.indexOf("?utm_"));
+ }
+
+ // Remove `&utm_`.
+ if (url.contains("&utm_")) {
+ url = url.substring(0, url.indexOf("&utm_"));
+ }
+ }
+
+ // Sanitize Facebook Click IDs.
+ if (sanitizeFacebookClickIds) {
+ // Remove `?fbclid=`.
+ if (url.contains("?fbclid=")) {
+ url = url.substring(0, url.indexOf("?fbclid="));
+ }
+
+ // Remove `&fbclid=`.
+ if (url.contains("&fbclid=")) {
+ url = url.substring(0, url.indexOf("&fbclid="));
+ }
+ }
+
+ // Sanitize Twitter AMP redirects.
+ if (sanitizeTwitterAmpRedirects) {
+ // Remove `?amp=1`.
+ if (url.contains("?amp=1")) {
+ url = url.substring(0, url.indexOf("?amp=1"));
+ }
+ }
+
+ // Return the sanitized URL.
+ return url;
+ }
+
+ public void finishedPopulatingBlocklists(ArrayList<ArrayList<List<String[]>>> combinedBlocklists) {
+ // Store the blocklists.
+ easyList = combinedBlocklists.get(0);
+ easyPrivacy = combinedBlocklists.get(1);
+ fanboysAnnoyanceList = combinedBlocklists.get(2);
+ fanboysSocialList = combinedBlocklists.get(3);
+ ultraPrivacy = combinedBlocklists.get(4);
+
+ // Add the first tab.
+ addNewTab("");
+ }
+
public void addTab(View view) {
+ // Add a new tab with a blank URL.
+ addNewTab("");
+ }
+
+ private void addNewTab(String url) {
+ // Sanitize the URL.
+ url = sanitizeUrl(url);
+
// Get a handle for the tab layout and the view pager.
TabLayout tabLayout = findViewById(R.id.tablayout);
ViewPager webViewPager = findViewById(R.id.webviewpager);
newTab.setCustomView(R.layout.tab_custom_view);
// Add the new WebView page.
- webViewPagerAdapter.addPage(newTabNumber, webViewPager);
+ webViewPagerAdapter.addPage(newTabNumber, webViewPager, url);
}
public void closeTab(View view) {
EditText urlEditText = findViewById(R.id.url_edittext);
SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
- //Stop the swipe to refresh indicator if it is running
+ // Stop the swipe to refresh indicator if it is running
swipeRefreshLayout.setRefreshing(false);
// Get the WebView tab fragment.
View fragmentView = webViewTabFragment.getView();
// Set the current WebView if the fragment view is not null.
- if (fragmentView != null) {
+ if (fragmentView != null) { // The fragment has been populated.
// Store the current WebView.
currentWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
// Update the status of swipe to refresh.
if (currentWebView.getSwipeToRefresh()) { // Swipe to refresh is enabled.
- if (Build.VERSION.SDK_INT >= 23) { // For API >= 23, swipe refresh layout is continuously updated with an on scroll change listener and only enabled if the WebView is scrolled to the top.
- // Enable the swipe refresh layout if the WebView is scrolled all the way to the top.
- swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
- } else {
- // Enable the swipe refresh layout.
- swipeRefreshLayout.setEnabled(true);
- }
+ // Enable the swipe refresh layout if the WebView is scrolled all the way to the top. It is updated every time the scroll changes.
+ swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
} else { // Swipe to refresh is disabled.
// Disable the swipe refresh layout.
swipeRefreshLayout.setEnabled(false);
} else {
urlRelativeLayout.setBackground(getResources().getDrawable(R.color.transparent));
}
+ } else { // The fragment has not been populated. Try again in 100 milliseconds.
+ // Create a handler to set the current WebView.
+ Handler setCurrentWebViewHandler = new Handler();
+
+ // Create a runnable to set the current WebView.
+ Runnable setCurrentWebWebRunnable = () -> {
+ // Set the current WebView.
+ setCurrentWebView(pageNumber);
+ };
+
+ // Try setting the current WebView again after 100 milliseconds.
+ setCurrentWebViewHandler.postDelayed(setCurrentWebWebRunnable, 100);
}
}
@Override
- public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar) {
+ public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, String url) {
// Get handles for the activity views.
FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
// Get a handle for the shared preferences.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
- // Get the relevant preferences.
- boolean downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
-
// Initialize the favorite icon.
nestedScrollWebView.initializeFavoriteIcon();
nestedScrollWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
}
- // Set the WebView to use a wide viewport. Otherwise, some web pages will be scrunched and some content will render outside the screen.
- nestedScrollWebView.getSettings().setUseWideViewPort(true);
-
// Set the WebView to load in overview mode (zoomed out to the maximum width).
nestedScrollWebView.getSettings().setLoadWithOverviewMode(true);
registerForContextMenu(nestedScrollWebView);
// Allow the downloading of files.
- nestedScrollWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
+ nestedScrollWebView.setDownloadListener((String downloadUrl, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
// Check if the download should be processed by an external app.
- if (downloadWithExternalApp) { // Download with an external app.
+ if (sharedPreferences.getBoolean("download_with_external_app", false)) { // Download with an external app.
// Create a download intent. Not specifying the action type will display the maximum number of options.
Intent downloadIntent = new Intent();
// Set the URI and the MIME type. Specifying `text/html` displays a good number of options.
- downloadIntent.setDataAndType(Uri.parse(url), "text/html");
+ downloadIntent.setDataAndType(Uri.parse(downloadUrl), "text/html");
// Flag the intent to open in a new task.
downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// The WRITE_EXTERNAL_STORAGE permission needs to be requested.
// Store the variables for future use by `onRequestPermissionsResult()`.
- downloadUrl = url;
+ this.downloadUrl = downloadUrl;
downloadContentDisposition = contentDisposition;
downloadContentLength = contentLength;
}
} else { // The storage permission has already been granted.
// Get a handle for the download file alert dialog.
- DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
+ DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, contentDisposition, contentLength);
// Show the download file alert dialog.
downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
// Get the title text view from the tab.
TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
- // Set the title as the tab text.
- tabTitleTextView.setText(title);
+ // Set the title according to the URL.
+ if (title.equals("about:blank")) {
+ // Set the title to indicate a new tab.
+ tabTitleTextView.setText(R.string.new_tab);
+ } else {
+ // Set the title as the tab text.
+ tabTitleTextView.setText(title);
+ }
}
}
// The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ // Sanitize the url.
+ url = sanitizeUrl(url);
+
if (url.startsWith("http")) { // Load the URL in Privacy Browser.
// Apply the domain settings for the new URL. This doesn't do anything if the domain has not changed.
boolean userAgentChanged = applyDomainSettings(nestedScrollWebView, url, true, false);
// Check requests against the block lists. The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
+ // Sanitize the URL.
+ url = sanitizeUrl(url);
+
// Get a handle for the navigation view.
NavigationView navigationView = findViewById(R.id.navigationview);
}
}
- // Replace Refresh with Stop if the options menu has been created. (The WebView typically begins loading before the menu items are instantiated.)
+ // Replace Refresh with Stop if the options menu has been created. (The first WebView typically begins loading before the menu items are instantiated.)
if (optionsMenu != null) {
// Get a handle for the refresh menu item.
MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
@Override
public void onPageFinished(WebView view, String url) {
- // Reset the wide view port if it has been turned off by the waiting for Orbot message.
- if (!waitingForOrbot) {
- // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`.
- nestedScrollWebView.getSettings().setUseWideViewPort(url.startsWith("http"));
- }
-
// Flush any cookies to persistent storage. The cookie manager has become very lazy about flushing cookies in recent versions.
if (nestedScrollWebView.getAcceptFirstPartyCookies() && Build.VERSION.SDK_INT >= 21) {
CookieManager.getInstance().flush();
// Get the current URL from the nested scroll WebView. This is more accurate than using the URL passed into the method, which is sometimes not the final one.
String currentUrl = nestedScrollWebView.getUrl();
+ // Get the current tab.
+ TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
+
// Update the URL text bar if the page is currently selected and the user is not currently typing in the URL edit text.
// Crash records show that, in some crazy way, it is possible for the current URL to be blank at this point.
// Probably some sort of race condition when Privacy Browser is being resumed.
// Display the keyboard.
inputMethodManager.showSoftInput(urlEditText, 0);
- // Hide the WebView, which causes the default background color to be displayed according to the theme.
- nestedScrollWebView.setVisibility(View.INVISIBLE);
-
// Apply the domain settings. This clears any settings from the previous domain.
applyDomainSettings(nestedScrollWebView, "", true, false);
+
+ // Only populate the title text view if the tab has been fully created.
+ if (tab != null) {
+ // Get the custom view from the tab.
+ View tabView = tab.getCustomView();
+
+ // Remove the incorrect warning below that the current tab view might be null.
+ assert tabView != null;
+
+ // Get the title text view from the tab.
+ TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
+
+ // Set the title as the tab text.
+ tabTitleTextView.setText(R.string.new_tab);
+ }
} else { // The WebView has loaded a webpage.
// Display the final URL. Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly.
urlEditText.setText(currentUrl);
// Apply text highlighting to the URL.
highlightUrlText();
- }
- }
-
- // Get the current tab.
- TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
- // Only populate the title text view if the tab has been fully created.
- if (tab != null) {
- // Get the custom view from the tab.
- View tabView = tab.getCustomView();
+ // Only populate the title text view if the tab has been fully created.
+ if (tab != null) {
+ // Get the custom view from the tab.
+ View tabView = tab.getCustomView();
- // Remove the incorrect warning below that the current tab view might be null.
- assert tabView != null;
+ // Remove the incorrect warning below that the current tab view might be null.
+ assert tabView != null;
- // Get the title text view from the tab.
- TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
+ // Get the title text view from the tab.
+ TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
- // Set the title as the tab text. Sometimes `onReceivedTitle()` is not called, especially when navigating history.
- tabTitleTextView.setText(nestedScrollWebView.getTitle());
+ // Set the title as the tab text. Sometimes `onReceivedTitle()` is not called, especially when navigating history.
+ tabTitleTextView.setText(nestedScrollWebView.getTitle());
+ }
+ }
}
}
}
}
}
}
+ } else { // This is not the first tab.
+ // Apply the domain settings.
+ applyDomainSettings(nestedScrollWebView, url, false, false);
+
+ // Load the URL.
+ nestedScrollWebView.loadUrl(url, customHeaders);
+
+ // Set the focus and display the keyboard if the URL is blank.
+ if (url.equals("")) {
+ // Request focus for the URL text box.
+ urlEditText.requestFocus();
+
+ // Create a display keyboard handler.
+ Handler displayKeyboardHandler = new Handler();
+
+ // Create a display keyboard runnable.
+ Runnable displayKeyboardRunnable = () -> {
+ // Display the keyboard.
+ inputMethodManager.showSoftInput(urlEditText, 0);
+ };
+
+ // Display the keyboard after 100 milliseconds, which leaves enough time for the tab to transition.
+ displayKeyboardHandler.postDelayed(displayKeyboardRunnable, 100);
+ }
}
}
}
\ No newline at end of file