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=7cbe286dcd81fdf529caed9acc2b8bc7547ea8f7;hp=c130a2671dd550e4b62943a0ac66d1029b5818f4;hb=4ffcfed7fe199cfbb3c8f27a2af38566dbc3e5ea;hpb=0488649384ddea89d768c1fc1cc5fb71f8af6528 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 c130a267..7cbe286d 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -31,6 +31,7 @@ import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ClipData; import android.content.ClipboardManager; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -46,6 +47,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.net.http.SslCertificate; import android.net.http.SslError; +import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -102,6 +104,8 @@ import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; +import androidx.core.content.res.ResourcesCompat; import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.DialogFragment; @@ -134,7 +138,7 @@ 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.SaveDialog; +import com.stoutner.privacybrowser.dialogs.SaveWebpageDialog; import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog; import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog; import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog; @@ -144,7 +148,6 @@ 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.FileNameHelper; import com.stoutner.privacybrowser.helpers.ProxyHelper; @@ -168,22 +171,23 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener, - PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveWebpageListener, StoragePermissionDialog.StoragePermissionDialogListener, + PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, StoragePermissionDialog.StoragePermissionDialogListener, UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener { + // The executor service handles background tasks. It is accessed from `ViewSourceActivity`. + public static ExecutorService executorService = Executors.newFixedThreadPool(4); + // `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; - // The load URL on restart variables are public static so they can be accessed from `BookmarksActivity`. They are used in `onRestart()`. - public static boolean loadUrlOnRestart; - public static String urlToLoadOnRestart; - // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`. public static boolean restartFromBookmarksActivity; @@ -209,12 +213,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // It will be updated in `applyAppSettings()`, but it needs to be initialized here or the first run of `onPrepareOptionsMenu()` crashes. public static String proxyMode = ProxyHelper.NONE; + // Define the saved instance state constants. + private final String SAVED_STATE_ARRAY_LIST = "saved_state_array_list"; + private final String SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST = "saved_nested_scroll_webview_state_array_list"; + private final String SAVED_TAB_POSITION = "saved_tab_position"; + private final String PROXY_MODE = "proxy_mode"; - // The permission result request codes are used in `onCreateContextMenu()`, `onRequestPermissionResult()`, `onSaveWebpage()`, `onCloseStoragePermissionDialog()`, and `initializeWebView()`. - private final int PERMISSION_OPEN_REQUEST_CODE = 0; - private final int PERMISSION_SAVE_URL_REQUEST_CODE = 1; - private final int PERMISSION_SAVE_AS_ARCHIVE_REQUEST_CODE = 2; - private final int PERMISSION_SAVE_AS_IMAGE_REQUEST_CODE = 3; + // Define the saved instance state variables. + private ArrayList savedStateArrayList; + private ArrayList savedNestedScrollWebViewStateArrayList; + private int savedTabPosition; + private String savedProxyMode; + + // Define the class variables. + @SuppressWarnings("rawtypes") + AsyncTask populateBlocklists; // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`. @@ -310,6 +323,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private String saveWebpageUrl; private String saveWebpageFilePath; + // Declare the class views. + private DrawerLayout drawerLayout; + private AppBarLayout appBarLayout; + private Toolbar toolbar; + private LinearLayout findOnPageLinearLayout; + private LinearLayout tabsLinearLayout; + private TabLayout tabLayout; + private SwipeRefreshLayout swipeRefreshLayout; + private ViewPager webViewPager; + @Override // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`. @SuppressLint("ClickableViewAccessibility") @@ -317,6 +340,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Run the default commands. super.onCreate(savedInstanceState); + // Check to see if the activity has been restarted. + if (savedInstanceState != null) { + // Store the saved instance state variables. + savedStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_STATE_ARRAY_LIST); + savedNestedScrollWebViewStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST); + savedTabPosition = savedInstanceState.getInt(SAVED_TAB_POSITION); + savedProxyMode = savedInstanceState.getString(PROXY_MODE); + } + // 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); @@ -325,7 +357,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the screenshot preference. String appTheme = sharedPreferences.getString("app_theme", getString(R.string.app_theme_default_value)); - boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false); + boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false); // Get the theme entry values string array. String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values); @@ -363,10 +395,15 @@ 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); - ViewPager webViewPager = findViewById(R.id.webviewpager); + // Get handles for the views. + drawerLayout = findViewById(R.id.drawerlayout); + appBarLayout = findViewById(R.id.appbar_layout); + toolbar = findViewById(R.id.toolbar); + findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout); + tabsLinearLayout = findViewById(R.id.tabs_linearlayout); + tabLayout = findViewById(R.id.tablayout); + swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); + webViewPager = findViewById(R.id.webviewpager); // Get a handle for the app compat delegate. AppCompatDelegate appCompatDelegate = getDelegate(); @@ -399,8 +436,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Store up to 100 tabs in memory. webViewPager.setOffscreenPageLimit(100); + // Initialize the app. + initializeApp(); + + // Apply the app settings from the shared preferences. + applyAppSettings(); + // Populate the blocklists. - new PopulateBlocklists(this, this).execute(); + populateBlocklists = new PopulateBlocklists(this, this).execute(); } @Override @@ -411,8 +454,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Replace the intent that started the app with this one. setIntent(intent); - // 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) { + // Check to see if the app is being restarted. + if (savedStateArrayList == null || savedStateArrayList.size() == 0) { // The activity is running for the first time. // Get the information from the intent. String intentAction = intent.getAction(); Uri intentUriData = intent.getData(); @@ -429,7 +472,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook String url; // If the intent action is a web search, perform the search. - if (isWebSearch) { + if (isWebSearch) { // The intent is a web search. // Create an encoded URL string. String encodedUrlString; @@ -459,9 +502,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook loadUrl(currentWebView, url); } - // 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); @@ -512,26 +552,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings. if (nestedScrollWebView.getUrl() != null) { - applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true); + applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true, false); } } } } - // Load the URL on restart (used when loading a bookmark). - if (loadUrlOnRestart) { - // Load the specified URL. - loadUrl(currentWebView, urlToLoadOnRestart); - - // Reset the load on restart tracker. - loadUrlOnRestart = false; - } - // Update the bookmarks drawer if returning from the Bookmarks activity. if (restartFromBookmarksActivity) { - // Get a handle for the drawer layout. - DrawerLayout drawerLayout = findViewById(R.id.drawerlayout); - // Close the bookmarks drawer. drawerLayout.closeDrawer(GravityCompat.END); @@ -629,6 +657,50 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } + @Override + public void onSaveInstanceState(@NonNull Bundle savedInstanceState) { + // Run the default commands. + super.onSaveInstanceState(savedInstanceState); + + // Create the saved state array lists. + ArrayList savedStateArrayList = new ArrayList<>(); + ArrayList savedNestedScrollWebViewStateArrayList = new ArrayList<>(); + + // Get the URLs from each tab. + 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(); + + if (fragmentView != null) { + // Get the nested scroll WebView from the tab fragment. + NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); + + // Create saved state bundle. + Bundle savedStateBundle = new Bundle(); + + // Get the current states. + nestedScrollWebView.saveState(savedStateBundle); + Bundle savedNestedScrollWebViewStateBundle = nestedScrollWebView.saveNestedScrollWebViewState(); + + // Store the saved states in the array lists. + savedStateArrayList.add(savedStateBundle); + savedNestedScrollWebViewStateArrayList.add(savedNestedScrollWebViewStateBundle); + } + } + + // Get the current tab position. + int currentTabPosition = tabLayout.getSelectedTabPosition(); + + // Store the saved states in the bundle. + savedInstanceState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList); + savedInstanceState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList); + savedInstanceState.putInt(SAVED_TAB_POSITION, currentTabPosition); + savedInstanceState.putString(PROXY_MODE, proxyMode); + } + @Override public void onDestroy() { // Unregister the orbot status broadcast receiver if it exists. @@ -646,6 +718,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook bookmarksDatabaseHelper.close(); } + // Stop populating the blocklists if the AsyncTask is running in the background. + if (populateBlocklists != null) { + populateBlocklists.cancel(true); + } + // Run the default commands. super.onDestroy(); } @@ -715,12 +792,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; // Set the icon according to the current theme status. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - // Set the dark stop icon. - refreshMenuItem.setIcon(R.drawable.close_night); - } else { - // Set the light stop icon. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { refreshMenuItem.setIcon(R.drawable.close_day); + } else { + refreshMenuItem.setIcon(R.drawable.close_night); } } } @@ -991,950 +1066,755 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @Override - // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. - @SuppressLint("SetJavaScriptEnabled") public boolean onOptionsItemSelected(MenuItem menuItem) { - // Get the selected menu item ID. - int menuItemId = menuItem.getItemId(); - // Get a handle for the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Get a handle for the cookie manager. CookieManager cookieManager = CookieManager.getInstance(); - // Run the commands that correlate to the selected menu item. - switch (menuItemId) { - case R.id.toggle_javascript: - // Toggle the JavaScript status. - currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled()); - - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. - updatePrivacyIcons(true); - - // Display a `Snackbar`. - if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScrip is enabled. - Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show(); - } else if (cookieManager.acceptCookie()) { // JavaScript is disabled, but first-party cookies are enabled. - Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show(); - } else { // Privacy mode. - Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show(); - } - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.refresh: - if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed. - // Reload the current WebView. - currentWebView.reload(); - } else { // The stop button was pushed. - // Stop the loading of the WebView. - currentWebView.stopLoading(); - } - - // Consume the event. - return true; - - case R.id.bookmarks: - // Get a handle for the drawer layout. - DrawerLayout drawerLayout = findViewById(R.id.drawerlayout); - - // Open the bookmarks drawer. - drawerLayout.openDrawer(GravityCompat.END); - - // Consume the event. - return true; - - case R.id.toggle_first_party_cookies: - // Switch the first-party cookie status. - cookieManager.setAcceptCookie(!cookieManager.acceptCookie()); + // Get the selected menu item ID. + int menuItemId = menuItem.getItemId(); - // Store the first-party cookie status. - currentWebView.setAcceptFirstPartyCookies(cookieManager.acceptCookie()); + // Run the commands that correlate to the selected menu item. + if (menuItemId == R.id.toggle_javascript) { // JavaScript. + // Toggle the JavaScript status. + currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled()); - // Update the menu checkbox. - menuItem.setChecked(cookieManager.acceptCookie()); + // Update the privacy icon. + updatePrivacyIcons(true); - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. - updatePrivacyIcons(true); + // Display a `Snackbar`. + if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScrip is enabled. + Snackbar.make(webViewPager, R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show(); + } else if (cookieManager.acceptCookie()) { // JavaScript is disabled, but first-party cookies are enabled. + Snackbar.make(webViewPager, R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show(); + } else { // Privacy mode. + Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show(); + } - // Display a snackbar. - if (cookieManager.acceptCookie()) { // First-party cookies are enabled. - Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show(); - } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is still enabled. - Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show(); - } else { // Privacy mode. - Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show(); - } + // Reload the current WebView. + currentWebView.reload(); + // Consume the event. + return true; + } else if (menuItemId == R.id.refresh) { // Refresh. + // Run the command that correlates to the current status of the menu item. + if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed. // Reload the current WebView. currentWebView.reload(); + } else { // The stop button was pushed. + // Stop the loading of the WebView. + currentWebView.stopLoading(); + } - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.bookmarks) { // Bookmarks. + // Open the bookmarks drawer. + drawerLayout.openDrawer(GravityCompat.END); - case R.id.toggle_third_party_cookies: - if (Build.VERSION.SDK_INT >= 21) { - // Switch the status of thirdPartyCookiesEnabled. - cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView)); + // Consume the event. + return true; + } else if (menuItemId == R.id.toggle_first_party_cookies) { // First-party cookies. + // Switch the first-party cookie status. + cookieManager.setAcceptCookie(!cookieManager.acceptCookie()); - // Update the menu checkbox. - menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView)); + // Store the first-party cookie status. + currentWebView.setAcceptFirstPartyCookies(cookieManager.acceptCookie()); - // Display a snackbar. - if (cookieManager.acceptThirdPartyCookies(currentWebView)) { - Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show(); - } + // Update the menu checkbox. + menuItem.setChecked(cookieManager.acceptCookie()); - // Reload the current WebView. - currentWebView.reload(); - } // Else do nothing because SDK < 21. + // Update the privacy icon. + updatePrivacyIcons(true); - // Consume the event. - return true; + // Display a snackbar. + if (cookieManager.acceptCookie()) { // First-party cookies are enabled. + Snackbar.make(webViewPager, R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show(); + } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is still enabled. + Snackbar.make(webViewPager, R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show(); + } else { // Privacy mode. + Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show(); + } - case R.id.toggle_dom_storage: - // Toggle the status of domStorageEnabled. - currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled()); + // Reload the current WebView. + currentWebView.reload(); - // Update the menu checkbox. - menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled()); + // Consume the event. + return true; + } else if (menuItemId == R.id.toggle_third_party_cookies) { // Third-party cookies. + // Only act if the API >= 21. Otherwise, there are no third-party cookie controls. + if (Build.VERSION.SDK_INT >= 21) { + // Toggle the status of thirdPartyCookiesEnabled. + cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView)); - // Update the privacy icon. `true` refreshes the app bar icons. - updatePrivacyIcons(true); + // Update the menu checkbox. + menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView)); // Display a snackbar. - if (currentWebView.getSettings().getDomStorageEnabled()) { - Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show(); + if (cookieManager.acceptThirdPartyCookies(currentWebView)) { + Snackbar.make(webViewPager, R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show(); } else { - Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show(); + Snackbar.make(webViewPager, R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show(); } // Reload the current WebView. currentWebView.reload(); + } - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.toggle_dom_storage) { // DOM storage. + // Toggle the status of domStorageEnabled. + currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled()); - // Form data can be removed once the minimum API >= 26. - case R.id.toggle_save_form_data: - // Switch the status of saveFormDataEnabled. - currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData()); + // Update the menu checkbox. + menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled()); - // Update the menu checkbox. - menuItem.setChecked(currentWebView.getSettings().getSaveFormData()); + // Update the privacy icon. + updatePrivacyIcons(true); - // Display a snackbar. - if (currentWebView.getSettings().getSaveFormData()) { - Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show(); - } + // Display a snackbar. + if (currentWebView.getSettings().getDomStorageEnabled()) { + Snackbar.make(webViewPager, R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show(); + } else { + Snackbar.make(webViewPager, R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show(); + } - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. - updatePrivacyIcons(true); + // Reload the current WebView. + currentWebView.reload(); - // Reload the current WebView. - currentWebView.reload(); + // Consume the event. + return true; + } else if (menuItemId == R.id.toggle_save_form_data) { // Form data. This can be removed once the minimum API >= 26. + // Switch the status of saveFormDataEnabled. + currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData()); - // Consume the event. - return true; + // Update the menu checkbox. + menuItem.setChecked(currentWebView.getSettings().getSaveFormData()); - case R.id.clear_cookies: - Snackbar.make(findViewById(R.id.webviewpager), R.string.cookies_deleted, Snackbar.LENGTH_LONG) - .setAction(R.string.undo, v -> { - // Do nothing because everything will be handled by `onDismissed()` below. - }) - .addCallback(new Snackbar.Callback() { - @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`. - @Override - public void onDismissed(Snackbar snackbar, int event) { - if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed. - // Delete the cookies, which command varies by SDK. - if (Build.VERSION.SDK_INT < 21) { - cookieManager.removeAllCookie(); - } else { - cookieManager.removeAllCookies(null); - } - } - } - }) - .show(); + // Display a snackbar. + if (currentWebView.getSettings().getSaveFormData()) { + Snackbar.make(webViewPager, R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show(); + } else { + Snackbar.make(webViewPager, R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show(); + } - // Consume the event. - return true; + // Update the privacy icon. + updatePrivacyIcons(true); - case R.id.clear_dom_storage: - Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG) - .setAction(R.string.undo, v -> { - // Do nothing because everything will be handled by `onDismissed()` below. - }) - .addCallback(new Snackbar.Callback() { - @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`. - @Override - public void onDismissed(Snackbar snackbar, int event) { - if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed. - // Delete the DOM Storage. - WebStorage webStorage = WebStorage.getInstance(); - webStorage.deleteAllData(); - - // Initialize a handler to manually delete the DOM storage files and directories. - Handler deleteDomStorageHandler = new Handler(); - - // Setup a runnable to manually delete the DOM storage files and directories. - Runnable deleteDomStorageRunnable = () -> { - try { - // Get a handle for the runtime. - Runtime runtime = Runtime.getRuntime(); - - // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, - // which links to `/data/data/com.stoutner.privacybrowser.standard`. - String privateDataDirectoryString = getApplicationInfo().dataDir; - - // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly. - Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"}); - - // Multiple commands must be used because `Runtime.exec()` does not like `*`. - Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB"); - Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager"); - Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal"); - Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases"); - - // Wait for the processes to finish. - deleteLocalStorageProcess.waitFor(); - deleteIndexProcess.waitFor(); - deleteQuotaManagerProcess.waitFor(); - deleteQuotaManagerJournalProcess.waitFor(); - deleteDatabasesProcess.waitFor(); - } catch (Exception exception) { - // Do nothing if an error is thrown. - } - }; - - // Manually delete the DOM storage files after 200 milliseconds. - deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200); + // Reload the current WebView. + currentWebView.reload(); + + // Consume the event. + return true; + } else if (menuItemId == R.id.clear_cookies) { // Clear cookies. + // Create a snackbar. + Snackbar.make(webViewPager, R.string.cookies_deleted, Snackbar.LENGTH_LONG) + .setAction(R.string.undo, v -> { + // Do nothing because everything will be handled by `onDismissed()` below. + }) + .addCallback(new Snackbar.Callback() { + @Override + public void onDismissed(Snackbar snackbar, int event) { + if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed. + // Delete the cookies, which command varies by SDK. + if (Build.VERSION.SDK_INT < 21) { + cookieManager.removeAllCookie(); + } else { + cookieManager.removeAllCookies(null); } } - }) - .show(); + } + }) + .show(); - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.clear_dom_storage) { // Clear DOM storage. + // Create a snackbar. + Snackbar.make(webViewPager, R.string.dom_storage_deleted, Snackbar.LENGTH_LONG) + .setAction(R.string.undo, v -> { + // Do nothing because everything will be handled by `onDismissed()` below. + }) + .addCallback(new Snackbar.Callback() { + @Override + public void onDismissed(Snackbar snackbar, int event) { + if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed. + // Delete the DOM Storage. + WebStorage webStorage = WebStorage.getInstance(); + webStorage.deleteAllData(); + + // Initialize a handler to manually delete the DOM storage files and directories. + Handler deleteDomStorageHandler = new Handler(); + + // Setup a runnable to manually delete the DOM storage files and directories. + Runnable deleteDomStorageRunnable = () -> { + try { + // Get a handle for the runtime. + Runtime runtime = Runtime.getRuntime(); + + // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, + // which links to `/data/data/com.stoutner.privacybrowser.standard`. + String privateDataDirectoryString = getApplicationInfo().dataDir; + + // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly. + Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"}); + + // Multiple commands must be used because `Runtime.exec()` does not like `*`. + Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB"); + Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager"); + Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal"); + Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases"); + + // Wait for the processes to finish. + deleteLocalStorageProcess.waitFor(); + deleteIndexProcess.waitFor(); + deleteQuotaManagerProcess.waitFor(); + deleteQuotaManagerJournalProcess.waitFor(); + deleteDatabasesProcess.waitFor(); + } catch (Exception exception) { + // Do nothing if an error is thrown. + } + }; - // Form data can be remove once the minimum API >= 26. - case R.id.clear_form_data: - Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_deleted, Snackbar.LENGTH_LONG) - .setAction(R.string.undo, v -> { - // Do nothing because everything will be handled by `onDismissed()` below. - }) - .addCallback(new Snackbar.Callback() { - @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`. - @Override - public void onDismissed(Snackbar snackbar, int event) { - if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed. - // Delete the form data. - WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext()); - mainWebViewDatabase.clearFormData(); - } + // Manually delete the DOM storage files after 200 milliseconds. + deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200); } - }) - .show(); + } + }) + .show(); - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.clear_form_data) { // Clear form data. This can be remove once the minimum API >= 26. + // Create a snackbar. + Snackbar.make(webViewPager, R.string.form_data_deleted, Snackbar.LENGTH_LONG) + .setAction(R.string.undo, v -> { + // Do nothing because everything will be handled by `onDismissed()` below. + }) + .addCallback(new Snackbar.Callback() { + @Override + public void onDismissed(Snackbar snackbar, int event) { + if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed. + // Get a handle for the webView database. + WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(getApplicationContext()); + + // Delete the form data. + webViewDatabase.clearFormData(); + } + } + }) + .show(); - case R.id.easylist: - // Toggle the EasyList status. - currentWebView.enableBlocklist(NestedScrollWebView.EASYLIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)); + // Consume the event. + return true; + } else if (menuItemId == R.id.easylist) { // EasyList. + // Toggle the EasyList status. + currentWebView.enableBlocklist(NestedScrollWebView.EASYLIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)); - // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)); + // Update the menu checkbox. + menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)); - // Reload the current WebView. - currentWebView.reload(); + // Reload the current WebView. + currentWebView.reload(); - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.easyprivacy) { // EasyPrivacy. + // Toggle the EasyPrivacy status. + currentWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)); - case R.id.easyprivacy: - // Toggle the EasyPrivacy status. - currentWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)); + // Update the menu checkbox. + menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)); - // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)); + // Reload the current WebView. + currentWebView.reload(); - // Reload the current WebView. - currentWebView.reload(); + // Consume the event. + return true; + } else if (menuItemId == R.id.fanboys_annoyance_list) { // Fanboy's Annoyance List. + // Toggle Fanboy's Annoyance List status. + currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); - // Consume the event. - return true; + // Update the menu checkbox. + menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); - case R.id.fanboys_annoyance_list: - // Toggle Fanboy's Annoyance List status. - currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); + // Get a handle for the Fanboy's Social Block List menu item. + MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list); - // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); + // Update the staus of Fanboy's Social Blocking List. + fanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); - // Update the staus of Fanboy's Social Blocking List. - MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list); - fanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); + // Reload the current WebView. + currentWebView.reload(); - // Reload the current WebView. - currentWebView.reload(); + // Consume the event. + return true; + } else if (menuItemId == R.id.fanboys_social_blocking_list) { // Fanboy's Social Blocking List. + // Toggle Fanboy's Social Blocking List status. + currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)); - // Consume the event. - return true; + // Update the menu checkbox. + menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)); - case R.id.fanboys_social_blocking_list: - // Toggle Fanboy's Social Blocking List status. - currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)); + // Reload the current WebView. + currentWebView.reload(); - // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)); + // Consume the event. + return true; + } else if (menuItemId == R.id.ultralist) { // UltraList. + // Toggle the UltraList status. + currentWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)); - // Reload the current WebView. - currentWebView.reload(); + // Update the menu checkbox. + menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)); - // Consume the event. - return true; + // Reload the current WebView. + currentWebView.reload(); - case R.id.ultralist: - // Toggle the UltraList status. - currentWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)); + // Consume the event. + return true; + } else if (menuItemId == R.id.ultraprivacy) { // UltraPrivacy. + // Toggle the UltraPrivacy status. + currentWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)); - // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)); + // Update the menu checkbox. + menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)); - // Reload the current WebView. - currentWebView.reload(); + // Reload the current WebView. + currentWebView.reload(); - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.block_all_third_party_requests) { // Block all third-party requests. + //Toggle the third-party requests blocker status. + currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); - case R.id.ultraprivacy: - // Toggle the UltraPrivacy status. - currentWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)); + // Update the menu checkbox. + menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); - // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)); + // Reload the current WebView. + currentWebView.reload(); - // Reload the current WebView. - currentWebView.reload(); + // Consume the event. + return true; + } else if (menuItemId == R.id.proxy_none) { // Proxy - None. + // Update the proxy mode. + proxyMode = ProxyHelper.NONE; - // Consume the event. - return true; + // Apply the proxy mode. + applyProxy(true); - case R.id.block_all_third_party_requests: - //Toggle the third-party requests blocker status. - currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); + // Consume the event. + return true; + } else if (menuItemId == R.id.proxy_tor) { // Proxy - Tor. + // Update the proxy mode. + proxyMode = ProxyHelper.TOR; - // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); + // Apply the proxy mode. + applyProxy(true); - // Reload the current WebView. - currentWebView.reload(); + // Consume the event. + return true; + } else if (menuItemId == R.id.proxy_i2p) { // Proxy - I2P. + // Update the proxy mode. + proxyMode = ProxyHelper.I2P; - // Consume the event. - return true; + // Apply the proxy mode. + applyProxy(true); - case R.id.proxy_none: - // Update the proxy mode. - proxyMode = ProxyHelper.NONE; + // Consume the event. + return true; + } else if (menuItemId == R.id.proxy_custom) { // Proxy - Custom. + // Update the proxy mode. + proxyMode = ProxyHelper.CUSTOM; - // Apply the proxy mode. - applyProxy(true); + // Apply the proxy mode. + applyProxy(true); - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.user_agent_privacy_browser) { // User Agent - Privacy Browser. + // Update the user agent. + currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]); - case R.id.proxy_tor: - // Update the proxy mode. - proxyMode = ProxyHelper.TOR; + // Reload the current WebView. + currentWebView.reload(); - // Apply the proxy mode. - applyProxy(true); + // Consume the event. + return true; + } else if (menuItemId == R.id.user_agent_webview_default) { // User Agent - WebView Default. + // Update the user agent. + currentWebView.getSettings().setUserAgentString(""); - // Consume the event. - return true; + // Reload the current WebView. + currentWebView.reload(); - case R.id.proxy_i2p: - // Update the proxy mode. - proxyMode = ProxyHelper.I2P; + // Consume the event. + return true; + } else if (menuItemId == R.id.user_agent_firefox_on_android) { // User Agent - Firefox on Android. + // Update the user agent. + currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]); - // Apply the proxy mode. - applyProxy(true); + // Reload the current WebView. + currentWebView.reload(); - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.user_agent_chrome_on_android) { // User Agent - Chrome on Android. + // Update the user agent. + currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]); - case R.id.proxy_custom: - // Update the proxy mode. - proxyMode = ProxyHelper.CUSTOM; + // Reload the current WebView. + currentWebView.reload(); - // Apply the proxy mode. - applyProxy(true); + // Consume the event. + return true; + } else if (menuItemId == R.id.user_agent_safari_on_ios) { // User Agent - Safari on iOS. + // Update the user agent. + currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]); - // Consume the event. - return true; + // Reload the current WebView. + currentWebView.reload(); - case R.id.user_agent_privacy_browser: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]); + // Consume the event. + return true; + } else if (menuItemId == R.id.user_agent_firefox_on_linux) { // User Agent - Firefox on Linux. + // Update the user agent. + currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]); - // Reload the current WebView. - currentWebView.reload(); + // Reload the current WebView. + currentWebView.reload(); - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.user_agent_chromium_on_linux) { // User Agent - Chromium on Linux. + // Update the user agent. + currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]); - case R.id.user_agent_webview_default: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(""); + // Reload the current WebView. + currentWebView.reload(); - // Reload the current WebView. - currentWebView.reload(); + // Consume the event. + return true; + } else if (menuItemId == R.id.user_agent_firefox_on_windows) { // User Agent - Firefox on Windows. + // Update the user agent. + currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]); - // Consume the event. - return true; + // Reload the current WebView. + currentWebView.reload(); - case R.id.user_agent_firefox_on_android: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]); + // Consume the event. + return true; + } else if (menuItemId == R.id.user_agent_chrome_on_windows) { // User Agent - Chrome on Windows. + // Update the user agent. + currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]); - // Reload the current WebView. - currentWebView.reload(); + // Reload the current WebView. + currentWebView.reload(); - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.user_agent_edge_on_windows) { // User Agent - Edge on Windows. + // Update the user agent. + currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]); - case R.id.user_agent_chrome_on_android: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]); + // Reload the current WebView. + currentWebView.reload(); - // Reload the current WebView. - currentWebView.reload(); + // Consume the event. + return true; + } else if (menuItemId == R.id.user_agent_internet_explorer_on_windows) { // User Agent - Internet Explorer on Windows. + // Update the user agent. + currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]); - // Consume the event. - return true; + // Reload the current WebView. + currentWebView.reload(); - case R.id.user_agent_safari_on_ios: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]); + // Consume the event. + return true; + } else if (menuItemId == R.id.user_agent_safari_on_macos) { // User Agent - Safari on macOS. + // Update the user agent. + currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]); - // Reload the current WebView. - currentWebView.reload(); + // Reload the current WebView. + currentWebView.reload(); - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.user_agent_custom) { // User Agent - Custom. + // Update the user agent. + currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value))); - case R.id.user_agent_firefox_on_linux: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]); + // Reload the current WebView. + currentWebView.reload(); - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_chromium_on_linux: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_firefox_on_windows: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_chrome_on_windows: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_edge_on_windows: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_internet_explorer_on_windows: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]); + // Consume the event. + return true; + } else if (menuItemId == R.id.font_size) { // Font size. + // Instantiate the font size dialog. + DialogFragment fontSizeDialogFragment = FontSizeDialog.displayDialog(currentWebView.getSettings().getTextZoom()); - // Reload the current WebView. - currentWebView.reload(); + // Show the font size dialog. + fontSizeDialogFragment.show(getSupportFragmentManager(), getString(R.string.font_size)); - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.swipe_to_refresh) { // Swipe to refresh. + // Toggle the stored status of swipe to refresh. + currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh()); - case R.id.user_agent_safari_on_macos: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]); + // Get a handle for the swipe refresh layout. + SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); - // Reload the current WebView. - currentWebView.reload(); + // Update the swipe refresh layout. + if (currentWebView.getSwipeToRefresh()) { // Swipe to refresh is enabled. + // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes. + swipeRefreshLayout.setEnabled(currentWebView.getY() == 0); + } else { // Swipe to refresh is disabled. + // Disable the swipe refresh layout. + swipeRefreshLayout.setEnabled(false); + } - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.wide_viewport) { // Wide viewport. + // Toggle the viewport. + currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort()); - case R.id.user_agent_custom: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value))); + // Consume the event. + return true; + } else if (menuItemId == R.id.display_images) { // Display images. + // Toggle the displaying of images. + if (currentWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically. + // Disable loading of images. + currentWebView.getSettings().setLoadsImagesAutomatically(false); - // Reload the current WebView. + // Reload the website to remove existing images. currentWebView.reload(); + } else { // Images are not currently loaded automatically. + // 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.font_size: - // Instantiate the font size dialog. - DialogFragment fontSizeDialogFragment = FontSizeDialog.displayDialog(currentWebView.getSettings().getTextZoom()); - - // Show the font size dialog. - fontSizeDialogFragment.show(getSupportFragmentManager(), getString(R.string.font_size)); - - // Consume the event. - return true; - - case R.id.swipe_to_refresh: - // Toggle the stored status of swipe to refresh. - currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh()); - - // Get a handle for the swipe refresh layout. - SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); - - // Update the swipe refresh layout. - if (currentWebView.getSwipeToRefresh()) { // Swipe to refresh is enabled. - // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes. - swipeRefreshLayout.setEnabled(currentWebView.getY() == 0); - } else { // Swipe to refresh is disabled. - // Disable the swipe refresh layout. - swipeRefreshLayout.setEnabled(false); - } - - // 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: - if (currentWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically. - // Disable loading of images. - currentWebView.getSettings().setLoadsImagesAutomatically(false); - - // Reload the website to remove existing images. - currentWebView.reload(); - } else { // Images are not currently loaded automatically. - // 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.dark_webview: - // Check to see if dark WebView is supported by this WebView. - if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { - // Toggle the dark WebView setting. - if (WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON) { // Dark WebView is currently enabled. - // Turn off dark WebView. - WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); - } else { // Dark WebView is currently disabled. - // turn on dark WebView. - WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - } + // Consume the event. + return true; + } else if (menuItemId == R.id.dark_webview) { // Dark WebView. + // Check to see if dark WebView is supported by this WebView. + if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { + // Toggle the dark WebView setting. + if (WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON) { // Dark WebView is currently enabled. + // Turn off dark WebView. + WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); + } else { // Dark WebView is currently disabled. + // Turn on dark WebView. + WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); } + } - // Consume the event. - return true; - - case R.id.find_on_page: - // 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); - - // Set the minimum height of the find on page linear layout to match the toolbar. - findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight()); - - // Hide the toolbar. - toolbar.setVisibility(View.GONE); - - // Show the find on page linear layout. - findOnPageLinearLayout.setVisibility(View.VISIBLE); - - // Display the keyboard. The app must wait 200 ms before running the command to work around a bug in Android. - // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working - findOnPageEditText.postDelayed(() -> { - // Set the focus on `findOnPageEditText`. - findOnPageEditText.requestFocus(); - - // 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; - - // Display the keyboard. `0` sets no input flags. - inputMethodManager.showSoftInput(findOnPageEditText, 0); - }, 200); - - // Consume the event. - return true; - - case R.id.print: - // Get a print manager instance. - PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE); - - // Remove the lint error below that print manager might be null. - assert printManager != null; - - // Create a print document adapter from the current WebView. - PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter(); - - // Print the document. - printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null); - - // Consume the event. - return true; - - case R.id.save_url: - // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired. - new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(), - currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl()); - - // Consume the event. - return true; - - case R.id.save_as_archive: - // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired. - new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_AS_ARCHIVE, currentWebView.getSettings().getUserAgentString(), - currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl()); - - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.find_on_page) { // Find on page. + // 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 R.id.save_as_image: - // Prepare the save dialog. The dialog will be displayed once the file size adn the content disposition have been acquired. - new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_AS_IMAGE, currentWebView.getSettings().getUserAgentString(), - currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl()); + // Set the minimum height of the find on page linear layout to match the toolbar. + findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight()); - // Consume the event. - return true; + // Hide the toolbar. + toolbar.setVisibility(View.GONE); - case R.id.add_to_homescreen: - // Instantiate the create home screen shortcut dialog. - DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(), - currentWebView.getFavoriteOrDefaultIcon()); + // Show the find on page linear layout. + findOnPageLinearLayout.setVisibility(View.VISIBLE); - // Show the create home screen shortcut dialog. - createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut)); + // Display the keyboard. The app must wait 200 ms before running the command to work around a bug in Android. + // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working + findOnPageEditText.postDelayed(() -> { + // Set the focus on the find on page edit text. + findOnPageEditText.requestFocus(); - // Consume the event. - return true; + // Get a handle for the input method manager. + InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - case R.id.view_source: - // Create an intent to launch the view source activity. - Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class); + // Remove the lint warning below that the input method manager might be null. + assert inputMethodManager != null; - // Add the variables to the intent. - viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString()); - viewSourceIntent.putExtra("current_url", currentWebView.getUrl()); + // Display the keyboard. `0` sets no input flags. + inputMethodManager.showSoftInput(findOnPageEditText, 0); + }, 200); - // Make it so. - startActivity(viewSourceIntent); + // Consume the event. + return true; + } else if (menuItemId == R.id.print) { // Print. + // Get a print manager instance. + PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE); - // Consume the event. - return true; + // Remove the lint error below that print manager might be null. + assert printManager != null; - case R.id.share_url: - // Setup the share string. - String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl(); + // Create a print document adapter from the current WebView. + PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter(); - // Create the share intent. - Intent shareIntent = new Intent(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_TEXT, shareString); - shareIntent.setType("text/plain"); + // Print the document. + printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null); - // Make it so. - startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url))); + // Consume the event. + return true; + } else if (menuItemId == R.id.save_url) { // Save URL. + // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired. + new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(), + currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl()); - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.save_archive) { // Save archive. + // Instantiate the save dialog. + DialogFragment saveArchiveFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_ARCHIVE, null, null, getString(R.string.webpage_mht), null, + false); - case R.id.open_with_app: - // Open the URL with an outside app. - openWithApp(currentWebView.getUrl()); + // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name. + saveArchiveFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog)); - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.save_image) { // Save image. + // Instantiate the save dialog. + DialogFragment saveImageFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_IMAGE, null, null, getString(R.string.webpage_png), null, + false); - case R.id.open_with_browser: - // Open the URL with an outside browser. - openWithBrowser(currentWebView.getUrl()); + // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name. + saveImageFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog)); - // Consume the event. - return true; + // Consume the event. + return true; + } else if (menuItemId == R.id.add_to_homescreen) { // Add to homescreen. + // Instantiate the create home screen shortcut dialog. + DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(), + currentWebView.getFavoriteOrDefaultIcon()); - case R.id.add_or_edit_domain: - if (currentWebView.getDomainSettingsApplied()) { // Edit the current domain settings. - // Reapply the domain settings on returning to `MainWebViewActivity`. - reapplyDomainSettingsOnRestart = true; - - // Create an intent to launch the domains activity. - Intent domainsIntent = new Intent(this, DomainsActivity.class); - - // Add the extra information to the intent. - domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId()); - domainsIntent.putExtra("close_on_back", true); - domainsIntent.putExtra("current_url", currentWebView.getUrl()); - - // Get the current certificate. - SslCertificate sslCertificate = currentWebView.getCertificate(); - - // Check to see if the SSL certificate is populated. - if (sslCertificate != null) { - // Extract the certificate to strings. - String issuedToCName = sslCertificate.getIssuedTo().getCName(); - String issuedToOName = sslCertificate.getIssuedTo().getOName(); - String issuedToUName = sslCertificate.getIssuedTo().getUName(); - String issuedByCName = sslCertificate.getIssuedBy().getCName(); - String issuedByOName = sslCertificate.getIssuedBy().getOName(); - String issuedByUName = sslCertificate.getIssuedBy().getUName(); - long startDateLong = sslCertificate.getValidNotBeforeDate().getTime(); - long endDateLong = sslCertificate.getValidNotAfterDate().getTime(); - - // Add the certificate to the intent. - domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName); - domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName); - domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName); - domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName); - domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName); - domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName); - domainsIntent.putExtra("ssl_start_date", startDateLong); - domainsIntent.putExtra("ssl_end_date", endDateLong); - } + // Show the create home screen shortcut dialog. + createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut)); - // Check to see if the current IP addresses have been received. - if (currentWebView.hasCurrentIpAddresses()) { - // Add the current IP addresses to the intent. - domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); - } + // Consume the event. + return true; + } else if (menuItemId == R.id.view_source) { // View source. + // Create an intent to launch the view source activity. + Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class); - // Make it so. - startActivity(domainsIntent); - } else { // Add a new domain. - // Apply the new domain settings on returning to `MainWebViewActivity`. - reapplyDomainSettingsOnRestart = true; - - // Get the current domain - Uri currentUri = Uri.parse(currentWebView.getUrl()); - String currentDomain = currentUri.getHost(); - - // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`. - DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0); - - // Create the domain and store the database ID. - int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain); - - // Create an intent to launch the domains activity. - Intent domainsIntent = new Intent(this, DomainsActivity.class); - - // Add the extra information to the intent. - domainsIntent.putExtra("load_domain", newDomainDatabaseId); - domainsIntent.putExtra("close_on_back", true); - domainsIntent.putExtra("current_url", currentWebView.getUrl()); - - // Get the current certificate. - SslCertificate sslCertificate = currentWebView.getCertificate(); - - // Check to see if the SSL certificate is populated. - if (sslCertificate != null) { - // Extract the certificate to strings. - String issuedToCName = sslCertificate.getIssuedTo().getCName(); - String issuedToOName = sslCertificate.getIssuedTo().getOName(); - String issuedToUName = sslCertificate.getIssuedTo().getUName(); - String issuedByCName = sslCertificate.getIssuedBy().getCName(); - String issuedByOName = sslCertificate.getIssuedBy().getOName(); - String issuedByUName = sslCertificate.getIssuedBy().getUName(); - long startDateLong = sslCertificate.getValidNotBeforeDate().getTime(); - long endDateLong = sslCertificate.getValidNotAfterDate().getTime(); - - // Add the certificate to the intent. - domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName); - domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName); - domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName); - domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName); - domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName); - domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName); - domainsIntent.putExtra("ssl_start_date", startDateLong); - domainsIntent.putExtra("ssl_end_date", endDateLong); - } + // Add the variables to the intent. + viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString()); + viewSourceIntent.putExtra("current_url", currentWebView.getUrl()); - // Check to see if the current IP addresses have been received. - if (currentWebView.hasCurrentIpAddresses()) { - // Add the current IP addresses to the intent. - domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); - } + // Make it so. + startActivity(viewSourceIntent); - // Make it so. - startActivity(domainsIntent); - } + // Consume the event. + return true; + } else if (menuItemId == R.id.share_url) { // Share URL. + // Setup the share string. + String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl(); - // Consume the event. - return true; + // Create the share intent. + Intent shareIntent = new Intent(Intent.ACTION_SEND); - case R.id.ad_consent: - // Instantiate the ad consent dialog. - DialogFragment adConsentDialogFragment = new AdConsentDialog(); + // Add the share string to the intent. + shareIntent.putExtra(Intent.EXTRA_TEXT, shareString); - // Display the ad consent dialog. - adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent)); + // Set the MIME type. + shareIntent.setType("text/plain"); - // Consume the event. - return true; + // Set the intent to open in a new task. + shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - default: - // Don't consume the event. - return super.onOptionsItemSelected(menuItem); - } - } + // Make it so. + startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url))); - // removeAllCookies is deprecated, but it is required for API < 21. - @Override - public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) { - // Get the menu item ID. - int menuItemId = menuItem.getItemId(); + // Consume the event. + return true; + } else if (menuItemId == R.id.open_with_app) { // Open with app. + // Open the URL with an outside app. + openWithApp(currentWebView.getUrl()); - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + // Consume the event. + return true; + } else if (menuItemId == R.id.open_with_browser) { // Open with browser. + // Open the URL with an outside browser. + openWithBrowser(currentWebView.getUrl()); - // Run the commands that correspond to the selected menu item. - switch (menuItemId) { - case R.id.clear_and_exit: - // Clear and exit Privacy Browser. - clearAndExit(); - break; + // Consume the event. + return true; + } else if (menuItemId == R.id.add_or_edit_domain) { // Add or edit domain. + // Check if domain settings currently exist. + if (currentWebView.getDomainSettingsApplied()) { // Edit the current domain settings. + // Reapply the domain settings on returning to `MainWebViewActivity`. + reapplyDomainSettingsOnRestart = true; - case R.id.home: - // Load the homepage. - loadUrl(currentWebView, sharedPreferences.getString("homepage", getString(R.string.homepage_default_value))); - break; + // Create an intent to launch the domains activity. + Intent domainsIntent = new Intent(this, DomainsActivity.class); - case R.id.back: - if (currentWebView.canGoBack()) { - // Get the current web back forward list. - WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList(); + // Add the extra information to the intent. + domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId()); + domainsIntent.putExtra("close_on_back", true); + domainsIntent.putExtra("current_url", currentWebView.getUrl()); - // Get the previous entry URL. - String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl(); + // Get the current certificate. + SslCertificate sslCertificate = currentWebView.getCertificate(); - // Apply the domain settings. - applyDomainSettings(currentWebView, previousUrl, false, false); + // Check to see if the SSL certificate is populated. + if (sslCertificate != null) { + // Extract the certificate to strings. + String issuedToCName = sslCertificate.getIssuedTo().getCName(); + String issuedToOName = sslCertificate.getIssuedTo().getOName(); + String issuedToUName = sslCertificate.getIssuedTo().getUName(); + String issuedByCName = sslCertificate.getIssuedBy().getCName(); + String issuedByOName = sslCertificate.getIssuedBy().getOName(); + String issuedByUName = sslCertificate.getIssuedBy().getUName(); + long startDateLong = sslCertificate.getValidNotBeforeDate().getTime(); + long endDateLong = sslCertificate.getValidNotAfterDate().getTime(); - // Load the previous website in the history. - currentWebView.goBack(); + // Add the certificate to the intent. + domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName); + domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName); + domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName); + domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName); + domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName); + domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName); + domainsIntent.putExtra("ssl_start_date", startDateLong); + domainsIntent.putExtra("ssl_end_date", endDateLong); } - break; - - case R.id.forward: - if (currentWebView.canGoForward()) { - // Get the current web back forward list. - WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList(); - - // Get the next entry URL. - String nextUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() + 1).getUrl(); - // Apply the domain settings. - applyDomainSettings(currentWebView, nextUrl, false, false); - - // Load the next website in the history. - currentWebView.goForward(); + // Check to see if the current IP addresses have been received. + if (currentWebView.hasCurrentIpAddresses()) { + // Add the current IP addresses to the intent. + domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); } - break; - - case R.id.history: - // Instantiate the URL history dialog. - DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId()); - - // Show the URL history dialog. - 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(); - - // Create an intent to launch the Requests activity. - Intent requestsIntent = new Intent(this, RequestsActivity.class); - - // Add the block third-party requests status to the intent. - requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); // Make it so. - startActivity(requestsIntent); - break; - - case R.id.downloads: - // Launch the system Download Manager. - Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS); + startActivity(domainsIntent); + } else { // Add a new domain. + // Apply the new domain settings on returning to `MainWebViewActivity`. + reapplyDomainSettingsOnRestart = true; - // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list. - downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // Get the current domain + Uri currentUri = Uri.parse(currentWebView.getUrl()); + String currentDomain = currentUri.getHost(); - // Make it so. - startActivity(downloadManagerIntent); - break; + // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`. + DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0); - case R.id.domains: - // Set the flag to reapply the domain settings on restart when returning from Domain Settings. - reapplyDomainSettingsOnRestart = true; + // Create the domain and store the database ID. + int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain); - // Launch the domains activity. + // Create an intent to launch the domains activity. Intent domainsIntent = new Intent(this, DomainsActivity.class); // Add the extra information to the intent. + domainsIntent.putExtra("load_domain", newDomainDatabaseId); + domainsIntent.putExtra("close_on_back", true); domainsIntent.putExtra("current_url", currentWebView.getUrl()); // Get the current certificate. @@ -1971,56 +1851,190 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Make it so. startActivity(domainsIntent); - break; + } - case R.id.settings: - // Set the flag to reapply app settings on restart when returning from Settings. - reapplyAppSettingsOnRestart = true; + // Consume the event. + return true; + } else if (menuItemId == R.id.ad_consent) { // Ad consent. + // Instantiate the ad consent dialog. + DialogFragment adConsentDialogFragment = new AdConsentDialog(); - // Set the flag to reapply the domain settings on restart when returning from Settings. - reapplyDomainSettingsOnRestart = true; + // Display the ad consent dialog. + adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent)); - // Launch the settings activity. - Intent settingsIntent = new Intent(this, SettingsActivity.class); - startActivity(settingsIntent); - break; + // Consume the event. + return true; + } else { // There is no match with the options menu. Pass the event up to the parent method. + // Don't consume the event. + return super.onOptionsItemSelected(menuItem); + } + } - case R.id.import_export: - // Launch the import/export activity. - Intent importExportIntent = new Intent (this, ImportExportActivity.class); - startActivity(importExportIntent); - break; + // removeAllCookies is deprecated, but it is required for API < 21. + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) { + // Get a handle for the shared preferences. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - case R.id.logcat: - // Launch the logcat activity. - Intent logcatIntent = new Intent(this, LogcatActivity.class); - startActivity(logcatIntent); - break; + // Get the menu item ID. + int menuItemId = menuItem.getItemId(); - case R.id.guide: - // Launch `GuideActivity`. - Intent guideIntent = new Intent(this, GuideActivity.class); - startActivity(guideIntent); - break; + // Run the commands that correspond to the selected menu item. + if (menuItemId == R.id.clear_and_exit) { // Clear and exit. + // Clear and exit Privacy Browser. + clearAndExit(); + } else if (menuItemId == R.id.home) { // Home. + // Load the homepage. + loadUrl(currentWebView, sharedPreferences.getString("homepage", getString(R.string.homepage_default_value))); + } else if (menuItemId == R.id.back) { // Back. + // Check if the WebView can go back. + if (currentWebView.canGoBack()) { + // 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, false); + + // Load the previous website in the history. + currentWebView.goBack(); + } + } else if (menuItemId == R.id.forward) { // Forward. + // Check if the WebView can go forward. + if (currentWebView.canGoForward()) { + // Get the current web back forward list. + WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList(); - case R.id.about: - // Create an intent to launch the about activity. - Intent aboutIntent = new Intent(this, AboutActivity.class); + // Get the next entry URL. + String nextUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() + 1).getUrl(); - // 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], - ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]}; + // Apply the domain settings. + applyDomainSettings(currentWebView, nextUrl, false, false, false); - // Add the blocklist versions to the intent. - aboutIntent.putExtra("blocklist_versions", blocklistVersions); + // Load the next website in the history. + currentWebView.goForward(); + } + } else if (menuItemId == R.id.history) { // History. + // Instantiate the URL history dialog. + DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId()); - // Make it so. - startActivity(aboutIntent); - break; - } + // Show the URL history dialog. + urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history)); + } else if (menuItemId == R.id.open) { // Open. + // Instantiate the open file dialog. + DialogFragment openDialogFragment = new OpenDialog(); + + // Show the open file dialog. + openDialogFragment.show(getSupportFragmentManager(), getString(R.string.open)); + } else if (menuItemId == R.id.requests) { // Requests. + // Populate the resource requests. + RequestsActivity.resourceRequests = currentWebView.getResourceRequests(); + + // Create an intent to launch the Requests activity. + Intent requestsIntent = new Intent(this, RequestsActivity.class); + + // Add the block third-party requests status to the intent. + requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); + + // Make it so. + startActivity(requestsIntent); + } else if (menuItemId == R.id.downloads) { // Downloads. + // Launch the system Download Manager. + Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS); + + // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list. + downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - // Get a handle for the drawer layout. - DrawerLayout drawerLayout = findViewById(R.id.drawerlayout); + // Make it so. + startActivity(downloadManagerIntent); + } else if (menuItemId == R.id.domains) { // Domains. + // Set the flag to reapply the domain settings on restart when returning from Domain Settings. + reapplyDomainSettingsOnRestart = true; + + // Launch the domains activity. + Intent domainsIntent = new Intent(this, DomainsActivity.class); + + // Add the extra information to the intent. + domainsIntent.putExtra("current_url", currentWebView.getUrl()); + + // Get the current certificate. + SslCertificate sslCertificate = currentWebView.getCertificate(); + + // Check to see if the SSL certificate is populated. + if (sslCertificate != null) { + // Extract the certificate to strings. + String issuedToCName = sslCertificate.getIssuedTo().getCName(); + String issuedToOName = sslCertificate.getIssuedTo().getOName(); + String issuedToUName = sslCertificate.getIssuedTo().getUName(); + String issuedByCName = sslCertificate.getIssuedBy().getCName(); + String issuedByOName = sslCertificate.getIssuedBy().getOName(); + String issuedByUName = sslCertificate.getIssuedBy().getUName(); + long startDateLong = sslCertificate.getValidNotBeforeDate().getTime(); + long endDateLong = sslCertificate.getValidNotAfterDate().getTime(); + + // Add the certificate to the intent. + domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName); + domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName); + domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName); + domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName); + domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName); + domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName); + domainsIntent.putExtra("ssl_start_date", startDateLong); + domainsIntent.putExtra("ssl_end_date", endDateLong); + } + + // Check to see if the current IP addresses have been received. + if (currentWebView.hasCurrentIpAddresses()) { + // Add the current IP addresses to the intent. + domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); + } + + // Make it so. + startActivity(domainsIntent); + } else if (menuItemId == R.id.settings) { // Settings. + // Set the flag to reapply app settings on restart when returning from Settings. + reapplyAppSettingsOnRestart = true; + + // Set the flag to reapply the domain settings on restart when returning from Settings. + reapplyDomainSettingsOnRestart = true; + + // Launch the settings activity. + Intent settingsIntent = new Intent(this, SettingsActivity.class); + startActivity(settingsIntent); + } else if (menuItemId == R.id.import_export) { // Import/Export. + // Create an intent to launch the import/export activity. + Intent importExportIntent = new Intent(this, ImportExportActivity.class); + + // Make it so. + startActivity(importExportIntent); + } else if (menuItemId == R.id.logcat) { // Logcat. + // Create an intent to launch the logcat activity. + Intent logcatIntent = new Intent(this, LogcatActivity.class); + + // Make it so. + startActivity(logcatIntent); + } else if (menuItemId == R.id.guide) { // Guide. + // Create an intent to launch the guide activity. + Intent guideIntent = new Intent(this, GuideActivity.class); + + // Make it so. + startActivity(guideIntent); + } else if (menuItemId == R.id.about) { // About + // Create an intent to launch the about activity. + Intent aboutIntent = new Intent(this, AboutActivity.class); + + // 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], + ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]}; + + // Add the blocklist versions to the intent. + aboutIntent.putExtra("blocklist_versions", blocklistVersions); + + // Make it so. + startActivity(aboutIntent); + } // Close the navigation drawer. drawerLayout.closeDrawer(GravityCompat.START); @@ -2054,7 +2068,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { - // Store the hit test result. + // Get the hit test result. final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult(); // Define the URL strings. @@ -2142,8 +2156,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the image URL. imageUrl = hitTestResult.getExtra(); - // Set the image URL as the title of the context menu. - menu.setHeaderTitle(imageUrl); + // Remove the incorrect lint warning below that the image URL might be null. + assert imageUrl != null; + + // Set the context menu title. + if (imageUrl.startsWith("data:")) { // The image data is contained in within the URL, making it exceedingly long. + // Truncate the image URL before making it the title. + menu.setHeaderTitle(imageUrl.substring(0, 100)); + } else { // The image URL does not contain the full image data. + // Set the image URL as the title of the context menu. + menu.setHeaderTitle(imageUrl); + } // Add an Open in New Tab entry. menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> { @@ -2335,8 +2358,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser. emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - // Make it so. - startActivity(emailIntent); + try { + // Make it so. + startActivity(emailIntent); + } catch (ActivityNotFoundException exception) { + // Display a snackbar. + Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show(); + } // Consume the event. return true; @@ -2471,13 +2499,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) { + // Remove the incorrect lint warning below that the dialog fragment might be null. + assert dialogFragment != null; + // 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`. + // Get handles for the views from the dialog. 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); @@ -2557,27 +2588,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook bookmarksCursorAdapter.changeCursor(bookmarksCursor); } - // Override `onBackPressed` to handle the navigation drawer and and the WebViews. + // 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. - DrawerLayout drawerLayout = findViewById(R.id.drawerlayout); - TabLayout tabLayout = findViewById(R.id.tablayout); - + // Check the different options for processing `back`. if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open. // Close the navigation drawer. drawerLayout.closeDrawer(GravityCompat.START); } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open. - if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed. - // close the bookmarks drawer. - drawerLayout.closeDrawer(GravityCompat.END); - } else { // A subfolder is displayed. - // Place the former parent folder in `currentFolder`. - currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder); - - // Load the new folder. - loadBookmarksFolder(); - } + // close the bookmarks drawer. + drawerLayout.closeDrawer(GravityCompat.END); } else if (displayingFullScreenVideo) { // A full screen video is shown. // Get a handle for the layouts. FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout); @@ -2641,7 +2661,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl(); // Apply the domain settings. - applyDomainSettings(currentWebView, previousUrl, false, false); + applyDomainSettings(currentWebView, previousUrl, false, false, false); // Go back. currentWebView.goBack(); @@ -2827,11 +2847,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Sanitize the URL. url = sanitizeUrl(url); - // Apply the domain settings. - applyDomainSettings(nestedScrollWebView, url, true, false); - - // Load the URL. - nestedScrollWebView.loadUrl(url, customHeaders); + // Apply the domain settings and load the URL. + applyDomainSettings(nestedScrollWebView, url, true, false, true); } public void findPreviousOnPage(View view) { @@ -2876,6 +2893,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onApplyNewFontSize(DialogFragment dialogFragment) { + // Remove the incorrect lint warning below that the dialog fragment might be null. + assert dialogFragment != null; + // Get the dialog. Dialog dialog = dialogFragment.getDialog(); @@ -2913,6 +2933,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the file path string. openFilePath = fileNameEditText.getText().toString(); + // Apply the domain settings. This resets the favorite icon and removes any domain settings. + applyDomainSettings(currentWebView, "file://" + openFilePath, true, false, false); + // 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. @@ -2941,14 +2964,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, StoragePermissionDialog.OPEN); } } } } @Override - public void onSaveWebpage(int saveType, DialogFragment dialogFragment) { + public void onSaveWebpage(int saveType, String originalUrlString, DialogFragment dialogFragment) { // Get the dialog. Dialog dialog = dialogFragment.getDialog(); @@ -2959,8 +2982,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook EditText urlEditText = dialog.findViewById(R.id.url_edittext); EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext); - // Get the strings from the edit texts. - saveWebpageUrl = urlEditText.getText().toString(); + // Store the URL. + if ((originalUrlString != null) && originalUrlString.startsWith("data:")) { + // Save the original URL. + saveWebpageUrl = originalUrlString; + } else { + // Get the URL from the edit text, which may have been modified. + saveWebpageUrl = urlEditText.getText().toString(); + } + + // Get the file path from the edit text. saveWebpageFilePath = fileNameEditText.getText().toString(); // Check to see if the storage permission is needed. @@ -2972,16 +3003,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); break; - case StoragePermissionDialog.SAVE_AS_ARCHIVE: + case StoragePermissionDialog.SAVE_ARCHIVE: // Save the webpage archive. - currentWebView.saveWebArchive(saveWebpageFilePath); + saveWebpageArchive(saveWebpageFilePath); break; - case StoragePermissionDialog.SAVE_AS_IMAGE: + case StoragePermissionDialog.SAVE_IMAGE: // Save the webpage image. - new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath); + new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute(); break; } + + // Reset the strings. + saveWebpageUrl = ""; + saveWebpageFilePath = ""; } else { // The storage permission has not been granted. // Get the external private directory file. File externalPrivateDirectoryFile = getExternalFilesDir(null); @@ -3001,286 +3036,97 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); break; - case StoragePermissionDialog.SAVE_AS_ARCHIVE: + case StoragePermissionDialog.SAVE_ARCHIVE: // Save the webpage archive. - currentWebView.saveWebArchive(saveWebpageFilePath); - break; - - case StoragePermissionDialog.SAVE_AS_IMAGE: - // Save the webpage image. - new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath); + saveWebpageArchive(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_URL: - // Request the write external storage permission. The URL will be saved when it finishes. - ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_URL_REQUEST_CODE); - - case StoragePermissionDialog.SAVE_AS_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_AS_ARCHIVE_REQUEST_CODE); - break; - - case StoragePermissionDialog.SAVE_AS_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_AS_IMAGE_REQUEST_CODE); - break; - } - } - } - } - } - - @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_URL: - // Request the write external storage permission. The URL will be saved when it finishes. - ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_URL_REQUEST_CODE); - break; - - case StoragePermissionDialog.SAVE_AS_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_AS_ARCHIVE_REQUEST_CODE); - break; - - case StoragePermissionDialog.SAVE_AS_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_AS_IMAGE_REQUEST_CODE); - break; - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - //Only process the results if they exist (this method is triggered when a dialog is presented the first time for an app, but no grant results are included). - if (grantResults.length > 0) { - switch (requestCode) { - 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[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(); - } - - // Reset the open file path. - openFilePath = ""; - break; - - case PERMISSION_SAVE_URL_REQUEST_CODE: - // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted. - // Save the raw URL. - new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); - } 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 strings. - saveWebpageUrl = ""; - saveWebpageFilePath = ""; - break; - - case PERMISSION_SAVE_AS_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[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(); - } - - // Reset the save webpage file path. - saveWebpageFilePath = ""; - break; - - case PERMISSION_SAVE_AS_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[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() { - // Initialize the app if this is the first run. This is done here instead of in `onCreate()` to shorten the time that an unthemed background is displayed on app startup. - if (webViewDefaultUserAgent == null) { - initializeApp(); - } - - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - // Store the values from the shared preferences in variables. - incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false); - boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false); - sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true); - sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true); - sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true); - 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 a handle for the app compat delegate. - AppCompatDelegate appCompatDelegate = getDelegate(); - - // Get handles for the views that need to be modified. - FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout); - AppBarLayout appBarLayout = findViewById(R.id.appbar_layout); - ActionBar actionBar = appCompatDelegate.getSupportActionBar(); - Toolbar toolbar = findViewById(R.id.toolbar); - LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout); - LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout); - SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); - - // Remove the incorrect lint warning below that the action bar might be null. - assert actionBar != null; - - // Apply the proxy. - applyProxy(false); - - // Set Do Not Track status. - if (doNotTrackEnabled) { - customHeaders.put("DNT", "1"); - } else { - customHeaders.remove("DNT"); - } - - // Get the current layout parameters. Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command. - CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams(); - AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams(); - AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams(); - AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams(); - - // Add the scrolling behavior to the layout parameters. - if (scrollAppBar) { - // Enable scrolling of the app bar. - swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior()); - toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP); - findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP); - tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP); - } else { - // Disable scrolling of the app bar. - swipeRefreshLayoutParams.setBehavior(null); - toolbarLayoutParams.setScrollFlags(0); - findOnPageLayoutParams.setScrollFlags(0); - tabsLayoutParams.setScrollFlags(0); - - // Expand the app bar if it is currently collapsed. - appBarLayout.setExpanded(true); - } - - // Apply the modified layout parameters. - swipeRefreshLayout.setLayoutParams(swipeRefreshLayoutParams); - toolbar.setLayoutParams(toolbarLayoutParams); - findOnPageLinearLayout.setLayoutParams(findOnPageLayoutParams); - tabsLinearLayout.setLayoutParams(tabsLayoutParams); - - // Set the app bar scrolling for each WebView. - for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - // Only modify 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); - - // Set the app bar scrolling. - nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar); - } - } - // Update the full screen browsing mode settings. - if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode. - // Update the visibility of the app bar, which might have changed in the settings. - if (hideAppBar) { - // Hide the tab linear layout. - tabsLinearLayout.setVisibility(View.GONE); + case StoragePermissionDialog.SAVE_IMAGE: + // Save the webpage image. + new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute(); + break; + } - // Hide the action bar. - actionBar.hide(); - } else { - // Show the tab linear layout. - tabsLinearLayout.setVisibility(View.VISIBLE); + // Reset the strings. + saveWebpageUrl = ""; + saveWebpageFilePath = ""; + } 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 action bar. - actionBar.show(); + // 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. + // Request the write external storage permission according to the save type. The URL will be saved when it finishes. + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, saveType); + } } + } + } - // Hide the banner ad in the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - AdHelper.hideAd(findViewById(R.id.adview)); - } + @Override + public void onCloseStoragePermissionDialog(int requestType) { + // Request the write external storage permission according to the request type. The file will be opened when it finishes. + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestType); - /* 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 { // Privacy Browser is not in full screen browsing mode. - // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled. - inFullScreenBrowsingMode = false; + } - // Show the tab linear layout. - tabsLinearLayout.setVisibility(View.VISIBLE); + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + //Only process the results if they exist (this method is triggered when a dialog is presented the first time for an app, but no grant results are included). + if (grantResults.length > 0) { + switch (requestCode) { + case StoragePermissionDialog.OPEN: + // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. + if (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(); + } + break; - // Show the action bar. - actionBar.show(); + case StoragePermissionDialog.SAVE_URL: + // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted. + // Save the raw URL. + new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); + } else { // The storage permission was not granted. + // Display an error snackbar. + Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); + } + break; - // Show the banner ad in the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - // Initialize the ads. If this isn't the first run, `loadAd()` will be automatically called instead. - AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getSupportFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id)); + case StoragePermissionDialog.SAVE_ARCHIVE: + // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted. + // Save the webpage archive. + saveWebpageArchive(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(); + } + break; + + case StoragePermissionDialog.SAVE_IMAGE: + // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted. + // Save the webpage image. + new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute(); + } else { // The storage permission was not granted. + // Display an error snackbar. + Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); + } + break; } - // Remove the `SYSTEM_UI` flags from the root frame layout. - rootFrameLayout.setSystemUiVisibility(0); + // Reset the strings. + openFilePath = ""; + saveWebpageUrl = ""; + saveWebpageFilePath = ""; } } @@ -3291,11 +3137,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Remove the lint warning below that the input method manager might be null. assert inputMethodManager != null; - // Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23. - redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700)); + // Initialize the gray foreground color spans for highlighting the URLs. The deprecated `getResources()` must be used until API >= 23. initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500)); finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500)); + // Get the current theme status. + int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + + // Set the red color span according to the theme. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { + redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700)); + } else { + redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_900)); + } + // Get handles for the URL views. EditText urlEditText = findViewById(R.id.url_edittext); @@ -3387,11 +3242,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS")); // Get handles for views that need to be modified. - DrawerLayout drawerLayout = findViewById(R.id.drawerlayout); NavigationView navigationView = findViewById(R.id.navigationview); - TabLayout tabLayout = findViewById(R.id.tablayout); SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); - ViewPager webViewPager = findViewById(R.id.webviewpager); ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview); FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab); FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab); @@ -3401,12 +3253,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 0 based. + // Get handles for the navigation menu and the back and forward menu items. Menu navigationMenu = navigationView.getMenu(); - MenuItem navigationBackMenuItem = navigationMenu.getItem(2); - MenuItem navigationForwardMenuItem = navigationMenu.getItem(3); - MenuItem navigationHistoryMenuItem = navigationMenu.getItem(4); - MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6); + MenuItem navigationBackMenuItem = navigationMenu.findItem(R.id.back); + MenuItem navigationForwardMenuItem = navigationMenu.findItem(R.id.forward); + MenuItem navigationHistoryMenuItem = navigationMenu.findItem(R.id.history); + MenuItem navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests); // Update the web view pager every time a tab is modified. webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @@ -3423,13 +3275,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the current WebView. setCurrentWebView(position); - // Select the corresponding tab if it does not match the currently selected page. This will happen if the page was scrolled via swiping in the view pager or by creating a new tab. + // Select the corresponding tab if it does not match the currently selected page. This will happen if the page was scrolled by creating a new tab. if (tabLayout.getSelectedTabPosition() != position) { - // Create a handler to select the tab. - Handler selectTabHandler = new Handler(); - - // Create a runnable to select the tab. - Runnable selectTabRunnable = () -> { + // Wait until the new tab has been created. + tabLayout.post(() -> { // Get a handle for the tab. TabLayout.Tab tab = tabLayout.getTabAt(position); @@ -3438,10 +3287,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Select the tab. tab.select(); - }; - - // Select the tab layout after 150 milliseconds, which leaves enough time for a new tab to be inflated. - selectTabHandler.postDelayed(selectTabRunnable, 150); + }); } } @@ -3561,14 +3407,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook defaultProgressViewStartOffset = swipeRefreshLayout.getProgressViewStartOffset(); defaultProgressViewEndOffset = swipeRefreshLayout.getProgressViewEndOffset(); - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - // Set the refresh color scheme according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - swipeRefreshLayout.setColorSchemeResources(R.color.blue_500); - } else { + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { swipeRefreshLayout.setColorSchemeResources(R.color.blue_700); + } else { + swipeRefreshLayout.setColorSchemeResources(R.color.violet_500); } // Initialize a color background typed value. @@ -3662,55 +3505,208 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook public void onDrawerSlide(@NonNull View drawerView, float slideOffset) { } - @Override - public void onDrawerOpened(@NonNull View drawerView) { - } + @Override + public void onDrawerOpened(@NonNull View drawerView) { + } + + @Override + public void onDrawerClosed(@NonNull View drawerView) { + } + + @Override + public void onDrawerStateChanged(int newState) { + if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing. + // Update the navigation menu items if the WebView is not null. + if (currentWebView != null) { + navigationBackMenuItem.setEnabled(currentWebView.canGoBack()); + navigationForwardMenuItem.setEnabled(currentWebView.canGoForward()); + navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward())); + navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); + + // Hide the keyboard (if displayed). + inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0); + } + + // Clear the focus from from the URL text box and the WebView. This removes any text selection markers and context menus, which otherwise draw above the open drawers. + urlEditText.clearFocus(); + currentWebView.clearFocus(); + } + } + }); + + // Replace the header that `WebView` creates for `X-Requested-With` with a null value. The default value is the application ID (com.stoutner.privacybrowser.standard). + customHeaders.put("X-Requested-With", ""); + + // Inflate a bare WebView to get the default user agent. It is not used to render content on the screen. + @SuppressLint("InflateParams") View webViewLayout = getLayoutInflater().inflate(R.layout.bare_webview, null, false); + + // Get a handle for the WebView. + WebView bareWebView = webViewLayout.findViewById(R.id.bare_webview); + + // Store the default user agent. + webViewDefaultUserAgent = bareWebView.getSettings().getUserAgentString(); + + // Destroy the bare WebView. + bareWebView.destroy(); + } + + private void applyAppSettings() { + // Get a handle for the shared preferences. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + + // Store the values from the shared preferences in variables. + incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false); + boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false); + sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true); + sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true); + sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true); + 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); + + // Apply the saved proxy mode if the app has been restarted. + if (savedProxyMode != null) { + // Apply the saved proxy mode. + proxyMode = savedProxyMode; + + // Reset the saved proxy mode. + savedProxyMode = null; + } + + // 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 a handle for the app compat delegate. + AppCompatDelegate appCompatDelegate = getDelegate(); + + // Get handles for the views that need to be modified. + FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout); + ActionBar actionBar = appCompatDelegate.getSupportActionBar(); + + // Remove the incorrect lint warning below that the action bar might be null. + assert actionBar != null; + + // Apply the proxy. + applyProxy(false); + + // Set Do Not Track status. + if (doNotTrackEnabled) { + customHeaders.put("DNT", "1"); + } else { + customHeaders.remove("DNT"); + } + + // Get the current layout parameters. Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command. + CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams(); + AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams(); + AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams(); + AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams(); + + // Add the scrolling behavior to the layout parameters. + if (scrollAppBar) { + // Enable scrolling of the app bar. + swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior()); + toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP); + findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP); + tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP); + } else { + // Disable scrolling of the app bar. + swipeRefreshLayoutParams.setBehavior(null); + toolbarLayoutParams.setScrollFlags(0); + findOnPageLayoutParams.setScrollFlags(0); + tabsLayoutParams.setScrollFlags(0); + + // Expand the app bar if it is currently collapsed. + appBarLayout.setExpanded(true); + } + + // Apply the modified layout parameters. + swipeRefreshLayout.setLayoutParams(swipeRefreshLayoutParams); + toolbar.setLayoutParams(toolbarLayoutParams); + findOnPageLinearLayout.setLayoutParams(findOnPageLayoutParams); + tabsLinearLayout.setLayoutParams(tabsLayoutParams); + + // Set the app bar scrolling for each WebView. + for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { + // Get the WebView tab fragment. + WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); + + // Get the fragment view. + View fragmentView = webViewTabFragment.getView(); + + // Only modify 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); + + // Set the app bar scrolling. + nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar); + } + } + + // Update the full screen browsing mode settings. + if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode. + // Update the visibility of the app bar, which might have changed in the settings. + if (hideAppBar) { + // Hide the tab linear layout. + tabsLinearLayout.setVisibility(View.GONE); + + // Hide the action bar. + actionBar.hide(); + } else { + // Show the tab linear layout. + tabsLinearLayout.setVisibility(View.VISIBLE); - @Override - public void onDrawerClosed(@NonNull View drawerView) { + // Show the action bar. + actionBar.show(); } - @Override - public void onDrawerStateChanged(int newState) { - if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing. - // Update the navigation menu items if the WebView is not null. - if (currentWebView != null) { - navigationBackMenuItem.setEnabled(currentWebView.canGoBack()); - navigationForwardMenuItem.setEnabled(currentWebView.canGoForward()); - navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward())); - navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - - // Hide the keyboard (if displayed). - inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0); - } - - // Clear the focus from from the URL text box and the WebView. This removes any text selection markers and context menus, which otherwise draw above the open drawers. - urlEditText.clearFocus(); - currentWebView.clearFocus(); - } + // Hide the banner ad in the free flavor. + if (BuildConfig.FLAVOR.contentEquals("free")) { + AdHelper.hideAd(findViewById(R.id.adview)); } - }); - // Replace the header that `WebView` creates for `X-Requested-With` with a null value. The default value is the application ID (com.stoutner.privacybrowser.standard). - customHeaders.put("X-Requested-With", ""); + /* 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 { // Privacy Browser is not in full screen browsing mode. + // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled. + inFullScreenBrowsingMode = false; - // Inflate a bare WebView to get the default user agent. It is not used to render content on the screen. - @SuppressLint("InflateParams") View webViewLayout = getLayoutInflater().inflate(R.layout.bare_webview, null, false); + // Show the tab linear layout. + tabsLinearLayout.setVisibility(View.VISIBLE); - // Get a handle for the WebView. - WebView bareWebView = webViewLayout.findViewById(R.id.bare_webview); + // Show the action bar. + actionBar.show(); - // Store the default user agent. - webViewDefaultUserAgent = bareWebView.getSettings().getUserAgentString(); + // Show the banner ad in the free flavor. + if (BuildConfig.FLAVOR.contentEquals("free")) { + // Initialize the ads. If this isn't the first run, `loadAd()` will be automatically called instead. + AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getSupportFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id)); + } - // Destroy the bare WebView. - bareWebView.destroy(); + // Remove the `SYSTEM_UI` flags from the root frame layout. + rootFrameLayout.setSystemUiVisibility(0); + } } @Override public void navigateHistory(String url, int steps) { // Apply the domain settings. - applyDomainSettings(currentWebView, url, false, false); + applyDomainSettings(currentWebView, url, false, false, false); // Load the history entry. currentWebView.goBackOrForward(steps); @@ -3725,7 +3721,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl(); // Apply the domain settings. - applyDomainSettings(currentWebView, previousUrl, false, false); + applyDomainSettings(currentWebView, previousUrl, false, false, false); // Go back. currentWebView.goBack(); @@ -3733,7 +3729,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled. @SuppressLint("SetJavaScriptEnabled") - private void applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetTab, boolean reloadWebsite) { + private void applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetTab, boolean reloadWebsite, boolean loadUrl) { // Store the current URL. nestedScrollWebView.setCurrentUrl(url); @@ -3768,9 +3764,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the current page position. int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); - // Get a handle for the tab layout. - TabLayout tabLayout = findViewById(R.id.tablayout); - // Get the corresponding tab. TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition); @@ -3855,9 +3848,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value)); String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value)); boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true); + String webViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value)); boolean wideViewport = sharedPreferences.getBoolean("wide_viewport", true); boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true); + // Get the WebView theme entry values string array. + String[] webViewThemeEntryValuesStringArray = getResources().getStringArray(R.array.webview_theme_entry_values); + // Get a handle for the cookie manager. CookieManager cookieManager = CookieManager.getInstance(); @@ -3911,22 +3908,26 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook boolean pinnedIpAddresses = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1); String pinnedHostIpAddresses = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES)); - // Create the pinned SSL date variables. + // Get the pinned SSL date longs. + long pinnedSslStartDateLong = currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)); + long pinnedSslEndDateLong = currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)); + + // Define the pinned SSL date variables. Date pinnedSslStartDate; Date pinnedSslEndDate; - // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0 because creating a new Date results in an error if the input is 0. - if (currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) { + // Set the pinned SSL certificate start date to `null` if the saved date long is 0 because creating a new date results in an error if the input is 0. + if (pinnedSslStartDateLong == 0) { pinnedSslStartDate = null; } else { - pinnedSslStartDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE))); + pinnedSslStartDate = new Date(pinnedSslStartDateLong); } - // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0 because creating a new Date results in an error if the input is 0. - if (currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) { + // Set the pinned SSL certificate end date to `null` if the saved date long is 0 because creating a new date results in an error if the input is 0. + if (pinnedSslEndDateLong == 0) { pinnedSslEndDate = null; } else { - pinnedSslEndDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE))); + pinnedSslEndDate = new Date(pinnedSslEndDateLong); } // Close the current host domain settings cursor. @@ -4052,16 +4053,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the WebView theme. switch (webViewThemeInt) { case DomainsDatabaseHelper.SYSTEM_DEFAULT: - // // Ge the current system theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Set the WebView theme according to the current system theme status. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { // The system is in night mode. - // Turn on the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - } else { // The system is in day mode. + // Set the WebView theme. A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant. + if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) { // The light theme is selected. // Turn off the WebView dark mode. WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); + } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) { // The dark theme is selected. + // Turn on the WebView dark mode. + WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); + } else { // The system default theme is selected. + // Get the current system theme status. + int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + + // Set the WebView theme according to the current system theme status. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { // The system is in day mode. + // Turn off the WebView dark mode. + WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); + } else { // The system is in night mode. + // Turn on the WebView dark mode. + WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); + } } break; @@ -4110,11 +4120,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the current theme status. int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - // Set a background on the URL relative layout to indicate that custom domain settings are being used. The deprecated `.getDrawable()` must be used until the minimum API >= 21. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue)); + // Set a background on the URL relative layout to indicate that custom domain settings are being used. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { + urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_light_green, null)); } else { - urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green)); + urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_dark_blue, null)); } } else { // The new URL does not have custom domain settings. Load the defaults. // Store the values from the shared preferences. @@ -4130,7 +4140,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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)); - String webViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value)); // Apply the default first-party cookie setting. cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies()); @@ -4194,9 +4203,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]); } - // Get the WebView theme entry values string array. - String[] webViewThemeEntryValuesStringArray = getResources().getStringArray(R.array.webview_theme_entry_values); - // Apply the WebView theme if supported by the installed WebView. if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { // Set the WebView theme. A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant. @@ -4211,12 +4217,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; // Set the WebView theme according to the current system theme status. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { // The system is in night mode. - // Turn on the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - } else { // The system is in day mode. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { // The system is in day mode. // Turn off the WebView dark mode. WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); + } else { // The system is in night mode. + // Turn on the WebView dark mode. + WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); } } } @@ -4227,8 +4233,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the loading of webpage images. nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages); - // Set a transparent background on URL edit text. The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21. - urlRelativeLayout.setBackground(getResources().getDrawable(R.color.transparent)); + // Set a transparent background on URL edit text. + urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null)); } // Close the domains database helper. @@ -4242,12 +4248,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook if (reloadWebsite) { nestedScrollWebView.reload(); } + + // Load the URL if directed. This makes sure that the domain settings are properly loaded before the URL. By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent. + if (loadUrl) { + nestedScrollWebView.loadUrl(url, customHeaders); + } } private void applyProxy(boolean reloadWebViews) { - // Get a handle for the app bar layout. - AppBarLayout appBarLayout = findViewById(R.id.appbar_layout); - // 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); @@ -4275,10 +4283,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case ProxyHelper.TOR: // Set the app bar background to indicate proxying through Orbot is enabled. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - appBarLayout.setBackgroundResource(R.color.dark_blue_30); - } else { + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { appBarLayout.setBackgroundResource(R.color.blue_50); + } else { + appBarLayout.setBackgroundResource(R.color.dark_blue_30); } // Check to see if Orbot is installed. @@ -4317,10 +4325,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case ProxyHelper.I2P: // Set the app bar background to indicate proxying through Orbot is enabled. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - appBarLayout.setBackgroundResource(R.color.dark_blue_30); - } else { + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { appBarLayout.setBackgroundResource(R.color.blue_50); + } else { + appBarLayout.setBackgroundResource(R.color.dark_blue_30); } // Check to see if I2P is installed. @@ -4344,10 +4352,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case ProxyHelper.CUSTOM: // Set the app bar background to indicate proxying through Orbot is enabled. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - appBarLayout.setBackgroundResource(R.color.dark_blue_30); - } else { + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { appBarLayout.setBackgroundResource(R.color.blue_50); + } else { + appBarLayout.setBackgroundResource(R.color.dark_blue_30); } break; } @@ -4399,10 +4407,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook if (currentWebView.getAcceptFirstPartyCookies()) { // First-party cookies are enabled. firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled); } else { // First-party cookies are disabled. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_night); - } else { + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_day); + } else { + firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_night); } } @@ -4410,24 +4418,24 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook if (currentWebView.getSettings().getJavaScriptEnabled() && currentWebView.getSettings().getDomStorageEnabled()) { // Both JavaScript and DOM storage are enabled. domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled); } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is enabled but DOM storage is disabled. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_night); - } else { + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_day); + } else { + domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_night); } } else { // JavaScript is disabled, so DOM storage is ghosted. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_night); - } else { + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_day); + } else { + domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_night); } } // Update the refresh icon. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - refreshMenuItem.setIcon(R.drawable.refresh_enabled_night); - } else { + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { refreshMenuItem.setIcon(R.drawable.refresh_enabled_day); + } else { + refreshMenuItem.setIcon(R.drawable.refresh_enabled_night); } // `invalidateOptionsMenu()` calls `onPrepareOptionsMenu()` and redraws the icons in the app bar. @@ -4653,8 +4661,95 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook ultraList = combinedBlocklists.get(4); ultraPrivacy = combinedBlocklists.get(5); - // Add the first tab. - addNewTab("", true); + // Check to see if the activity has been restarted. + if ((savedStateArrayList == null) || (savedStateArrayList.size() == 0)) { // The activity has not been restarted or it was restarted on start to force the night theme. + // Add the first tab. + addNewTab("", true); + } else { // The activity has been restarted. + // Restore each tab. Once the minimum API >= 24, a `forEach()` command can be used. + for (int i = 0; i < savedStateArrayList.size(); i++) { + // Add a new tab. + tabLayout.addTab(tabLayout.newTab()); + + // Get the new tab. + TabLayout.Tab newTab = tabLayout.getTabAt(i); + + // Remove the lint warning below that the current tab might be null. + assert newTab != null; + + // Set a custom view on the new tab. + newTab.setCustomView(R.layout.tab_custom_view); + + // Add the new page. + webViewPagerAdapter.restorePage(savedStateArrayList.get(i), savedNestedScrollWebViewStateArrayList.get(i)); + } + + // Reset the saved state variables. + savedStateArrayList = null; + savedNestedScrollWebViewStateArrayList = null; + + // Restore the selected tab position. + if (savedTabPosition == 0) { // The first tab is selected. + // Set the first page as the current WebView. + setCurrentWebView(0); + } else { // the first tab is not selected. + // Move to the selected tab. + webViewPager.setCurrentItem(savedTabPosition); + } + + // Get the intent that started the app. + Intent intent = getIntent(); + + // Reset the intent. This prevents a duplicate tab from being created on restart. + setIntent(new Intent()); + + // Get the information from the intent. + String intentAction = intent.getAction(); + Uri intentUriData = intent.getData(); + + // Determine if this is a web search. + boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)); + + // 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); + + // Create a URL string. + String url; + + // If the intent action is a web search, perform the search. + if (isWebSearch) { // The intent is a web search. + // Create an encoded URL string. + String encodedUrlString; + + // 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 the base search URL. + url = searchURL + encodedUrlString; + } else { // The intent should contain a URL. + // Set the intent data as the url. + url = intentUriData.toString(); + } + + // Add a new tab if specified in the preferences. + if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) { // Load the URL in a new tab. + // Set the loading new intent flag. + loadingNewIntent = true; + + // Add a new tab. + addNewTab(url, true); + } else { // Load the URL in the current tab. + // Make it so. + loadUrl(currentWebView, url); + } + } + } } public void addTab(View view) { @@ -4663,13 +4758,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } private void addNewTab(String url, boolean moveToTab) { - // Sanitize the URL. - url = sanitizeUrl(url); - - // Get a handle for the tab layout and the view pager. - TabLayout tabLayout = findViewById(R.id.tablayout); - ViewPager webViewPager = findViewById(R.id.webviewpager); - // Get the new page number. The page numbers are 0 indexed, so the new page number will match the current count. int newTabNumber = tabLayout.getTabCount(); @@ -4690,9 +4778,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } public void closeTab(View view) { - // Get a handle for the tab layout. - TabLayout tabLayout = findViewById(R.id.tablayout); - // Run the command according to the number of tabs. if (tabLayout.getTabCount() > 1) { // There is more than one tab open. // Close the current tab. @@ -4703,10 +4788,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } private void closeCurrentTab() { - // Get handles for the views. - AppBarLayout appBarLayout = findViewById(R.id.appbar_layout); - TabLayout tabLayout = findViewById(R.id.tablayout); - ViewPager webViewPager = findViewById(R.id.webviewpager); + // Pause the current WebView. + currentWebView.onPause(); + + // Pause the current WebView JavaScript timers. + currentWebView.pauseTimers(); // Get the current tab number. int currentTabNumber = tabLayout.getSelectedTabPosition(); @@ -4723,6 +4809,48 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook appBarLayout.setExpanded(true); } + private void saveWebpageArchive(String filePath) { + // Save the webpage archive. + currentWebView.saveWebArchive(filePath); + + // Display a snackbar. + Snackbar saveWebpageArchiveSnackbar = Snackbar.make(currentWebView, getString(R.string.file_saved) + " " + filePath, Snackbar.LENGTH_SHORT); + + // Add an open option to the snackbar. + saveWebpageArchiveSnackbar.setAction(R.string.open, (View view) -> { + // Get a file for the file name string. + File file = new File(filePath); + + // Declare a file URI variable. + Uri fileUri; + + // Get the URI for the file according to the Android version. + if (Build.VERSION.SDK_INT >= 24) { // Use a file provider. + fileUri = FileProvider.getUriForFile(this, getString(R.string.file_provider), file); + } else { // Get the raw file path URI. + fileUri = Uri.fromFile(file); + } + + // Get a handle for the content resolver. + ContentResolver contentResolver = getContentResolver(); + + // Create an open intent with `ACTION_VIEW`. + Intent openIntent = new Intent(Intent.ACTION_VIEW); + + // Set the URI and the MIME type. + openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri)); + + // Allow the app to read the file URI. + openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + // Show the chooser. + startActivity(Intent.createChooser(openIntent, getString(R.string.open))); + }); + + // Show the snackbar. + saveWebpageArchiveSnackbar.show(); + } + private void clearAndExit() { // Get a handle for the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); @@ -4811,6 +4939,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } + // Clear the logcat. + if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_logcat_key), true)) { + try { + // Clear the logcat. `-c` clears the logcat. `-b all` clears all the buffers (instead of just crash, main, and system). + Process process = Runtime.getRuntime().exec("logcat -b all -c"); + + // Wait for the process to finish. + process.waitFor(); + } catch (IOException|InterruptedException exception) { + // Do nothing. + } + } + // Clear the cache. if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) { // Clear the cache from each WebView. @@ -4867,7 +5008,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Clear the back/forward history for this WebView. nestedScrollWebView.clearHistory(); - // Destroy the internal state of `mainWebView`. + // Destroy the internal state of the WebView. nestedScrollWebView.destroy(); } } @@ -4900,6 +5041,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook System.exit(0); } + public void bookmarksBack(View view) { + if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed. + // close the bookmarks drawer. + drawerLayout.closeDrawer(GravityCompat.END); + } else { // A subfolder is displayed. + // Place the former parent folder in `currentFolder`. + currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder); + + // Load the new folder. + loadBookmarksFolder(); + } + } + private void setCurrentWebView(int pageNumber) { // Get handles for the URL views. RelativeLayout urlRelativeLayout = findViewById(R.id.url_relativelayout); @@ -4981,14 +5135,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the current theme status. int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - // Set a green background on the URL relative layout to indicate that custom domain settings are being used. The deprecated `.getDrawable()` must be used until the minimum API >= 21. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue)); + // Set a green background on the URL relative layout to indicate that custom domain settings are being used. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { + urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_light_green, null)); } else { - urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green)); + urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_dark_blue, null)); } } else { - urlRelativeLayout.setBackground(getResources().getDrawable(R.color.transparent)); + urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null)); } } else { // The fragment has not been populated. Try again in 100 milliseconds. // Create a handler to set the current WebView. @@ -5006,7 +5160,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @Override - public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, String url) { + public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, String url, Boolean restoringState) { // Get a handle for the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); @@ -5022,6 +5176,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) { // The light theme is selected. // Turn off the WebView dark mode. WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); + + // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode. + // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`. + nestedScrollWebView.setVisibility(View.VISIBLE); } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) { // The dark theme is selected. // Turn on the WebView dark mode. WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); @@ -5030,12 +5188,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; // Set the WebView theme according to the current system theme status. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { // The system is in night mode. - // Turn on the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - } else { // The system is in day mode. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { // The system is in day mode. // Turn off the WebView dark mode. WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); + + // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode. + // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`. + nestedScrollWebView.setVisibility(View.VISIBLE); + } else { // The system is in night mode. + // Turn on the WebView dark mode. + WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); } } } @@ -5045,12 +5207,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get handles for the activity views. FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout); - DrawerLayout drawerLayout = findViewById(R.id.drawerlayout); RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout); ActionBar actionBar = appCompatDelegate.getSupportActionBar(); LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout); EditText urlEditText = findViewById(R.id.url_edittext); - TabLayout tabLayout = findViewById(R.id.tablayout); SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); // Remove the incorrect lint warning below that the action bar might be null. @@ -5113,8 +5273,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Hide the action bar. actionBar.hide(); - // If the app bar is not being scrolled, the swipe refresh layout needs to be adjusted. - if (!scrollAppBar) { + // Check to see if the app bar is normally scrolled. + if (scrollAppBar) { // The app bar is scrolled when it is displayed. + // Get the swipe refresh layout parameters. + CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams(); + + // Remove the off-screen scrolling layout. + swipeRefreshLayoutParams.setBehavior(null); + } else { // The app bar is not scrolled when it is displayed. // Remove the padding from the top of the swipe refresh layout. swipeRefreshLayout.setPadding(0, 0, 0, 0); @@ -5145,8 +5311,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show the action bar. actionBar.show(); - // If the app bar is not being scrolled, the swipe refresh layout needs to be adjusted. - if (!scrollAppBar) { + // Check to see if the app bar is normally scrolled. + if (scrollAppBar) { // The app bar is scrolled when it is displayed. + // Get the swipe refresh layout parameters. + CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams(); + + // Add the off-screen scrolling layout. + swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior()); + } else { // The app bar is not scrolled when it is displayed. // The swipe refresh layout must be manually moved below the app bar layout. swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0); @@ -5200,10 +5372,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Get the file name from the content disposition. - String fileNameString = PrepareSaveDialog.getFileNameFromContentDisposition(this, contentDisposition, downloadUrl); + String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl); // Instantiate the save dialog. - DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent, + DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent, nestedScrollWebView.getAcceptFirstPartyCookies()); // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name. @@ -5302,12 +5474,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook //Stop the swipe to refresh indicator if it is running swipeRefreshLayout.setRefreshing(false); - } - // If this is a new tab, the current WebView would have been created invisible in `webview_framelayout` to prevent a white background splash in night mode. - if (progress >= 50) { - // Make the current WebView visible. - currentWebView.setVisibility(View.VISIBLE); + // Make the current WebView visible. If this is a new tab, the current WebView would have been created invisible in `webview_framelayout` to prevent a white background splash in night mode. + nestedScrollWebView.setVisibility(View.VISIBLE); } } @@ -5356,19 +5525,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; + // Only populate the title text view if the tab view has been fully populated. + if (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 according to the URL. - if (title.equals("about:blank")) { - // Set the title to indicate a new tab. - tabTitleTextView.setText(R.string.new_tab); - } else { - // Set the title as the tab text. - tabTitleTextView.setText(title); + // Set the title according to the URL. + if (title.equals("about:blank")) { + // Set the title to indicate a new tab. + tabTitleTextView.setText(R.string.new_tab); + } else { + // Set the title as the tab text. + tabTitleTextView.setText(title); + } } } } @@ -5522,11 +5691,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Handle the URL according to the type. if (url.startsWith("http")) { // Load the URL in Privacy Browser. - // Apply the domain settings for the new URL. This doesn't do anything if the domain has not changed. - applyDomainSettings(nestedScrollWebView, url, true, false); - // Load the URL. By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent. - nestedScrollWebView.loadUrl(url, customHeaders); + loadUrl(nestedScrollWebView, url); // Returning true indicates that Privacy Browser is manually handling the loading of the URL. // Custom headers cannot be added if false is returned and the WebView handles the loading of the URL. @@ -5541,8 +5707,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Open the email program in a new task instead of as part of Privacy Browser. emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - // Make it so. - startActivity(emailIntent); + try { + // Make it so. + startActivity(emailIntent); + } catch (ActivityNotFoundException exception) { + // Display a snackbar. + Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show(); + } + // Returning true indicates Privacy Browser is handling the URL by creating an intent. return true; @@ -5556,8 +5728,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Open the dialer in a new task instead of as part of Privacy Browser. dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - // Make it so. - startActivity(dialIntent); + try { + // Make it so. + startActivity(dialIntent); + } catch (ActivityNotFoundException exception) { + // Display a snackbar. + Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show(); + } // Returning true indicates Privacy Browser is handling the URL by creating an intent. return true; @@ -5617,8 +5794,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get a handle for the navigation menu. Menu navigationMenu = navigationView.getMenu(); - // Get a handle for the navigation requests menu item. The menu is 0 based. - MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6); + // Get a handle for the navigation requests menu item. + MenuItem navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests); // 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())); @@ -5960,9 +6137,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the preferences. boolean scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true); - // Get a handler for the app bar layout. - AppBarLayout appBarLayout = findViewById(R.id.appbar_layout); - // Set the top padding of the swipe refresh layout according to the app bar scrolling preference. This can't be done in `appAppSettings()` because the app bar is not yet populated there. if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) { // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior. @@ -5987,22 +6161,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reset the requests counters. nestedScrollWebView.resetRequestsCounters(); - // Hide the keyboard. - inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0); - // 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(); - + // Update the URL text bar if the page is currently selected and the URL edit text is not currently being edited. + if ((tabLayout.getSelectedTabPosition() == currentPagePosition) && !urlEditText.hasFocus()) { // Display the formatted URL text. urlEditText.setText(url); // Apply text highlighting to `urlTextBox`. highlightUrlText(); + + // Hide the keyboard. + inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0); } // Reset the list of host IP addresses. @@ -6031,10 +6202,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; // Set the stop icon according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - refreshMenuItem.setIcon(R.drawable.close_night); - } else { + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { refreshMenuItem.setIcon(R.drawable.close_day); + } else { + refreshMenuItem.setIcon(R.drawable.close_night); } } } @@ -6064,15 +6235,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; // Set the icon according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - refreshMenuItem.setIcon(R.drawable.refresh_enabled_night); - } else { + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { refreshMenuItem.setIcon(R.drawable.refresh_enabled_day); + } else { + refreshMenuItem.setIcon(R.drawable.refresh_enabled_night); } } } - // Clear the cache and history if Incognito Mode is enabled. + // Clear the cache, history, and logcat if Incognito Mode is enabled. if (incognitoModeEnabled) { // Clear the cache. `true` includes disk files. nestedScrollWebView.clearCache(true); @@ -6092,20 +6263,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Delete the secondary `Service Worker` cache directory. // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise. Runtime.getRuntime().exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"}); - } catch (IOException e) { + } catch (IOException exception) { // Do nothing if an error is thrown. } + + // Clear the logcat. + try { + // Clear the logcat. `-c` clears the logcat. `-b all` clears all the buffers (instead of just crash, main, and system). + Runtime.getRuntime().exec("logcat -b all -c"); + } catch (IOException exception) { + // Do nothing. + } } // 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); - } - // 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(); @@ -6128,7 +6301,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook inputMethodManager.showSoftInput(urlEditText, 0); // Apply the domain settings. This clears any settings from the previous domain. - applyDomainSettings(nestedScrollWebView, "", true, false); + applyDomainSettings(nestedScrollWebView, "", true, false, false); // Only populate the title text view if the tab has been fully created. if (tab != null) { @@ -6222,20 +6395,23 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }); - // Check to see if this is the first page. - if (pageNumber == 0) { + // Check to see if the state is being restored. + if (restoringState) { // The state is being restored. + // Resume the nested scroll WebView JavaScript timers. + nestedScrollWebView.resumeTimers(); + } else if (pageNumber == 0) { // The first page is being loaded. // Set this nested scroll WebView as the current WebView. currentWebView = nestedScrollWebView; - // Apply the app settings from the shared preferences. - applyAppSettings(); - // Initialize the URL to load string. String urlToLoadString; // Get the intent that started the app. Intent launchingIntent = getIntent(); + // Reset the intent. This prevents a duplicate tab from being created on restart. + setIntent(new Intent()); + // Get the information from the intent. String launchingIntentAction = launchingIntent.getAction(); Uri launchingIntentUriData = launchingIntent.getData(); @@ -6257,6 +6433,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } else if (launchingIntentUriData != null){ // The intent contains a URL. // Store the URL. urlToLoadString = launchingIntentUriData.toString(); + + // Reset the intent. This prevents a duplicate tab from being created on a subsequent restart if loading an link from a new intent on restart. + // For example, this prevents a duplicate tab if a link is loaded from the Guide after changing the theme in the guide and then changing the theme again in the main activity. + setIntent(new Intent()); + } else if (!url.equals("")) { // The activity has been restarted. + // Load the saved URL. + urlToLoadString = url; } else { // The is no URL in the intent. // Store the homepage to be loaded. urlToLoadString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)); @@ -6269,11 +6452,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook loadUrl(nestedScrollWebView, urlToLoadString); } } else { // This is not the first tab. - // Apply the domain settings. - applyDomainSettings(nestedScrollWebView, url, false, false); - // Load the URL. - nestedScrollWebView.loadUrl(url, customHeaders); + loadUrl(nestedScrollWebView, url); // Set the focus and display the keyboard if the URL is blank. if (url.equals("")) {