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=1904673b5ff6543a0617772e62d03cc9ac828eba;hp=895f20ad7547b9120066d49b61d00820263877f0;hb=c45e682700d98c6feb0a766e6b3b6665cc80d10e;hpb=c59144b04912acea0dd664cb677b4fc19effe9c2 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 895f20ad..1904673b 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -24,6 +24,7 @@ package com.stoutner.privacybrowser.activities; import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; +import android.app.Dialog; import android.app.DownloadManager; import android.app.SearchManager; import android.content.ActivityNotFoundException; @@ -49,6 +50,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; +import android.os.Message; import android.preference.PreferenceManager; import android.print.PrintDocumentAdapter; import android.print.PrintManager; @@ -71,6 +73,7 @@ import android.webkit.CookieManager; import android.webkit.HttpAuthHandler; import android.webkit.SslErrorHandler; import android.webkit.ValueCallback; +import android.webkit.WebBackForwardList; import android.webkit.WebChromeClient; import android.webkit.WebResourceResponse; import android.webkit.WebSettings; @@ -116,6 +119,7 @@ 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.asynctasks.SaveWebpageImage; import com.stoutner.privacybrowser.dialogs.AdConsentDialog; import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog; import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog; @@ -125,17 +129,25 @@ import com.stoutner.privacybrowser.dialogs.DownloadImageDialog; import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog; import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog; import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog; +import com.stoutner.privacybrowser.dialogs.FontSizeDialog; import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog; +import com.stoutner.privacybrowser.dialogs.OpenDialog; +import com.stoutner.privacybrowser.dialogs.ProxyNotInstalledDialog; +import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog; +import com.stoutner.privacybrowser.dialogs.SaveWebpageDialog; import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog; +import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog; import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog; import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog; +import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog; import com.stoutner.privacybrowser.fragments.WebViewTabFragment; import com.stoutner.privacybrowser.helpers.AdHelper; import com.stoutner.privacybrowser.helpers.BlocklistHelper; import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; import com.stoutner.privacybrowser.helpers.CheckPinnedMismatchHelper; import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper; -import com.stoutner.privacybrowser.helpers.OrbotProxyHelper; +import com.stoutner.privacybrowser.helpers.FileNameHelper; +import com.stoutner.privacybrowser.helpers.ProxyHelper; import com.stoutner.privacybrowser.views.NestedScrollWebView; import java.io.ByteArrayInputStream; @@ -153,15 +165,18 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; // 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, PopulateBlocklists.PopulateBlocklistsListener, WebViewTabFragment.NewTabListener { + EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener, + PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, StoragePermissionDialog.StoragePermissionDialogListener, + UrlHistoryDialog.NavigateHistoryListener, 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; + // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxy()`. + public static String orbotStatus = "unknown"; // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`. It is also used in `onCreate()`, `onResume()`, and `addTab()`. public static WebViewPagerAdapter webViewPagerAdapter; @@ -185,16 +200,28 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2; public final static int DOMAINS_CUSTOM_USER_AGENT = 13; + // Start activity for result request codes. The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`. + public static final int BROWSE_OPEN_REQUEST_CODE = 0; + public static final int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 1; + private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 2; + // The permission result request codes are used in `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, `onRequestPermissionResult()`, `onSaveWebpage()`, + // `onCloseStoragePermissionDialog()`, and `initializeWebView()`. + private final int PERMISSION_DOWNLOAD_FILE_REQUEST_CODE = 0; + private final int PERMISSION_DOWNLOAD_IMAGE_REQUEST_CODE = 1; + private final int PERMISSION_OPEN_REQUEST_CODE = 2; + private final int PERMISSION_SAVE_WEBPAGE_ARCHIVE_REQUEST_CODE = 3; + private final int PERMISSION_SAVE_WEBPAGE_IMAGE_REQUEST_CODE = 4; + // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, - // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxyThroughOrbot()`, and `applyDomainSettings()`. + // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`. private NestedScrollWebView currentWebView; // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`. private final Map customHeaders = new HashMap<>(); - // The search URL is set in `applyProxyThroughOrbot()` and used in `onCreate()`, `onNewIntent()`, `loadURLFromTextBox()`, and `initializeWebView()`. + // The search URL is set in `applyAppSettings()` and used in `onNewIntent()`, `loadUrlFromTextBox()`, `initializeApp()`, and `initializeWebView()`. private String searchURL; // The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()`, `updatePrivacyIcons()`, and `initializeWebView()`. @@ -205,13 +232,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private ArrayList> easyPrivacy; private ArrayList> fanboysAnnoyanceList; private ArrayList> fanboysSocialList; + private ArrayList> ultraList; private ArrayList> ultraPrivacy; // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`. private String webViewDefaultUserAgent; - // `proxyThroughOrbot` is used in `onRestart()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`. - private boolean proxyThroughOrbot; + // The proxy mode is used in `onRestart()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxy()`. + // It will be updated in `applyAppSettings()`, but it needs to be initialized here or the first run of `onPrepareOptionsMenu()` crashes. + private String proxyMode = ProxyHelper.NONE; // The incognito mode is set in `applyAppSettings()` and used in `initializeWebView()`. private boolean incognitoModeEnabled; @@ -241,8 +270,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`. private BroadcastReceiver orbotStatusBroadcastReceiver; - // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`. - private boolean waitingForOrbot; + // The waiting for proxy boolean is used in `onResume()`, `initializeApp()` and `applyProxy()`. + private boolean waitingForProxy = false; // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`. private ActionBarDrawerToggle actionBarDrawerToggle; @@ -293,14 +322,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`. private String downloadImageUrl; - // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, `onRequestPermissionResult()`, and `initializeWebView()`. - private final int DOWNLOAD_FILE_REQUEST_CODE = 1; - private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2; + // The file path strings are used in `onSaveWebpageImage()` and `onRequestPermissionResult()` + private String openFilePath; + private String saveWebpageFilePath; @Override // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`. @SuppressLint("ClickableViewAccessibility") protected void onCreate(Bundle savedInstanceState) { + if (Build.VERSION.SDK_INT >= 21) { + WebView.enableSlowWholeDocumentDraw(); + } + // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default. PreferenceManager.setDefaultValues(this, R.xml.preferences, false); @@ -328,6 +361,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the content view. setContentView(R.layout.main_framelayout); + // Get handles for the views that need to be modified. DrawerLayout drawerLayout = findViewById(R.id.drawerlayout); Toolbar toolbar = findViewById(R.id.toolbar); @@ -367,63 +401,72 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override protected void onNewIntent(Intent intent) { - // Get the information from the intent. - String intentAction = intent.getAction(); - Uri intentUriData = intent.getData(); + // Run the default commands. + super.onNewIntent(intent); - // Determine if this is a web search. - boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)); + // Replace the intent that started the app with this one. + setIntent(intent); - // Only process the URI if it contains data or it is a web search. If the user pressed the desktop icon after the app was already running the URI will be null. - if (intentUriData != null || isWebSearch) { - // Get the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + // Process the intent here if Privacy Browser is fully initialized. If the process has been killed by the system while sitting in the background, this will be handled in `initializeWebView()`. + if (ultraPrivacy != null) { + // Get the information from the intent. + String intentAction = intent.getAction(); + Uri intentUriData = intent.getData(); - // Create a URL string. - String url; + // Determine if this is a web search. + boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)); - // If the intent action is a web search, perform the search. - if (isWebSearch) { - // Create an encoded URL string. - String encodedUrlString; + // Only process the URI if it contains data or it is a web search. If the user pressed the desktop icon after the app was already running the URI will be null. + if (intentUriData != null || isWebSearch) { + // Get the shared preferences. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - // Sanitize the search input and convert it to a search. - try { - encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8"); - } catch (UnsupportedEncodingException exception) { - encodedUrlString = ""; - } + // Create a URL string. + String url; - // Add the base search URL. - url = searchURL + encodedUrlString; - } else { // The intent should contain a URL. - // Set the intent data as the URL. - url = intentUriData.toString(); - } + // If the intent action is a web search, perform the search. + if (isWebSearch) { + // Create an encoded URL string. + String encodedUrlString; - // 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; + // Sanitize the search input and convert it to a search. + try { + encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8"); + } catch (UnsupportedEncodingException exception) { + encodedUrlString = ""; + } - // Add a new tab. - addNewTab(url); - } else { // Load the URL in the current tab. - // Make it so. - loadUrl(url); - } + // Add the base search URL. + url = searchURL + encodedUrlString; + } else { // The intent should contain a URL. + // Set the intent data as the URL. + url = intentUriData.toString(); + } - // Get a handle for the drawer layout. - DrawerLayout drawerLayout = findViewById(R.id.drawerlayout); + // 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; - // Close the navigation drawer if it is open. - if (drawerLayout.isDrawerVisible(GravityCompat.START)) { - drawerLayout.closeDrawer(GravityCompat.START); - } + // Add a new tab. + addNewTab(url, true); + } else { // Load the URL in the current tab. + // Make it so. + loadUrl(currentWebView, url); + } - // Close the bookmarks drawer if it is open. - if (drawerLayout.isDrawerVisible(GravityCompat.END)) { - drawerLayout.closeDrawer(GravityCompat.END); + // Get a handle for the drawer layout. + DrawerLayout drawerLayout = findViewById(R.id.drawerlayout); + + // Close the navigation drawer if it is open. + if (drawerLayout.isDrawerVisible(GravityCompat.START)) { + drawerLayout.closeDrawer(GravityCompat.START); + } + + // Close the bookmarks drawer if it is open. + if (drawerLayout.isDrawerVisible(GravityCompat.END)) { + drawerLayout.closeDrawer(GravityCompat.END); + } } } } @@ -433,18 +476,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Run the default commands. super.onRestart(); - // Make sure Orbot is running if Privacy Browser is proxying through Orbot. - if (proxyThroughOrbot) { - // Request Orbot to start. If Orbot is already running no hard will be caused by this request. - Intent orbotIntent = new Intent("org.torproject.android.intent.action.START"); - - // Send the intent to the Orbot package. - orbotIntent.setPackage("org.torproject.android"); - - // Make it so. - sendBroadcast(orbotIntent); - } - // Apply the app settings if returning from the Settings activity. if (reapplyAppSettingsOnRestart) { // Reset the reapply app settings on restart tracker. @@ -486,7 +517,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Load the URL on restart (used when loading a bookmark). if (loadUrlOnRestart) { // Load the specified URL. - loadUrl(urlToLoadOnRestart); + loadUrl(currentWebView, urlToLoadOnRestart); // Reset the load on restart tracker. loadUrlOnRestart = false; @@ -517,6 +548,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Run the default commands. super.onResume(); + // Resume any WebViews. for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { // Get the WebView tab fragment. WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); @@ -537,16 +569,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Display a message to the user if waiting for Orbot. - if (waitingForOrbot && !orbotStatus.equals("ON")) { - // Disable the wide view port so that the waiting for Orbot text is displayed correctly. - currentWebView.getSettings().setUseWideViewPort(false); - - // Load a waiting page. `null` specifies no encoding, which defaults to ASCII. - currentWebView.loadData("

" + getString(R.string.waiting_for_orbot) + "

", "text/html", null); + // Reapply the proxy settings if the system is using a proxy. This redisplays the appropriate alert dialog. + if (!proxyMode.equals(ProxyHelper.NONE)) { + applyProxy(false); } - if (displayingFullScreenVideo || inFullScreenBrowsingMode) { + // Reapply any system UI flags and the ad in the free flavor. + if (displayingFullScreenVideo || inFullScreenBrowsingMode) { // The system is displaying a website or a video in full screen mode. // Get a handle for the root frame layouts. FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout); @@ -561,7 +590,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook */ rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } else if (BuildConfig.FLAVOR.contentEquals("free")) { // Resume the adView for the free flavor. + } else if (BuildConfig.FLAVOR.contentEquals("free")) { // The system in not in full screen mode. // Resume the ad. AdHelper.resumeAd(findViewById(R.id.adview)); } @@ -601,7 +630,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onDestroy() { - // Unregister the Orbot status broadcast receiver. + // Unregister the orbot status broadcast receiver. this.unregisterReceiver(orbotStatusBroadcastReceiver); // Close the bookmarks cursor and database. @@ -699,14 +728,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook MenuItem easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy); MenuItem fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list); MenuItem fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list); + MenuItem ultraListMenuItem = menu.findItem(R.id.ultralist); MenuItem ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy); MenuItem blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests); + MenuItem proxyMenuItem = menu.findItem(R.id.proxy); + MenuItem userAgentMenuItem = menu.findItem(R.id.user_agent); 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); // Get a handle for the cookie manager. CookieManager cookieManager = CookieManager.getInstance(); @@ -733,11 +764,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the status of the menu item checkboxes. domStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled()); saveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData()); // Form data can be removed once the minimum API >= 26. - easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST)); - easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY)); + easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)); + easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)); fanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); fanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)); - ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY)); + ultraListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)); + ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)); blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh()); wideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort()); @@ -746,11 +778,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Initialize the display names for the blocklists with the number of blocked requests. blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_LIST) + " - " + getString(R.string.easylist)); - easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY) + " - " + getString(R.string.easyprivacy)); + easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist)); + easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy)); fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list)); fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list)); - ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY) + " - " + getString(R.string.ultraprivacy)); + ultraListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist)); + ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy)); blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests)); // Only modify third-party cookies if the API >= 21. @@ -766,9 +799,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook domStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled()); } - // Set the status of the menu item checkboxes. + // Set the checked status of the first party cookies menu item. firstPartyCookiesMenuItem.setChecked(cookieManager.acceptCookie()); - proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot); // Enable Clear Cookies if there are any. clearCookiesMenuItem.setEnabled(cookieManager.hasCookies()); @@ -780,14 +812,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/"); int localStorageDirectoryNumberOfFiles = 0; if (localStorageDirectory.exists()) { - localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length; + // `Objects.requireNonNull` removes a lint warning that `localStorageDirectory.list` might produce a null pointed exception if it is dereferenced. + localStorageDirectoryNumberOfFiles = Objects.requireNonNull(localStorageDirectory.list()).length; } // Get a count of the number of files in the IndexedDB directory. File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB"); int indexedDBDirectoryNumberOfFiles = 0; if (indexedDBDirectory.exists()) { - indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length; + // `Objects.requireNonNull` removes a lint warning that `indexedDBDirectory.list` might produce a null pointed exception if it is dereferenced. + indexedDBDirectoryNumberOfFiles = Objects.requireNonNull(indexedDBDirectory.list()).length; } // Enable Clear DOM Storage if there is any. @@ -808,90 +842,124 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked. fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListMenuItem.isChecked()); + // Set the proxy title and check the applied proxy. + switch (proxyMode) { + case ProxyHelper.NONE: + // Set the proxy title. + proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_none)); + + // Check the proxy None radio button. + menu.findItem(R.id.proxy_none).setChecked(true); + break; + + case ProxyHelper.TOR: + // Set the proxy title. + proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_tor)); + + // Check the proxy Tor radio button. + menu.findItem(R.id.proxy_tor).setChecked(true); + break; + + case ProxyHelper.I2P: + // Set the proxy title. + proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_i2p)); + + // Check the proxy I2P radio button. + menu.findItem(R.id.proxy_i2p).setChecked(true); + break; + + case ProxyHelper.CUSTOM: + // Set the proxy title. + proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_custom)); + + // Check the proxy Custom radio button. + menu.findItem(R.id.proxy_custom).setChecked(true); + break; + } + // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants. if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser. + // Update the user agent menu item title. + userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_privacy_browser)); + + // Select the Privacy Browser radio box. menu.findItem(R.id.user_agent_privacy_browser).setChecked(true); } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default. + // Update the user agent menu item title. + userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_webview_default)); + + // Select the WebView Default radio box. menu.findItem(R.id.user_agent_webview_default).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android. + // Update the user agent menu item title. + userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_firefox_on_android)); + + // Select the Firefox on Android radio box. menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android. + // Update the user agent menu item title. + userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_chrome_on_android)); + + // Select the Chrome on Android radio box. menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS. + // Update the user agent menu item title. + userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_safari_on_ios)); + + // Select the Safari on iOS radio box. menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux. + // Update the user agent menu item title. + userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_firefox_on_linux)); + + // Select the Firefox on Linux radio box. menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux. + // Update the user agent menu item title. + userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_chromium_on_linux)); + + // Select the Chromium on Linux radio box. menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows. + // Update the user agent menu item title. + userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_firefox_on_windows)); + + // Select the Firefox on Windows radio box. menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows. + // Update the user agent menu item title. + userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_chrome_on_windows)); + + // Select the Chrome on Windows radio box. menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows. + // Update the user agent menu item title. + userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_edge_on_windows)); + + // Select the Edge on Windows radio box. menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows. + // Update the user agent menu item title. + userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_internet_explorer_on_windows)); + + // Select the Internet on Windows radio box. menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true); } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS. + // Update the user agent menu item title. + userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_safari_on_macos)); + + // Select the Safari on macOS radio box. menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true); } else { // Custom user agent. - menu.findItem(R.id.user_agent_custom).setChecked(true); - } - - // Instantiate the font size title and the selected font size menu item. - String fontSizeTitle; - MenuItem selectedFontSizeMenuItem; - - // Prepare the font size title and current size menu item. - switch (fontSize) { - case 25: - fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent); - break; - - case 50: - fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent); - break; - - case 75: - fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent); - break; - - case 100: - fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent); - break; - - case 125: - fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent); - break; + // Update the user agent menu item title. + userAgentMenuItem.setTitle(getString(R.string.user_agent) + " - " + getString(R.string.user_agent_custom)); - case 150: - fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent); - break; - - case 175: - fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent); - break; - - case 200: - fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent); - break; - - default: - fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent); - break; + // Select the Custom radio box. + menu.findItem(R.id.user_agent_custom).setChecked(true); } - // Set the font size title and select the current size menu item. - fontSizeMenuItem.setTitle(fontSizeTitle); - selectedFontSizeMenuItem.setChecked(true); + // Set the font size title. + fontSizeMenuItem.setTitle(getString(R.string.font_size) + " - " + fontSize + "%"); // Run all the other default commands. super.onPrepareOptionsMenu(menu); @@ -904,23 +972,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. @SuppressLint("SetJavaScriptEnabled") public boolean onOptionsItemSelected(MenuItem menuItem) { - // Reenter full screen browsing mode if it was interrupted by the options menu. - if (inFullScreenBrowsingMode) { - // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen. - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - - FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout); - - /* Hide the system bars. - * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. - * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar. - * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen. - * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown. - */ - rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } - // Get the selected menu item ID. int menuItemId = menuItem.getItemId(); @@ -950,6 +1001,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.add_or_edit_domain: @@ -1056,6 +1109,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Make it so. startActivity(domainsIntent); } + + // Consume the event. return true; case R.id.toggle_first_party_cookies: @@ -1082,6 +1137,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.toggle_third_party_cookies: @@ -1102,6 +1159,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); } // Else do nothing because SDK < 21. + + // Consume the event. return true; case R.id.toggle_dom_storage: @@ -1123,6 +1182,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; // Form data can be removed once the minimum API >= 26. @@ -1145,6 +1206,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.clear_cookies: @@ -1167,6 +1230,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }) .show(); + + // Consume the event. return true; case R.id.clear_dom_storage: @@ -1222,6 +1287,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }) .show(); + + // Consume the event. return true; // Form data can be remove once the minimum API >= 26. @@ -1242,28 +1309,34 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }) .show(); + + // Consume the event. return true; case R.id.easylist: // Toggle the EasyList status. - currentWebView.enableBlocklist(NestedScrollWebView.EASY_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST)); + currentWebView.enableBlocklist(NestedScrollWebView.EASYLIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)); // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST)); + menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)); // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.easyprivacy: // Toggle the EasyPrivacy status. - currentWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY)); + currentWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)); // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY)); + menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)); // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.fanboys_annoyance_list: @@ -1279,6 +1352,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.fanboys_social_blocking_list: @@ -1290,17 +1365,34 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. + return true; + + case R.id.ultralist: + // Toggle the UltraList status. + currentWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)); + + // Update the menu checkbox. + menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)); + + // Reload the current WebView. + currentWebView.reload(); + + // Consume the event. return true; case R.id.ultraprivacy: // Toggle the UltraPrivacy status. - currentWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY)); + currentWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)); // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY)); + menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)); // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.block_all_third_party_requests: @@ -1312,6 +1404,48 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. + return true; + + case R.id.proxy_none: + // Update the proxy mode. + proxyMode = ProxyHelper.NONE; + + // Apply the proxy mode. + applyProxy(true); + + // Consume the event. + return true; + + case R.id.proxy_tor: + // Update the proxy mode. + proxyMode = ProxyHelper.TOR; + + // Apply the proxy mode. + applyProxy(true); + + // Consume the event. + return true; + + case R.id.proxy_i2p: + // Update the proxy mode. + proxyMode = ProxyHelper.I2P; + + // Apply the proxy mode. + applyProxy(true); + + // Consume the event. + return true; + + case R.id.proxy_custom: + // Update the proxy mode. + proxyMode = ProxyHelper.CUSTOM; + + // Apply the proxy mode. + applyProxy(true); + + // Consume the event. return true; case R.id.user_agent_privacy_browser: @@ -1320,6 +1454,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.user_agent_webview_default: @@ -1328,6 +1464,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.user_agent_firefox_on_android: @@ -1336,6 +1474,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.user_agent_chrome_on_android: @@ -1344,6 +1484,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.user_agent_safari_on_ios: @@ -1352,6 +1494,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.user_agent_firefox_on_linux: @@ -1360,6 +1504,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.user_agent_chromium_on_linux: @@ -1368,6 +1514,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.user_agent_firefox_on_windows: @@ -1376,6 +1524,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.user_agent_chrome_on_windows: @@ -1384,6 +1534,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.user_agent_edge_on_windows: @@ -1392,6 +1544,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.user_agent_internet_explorer_on_windows: @@ -1400,6 +1554,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.user_agent_safari_on_macos: @@ -1408,6 +1564,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); + + // Consume the event. return true; case R.id.user_agent_custom: @@ -1416,38 +1574,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the current WebView. currentWebView.reload(); - return true; - - case R.id.font_size_twenty_five_percent: - currentWebView.getSettings().setTextZoom(25); - return true; - - case R.id.font_size_fifty_percent: - currentWebView.getSettings().setTextZoom(50); - return true; - case R.id.font_size_seventy_five_percent: - currentWebView.getSettings().setTextZoom(75); - return true; - - case R.id.font_size_one_hundred_percent: - currentWebView.getSettings().setTextZoom(100); - return true; - - case R.id.font_size_one_hundred_twenty_five_percent: - currentWebView.getSettings().setTextZoom(125); + // Consume the event. return true; - case R.id.font_size_one_hundred_fifty_percent: - currentWebView.getSettings().setTextZoom(150); - return true; + case R.id.font_size: + // Instantiate the font size dialog. + DialogFragment fontSizeDialogFragment = FontSizeDialog.displayDialog(currentWebView.getSettings().getTextZoom()); - case R.id.font_size_one_hundred_seventy_five_percent: - currentWebView.getSettings().setTextZoom(175); - return true; + // Show the font size dialog. + fontSizeDialogFragment.show(getSupportFragmentManager(), getString(R.string.font_size)); - case R.id.font_size_two_hundred_percent: - currentWebView.getSettings().setTextZoom(200); + // Consume the event. return true; case R.id.swipe_to_refresh: @@ -1465,11 +1603,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Disable the swipe refresh layout. swipeRefreshLayout.setEnabled(false); } + + // Consume the event. return true; case R.id.wide_viewport: // Toggle the viewport. currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort()); + + // Consume the event. return true; case R.id.display_images: @@ -1483,6 +1625,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Enable loading of images. Missing images will be loaded without the need for a reload. currentWebView.getSettings().setLoadsImagesAutomatically(true); } + + // Consume the event. return true; case R.id.night_mode: @@ -1506,6 +1650,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the website. currentWebView.reload(); + + // Consume the event. return true; case R.id.find_on_page: @@ -1538,6 +1684,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Display the keyboard. `0` sets no input flags. inputMethodManager.showSoftInput(findOnPageEditText, 0); }, 200); + + // Consume the event. return true; case R.id.print: @@ -1552,6 +1700,28 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Print the document. printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null); + + // Consume the event. + return true; + + case R.id.save_as_archive: + // Instantiate the save webpage archive dialog. + DialogFragment saveWebpageArchiveDialogFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_ARCHIVE); + + // Show the save webpage archive dialog. + saveWebpageArchiveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_webpage)); + + // Consume the event. + return true; + + case R.id.save_as_image: + // Instantiate the save webpage image dialog. + DialogFragment saveWebpageImageDialogFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_IMAGE); + + // Show the save webpage image dialog. + saveWebpageImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_webpage)); + + // Consume the event. return true; case R.id.add_to_homescreen: @@ -1561,6 +1731,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show the create home screen shortcut dialog. createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut)); + + // Consume the event. return true; case R.id.view_source: @@ -1573,6 +1745,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Make it so. startActivity(viewSourceIntent); + + // Consume the event. return true; case R.id.share_url: @@ -1586,22 +1760,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Make it so. startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url))); + + // Consume the event. return true; case R.id.open_with_app: + // Open the URL with an outside app. openWithApp(currentWebView.getUrl()); + + // Consume the event. return true; case R.id.open_with_browser: + // Open the URL with an outside browser. openWithBrowser(currentWebView.getUrl()); - return true; - - case R.id.proxy_through_orbot: - // Toggle the proxy through Orbot variable. - proxyThroughOrbot = !proxyThroughOrbot; - // Apply the proxy through Orbot settings. - applyProxyThroughOrbot(true); + // Consume the event. return true; case R.id.refresh: @@ -1612,12 +1786,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Stop the loading of the WebView. currentWebView.stopLoading(); } + + // Consume the event. return true; case R.id.ad_consent: - // Display the ad consent dialog. + // Instantiate the ad consent dialog. DialogFragment adConsentDialogFragment = new AdConsentDialog(); + + // Display the ad consent dialog. adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent)); + + // Consume the event. return true; default: @@ -1643,23 +1823,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook break; case R.id.home: - // Select the homepage based on the proxy through Orbot status. - if (proxyThroughOrbot) { - // Load the Tor homepage. - loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value))); - } else { - // Load the normal homepage. - loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value))); - } + // Load the homepage. + loadUrl(currentWebView, sharedPreferences.getString("homepage", getString(R.string.homepage_default_value))); break; case R.id.back: if (currentWebView.canGoBack()) { - // Reset the current domain name so that navigation works if third-party requests are blocked. - currentWebView.resetCurrentDomainName(); + // Get the current web back forward list. + WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList(); - // Set navigating history so that the domain settings are applied when the new URL is loaded. - currentWebView.setNavigatingHistory(true); + // Get the previous entry URL. + String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl(); + + // Apply the domain settings. + applyDomainSettings(currentWebView, previousUrl, false, false); // Load the previous website in the history. currentWebView.goBack(); @@ -1668,11 +1845,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case R.id.forward: if (currentWebView.canGoForward()) { - // Reset the current domain name so that navigation works if third-party requests are blocked. - currentWebView.resetCurrentDomainName(); + // Get the current web back forward list. + WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList(); + + // Get the next entry URL. + String nextUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() + 1).getUrl(); - // Set navigating history so that the domain settings are applied when the new URL is loaded. - currentWebView.setNavigatingHistory(true); + // Apply the domain settings. + applyDomainSettings(currentWebView, nextUrl, false, false); // Load the next website in the history. currentWebView.goForward(); @@ -1687,6 +1867,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history)); break; + case R.id.open: + // Instantiate the open file dialog. + DialogFragment openDialogFragment = new OpenDialog(); + + // Show the open file dialog. + openDialogFragment.show(getSupportFragmentManager(), getString(R.string.open)); + break; + case R.id.requests: // Populate the resource requests. RequestsActivity.resourceRequests = currentWebView.getResourceRequests(); @@ -1793,7 +1981,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Create a string array for the blocklist versions. String[] blocklistVersions = new String[] {easyList.get(0).get(0)[0], easyPrivacy.get(0).get(0)[0], fanboysAnnoyanceList.get(0).get(0)[0], fanboysSocialList.get(0).get(0)[0], - ultraPrivacy.get(0).get(0)[0]}; + ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]}; // Add the blocklist versions to the intent. aboutIntent.putExtra("blocklist_versions", blocklistVersions); @@ -1821,7 +2009,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @Override - public void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(@NonNull Configuration newConfig) { // Run the default commands. super.onConfigurationChanged(newConfig); @@ -1853,7 +2041,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Store the hit test result. final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult(); - // Create the URL strings. + // Define the URL strings. final String imageUrl; final String linkUrl; @@ -1877,21 +2065,36 @@ 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) -> { - // Load the link URL in a new tab. - addNewTab(linkUrl); - return false; + // Load the link URL in a new tab and move to it. + addNewTab(linkUrl, true); + + // Consume the event. + return true; + }); + + // Add an Open in Background entry. + menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> { + // Load the link URL in a new tab but do not move to it. + addNewTab(linkUrl, false); + + // Consume the event. + return true; }); // Add an Open with App entry. menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> { openWithApp(linkUrl); - return false; - }); + + // Consume the event. + return true; + }); // Add an Open with Browser entry. menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> { openWithBrowser(linkUrl); - return false; + + // Consume the event. + return true; }); // Add a Copy URL entry. @@ -1901,7 +2104,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the `ClipData` as the clipboard's primary clip. clipboardManager.setPrimaryClip(srcAnchorTypeClipData); - return false; + + // Consume the event. + return true; }); // Add a Download URL entry. @@ -1926,7 +2131,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location)); } else { // Show the permission request directly. // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`. - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE); + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_DOWNLOAD_FILE_REQUEST_CODE); } } else { // The storage permission has already been granted. // Get a handle for the download file alert dialog. @@ -1936,7 +2141,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)); } } - return false; + + // Consume the event. + return true; }); // Add a Cancel entry, which by default closes the context menu. @@ -1963,7 +2170,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Make it so. startActivity(emailIntent); - return false; + + // Consume the event. + return true; }); // Add a Copy Email Address entry. @@ -1973,16 +2182,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the `ClipData` as the clipboard's primary clip. clipboardManager.setPrimaryClip(srcEmailTypeClipData); - return false; + + // Consume the event. + return true; }); // Add a `Cancel` entry, which by default closes the `ContextMenu`. menu.add(R.string.cancel); break; - // `IMAGE_TYPE` is an image. `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link. Privacy Browser processes them the same. + // `IMAGE_TYPE` is an image. case WebView.HitTestResult.IMAGE_TYPE: - case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: // Get the image URL. imageUrl = hitTestResult.getExtra(); @@ -1990,19 +2200,24 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook menu.setHeaderTitle(imageUrl); // Add an Open in New Tab entry. - menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> { - // Load the image URL in a new tab. - addNewTab(imageUrl); - return false; + menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> { + // Load the image in a new tab. + addNewTab(imageUrl, true); + + // Consume the event. + return true; }); // Add a View Image entry. menu.add(R.string.view_image).setOnMenuItemClickListener(item -> { - loadUrl(imageUrl); - return false; + // Load the image in the current tab. + loadUrl(currentWebView, imageUrl); + + // Consume the event. + return true; }); - // Add a `Download Image` entry. + // Add a Download Image entry. menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> { // Check if the download should be processed by an external app. if (sharedPreferences.getBoolean("download_with_external_app", false)) { // Download with an external app. @@ -2021,8 +2236,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show the download location permission alert dialog. The permission will be requested when the dialog is closed. downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location)); } else { // Show the permission request directly. - // Request the permission. The download dialog will be launched by `onRequestPermissionResult(). - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE); + // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`. + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_DOWNLOAD_IMAGE_REQUEST_CODE); } } else { // The storage permission has already been granted. // Get a handle for the download image alert dialog. @@ -2032,32 +2247,167 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)); } } - return false; + + // Consume the event. + return true; }); - // Add a `Copy URL` entry. - menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> { - // Save the image URL in a `ClipData`. - ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl); + // Add a Copy URL entry. + menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> { + // Save the image URL in a clip data. + ClipData imageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl); - // Set the `ClipData` as the clipboard's primary clip. - clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData); - return false; + // Set the clip data as the clipboard's primary clip. + clipboardManager.setPrimaryClip(imageTypeClipData); + + // Consume the event. + return true; }); // Add an Open with App entry. menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> { + // Open the image URL with an external app. openWithApp(imageUrl); - return false; + + // Consume the event. + return true; }); // Add an Open with Browser entry. menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> { + // Open the image URL with an external browser. openWithBrowser(imageUrl); - return false; + + // Consume the event. + return true; }); - // Add a `Cancel` entry, which by default closes the `ContextMenu`. + // Add a Cancel entry, which by default closes the context menu. + menu.add(R.string.cancel); + break; + + // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link. + case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: + // Get the image URL. + imageUrl = hitTestResult.getExtra(); + + // Instantiate a handler. + Handler handler = new Handler(); + + // Get a message from the handler. + Message message = handler.obtainMessage(); + + // Request the image details from the last touched node be returned in the message. + currentWebView.requestFocusNodeHref(message); + + // Get the link URL from the message data. + linkUrl = message.getData().getString("url"); + + // Set the link URL as the title of the context menu. + menu.setHeaderTitle(linkUrl); + + // Add an Open in New Tab entry. + menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> { + // Load the link URL in a new tab and move to it. + addNewTab(linkUrl, true); + + // Consume the event. + return true; + }); + + // Add an Open in Background entry. + menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> { + // Lod the link URL in a new tab but do not move to it. + addNewTab(linkUrl, false); + + // Consume the event. + return true; + }); + + // Add an Open Image in New Tab entry. + menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> { + // Load the image in a new tab and move to it. + addNewTab(imageUrl, true); + + // Consume the event. + return true; + }); + + // Add a View Image entry. + menu.add(R.string.view_image).setOnMenuItemClickListener((MenuItem item) -> { + // View the image in the current tab. + loadUrl(currentWebView, imageUrl); + + // Consume the event. + return true; + }); + + // Add a Download Image entry. + menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> { + // Check if the download should be processed by an external app. + if (sharedPreferences.getBoolean("download_with_external_app", false)) { // Download with an external app. + openUrlWithExternalApp(imageUrl); + } else { // Download with Android's download manager. + // Check to see if the storage permission has already been granted. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested. + // Store the image URL for use by `onRequestPermissionResult()`. + downloadImageUrl = imageUrl; + + // Show a dialog if the user has previously denied the permission. + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. + // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE. + DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE); + + // Show the download location permission alert dialog. The permission will be requested when the dialog is closed. + downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location)); + } else { // Show the permission request directly. + // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`. + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_DOWNLOAD_IMAGE_REQUEST_CODE); + } + } else { // The storage permission has already been granted. + // Get a handle for the download image alert dialog. + DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); + + // Show the download image alert dialog. + downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)); + } + } + + // Consume the event. + return true; + }); + + // Add a Copy URL entry. + menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> { + // Save the link URL in a clip data. + ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl); + + // Set the clip data as the clipboard's primary clip. + clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData); + + // Consume the event. + return true; + }); + + // Add an Open with App entry. + menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> { + // Open the link URL with an external app. + openWithApp(linkUrl); + + // Consume the event. + return true; + }); + + // Add an Open with Browser entry. + menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> { + // Open the link URL with an external browser. + openWithBrowser(linkUrl); + + // Consume the event. + return true; + }); + + // Add a cancel entry, which by default closes the context menu. menu.add(R.string.cancel); break; } @@ -2068,9 +2418,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get a handle for the bookmarks list view. ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview); + // Get the dialog. + Dialog dialog = dialogFragment.getDialog(); + + // Remove the incorrect lint warning below that the dialog might be null. + assert dialog != null; + // Get the views from the dialog fragment. - EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext); - EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext); + EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext); + EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext); // Extract the strings from the edit texts. String bookmarkNameString = createBookmarkNameEditText.getText().toString(); @@ -2106,10 +2462,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get a handle for the bookmarks list view. ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview); + // Get the dialog. + Dialog dialog = dialogFragment.getDialog(); + + // Remove the incorrect lint warning below that the dialog might be null. + assert dialog != null; + // Get handles for the views in the dialog fragment. - EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext); - RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton); - ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon); + EditText createFolderNameEditText = dialog.findViewById(R.id.create_folder_name_edittext); + RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.create_folder_default_icon_radiobutton); + ImageView folderIconImageView = dialog.findViewById(R.id.create_folder_default_icon); // Get new folder name string. String folderNameString = createFolderNameEditText.getText().toString(); @@ -2162,10 +2524,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId, Bitmap favoriteIconBitmap) { - // Get handles for the views from `dialogFragment`. - EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext); - EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext); - RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton); + // Get the dialog. + Dialog dialog = dialogFragment.getDialog(); + + // Remove the incorrect lint warning below that the dialog might be null. + assert dialog != null; + + // Get handles for the views from the dialog. + EditText editBookmarkNameEditText = dialog.findViewById(R.id.edit_bookmark_name_edittext); + EditText editBookmarkUrlEditText = dialog.findViewById(R.id.edit_bookmark_url_edittext); + RadioButton currentBookmarkIconRadioButton = dialog.findViewById(R.id.edit_bookmark_current_icon_radiobutton); // Store the bookmark strings. String bookmarkNameString = editBookmarkNameEditText.getText().toString(); @@ -2197,11 +2565,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) { + // Get the dialog. + Dialog dialog = dialogFragment.getDialog(); + + // Remove the incorrect lint warning below that the dialog might be null. + assert dialog != null; + // Get handles for the views from `dialogFragment`. - EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext); - RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton); - RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton); - ImageView defaultFolderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview); + EditText editFolderNameEditText = dialog.findViewById(R.id.edit_folder_name_edittext); + RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_current_icon_radiobutton); + RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_default_icon_radiobutton); + ImageView defaultFolderIconImageView = dialog.findViewById(R.id.edit_folder_default_icon_imageview); // Get the new folder name. String newFolderNameString = editFolderNameEditText.getText().toString(); @@ -2282,52 +2656,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook switch (downloadType) { case DownloadLocationPermissionDialog.DOWNLOAD_FILE: // Request the WRITE_EXTERNAL_STORAGE permission with a file request code. - ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE); + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_DOWNLOAD_FILE_REQUEST_CODE); break; case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE: // Request the WRITE_EXTERNAL_STORAGE permission with an image request code. - ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE); - break; - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - // Get a handle for the fragment manager. - FragmentManager fragmentManager = getSupportFragmentManager(); - - switch (requestCode) { - case DOWNLOAD_FILE_REQUEST_CODE: - // Show the download file alert dialog. When the dialog closes, the correct command will be used based on the permission status. - DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength); - - // On API 23, displaying the fragment must be delayed or the app will crash. - if (Build.VERSION.SDK_INT == 23) { - new Handler().postDelayed(() -> downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)), 500); - } else { - downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)); - } - - // Reset the download variables. - downloadUrl = ""; - downloadContentDisposition = ""; - downloadContentLength = 0; - break; - - case DOWNLOAD_IMAGE_REQUEST_CODE: - // Show the download image alert dialog. When the dialog closes, the correct command will be used based on the permission status. - DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl); - - // On API 23, displaying the fragment must be delayed or the app will crash. - if (Build.VERSION.SDK_INT == 23) { - new Handler().postDelayed(() -> downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)), 500); - } else { - downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)); - } - - // Reset the image URL variable. - downloadImageUrl = ""; + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_DOWNLOAD_IMAGE_REQUEST_CODE); break; } } @@ -2355,8 +2689,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook downloadRequest.addRequestHeader("Cookie", cookies); } + // Get the dialog. + Dialog dialog = dialogFragment.getDialog(); + + // Remove the incorrect lint warning below that the dialog might be null. + assert dialog != null; + // Get the file name from the dialog fragment. - EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name); + EditText downloadImageNameEditText = dialog.findViewById(R.id.download_image_name); String imageName = downloadImageNameEditText.getText().toString(); // Specify the download location. @@ -2410,8 +2750,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook downloadRequest.addRequestHeader("Cookie", cookies); } - // Get the file name from the dialog fragment. - EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name); + // Get the dialog. + Dialog dialog = dialogFragment.getDialog(); + + // Remove the incorrect lint warning below that the dialog might be null. + assert dialog != null; + + // Get the file name from the dialog. + EditText downloadFileNameEditText = dialog.findViewById(R.id.download_file_name); String fileName = downloadFileNameEditText.getText().toString(); // Specify the download location. @@ -2442,7 +2788,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Override `onBackPressed` to handle the navigation drawer and and the WebView. + // Override `onBackPressed` to handle the navigation drawer and and the WebViews. @Override public void onBackPressed() { // Get a handle for the drawer layout and the tab layout. @@ -2463,12 +2809,86 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Load the new folder. loadBookmarksFolder(); } + } else if (displayingFullScreenVideo) { // A full screen video is shown. + // Get a handle for the layouts. + FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout); + RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout); + FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout); + + // Re-enable the screen timeout. + fullScreenVideoFrameLayout.setKeepScreenOn(false); + + // Unset the full screen video flag. + displayingFullScreenVideo = false; + + // Remove all the views from the full screen video frame layout. + fullScreenVideoFrameLayout.removeAllViews(); + + // Hide the full screen video frame layout. + fullScreenVideoFrameLayout.setVisibility(View.GONE); + + // Enable the sliding drawers. + drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); + + // Show the main content relative layout. + mainContentRelativeLayout.setVisibility(View.VISIBLE); + + // Apply the appropriate full screen mode flags. + if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode. + // Hide the app bar if specified. + if (hideAppBar) { + // Get handles for the views. + LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout); + ActionBar actionBar = getSupportActionBar(); + + // Remove the incorrect lint warning below that the action bar might be null. + assert actionBar != null; + + // Hide the tab linear layout. + tabsLinearLayout.setVisibility(View.GONE); + + // Hide the action bar. + actionBar.hide(); + } + + // Hide the banner ad in the free flavor. + if (BuildConfig.FLAVOR.contentEquals("free")) { + AdHelper.hideAd(findViewById(R.id.adview)); + } + + // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen. + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + + /* Hide the system bars. + * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. + * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar. + * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen. + * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown. + */ + rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } else { // Switch to normal viewing mode. + // Remove the `SYSTEM_UI` flags from the root frame layout. + rootFrameLayout.setSystemUiVisibility(0); + + // Add the translucent status flag. + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + + // Reload the ad for the free flavor if not in full screen mode. + if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) { + // Reload the ad. + AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id)); + } } else if (currentWebView.canGoBack()) { // There is at least one item in the current WebView history. - // Reset the current domain name so that navigation works if third-party requests are blocked. - currentWebView.resetCurrentDomainName(); + // Get the current web back forward list. + WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList(); + + // Get the previous entry URL. + String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl(); - // Set navigating history so that the domain settings are applied when the new URL is loaded. - currentWebView.setNavigatingHistory(true); + // Apply the domain settings. + applyDomainSettings(currentWebView, previousUrl, false, false); // Go back. currentWebView.goBack(); @@ -2476,21 +2896,103 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Close the current tab. closeCurrentTab(); } else { // There isn't anything to do in Privacy Browser. - // Run the default commands. - super.onBackPressed(); + // Close Privacy Browser. `finishAndRemoveTask()` also removes Privacy Browser from the recent app list. + if (Build.VERSION.SDK_INT >= 21) { + finishAndRemoveTask(); + } else { + finish(); + } // Manually kill Privacy Browser. Otherwise, it is glitchy when restarted. System.exit(0); } } - // Process the results of an upload file chooser. Currently there is only one `startActivityForResult` in this activity, so the request code, used to differentiate them, is ignored. + // Process the results of a file browse. @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - // File uploads only work on API >= 21. - if (Build.VERSION.SDK_INT >= 21) { - // Pass the file to the WebView. - fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data)); + public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) { + // Run the default commands. + super.onActivityResult(requestCode, resultCode, returnedIntent); + + // Run the commands that correlate to the specified request code. + switch (requestCode) { + case BROWSE_FILE_UPLOAD_REQUEST_CODE: + // File uploads only work on API >= 21. + if (Build.VERSION.SDK_INT >= 21) { + // Pass the file to the WebView. + fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent)); + } + break; + + case BROWSE_SAVE_WEBPAGE_REQUEST_CODE: + // Don't do anything if the user pressed back from the file picker. + if (resultCode == Activity.RESULT_OK) { + // Get a handle for the save dialog fragment. + DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_webpage)); + + // Only update the file name if the dialog still exists. + if (saveWebpageDialogFragment != null) { + // Get a handle for the save webpage dialog. + Dialog saveWebpageDialog = saveWebpageDialogFragment.getDialog(); + + // Remove the incorrect lint warning below that the dialog might be null. + assert saveWebpageDialog != null; + + // Get a handle for the file name edit text. + EditText fileNameEditText = saveWebpageDialog.findViewById(R.id.file_name_edittext); + + // Instantiate the file name helper. + FileNameHelper fileNameHelper = new FileNameHelper(); + + // Get the file path if it isn't null. + if (returnedIntent.getData() != null) { + // Convert the file name URI to a file name path. + String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData()); + + // Set the file name path as the text of the file name edit text. + fileNameEditText.setText(fileNamePath); + + // Move the cursor to the end of the file name edit text. + fileNameEditText.setSelection(fileNamePath.length()); + } + } + } + break; + + case BROWSE_OPEN_REQUEST_CODE: + // Don't do anything if the user pressed back from the file picker. + if (resultCode == Activity.RESULT_OK) { + // Get a handle for the open dialog fragment. + DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open)); + + // Only update the file name if the dialog still exists. + if (openDialogFragment != null) { + // Get a handle for the open dialog. + Dialog openDialog = openDialogFragment.getDialog(); + + // Remove the incorrect lint warning below that the dialog might be null. + assert openDialog != null; + + // Get a handle for the file name edit text. + EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext); + + // Instantiate the file name helper. + FileNameHelper fileNameHelper = new FileNameHelper(); + + // Get the file path if it isn't null. + if (returnedIntent.getData() != null) { + // Convert the file name URI to a file name path. + String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData()); + + // Set the file name path as the text of the file name edit text. + fileNameEditText.setText(fileNamePath); + + // Move the cursor to the end of the file name edit text. + fileNameEditText.setSelection(fileNamePath.length()); + } + } + } + break; } } @@ -2561,58 +3063,310 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook urlEditText.clearFocus(); // Make it so. - loadUrl(url); + loadUrl(currentWebView, url); } - private void loadUrl(String url) { + private void loadUrl(NestedScrollWebView nestedScrollWebView, String url) { // Sanitize the URL. url = sanitizeUrl(url); // Apply the domain settings. - applyDomainSettings(currentWebView, url, true, false); + applyDomainSettings(nestedScrollWebView, url, true, false); // Load the URL. - currentWebView.loadUrl(url, customHeaders); + nestedScrollWebView.loadUrl(url, customHeaders); + } + + public void findPreviousOnPage(View view) { + // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards. + currentWebView.findNext(false); + } + + public void findNextOnPage(View view) { + // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards. + currentWebView.findNext(true); + } + + public void closeFindOnPage(View view) { + // Get a handle for the views. + Toolbar toolbar = findViewById(R.id.toolbar); + LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout); + EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext); + + // Delete the contents of `find_on_page_edittext`. + findOnPageEditText.setText(null); + + // Clear the highlighted phrases if the WebView is not null. + if (currentWebView != null) { + currentWebView.clearMatches(); + } + + // Hide the find on page linear layout. + findOnPageLinearLayout.setVisibility(View.GONE); + + // Show the toolbar. + toolbar.setVisibility(View.VISIBLE); + + // Get a handle for the input method manager. + InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + + // Remove the lint warning below that the input method manager might be null. + assert inputMethodManager != null; + + // Hide the keyboard. + inputMethodManager.hideSoftInputFromWindow(toolbar.getWindowToken(), 0); + } + + @Override + public void onApplyNewFontSize(DialogFragment dialogFragment) { + // Get the dialog. + Dialog dialog = dialogFragment.getDialog(); + + // Remove the incorrect lint warning below tha the dialog might be null. + assert dialog != null; + + // Get a handle for the font size edit text. + EditText fontSizeEditText = dialog.findViewById(R.id.font_size_edittext); + + // Initialize the new font size variable with the current font size. + int newFontSize = currentWebView.getSettings().getTextZoom(); + + // Get the font size from the edit text. + try { + newFontSize = Integer.valueOf(fontSizeEditText.getText().toString()); + } catch (Exception exception) { + // If the edit text does not contain a valid font size do nothing. + } + + // Apply the new font size. + currentWebView.getSettings().setTextZoom(newFontSize); + } + + @Override + public void onOpen(DialogFragment dialogFragment) { + // Get the dialog. + Dialog dialog = dialogFragment.getDialog(); + + // Remove the incorrect lint warning below that the dialog might be null. + assert dialog != null; + + // Get a handle for the file name edit text. + EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext); + + // Get the file path string. + openFilePath = fileNameEditText.getText().toString(); + + // Check to see if the storage permission is needed. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted. + // Open the file. + currentWebView.loadUrl("file://" + openFilePath); + } else { // The storage permission has not been granted. + // Get the external private directory file. + File externalPrivateDirectoryFile = getExternalFilesDir(null); + + // Remove the incorrect lint error below that the file might be null. + assert externalPrivateDirectoryFile != null; + + // Get the external private directory string. + String externalPrivateDirectory = externalPrivateDirectoryFile.toString(); + + // Check to see if the file path is in the external private directory. + if (openFilePath.startsWith(externalPrivateDirectory)) { // the file path is in the external private directory. + // Open the file. + currentWebView.loadUrl("file://" + openFilePath); + } else { // The file path is in a public directory. + // Check if the user has previously denied the storage permission. + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. + // Instantiate the storage permission alert dialog. + DialogFragment storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(StoragePermissionDialog.OPEN); + + // Show the storage permission alert dialog. The permission will be requested the the dialog is closed. + storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission)); + } else { // Show the permission request directly. + // Request the write external storage permission. The file will be opened when it finishes. + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_OPEN_REQUEST_CODE); + } + } + } + } + + @Override + public void onSaveWebpage(int saveType, DialogFragment dialogFragment) { + // Get the dialog. + Dialog dialog = dialogFragment.getDialog(); + + // Remove the incorrect lint warning below that the dialog might be null. + assert dialog != null; + + // Get a handle for the file name edit text. + EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext); + + // Get the file path string. + saveWebpageFilePath = fileNameEditText.getText().toString(); + + // Check to see if the storage permission is needed. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted. + //Save the webpage according to the save type. + switch (saveType) { + case StoragePermissionDialog.SAVE_ARCHIVE: + // Save the webpage archive. + currentWebView.saveWebArchive(saveWebpageFilePath); + break; + + case StoragePermissionDialog.SAVE_IMAGE: + // Save the webpage image. + new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath); + break; + } + } else { // The storage permission has not been granted. + // Get the external private directory file. + File externalPrivateDirectoryFile = getExternalFilesDir(null); + + // Remove the incorrect lint error below that the file might be null. + assert externalPrivateDirectoryFile != null; + + // Get the external private directory string. + String externalPrivateDirectory = externalPrivateDirectoryFile.toString(); + + // Check to see if the file path is in the external private directory. + if (saveWebpageFilePath.startsWith(externalPrivateDirectory)) { // The file path is in the external private directory. + // Save the webpage according to the save type. + switch (saveType) { + case StoragePermissionDialog.SAVE_ARCHIVE: + // Save the webpage archive. + currentWebView.saveWebArchive(saveWebpageFilePath); + break; + + case StoragePermissionDialog.SAVE_IMAGE: + // Save the webpage image. + new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath); + break; + } + } else { // The file path is in a public directory. + // Check if the user has previously denied the storage permission. + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. + // Instantiate the storage permission alert dialog. + DialogFragment storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(saveType); + + // Show the storage permission alert dialog. The permission will be requested when the dialog is closed. + storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission)); + } else { // Show the permission request directly. + switch (saveType) { + case StoragePermissionDialog.SAVE_ARCHIVE: + // Request the write external storage permission. The webpage archive will be saved when it finishes. + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_WEBPAGE_ARCHIVE_REQUEST_CODE); + break; + + case StoragePermissionDialog.SAVE_IMAGE: + // Request the write external storage permission. The webpage image will be saved when it finishes. + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_WEBPAGE_IMAGE_REQUEST_CODE); + break; + } + } + } + } } - public void findPreviousOnPage(View view) { - // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards. - currentWebView.findNext(false); - } + @Override + public void onCloseStoragePermissionDialog(int requestType) { + switch (requestType) { + case StoragePermissionDialog.OPEN: + // Request the write external storage permission. The file will be opened when it finishes. + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_OPEN_REQUEST_CODE); + break; + + case StoragePermissionDialog.SAVE_ARCHIVE: + // Request the write external storage permission. The webpage archive will be saved when it finishes. + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_WEBPAGE_ARCHIVE_REQUEST_CODE); + break; + + case StoragePermissionDialog.SAVE_IMAGE: + // Request the write external storage permission. The webpage image will be saved when it finishes. + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_WEBPAGE_IMAGE_REQUEST_CODE); + break; + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + // Get a handle for the fragment manager. + FragmentManager fragmentManager = getSupportFragmentManager(); + + switch (requestCode) { + case PERMISSION_DOWNLOAD_FILE_REQUEST_CODE: + // Show the download file alert dialog. When the dialog closes, the correct command will be used based on the permission status. + DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength); + + // On API 23, displaying the fragment must be delayed or the app will crash. + if (Build.VERSION.SDK_INT == 23) { + new Handler().postDelayed(() -> downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)), 500); + } else { + downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)); + } - public void findNextOnPage(View view) { - // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards. - currentWebView.findNext(true); - } + // Reset the download variables. + downloadUrl = ""; + downloadContentDisposition = ""; + downloadContentLength = 0; + break; - public void closeFindOnPage(View view) { - // Get a handle for the views. - Toolbar toolbar = findViewById(R.id.toolbar); - LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout); - EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext); + case PERMISSION_DOWNLOAD_IMAGE_REQUEST_CODE: + // Show the download image alert dialog. When the dialog closes, the correct command will be used based on the permission status. + DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl); - // Delete the contents of `find_on_page_edittext`. - findOnPageEditText.setText(null); + // On API 23, displaying the fragment must be delayed or the app will crash. + if (Build.VERSION.SDK_INT == 23) { + new Handler().postDelayed(() -> downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)), 500); + } else { + downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)); + } - // Clear the highlighted phrases if the WebView is not null. - if (currentWebView != null) { - currentWebView.clearMatches(); - } + // Reset the image URL variable. + downloadImageUrl = ""; + break; - // Hide the find on page linear layout. - findOnPageLinearLayout.setVisibility(View.GONE); + case PERMISSION_OPEN_REQUEST_CODE: + // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. + if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted. + // Load the file. + currentWebView.loadUrl("file://" + openFilePath); + } else { // The storage permission was not granted. + // Display an error snackbar. + Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); + } - // Show the toolbar. - toolbar.setVisibility(View.VISIBLE); + // Reset the open file path. + openFilePath = ""; + break; - // Get a handle for the input method manager. - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + case PERMISSION_SAVE_WEBPAGE_ARCHIVE_REQUEST_CODE: + // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. + if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted. + // Save the webpage archive. + currentWebView.saveWebArchive(saveWebpageFilePath); + } else { // The storage permission was not granted. + // Display an error snackbar. + Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); + } - // Remove the lint warning below that the input method manager might be null. - assert inputMethodManager != null; + // Reset the save webpage file path. + saveWebpageFilePath = ""; + break; - // Hide the keyboard. - inputMethodManager.hideSoftInputFromWindow(toolbar.getWindowToken(), 0); + case PERMISSION_SAVE_WEBPAGE_IMAGE_REQUEST_CODE: + // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. + if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted. + // Save the webpage image. + new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath); + } else { // The storage permission was not granted. + // Display an error snackbar. + Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); + } + + // Reset the save webpage file path. + saveWebpageFilePath = ""; + break; + } } private void applyAppSettings() { @@ -2630,11 +3384,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); + proxyMode = sharedPreferences.getString("proxy", getString(R.string.proxy_default_value)); fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false); hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true); scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true); + // Get the search string. + String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value)); + + // Set the search string. + if (searchString.equals("Custom URL")) { // A custom search string is used. + searchURL = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value)); + } else { // A custom search string is not used. + searchURL = searchString; + } + // Get handles for the views that need to be modified. FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout); AppBarLayout appBarLayout = findViewById(R.id.appbar_layout); @@ -2647,8 +3411,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Remove the incorrect lint warning below that the action bar might be null. assert actionBar != null; - // Apply the proxy through Orbot settings. - applyProxyThroughOrbot(false); + // Apply the proxy. + applyProxy(false); // Set Do Not Track status. if (doNotTrackEnabled) { @@ -2814,61 +3578,60 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }); - // Initialize the Orbot status and the waiting for Orbot trackers. - orbotStatus = "unknown"; - waitingForOrbot = false; - - // Create an Orbot status `BroadcastReceiver`. + // Create an Orbot status broadcast receiver. orbotStatusBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // Store the content of the status message in `orbotStatus`. orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS"); - // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected. - if (orbotStatus.equals("ON") && waitingForOrbot) { - // Reset the waiting for Orbot status. - waitingForOrbot = false; + // If Privacy Browser is waiting on the proxy, load the website now that Orbot is connected. + if ((orbotStatus != null) && orbotStatus.equals("ON") && waitingForProxy) { + // Reset the waiting for proxy status. + waitingForProxy = false; - // Get the intent that started the app. - Intent launchingIntent = getIntent(); + // Get a handle for the waiting for proxy dialog. + DialogFragment waitingForProxyDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.waiting_for_proxy_dialog)); - // Get the information from the intent. - String launchingIntentAction = launchingIntent.getAction(); - Uri launchingIntentUriData = launchingIntent.getData(); + // Dismiss the waiting for proxy dialog if it is displayed. + if (waitingForProxyDialogFragment != null) { + waitingForProxyDialogFragment.dismiss(); + } - // If the intent action is a web search, perform the search. - if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) { - // Create an encoded URL string. - String encodedUrlString; + // Reload existing URLs and load any URLs that are waiting for the proxy. + for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { + // Get the WebView tab fragment. + WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); - // Sanitize the search input and convert it to a search. - try { - encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8"); - } catch (UnsupportedEncodingException exception) { - encodedUrlString = ""; - } + // Get the fragment view. + View fragmentView = webViewTabFragment.getView(); - // Load the completed search URL. - loadUrl(searchURL + encodedUrlString); - } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL. - // Load the URL from the intent. - loadUrl(launchingIntentUriData.toString()); - } else { // The is no URL in the intent. - // Select the homepage based on the proxy through Orbot status. - if (proxyThroughOrbot) { - // Load the Tor homepage. - loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value))); - } else { - // Load the normal homepage. - loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value))); + // Only process the WebViews if they exist. + if (fragmentView != null) { + // Get the nested scroll WebView from the tab fragment. + NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); + + // Get the waiting for proxy URL string. + String waitingForProxyUrlString = nestedScrollWebView.getWaitingForProxyUrlString(); + + // Load the pending URL if it exists. + if (!waitingForProxyUrlString.isEmpty()) { // A URL is waiting to be loaded. + // Load the URL. + loadUrl(nestedScrollWebView, waitingForProxyUrlString); + + // Reset the waiting for proxy URL string. + nestedScrollWebView.resetWaitingForProxyUrlString(); + } else { // No URL is waiting to be loaded. + // Reload the existing URL. + nestedScrollWebView.reload(); + } } } } } }; - // Register `orbotStatusBroadcastReceiver` on `this` context. + // Register the Orbot status broadcast receiver on `this` context. this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS")); // Get handles for views that need to be modified. @@ -2886,12 +3649,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Listen for touches on the navigation menu. navigationView.setNavigationItemSelectedListener(this); - // Get handles for the navigation menu and the back and forward menu items. The menu is zero-based. + // Get handles for the navigation menu and the back and forward menu items. The menu is 0 based. Menu navigationMenu = navigationView.getMenu(); MenuItem navigationBackMenuItem = navigationMenu.getItem(2); MenuItem navigationForwardMenuItem = navigationMenu.getItem(3); MenuItem navigationHistoryMenuItem = navigationMenu.getItem(4); - MenuItem navigationRequestsMenuItem = navigationMenu.getItem(5); + MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6); // Update the web view pager every time a tab is modified. webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @@ -3083,10 +3846,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook bookmarksListView.setOnItemClickListener((parent, view, position, id) -> { // Convert the id from long to int to match the format of the bookmarks database. - int databaseID = (int) id; + int databaseId = (int) id; + + // Get the bookmark cursor for this ID. + Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId); - // Get the bookmark cursor for this ID and move it to the first row. - Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID); + // Move the bookmark cursor to the first row. bookmarkCursor.moveToFirst(); // Act upon the bookmark according to the type. @@ -3098,7 +3863,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook loadBookmarksFolder(); } else { // The selected bookmark is not a folder. // Load the bookmark URL. - loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL))); + loadUrl(currentWebView, bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL))); // Close the bookmarks drawer. drawerLayout.closeDrawer(GravityCompat.END); @@ -3119,13 +3884,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`. oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); - // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`. + // Instantiate the edit folder bookmark dialog. DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon()); + + // Show the edit folder bookmark dialog. editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder)); } else { - // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`. - DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon()); - editBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.edit_bookmark)); + // Get the bookmark cursor for this ID. + Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId); + + // Move the bookmark cursor to the first row. + bookmarkCursor.moveToFirst(); + + // Load the bookmark in a new tab but do not switch to the tab or close the drawer. + addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)), false); } // Consume the event. @@ -3144,7 +3916,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity); drawerHeaderPaddingBottom = (int) (8 * screenDensity); - // The drawer listener is used to update the navigation menu.` + // The drawer listener is used to update the navigation menu. drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() { @Override public void onDrawerSlide(@NonNull View drawerView, float slideOffset) { @@ -3209,12 +3981,39 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook bareWebView.destroy(); } + @Override + public void navigateHistory(String url, int steps) { + // Apply the domain settings. + applyDomainSettings(currentWebView, url, false, false); + + // Load the history entry. + currentWebView.goBackOrForward(steps); + } + + @Override + public void pinnedErrorGoBack() { + // Get the current web back forward list. + WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList(); + + // Get the previous entry URL. + String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl(); + + // Apply the domain settings. + applyDomainSettings(currentWebView, previousUrl, false, false); + + // Go back. + currentWebView.goBack(); + } + // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled. @SuppressLint("SetJavaScriptEnabled") private boolean applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetTab, boolean reloadWebsite) { // Store a copy of the current user agent to track changes for the return boolean. String initialUserAgent = nestedScrollWebView.getSettings().getUserAgentString(); + // Store the current URL. + nestedScrollWebView.setCurrentUrl(url); + // Parse the URL into a URI. Uri uri = Uri.parse(url); @@ -3361,15 +4160,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1); // Form data can be removed once the minimum API >= 26. boolean saveFormData = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_LIST, + nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST, currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, + nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1); nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1); nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY, + nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ULTRALIST)) == 1); + nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1); nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1); @@ -3462,10 +4262,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Apply the font size. - if (fontSize == 0) { // Apply the default font size. - nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); - } else { // Apply the specified font size. - nestedScrollWebView.getSettings().setTextZoom(fontSize); + try { // Try the specified font size to see if it is valid. + if (fontSize == 0) { // Apply the default font size. + // Try to set the font size from the value in the app settings. + nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); + } else { // Apply the font size from domain settings. + nestedScrollWebView.getSettings().setTextZoom(fontSize); + } + } catch (Exception exception) { // The specified font size is invalid + // Set the font size to be 100% + nestedScrollWebView.getSettings().setTextZoom(100); } // Set the user agent. @@ -3583,11 +4389,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook boolean defaultThirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false); nestedScrollWebView.getSettings().setDomStorageEnabled(sharedPreferences.getBoolean("dom_storage", false)); boolean saveFormData = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26. - nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_LIST, sharedPreferences.getBoolean("easylist", true)); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, sharedPreferences.getBoolean("easyprivacy", true)); + nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST, sharedPreferences.getBoolean("easylist", true)); + nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, sharedPreferences.getBoolean("easyprivacy", true)); nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean("fanboys_annoyance_list", true)); nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean("fanboys_social_blocking_list", true)); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY, sharedPreferences.getBoolean("ultraprivacy", true)); + nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, sharedPreferences.getBoolean("ultralist", true)); + nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, sharedPreferences.getBoolean("ultraprivacy", true)); nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false)); nestedScrollWebView.setNightMode(sharedPreferences.getBoolean("night_mode", false)); @@ -3600,9 +4407,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.getSettings().setJavaScriptEnabled(defaultJavaScriptEnabled); } - // Apply the default settings. + // Apply the default first-party cookie setting. cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies()); - nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); + + // Apply the default font size setting. + try { + // Try to set the font size from the value in the app settings. + nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); + } catch (Exception exception) { + // If the app settings value is invalid, set the font size to 100%. + nestedScrollWebView.getSettings().setTextZoom(100); + } // Apply the form data setting if the API < 26. if (Build.VERSION.SDK_INT < 26) { @@ -3674,92 +4489,129 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return !nestedScrollWebView.getSettings().getUserAgentString().equals(initialUserAgent); } - private void applyProxyThroughOrbot(boolean reloadWebsite) { + private void applyProxy(boolean reloadWebViews) { // Get a handle for the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - // Get the search and theme preferences. - String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value)); - String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value)); - String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value)); - String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value)); + // Get the theme preferences. boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false); // Get a handle for the app bar layout. AppBarLayout appBarLayout = findViewById(R.id.appbar_layout); - // Set the homepage, search, and proxy options. - if (proxyThroughOrbot) { // Set the Tor options. - // Set the search URL. - if (torSearchString.equals("Custom URL")) { // Get the custom URL string. - searchURL = torSearchCustomUrlString; - } else { // Use the string from the pre-built list. - searchURL = torSearchString; - } + // Set the proxy according to the mode. `this` refers to the current activity where an alert dialog might be displayed. + ProxyHelper.setProxy(getApplicationContext(), appBarLayout, proxyMode); - // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed. - OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118"); + // Reset the waiting for proxy tracker. + waitingForProxy = false; - // Set the app bar background to indicate proxying through Orbot is enabled. - if (darkTheme) { - appBarLayout.setBackgroundResource(R.color.dark_blue_30); - } else { - appBarLayout.setBackgroundResource(R.color.blue_50); - } + // Update the user interface and reload the WebViews if requested. + switch (proxyMode) { + case ProxyHelper.NONE: + // Set the default app bar layout background. + if (darkTheme) { + appBarLayout.setBackgroundResource(R.color.gray_900); + } else { + appBarLayout.setBackgroundResource(R.color.gray_100); + } + break; - // Check to see if Orbot is ready. - if (!orbotStatus.equals("ON")) { // Orbot is not ready. - // Set `waitingForOrbot`. - waitingForOrbot = true; + case ProxyHelper.TOR: + // Set the app bar background to indicate proxying through Orbot is enabled. + if (darkTheme) { + appBarLayout.setBackgroundResource(R.color.dark_blue_30); + } else { + appBarLayout.setBackgroundResource(R.color.blue_50); + } - // Disable the wide view port so that the waiting for Orbot text is displayed correctly. - currentWebView.getSettings().setUseWideViewPort(false); + // Check to see if Orbot is installed. + try { + // Get the package manager. + PackageManager packageManager = getPackageManager(); - // Load a waiting page. `null` specifies no encoding, which defaults to ASCII. - currentWebView.loadData("

" + getString(R.string.waiting_for_orbot) + "

", "text/html", null); - } else if (reloadWebsite) { // Orbot is ready and the website should be reloaded. - // Reload the website. - currentWebView.reload(); - } - } else { // Set the non-Tor options. - // Set the search URL. - if (searchString.equals("Custom URL")) { // Get the custom URL string. - searchURL = searchCustomUrlString; - } else { // Use the string from the pre-built list. - searchURL = searchString; - } + // Check to see if Orbot is in the list. This will throw an error and drop to the catch section if it isn't installed. + packageManager.getPackageInfo("org.torproject.android", 0); - // Reset the proxy to default. The host is `""` and the port is `"0"`. - OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0"); + // Check to see if the proxy is ready. + if (!orbotStatus.equals("ON")) { // Orbot is not ready. + // Set the waiting for proxy status. + waitingForProxy = true; - // Set the default app bar layout background. - if (darkTheme) { - appBarLayout.setBackgroundResource(R.color.gray_900); - } else { - appBarLayout.setBackgroundResource(R.color.gray_100); - } + // Show the waiting for proxy dialog if it isn't already displayed. + if (getSupportFragmentManager().findFragmentByTag(getString(R.string.waiting_for_proxy_dialog)) == null) { + // Get a handle for the waiting for proxy alert dialog. + DialogFragment waitingForProxyDialogFragment = new WaitingForProxyDialog(); + + // Display the waiting for proxy alert dialog. + waitingForProxyDialogFragment.show(getSupportFragmentManager(), getString(R.string.waiting_for_proxy_dialog)); + } + } + } catch (PackageManager.NameNotFoundException exception) { // Orbot is not installed. + // Show the Orbot not installed dialog if it is not already displayed. + if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) { + // Get a handle for the Orbot not installed alert dialog. + DialogFragment orbotNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode); + + // Display the Orbot not installed alert dialog. + orbotNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog)); + } + } + break; + + case ProxyHelper.I2P: + // Set the app bar background to indicate proxying through Orbot is enabled. + if (darkTheme) { + appBarLayout.setBackgroundResource(R.color.dark_blue_30); + } else { + appBarLayout.setBackgroundResource(R.color.blue_50); + } + + // Check to see if I2P is installed. + try { + // Get the package manager. + PackageManager packageManager = getPackageManager(); + + // Check to see if I2P is in the list. This will throw an error and drop to the catch section if it isn't installed. + packageManager.getPackageInfo("org.torproject.android", 0); + } catch (PackageManager.NameNotFoundException exception) { // I2P is not installed. + // Sow the I2P not installed dialog if it is not already displayed. + if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) { + // Get a handle for the waiting for proxy alert dialog. + DialogFragment i2pNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode); + + // Display the I2P not installed alert dialog. + i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog)); + } + } + break; - // Reset `waitingForOrbot. - waitingForOrbot = false; + case ProxyHelper.CUSTOM: + // Set the app bar background to indicate proxying through Orbot is enabled. + if (darkTheme) { + appBarLayout.setBackgroundResource(R.color.dark_blue_30); + } else { + appBarLayout.setBackgroundResource(R.color.blue_50); + } + break; + } - // Reload the WebViews if requested. - if (reloadWebsite) { - // Reload the WebViews. - for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); + // Reload the WebViews if requested and not waiting for the proxy. + if (reloadWebViews && !waitingForProxy) { + // Reload the WebViews. + for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { + // Get the WebView tab fragment. + WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); + // Get the fragment view. + View fragmentView = webViewTabFragment.getView(); - // Only reload the WebViews if they exist. - if (fragmentView != null) { - // Get the nested scroll WebView from the tab fragment. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); + // Only reload the WebViews if they exist. + if (fragmentView != null) { + // Get the nested scroll WebView from the tab fragment. + NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - // Reload the WebView. - nestedScrollWebView.reload(); - } + // Reload the WebView. + nestedScrollWebView.reload(); } } } @@ -3855,12 +4707,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook String urlString = urlEditText.getText().toString(); // Highlight the URL according to the protocol. - if (urlString.startsWith("file://")) { // This is a file URL. - // De-emphasize only the protocol. - urlEditText.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else if (urlString.startsWith("content://")) { - // De-emphasize only the protocol. - urlEditText.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + if (urlString.startsWith("file://") || urlString.startsWith("content://")) { // This is a file or content URL. + // De-emphasize everything before the file name. + urlEditText.getText().setSpan(initialGrayColorSpan, 0, urlString.lastIndexOf("/") + 1,Spanned.SPAN_INCLUSIVE_INCLUSIVE); } else { // This is a web URL. // Get the index of the `/` immediately after the domain name. int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2)); @@ -3976,8 +4825,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Flag the intent to open in a new task. openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - // Show the chooser. - startActivity(openWithAppIntent); + try { + // Show the chooser. + startActivity(openWithAppIntent); + } catch (ActivityNotFoundException exception) { + // Show a snackbar with the error. + Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show(); + } } private void openWithBrowser(String url) { @@ -3990,8 +4844,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Flag the intent to open in a new task. openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - // Show the chooser. - startActivity(openWithBrowserIntent); + try { + // Show the chooser. + startActivity(openWithBrowserIntent); + } catch (ActivityNotFoundException exception) { + // Show a snackbar with the error. + Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show(); + } } private String sanitizeUrl(String url) { @@ -4019,6 +4878,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook if (url.contains("&fbclid=")) { url = url.substring(0, url.indexOf("&fbclid=")); } + + // Remove `?fbadid=`. + if (url.contains("?fbadid=")) { + url = url.substring(0, url.indexOf("?fbadid=")); + } + + // Remove `&fbadid=`. + if (url.contains("&fbadid=")) { + url = url.substring(0, url.indexOf("&fbadid=")); + } } // Sanitize Twitter AMP redirects. @@ -4039,18 +4908,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook easyPrivacy = combinedBlocklists.get(1); fanboysAnnoyanceList = combinedBlocklists.get(2); fanboysSocialList = combinedBlocklists.get(3); - ultraPrivacy = combinedBlocklists.get(4); + ultraList = combinedBlocklists.get(4); + ultraPrivacy = combinedBlocklists.get(5); // Add the first tab. - addNewTab(""); + addNewTab("", true); } public void addTab(View view) { // Add a new tab with a blank URL. - addNewTab(""); + addNewTab("", true); } - private void addNewTab(String url) { + private void addNewTab(String url, boolean moveToTab) { // Sanitize the URL. url = sanitizeUrl(url); @@ -4074,7 +4944,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook newTab.setCustomView(R.layout.tab_custom_view); // Add the new WebView page. - webViewPagerAdapter.addPage(newTabNumber, webViewPager, url); + webViewPagerAdapter.addPage(newTabNumber, webViewPager, url, moveToTab); } public void closeTab(View view) { @@ -4577,7 +5447,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook downloadLocationPermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.download_location)); } else { // Show the permission request directly. // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`. - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE); + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_DOWNLOAD_FILE_REQUEST_CODE); } } else { // The storage permission has already been granted. // Get a handle for the download file alert dialog. @@ -4612,13 +5482,26 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }); - // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView. + // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView. Also reinforce full screen browsing mode. // Once the minimum API >= 23 this can be replaced with `nestedScrollWebView.setOnScrollChangeListener()`. nestedScrollWebView.getViewTreeObserver().addOnScrollChangedListener(() -> { if (nestedScrollWebView.getSwipeToRefresh()) { // Only enable swipe to refresh if the WebView is scrolled to the top. swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0); } + + // Reinforce the system UI visibility flags if in full screen browsing mode. + // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard. + if (inFullScreenBrowsingMode) { + /* Hide the system bars. + * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. + * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar. + * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen. + * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown. + */ + rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } }); // Set the web chrome client. @@ -4778,6 +5661,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show the full screen video frame layout. fullScreenVideoFrameLayout.setVisibility(View.VISIBLE); + + // Disable the screen timeout while the video is playing. YouTube does this automatically, but not all other videos do. + fullScreenVideoFrameLayout.setKeepScreenOn(true); } // Exit full screen video. @@ -4786,6 +5672,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get a handle for the full screen video frame layout. FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout); + // Re-enable the screen timeout. + fullScreenVideoFrameLayout.setKeepScreenOn(false); + // Unset the full screen video flag. displayingFullScreenVideo = false; @@ -4854,8 +5743,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Create an intent to open a chooser based ont the file chooser parameters. Intent fileChooserIntent = fileChooserParams.createIntent(); - // Open the file chooser. Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`. - startActivityForResult(fileChooserIntent, 0); + // Open the file chooser. + startActivityForResult(fileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE); } return true; } @@ -4942,6 +5831,25 @@ 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) { + // Check to see if the resource request is for the main URL. + if (url.equals(nestedScrollWebView.getCurrentUrl())) { + // `return null` loads the resource request, which should never be blocked if it is the main URL. + return null; + } + + // Wait until the blocklists have been populated. When Privacy Browser is being resumed after having the process killed in the background it will try to load the URLs immediately. + while (ultraPrivacy == null) { + // The wait must be synchronized, which only lets one thread run on it at a time, or `java.lang.IllegalMonitorStateException` is thrown. + synchronized (this) { + try { + // Check to see if the blocklists have been populated after 100 ms. + wait(100); + } catch (InterruptedException exception) { + // Do nothing. + } + } + } + // Sanitize the URL. url = sanitizeUrl(url); @@ -4952,7 +5860,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(5); + MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6); // 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())); @@ -5031,8 +5939,48 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return emptyWebResourceResponse; } + // Check UltraList if it is enabled. + if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)) { + // Check the URL against UltraList. + String[] ultraListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraList); + + // Process the UltraList results. + if (ultraListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched UltraLists's blacklist. + // Add the result to the resource requests. + nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]}); + + // Increment the blocked requests counters. + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRALIST); + + // Update the titles of the blocklist menu items if the WebView is currently displayed. + if (webViewDisplayed) { + // Updating the UI must be run from the UI thread. + activity.runOnUiThread(() -> { + // Update the menu item titles. + navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); + + // Update the options menu if it has been populated. + if (optionsMenu != null) { + optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); + optionsMenu.findItem(R.id.ultralist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist)); + } + }); + } + + // The resource request was blocked. Return an empty web resource response. + return emptyWebResourceResponse; + } else if (ultraListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched UltraList's whitelist. + // Add a whitelist entry to the resource requests array. + nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]}); + + // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource. + return null; + } + } + // Check UltraPrivacy if it is enabled. - if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY)) { + if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)) { // Check the URL against UltraPrivacy. String[] ultraPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy); @@ -5044,7 +5992,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Increment the blocked requests counters. nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRA_PRIVACY); + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRAPRIVACY); // Update the titles of the blocklist menu items if the WebView is currently displayed. if (webViewDisplayed) { @@ -5056,7 +6004,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Update the options menu if it has been populated. if (optionsMenu != null) { optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - optionsMenu.findItem(R.id.ultraprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY) + " - " + getString(R.string.ultraprivacy)); + optionsMenu.findItem(R.id.ultraprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy)); } }); } @@ -5074,7 +6022,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Check EasyList if it is enabled. - if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST)) { + if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)) { // Check the URL against EasyList. String[] easyListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList); @@ -5085,7 +6033,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Increment the blocked requests counters. nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_LIST); + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASYLIST); // Update the titles of the blocklist menu items if the WebView is currently displayed. if (webViewDisplayed) { @@ -5097,7 +6045,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Update the options menu if it has been populated. if (optionsMenu != null) { optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - optionsMenu.findItem(R.id.easylist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_LIST) + " - " + getString(R.string.easylist)); + optionsMenu.findItem(R.id.easylist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist)); } }); } @@ -5111,7 +6059,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Check EasyPrivacy if it is enabled. - if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY)) { + if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)) { // Check the URL against EasyPrivacy. String[] easyPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyPrivacy); @@ -5123,7 +6071,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Increment the blocked requests counters. nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_PRIVACY); + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASYPRIVACY); // Update the titles of the blocklist menu items if the WebView is currently displayed. if (webViewDisplayed) { @@ -5135,7 +6083,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Update the options menu if it has been populated. if (optionsMenu != null) { optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - optionsMenu.findItem(R.id.easyprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY) + " - " + getString(R.string.easyprivacy)); + optionsMenu.findItem(R.id.easyprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy)); } }); } @@ -5292,64 +6240,47 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Hide the keyboard. inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0); - // Check to see if Privacy Browser is waiting on Orbot. - if (!waitingForOrbot) { // Process the URL. - // Get the current page position. - int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); - - // Update the URL text bar if the page is currently selected. - if (tabLayout.getSelectedTabPosition() == currentPagePosition) { - // Clear the focus from the URL edit text. - urlEditText.clearFocus(); - - // Display the formatted URL text. - urlEditText.setText(url); - - // Apply text highlighting to `urlTextBox`. - highlightUrlText(); - } + // Get the current page position. + int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); - // Reset the list of host IP addresses. - nestedScrollWebView.clearCurrentIpAddresses(); + // Update the URL text bar if the page is currently selected. + if (tabLayout.getSelectedTabPosition() == currentPagePosition) { + // Clear the focus from the URL edit text. + urlEditText.clearFocus(); - // Get a URI for the current URL. - Uri currentUri = Uri.parse(url); + // Display the formatted URL text. + urlEditText.setText(url); - // Get the IP addresses for the host. - new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost()); + // Apply text highlighting to `urlTextBox`. + highlightUrlText(); + } - // Apply any custom domain settings if the URL was loaded by navigating history. - if (nestedScrollWebView.getNavigatingHistory()) { - // Reset navigating history. - nestedScrollWebView.setNavigatingHistory(false); + // Reset the list of host IP addresses. + nestedScrollWebView.clearCurrentIpAddresses(); - // Apply the domain settings. - boolean userAgentChanged = applyDomainSettings(nestedScrollWebView, url, true, false); + // Get a URI for the current URL. + Uri currentUri = Uri.parse(url); - // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded. - if (userAgentChanged) { - loadUrl(url); - } - } + // Get the IP addresses for the host. + new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost()); - // 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); + // 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); - // Set the title. - refreshMenuItem.setTitle(R.string.stop); + // Set the title. + refreshMenuItem.setTitle(R.string.stop); - // Get the app bar and theme preferences. - boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false); + // Get the app bar and theme preferences. + boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false); - // If the icon is displayed in the AppBar, set it according to the theme. - if (displayAdditionalAppBarIcons) { - if (darkTheme) { - refreshMenuItem.setIcon(R.drawable.close_dark); - } else { - refreshMenuItem.setIcon(R.drawable.close_light); - } + // If the icon is displayed in the AppBar, set it according to the theme. + if (displayAdditionalAppBarIcons) { + if (darkTheme) { + refreshMenuItem.setIcon(R.drawable.close_dark); + } else { + refreshMenuItem.setIcon(R.drawable.close_light); } } } @@ -5409,76 +6340,79 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Update the URL text box and apply domain settings if not waiting on Orbot. - if (!waitingForOrbot) { - // Get the current page position. - int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); + // Get the current page position. + int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); - // Check the current website information against any pinned domain information if the current IP addresses have been loaded. - if ((nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses()) && nestedScrollWebView.hasCurrentIpAddresses() && - !nestedScrollWebView.ignorePinnedDomainInformation()) { - CheckPinnedMismatchHelper.checkPinnedMismatch(getSupportFragmentManager(), nestedScrollWebView); - } + // Check the current website information against any pinned domain information if the current IP addresses have been loaded. + if ((nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses()) && nestedScrollWebView.hasCurrentIpAddresses() && + !nestedScrollWebView.ignorePinnedDomainInformation()) { + CheckPinnedMismatchHelper.checkPinnedMismatch(getSupportFragmentManager(), nestedScrollWebView); + } - // 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 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); + // 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. - if ((tabLayout.getSelectedTabPosition() == currentPagePosition) && !urlEditText.hasFocus() && (currentUrl != null)) { - // Check to see if the URL is `about:blank`. - if (currentUrl.equals("about:blank")) { // The WebView is blank. - // Display the hint in the URL edit text. - urlEditText.setText(""); + // 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. + if ((tabLayout.getSelectedTabPosition() == currentPagePosition) && !urlEditText.hasFocus() && (currentUrl != null)) { + // Check to see if the URL is `about:blank`. + if (currentUrl.equals("about:blank")) { // The WebView is blank. + // Display the hint in the URL edit text. + urlEditText.setText(""); - // Request focus for the URL text box. - urlEditText.requestFocus(); + // Request focus for the URL text box. + urlEditText.requestFocus(); - // Display the keyboard. - inputMethodManager.showSoftInput(urlEditText, 0); + // Display the keyboard. + inputMethodManager.showSoftInput(urlEditText, 0); - // Apply the domain settings. This clears any settings from the previous domain. - applyDomainSettings(nestedScrollWebView, "", true, false); + // 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(); + // 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. + tabTitleTextView.setText(R.string.new_tab); + } + } else { // The WebView has loaded a webpage. + // Update the URL edit text if it is not currently being edited. + if (!urlEditText.hasFocus()) { + // Sanitize the current URL. This removes unwanted URL elements that were added by redirects, so that they won't be included if the URL is shared. + String sanitizedUrl = sanitizeUrl(currentUrl); - // 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); + urlEditText.setText(sanitizedUrl); // Apply text highlighting to the URL. highlightUrlText(); + } - // 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()); } } } @@ -5539,42 +6473,43 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Apply the app settings from the shared preferences. applyAppSettings(); - // Load the website if not waiting for Orbot to connect. - if (!waitingForOrbot) { - // Get the intent that started the app. - Intent launchingIntent = getIntent(); + // Initialize the URL to load string. + String urlToLoadString; - // Get the information from the intent. - String launchingIntentAction = launchingIntent.getAction(); - Uri launchingIntentUriData = launchingIntent.getData(); + // Get the intent that started the app. + Intent launchingIntent = getIntent(); - // If the intent action is a web search, perform the search. - if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) { - // Create an encoded URL string. - String encodedUrlString; + // Get the information from the intent. + String launchingIntentAction = launchingIntent.getAction(); + Uri launchingIntentUriData = launchingIntent.getData(); - // Sanitize the search input and convert it to a search. - try { - encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8"); - } catch (UnsupportedEncodingException exception) { - encodedUrlString = ""; - } + // Parse the launching intent URL. + if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) { // The intent contains a search string. + // Create an encoded URL string. + String encodedUrlString; - // Load the completed search URL. - loadUrl(searchURL + encodedUrlString); - } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL. - // Load the URL from the intent. - loadUrl(launchingIntentUriData.toString()); - } else { // The is no URL in the intent. - // Select the homepage based on the proxy through Orbot status. - if (proxyThroughOrbot) { - // Load the Tor homepage. - loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value))); - } else { - // Load the normal homepage. - loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value))); - } + // Sanitize the search input and convert it to a search. + try { + encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8"); + } catch (UnsupportedEncodingException exception) { + encodedUrlString = ""; } + + // Store the web search as the URL to load. + urlToLoadString = searchURL + encodedUrlString; + } else if (launchingIntentUriData != null){ // The intent contains a URL. + // Store the URL. + urlToLoadString = launchingIntentUriData.toString(); + } else { // The is no URL in the intent. + // Store the homepage to be loaded. + urlToLoadString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)); + } + + // Load the website if not waiting for the proxy. + if (waitingForProxy) { // Store the URL to be loaded in the Nested Scroll WebView. + nestedScrollWebView.setWaitingForProxyUrlString(urlToLoadString); + } else { // Load the URL. + loadUrl(nestedScrollWebView, urlToLoadString); } } else { // This is not the first tab. // Apply the domain settings.