X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FMainWebViewActivity.java;h=fbab8bfb37d977d82e659f28b4ab3fe100d60669;hp=0e303a2e19524285a14105592994415334df4163;hb=b692dfc0bae740891c806bb2ea1c18041bf6b66f;hpb=f7abc45b8d69f3f3804397428005b4ffd9a10f99 diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index 0e303a2e..fbab8bfb 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -279,6 +279,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; @@ -465,11 +470,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get handles for the navigation menu and the back and forward menu items. The menu is zero-based. Menu navigationMenu = navigationView.getMenu(); - MenuItem navigationCloseTabMenuItem = navigationMenu.getItem(0); - MenuItem navigationBackMenuItem = navigationMenu.getItem(3); - MenuItem navigationForwardMenuItem = navigationMenu.getItem(4); - MenuItem navigationHistoryMenuItem = navigationMenu.getItem(5); - MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6); + MenuItem navigationBackMenuItem = navigationMenu.getItem(2); + MenuItem navigationForwardMenuItem = navigationMenu.getItem(3); + MenuItem navigationHistoryMenuItem = navigationMenu.getItem(4); + MenuItem navigationRequestsMenuItem = navigationMenu.getItem(5); // Initialize the web view pager adapter. webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager()); @@ -500,7 +504,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); @@ -512,8 +516,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); } } @@ -766,7 +770,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Update the navigation menu items. - navigationCloseTabMenuItem.setEnabled(tabLayout.getTabCount() > 1); navigationBackMenuItem.setEnabled(currentWebView.canGoBack()); navigationForwardMenuItem.setEnabled(currentWebView.canGoForward()); navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward())); @@ -818,15 +821,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; @@ -849,8 +843,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); @@ -864,9 +867,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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(); } } @@ -1144,6 +1144,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); @@ -1180,6 +1181,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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()); @@ -1898,19 +1900,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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. @@ -1979,6 +1981,29 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook }, 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); @@ -2004,20 +2029,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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; @@ -2026,15 +2037,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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; @@ -2076,183 +2078,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Run the commands that correspond to the selected menu item. switch (menuItemId) { - case R.id.close_tab: - // Close the current tab. - closeCurrentTab(); - break; - case R.id.clear_and_exit: - // Close the bookmarks cursor and database. - bookmarksCursor.close(); - bookmarksDatabaseHelper.close(); - - // Get the status of the clear everything preference. - boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true); - - // Get a handle for the runtime. - Runtime runtime = Runtime.getRuntime(); - - // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, - // which links to `/data/data/com.stoutner.privacybrowser.standard`. - String privateDataDirectoryString = getApplicationInfo().dataDir; - - // Clear cookies. - if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) { - // The command to remove cookies changed slightly in API 21. - if (Build.VERSION.SDK_INT >= 21) { - CookieManager.getInstance().removeAllCookies(null); - } else { - CookieManager.getInstance().removeAllCookie(); - } - - // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run. - try { - // Two commands must be used because `Runtime.exec()` does not like `*`. - Process deleteCookiesProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies"); - Process deleteCookiesJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal"); - - // Wait until the processes have finished. - deleteCookiesProcess.waitFor(); - deleteCookiesJournalProcess.waitFor(); - } catch (Exception exception) { - // Do nothing if an error is thrown. - } - } - - // Clear DOM storage. - if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) { - // Ask `WebStorage` to clear the DOM storage. - WebStorage webStorage = WebStorage.getInstance(); - webStorage.deleteAllData(); - - // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run. - try { - // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly. - Process deleteLocalStorageProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"}); - - // Multiple commands must be used because `Runtime.exec()` does not like `*`. - Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB"); - Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager"); - Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal"); - Process deleteDatabaseProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases"); - - // Wait until the processes have finished. - deleteLocalStorageProcess.waitFor(); - deleteIndexProcess.waitFor(); - deleteQuotaManagerProcess.waitFor(); - deleteQuotaManagerJournalProcess.waitFor(); - deleteDatabaseProcess.waitFor(); - } catch (Exception exception) { - // Do nothing if an error is thrown. - } - } - - // Clear form data if the API < 26. - if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) { - WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this); - webViewDatabase.clearFormData(); - - // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run. - try { - // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly. - Process deleteWebDataProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"}); - Process deleteWebDataJournalProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"}); - - // Wait until the processes have finished. - deleteWebDataProcess.waitFor(); - deleteWebDataJournalProcess.waitFor(); - } catch (Exception exception) { - // Do nothing if an error is thrown. - } - } - - // Clear the cache. - if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) { - // Clear the cache from each WebView. - for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - // Only clear the cache if the WebView exists. - if (fragmentView != null) { - // Get the nested scroll WebView from the tab fragment. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - - // Clear the cache for this WebView. - nestedScrollWebView.clearCache(true); - } - } - - // Manually delete the cache directories. - try { - // Delete the main cache directory. - Process deleteCacheProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/cache"); - - // Delete the secondary `Service Worker` cache directory. - // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly. - Process deleteServiceWorkerProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"}); - - // Wait until the processes have finished. - deleteCacheProcess.waitFor(); - deleteServiceWorkerProcess.waitFor(); - } catch (Exception exception) { - // Do nothing if an error is thrown. - } - } - - // Wipe out each WebView. - for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - // Only wipe out the WebView if it exists. - if (fragmentView != null) { - // Get the nested scroll WebView from the tab fragment. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - - // Clear SSL certificate preferences for this WebView. - nestedScrollWebView.clearSslPreferences(); - - // Clear the back/forward history for this WebView. - nestedScrollWebView.clearHistory(); - - // Destroy the internal state of `mainWebView`. - nestedScrollWebView.destroy(); - } - } - - // Clear the custom headers. - customHeaders.clear(); - - // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache. - // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`. - if (clearEverything) { - try { - // Delete the folder. - Process deleteAppWebviewProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview"); - - // Wait until the process has finished. - deleteAppWebviewProcess.waitFor(); - } catch (Exception exception) { - // Do nothing if an error is thrown. - } - } - - // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list. - if (Build.VERSION.SDK_INT >= 21) { - finishAndRemoveTask(); - } else { - finish(); - } - - // Remove the terminated program from RAM. The status code is `0`. - System.exit(0); + // Clear and exit Privacy Browser. + clearAndExit(); break; case R.id.home: @@ -2490,11 +2318,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; }); @@ -2607,11 +2432,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; }); @@ -3184,6 +3006,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } private void loadUrl(String url) { + // Sanitize the URL. + url = sanitizeUrl(url); + // Apply the domain settings. applyDomainSettings(currentWebView, url, true, false); @@ -3238,6 +3063,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); @@ -3388,8 +3216,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); @@ -3495,8 +3323,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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(); @@ -3538,6 +3367,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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)); @@ -3580,17 +3410,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; @@ -3628,59 +3458,55 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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. - // - 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); @@ -3688,7 +3514,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); @@ -3696,7 +3522,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); @@ -3704,17 +3530,32 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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; } @@ -3772,35 +3613,34 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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. - // - 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); @@ -4144,7 +3984,54 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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 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); @@ -4165,7 +4052,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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) { + // Get a handle for the tab layout. + TabLayout tabLayout = findViewById(R.id.tablayout); + + // Run the command according to the number of tabs. + if (tabLayout.getTabCount() > 1) { // There is more than one tab open. + // Close the current tab. + closeCurrentTab(); + } else { // There is only one tab open. + clearAndExit(); + } } private void closeCurrentTab() { @@ -4189,6 +4089,183 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook appBarLayout.setExpanded(true); } + private void clearAndExit() { + // Get a handle for the shared preferences. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + + // Close the bookmarks cursor and database. + bookmarksCursor.close(); + bookmarksDatabaseHelper.close(); + + // Get the status of the clear everything preference. + boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true); + + // Get a handle for the runtime. + Runtime runtime = Runtime.getRuntime(); + + // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, + // which links to `/data/data/com.stoutner.privacybrowser.standard`. + String privateDataDirectoryString = getApplicationInfo().dataDir; + + // Clear cookies. + if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) { + // The command to remove cookies changed slightly in API 21. + if (Build.VERSION.SDK_INT >= 21) { + CookieManager.getInstance().removeAllCookies(null); + } else { + CookieManager.getInstance().removeAllCookie(); + } + + // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run. + try { + // Two commands must be used because `Runtime.exec()` does not like `*`. + Process deleteCookiesProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies"); + Process deleteCookiesJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal"); + + // Wait until the processes have finished. + deleteCookiesProcess.waitFor(); + deleteCookiesJournalProcess.waitFor(); + } catch (Exception exception) { + // Do nothing if an error is thrown. + } + } + + // Clear DOM storage. + if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) { + // Ask `WebStorage` to clear the DOM storage. + WebStorage webStorage = WebStorage.getInstance(); + webStorage.deleteAllData(); + + // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run. + try { + // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly. + Process deleteLocalStorageProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"}); + + // Multiple commands must be used because `Runtime.exec()` does not like `*`. + Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB"); + Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager"); + Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal"); + Process deleteDatabaseProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases"); + + // Wait until the processes have finished. + deleteLocalStorageProcess.waitFor(); + deleteIndexProcess.waitFor(); + deleteQuotaManagerProcess.waitFor(); + deleteQuotaManagerJournalProcess.waitFor(); + deleteDatabaseProcess.waitFor(); + } catch (Exception exception) { + // Do nothing if an error is thrown. + } + } + + // Clear form data if the API < 26. + if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) { + WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this); + webViewDatabase.clearFormData(); + + // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run. + try { + // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly. + Process deleteWebDataProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"}); + Process deleteWebDataJournalProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"}); + + // Wait until the processes have finished. + deleteWebDataProcess.waitFor(); + deleteWebDataJournalProcess.waitFor(); + } catch (Exception exception) { + // Do nothing if an error is thrown. + } + } + + // Clear the cache. + if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) { + // Clear the cache from each WebView. + for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { + // Get the WebView tab fragment. + WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); + + // Get the fragment view. + View fragmentView = webViewTabFragment.getView(); + + // Only clear the cache if the WebView exists. + if (fragmentView != null) { + // Get the nested scroll WebView from the tab fragment. + NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); + + // Clear the cache for this WebView. + nestedScrollWebView.clearCache(true); + } + } + + // Manually delete the cache directories. + try { + // Delete the main cache directory. + Process deleteCacheProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/cache"); + + // Delete the secondary `Service Worker` cache directory. + // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly. + Process deleteServiceWorkerProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"}); + + // Wait until the processes have finished. + deleteCacheProcess.waitFor(); + deleteServiceWorkerProcess.waitFor(); + } catch (Exception exception) { + // Do nothing if an error is thrown. + } + } + + // Wipe out each WebView. + for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { + // Get the WebView tab fragment. + WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); + + // Get the fragment view. + View fragmentView = webViewTabFragment.getView(); + + // Only wipe out the WebView if it exists. + if (fragmentView != null) { + // Get the nested scroll WebView from the tab fragment. + NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); + + // Clear SSL certificate preferences for this WebView. + nestedScrollWebView.clearSslPreferences(); + + // Clear the back/forward history for this WebView. + nestedScrollWebView.clearHistory(); + + // Destroy the internal state of `mainWebView`. + nestedScrollWebView.destroy(); + } + } + + // Clear the custom headers. + customHeaders.clear(); + + // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache. + // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`. + if (clearEverything) { + try { + // Delete the folder. + Process deleteAppWebviewProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview"); + + // Wait until the process has finished. + deleteAppWebviewProcess.waitFor(); + } catch (Exception exception) { + // Do nothing if an error is thrown. + } + } + + // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list. + if (Build.VERSION.SDK_INT >= 21) { + finishAndRemoveTask(); + } else { + finish(); + } + + // Remove the terminated program from RAM. The status code is `0`. + System.exit(0); + } + private void setCurrentWebView(int pageNumber) { // Get a handle for the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); @@ -4201,7 +4278,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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. @@ -4211,19 +4288,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); @@ -4287,11 +4359,23 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } 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); @@ -4340,9 +4424,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); @@ -4444,14 +4525,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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. // 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); @@ -4464,7 +4545,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; @@ -4481,7 +4562,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } 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)); @@ -4623,8 +4704,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); + } } } @@ -4760,6 +4847,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); @@ -4833,6 +4923,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); @@ -4840,7 +4933,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook Menu navigationMenu = navigationView.getMenu(); // Get a handle for the navigation requests menu item. The menu is 0 based. - MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6); + MenuItem navigationRequestsMenuItem = navigationMenu.getItem(5); // Create an empty web resource response to be used if the resource request is blocked. WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes())); @@ -5220,7 +5313,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // 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); @@ -5245,12 +5338,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @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(); @@ -5317,6 +5404,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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. @@ -5332,36 +5422,45 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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()); + } + } } } } @@ -5458,6 +5557,30 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } } + } 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