X-Git-Url: https://gitweb.stoutner.com/?a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FMainWebViewActivity.java;h=39b95dca65d30d4ab5f35a3b31b1d47fd6969873;hb=514e93baaa8389dc9c5abdb79e68c890c260b8d3;hp=649cf6448889e46b7f4736b570563ffb10072297;hpb=c2b5bdf009503f6761dc830fb65502ad2910c284;p=PrivacyBrowserAndroid.git 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 649cf644..39b95dca 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2022 Soren Stoutner . + * Copyright 2015-2023 Soren Stoutner . * * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner . * @@ -56,7 +56,6 @@ import android.print.PrintManager; import android.provider.DocumentsContract; import android.provider.OpenableColumns; import android.text.Editable; -import android.text.Spanned; import android.text.TextWatcher; import android.text.style.ForegroundColorSpan; import android.util.Patterns; @@ -153,6 +152,7 @@ import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper; import com.stoutner.privacybrowser.helpers.ProxyHelper; import com.stoutner.privacybrowser.helpers.SanitizeUrlHelper; +import com.stoutner.privacybrowser.helpers.UrlHelper; import com.stoutner.privacybrowser.views.NestedScrollWebView; import java.io.ByteArrayInputStream; @@ -240,11 +240,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`. private ActionBarDrawerToggle actionBarDrawerToggle; - // The color spans are used in `onCreate()` and `highlightUrlText()`. - private ForegroundColorSpan redColorSpan; - private ForegroundColorSpan initialGrayColorSpan; - private ForegroundColorSpan finalGrayColorSpan; - // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`. private Cursor bookmarksCursor; @@ -263,7 +258,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private BookmarksDatabaseHelper bookmarksDatabaseHelper; private DomainsDatabaseHelper domainsDatabaseHelper; private ProxyHelper proxyHelper; - private SanitizeUrlHelper sanitizeUrlHelper; // Declare the class variables private boolean bookmarksDrawerPinned; @@ -271,14 +265,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private boolean displayAdditionalAppBarIcons; private boolean displayingFullScreenVideo; private boolean downloadWithExternalApp; + private ForegroundColorSpan finalGrayColorSpan; private boolean fullScreenBrowsingModeEnabled; private boolean hideAppBar; - private boolean incognitoModeEnabled; private boolean inFullScreenBrowsingMode; + private boolean incognitoModeEnabled; + private ForegroundColorSpan initialGrayColorSpan; private boolean loadingNewIntent; private BroadcastReceiver orbotStatusBroadcastReceiver; private boolean reapplyAppSettingsOnRestart; private boolean reapplyDomainSettingsOnRestart; + private ForegroundColorSpan redColorSpan; private boolean sanitizeAmpRedirects; private boolean sanitizeTrackingQueries; private boolean scrollAppBar; @@ -485,16 +482,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Populate the result launcher activity. This will no longer be needed once the activity has transitioned to Kotlin. resultLauncherActivityHandle = this; - // Check to see if the activity has been restarted. - if (savedInstanceState != null) { - // Store the saved instance state variables. - bookmarksDrawerPinned = savedInstanceState.getBoolean(BOOKMARKS_DRAWER_PINNED); - 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); @@ -510,6 +497,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the theme entry values string array. String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values); + // Get the current theme status. + int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + // Set the app theme according to the preference. A switch statement cannot be used because the theme entry values string array is not a compile time constant. if (appTheme.equals(appThemeEntryValuesStringArray[1])) { // The light theme is selected. // Apply the light theme. @@ -527,143 +517,161 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Disable screenshots if not allowed. - if (!allowScreenshots) { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); - } + // Do not continue if the app theme is different than the OS theme. The app always initially starts in the OS theme. + // If the user has specified the opposite theme should be used, the app will restart in that mode after the above `setDefaultNightMode()` code processes. However, the restart is delayed. + // If the blacklist coroutine starts below it will continue to run during the restart, which leads to indeterminate behavior, with the system often not knowing how many tabs exist. + // See https://redmine.stoutner.com/issues/952. + if (appTheme.equals(appThemeEntryValuesStringArray[0]) || // The system default theme is used. + (appTheme.equals(appThemeEntryValuesStringArray[1]) && currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) || // The app is running in day theme as desired. + (appTheme.equals(appThemeEntryValuesStringArray[2]) && currentThemeStatus == Configuration.UI_MODE_NIGHT_YES)) { // The app is running in night theme as desired. + + // Disable screenshots if not allowed. + if (!allowScreenshots) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); + } - // Enable the drawing of the entire webpage. This makes it possible to save a website image. This must be done before anything else happens with the WebView. - WebView.enableSlowWholeDocumentDraw(); + // Check to see if the activity has been restarted. + if (savedInstanceState != null) { + // Store the saved instance state variables. + bookmarksDrawerPinned = savedInstanceState.getBoolean(BOOKMARKS_DRAWER_PINNED); + 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); + } - // Set the content view according to the position of the app bar. - if (bottomAppBar) setContentView(R.layout.main_framelayout_bottom_appbar); - else setContentView(R.layout.main_framelayout_top_appbar); + // Enable the drawing of the entire webpage. This makes it possible to save a website image. This must be done before anything else happens with the WebView. + WebView.enableSlowWholeDocumentDraw(); - // Get handles for the views. - rootFrameLayout = findViewById(R.id.root_framelayout); - drawerLayout = findViewById(R.id.drawerlayout); - coordinatorLayout = findViewById(R.id.coordinatorlayout); - 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); - NavigationView navigationView = findViewById(R.id.navigationview); - bookmarksDrawerPinnedImageView = findViewById(R.id.bookmarks_drawer_pinned_imageview); - fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout); + // Set the content view according to the position of the app bar. + if (bottomAppBar) setContentView(R.layout.main_framelayout_bottom_appbar); + else setContentView(R.layout.main_framelayout_top_appbar); - // Get a handle for the navigation menu. - Menu navigationMenu = navigationView.getMenu(); + // Get handles for the views. + rootFrameLayout = findViewById(R.id.root_framelayout); + drawerLayout = findViewById(R.id.drawerlayout); + coordinatorLayout = findViewById(R.id.coordinatorlayout); + 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); + NavigationView navigationView = findViewById(R.id.navigationview); + bookmarksDrawerPinnedImageView = findViewById(R.id.bookmarks_drawer_pinned_imageview); + fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout); - // Get handles for the navigation menu items. - navigationBackMenuItem = navigationMenu.findItem(R.id.back); - navigationForwardMenuItem = navigationMenu.findItem(R.id.forward); - navigationHistoryMenuItem = navigationMenu.findItem(R.id.history); - navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests); + // Get a handle for the navigation menu. + Menu navigationMenu = navigationView.getMenu(); - // Listen for touches on the navigation menu. - navigationView.setNavigationItemSelectedListener(this); + // Get handles for the navigation menu items. + navigationBackMenuItem = navigationMenu.findItem(R.id.back); + navigationForwardMenuItem = navigationMenu.findItem(R.id.forward); + navigationHistoryMenuItem = navigationMenu.findItem(R.id.history); + navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests); - // Get a handle for the app compat delegate. - AppCompatDelegate appCompatDelegate = getDelegate(); + // Listen for touches on the navigation menu. + navigationView.setNavigationItemSelectedListener(this); - // Set the support action bar. - appCompatDelegate.setSupportActionBar(toolbar); + // Get a handle for the app compat delegate. + AppCompatDelegate appCompatDelegate = getDelegate(); - // Get a handle for the action bar. - actionBar = appCompatDelegate.getSupportActionBar(); + // Set the support action bar. + appCompatDelegate.setSupportActionBar(toolbar); - // Remove the incorrect lint warning below that the action bar might be null. - assert actionBar != null; + // Get a handle for the action bar. + actionBar = appCompatDelegate.getSupportActionBar(); - // Add the custom layout, which shows the URL text bar. - actionBar.setCustomView(R.layout.url_app_bar); - actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); + // Remove the incorrect lint warning below that the action bar might be null. + assert actionBar != null; - // Get handles for the views in the URL app bar. - urlRelativeLayout = findViewById(R.id.url_relativelayout); - urlEditText = findViewById(R.id.url_edittext); + // Add the custom layout, which shows the URL text bar. + actionBar.setCustomView(R.layout.url_app_bar); + actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); - // Create the hamburger icon at the start of the AppBar. - actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer); + // Get handles for the views in the URL app bar. + urlRelativeLayout = findViewById(R.id.url_relativelayout); + urlEditText = findViewById(R.id.url_edittext); - // Initially disable the sliding drawers. They will be enabled once the blocklists are loaded. - drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + // Create the hamburger icon at the start of the AppBar. + actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer); - // Initially hide the user interface so that only the blocklist loading screen is shown (if reloading). - drawerLayout.setVisibility(View.GONE); + // Initially disable the sliding drawers. They will be enabled once the blocklists are loaded. + drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); - // Initialize the web view pager adapter. - webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager()); + // Initially hide the user interface so that only the blocklist loading screen is shown (if reloading). + drawerLayout.setVisibility(View.GONE); - // Set the pager adapter on the web view pager. - webViewPager.setAdapter(webViewPagerAdapter); + // Initialize the web view pager adapter. + webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager()); - // Store up to 100 tabs in memory. - webViewPager.setOffscreenPageLimit(100); + // Set the pager adapter on the web view pager. + webViewPager.setAdapter(webViewPagerAdapter); - // Instantiate the helpers. - bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this); - domainsDatabaseHelper = new DomainsDatabaseHelper(this); - proxyHelper = new ProxyHelper(); - sanitizeUrlHelper = new SanitizeUrlHelper(); + // Store up to 100 tabs in memory. + webViewPager.setOffscreenPageLimit(100); - // Update the bookmarks drawer pinned image view. - updateBookmarksDrawerPinnedImageView(); + // Instantiate the helpers. + bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this); + domainsDatabaseHelper = new DomainsDatabaseHelper(this); + proxyHelper = new ProxyHelper(); - // Initialize the app. - initializeApp(); + // Update the bookmarks drawer pinned image view. + updateBookmarksDrawerPinnedImageView(); - // Apply the app settings from the shared preferences. - applyAppSettings(); + // Initialize the app. + initializeApp(); - // Control what the system back command does. - OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - // Process the different back options. - 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. - // close the bookmarks drawer. - drawerLayout.closeDrawer(GravityCompat.END); - } else if (displayingFullScreenVideo) { // A full screen video is shown. - // Exit the full screen video. - exitFullScreenVideo(); - // It shouldn't be possible for the currentWebView to be null, but crash logs indicate it sometimes happens. - } else if ((currentWebView != null) && (currentWebView.canGoBack())) { // There is at least one item in the current WebView history. - // 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); - - // Go back. - currentWebView.goBack(); - } else if (tabLayout.getTabCount() > 1) { // There are at least two tabs. - // Close the current tab. - closeCurrentTab(); - } else { // There isn't anything to do in Privacy Browser. - // Run clear and exit. - clearAndExit(); + // Apply the app settings from the shared preferences. + applyAppSettings(); + + // Control what the system back command does. + OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + // Process the different back options. + 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. + // close the bookmarks drawer. + drawerLayout.closeDrawer(GravityCompat.END); + } else if (displayingFullScreenVideo) { // A full screen video is shown. + // Exit the full screen video. + exitFullScreenVideo(); + // It shouldn't be possible for the currentWebView to be null, but crash logs indicate it sometimes happens. + } else if ((currentWebView != null) && (currentWebView.canGoBack())) { // There is at least one item in the current WebView history. + // 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); + + // Go back. + currentWebView.goBack(); + } else if (tabLayout.getTabCount() > 1) { // There are at least two tabs. + // Close the current tab. + closeCurrentTab(); + } else { // There isn't anything to do in Privacy Browser. + // Run clear and exit. + clearAndExit(); + } } - } - }; + }; - // Register the on back pressed callback. - getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback); + // Register the on back pressed callback. + getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback); - // Instantiate the populate blocklists coroutine. - PopulateBlocklistsCoroutine populateBlocklistsCoroutine = new PopulateBlocklistsCoroutine(this); + // Instantiate the populate blocklists coroutine. + PopulateBlocklistsCoroutine populateBlocklistsCoroutine = new PopulateBlocklistsCoroutine(this); - // Populate the blocklists. - populateBlocklistsCoroutine.populateBlocklists(this); + // Populate the blocklists. + populateBlocklistsCoroutine.populateBlocklists(this); + } } @Override @@ -743,6 +751,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } } else { // The app has been restarted. + // Set the saved tab position to be the size of the saved state array list. The tab position is 0 based, meaning the at the new tab will be the tab position that is restored. + savedTabPosition = savedStateArrayList.size(); + // Replace the intent that started the app with this one. This will load the tab after the others have been restored. setIntent(intent); } @@ -813,21 +824,23 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Run the default commands. super.onStart(); - // Resume any WebViews. - for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); + // Resume any WebViews if the pager adapter exists. If the app is restarting to change the initial app theme it won't have been populated yet. + if (webViewPagerAdapter != null) { + for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { + // Get the WebView tab fragment. + WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); + // Get the fragment view. + View fragmentView = webViewTabFragment.getView(); - // Only resume the WebViews if they exist (they won't when the app is first created). - if (fragmentView != null) { - // Get the nested scroll WebView from the tab fragment. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); + // Only resume the WebViews if they exist (they won't when the app is first created). + if (fragmentView != null) { + // Get the nested scroll WebView from the tab fragment. + NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - // Resume the nested scroll WebView. - nestedScrollWebView.onResume(); + // Resume the nested scroll WebView. + nestedScrollWebView.onResume(); + } } } @@ -872,20 +885,24 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Run the default commands. super.onStop(); - for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); + // Only pause the WebViews if the pager adapter is not null, which is the case if the app is restarting to change the initial app theme. + if (webViewPagerAdapter != null) { + // Pause each web view. + for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { + // Get the WebView tab fragment. + WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); + // Get the fragment view. + View fragmentView = webViewTabFragment.getView(); - // Only pause the WebViews if they exist (they won't when the app is first created). - if (fragmentView != null) { - // Get the nested scroll WebView from the tab fragment. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); + // Only pause the WebViews if they exist (they won't when the app is first created). + if (fragmentView != null) { + // Get the nested scroll WebView from the tab fragment. + NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - // Pause the nested scroll WebView. - nestedScrollWebView.onPause(); + // Pause the nested scroll WebView. + nestedScrollWebView.onPause(); + } } } @@ -900,44 +917,47 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Run the default commands. super.onSaveInstanceState(savedInstanceState); - // Create the saved state array lists. - ArrayList savedStateArrayList = new ArrayList<>(); - ArrayList savedNestedScrollWebViewStateArrayList = new ArrayList<>(); + // Only save the instance state if the WebView pager adapter is not null, which will be the case if the app is restarting to change the initial app theme. + if (webViewPagerAdapter != null) { + // 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 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(); + // 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); + 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(); + // Create saved state bundle. + Bundle savedStateBundle = new Bundle(); - // Get the current states. - nestedScrollWebView.saveState(savedStateBundle); - Bundle savedNestedScrollWebViewStateBundle = nestedScrollWebView.saveNestedScrollWebViewState(); + // 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); + // Store the saved states in the array lists. + savedStateArrayList.add(savedStateBundle); + savedNestedScrollWebViewStateArrayList.add(savedNestedScrollWebViewStateBundle); + } } - } - // Get the current tab position. - int currentTabPosition = tabLayout.getSelectedTabPosition(); + // Get the current tab position. + int currentTabPosition = tabLayout.getSelectedTabPosition(); - // Store the saved states in the bundle. - savedInstanceState.putBoolean(BOOKMARKS_DRAWER_PINNED, bookmarksDrawerPinned); - savedInstanceState.putString(PROXY_MODE, proxyMode); - savedInstanceState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList); - savedInstanceState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList); - savedInstanceState.putInt(SAVED_TAB_POSITION, currentTabPosition); + // Store the saved states in the bundle. + savedInstanceState.putBoolean(BOOKMARKS_DRAWER_PINNED, bookmarksDrawerPinned); + savedInstanceState.putString(PROXY_MODE, proxyMode); + savedInstanceState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList); + savedInstanceState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList); + savedInstanceState.putInt(SAVED_TAB_POSITION, currentTabPosition); + } } @Override @@ -1870,7 +1890,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } 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()); + currentWebView.getFavoriteIcon()); // Show the create home screen shortcut dialog. createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut)); @@ -2265,7 +2285,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook super.onPostCreate(savedInstanceState); // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished. This creates the navigation drawer icon. - actionBarDrawerToggle.syncState(); + // If the app is restarting to change the app theme the action bar drawer toggle will not yet be populated. + if (actionBarDrawerToggle != null) + actionBarDrawerToggle.syncState(); } @Override @@ -2972,7 +2994,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Remove the formatting from the URL edit text when the user is editing the text. urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> { if (hasFocus) { // The user is editing the URL text box. - // Remove the highlighting. + // Remove the syntax highlighting. urlEditText.getText().removeSpan(redColorSpan); urlEditText.getText().removeSpan(initialGrayColorSpan); urlEditText.getText().removeSpan(finalGrayColorSpan); @@ -2980,8 +3002,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Move to the beginning of the string. urlEditText.setSelection(0); - // Reapply the highlighting. - highlightUrlText(); + // Reapply the syntax highlighting. + UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan); } }); @@ -3124,7 +3146,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onTabReselected(TabLayout.Tab tab) { // Instantiate the View SSL Certificate dialog. - DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId(), currentWebView.getFavoriteOrDefaultIcon()); + DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId(), currentWebView.getFavoriteIcon()); // Display the View SSL Certificate dialog. viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate)); @@ -3140,7 +3162,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the launch bookmarks activity FAB to launch the bookmarks activity. launchBookmarksActivityFab.setOnClickListener(v -> { // Get a copy of the favorite icon bitmap. - Bitmap favoriteIconBitmap = currentWebView.getFavoriteOrDefaultIcon(); + Bitmap favoriteIconBitmap = currentWebView.getFavoriteIcon(); // Create a favorite icon byte array output stream. ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream(); @@ -3167,7 +3189,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the create new bookmark folder FAB to display an alert dialog. createBookmarkFolderFab.setOnClickListener(v -> { // Create a create bookmark folder dialog. - DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteOrDefaultIcon()); + DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteIcon()); // Show the create bookmark folder dialog. createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder)); @@ -3176,7 +3198,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the create new bookmark FAB to display an alert dialog. createBookmarkFab.setOnClickListener(view -> { // Instantiate the create bookmark dialog. - DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteOrDefaultIcon()); + DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteIcon()); // Display the create bookmark dialog. createBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.create_bookmark)); @@ -3599,7 +3621,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook TextView tabTitleTextView = tabCustomView.findViewById(R.id.title_textview); // Set the default favorite icon as the favorite icon for this tab. - tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteOrDefaultIcon(), 64, 64, true)); + tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteIcon(), 64, 64, true)); // Set the loading title text. tabTitleTextView.setText(R.string.loading); @@ -4213,64 +4235,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - private void highlightUrlText() { - // Only highlight the URL text if the box is not currently selected. - if (!urlEditText.hasFocus()) { - // Get the URL string. - String urlString = urlEditText.getText().toString(); - - // Highlight the URL according to the protocol. - if (urlString.startsWith("file://") || urlString.startsWith("content://")) { // This is a file or content URL. - // De-emphasize everything before the file name. - urlEditText.getText().setSpan(initialGrayColorSpan, 0, urlString.lastIndexOf("/") + 1,Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { // This is a web URL. - // Get the index of the `/` immediately after the domain name. - int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2)); - - // Create a base URL string. - String baseUrl; - - // Get the base URL. - if (endOfDomainName > 0) { // There is at least one character after the base URL. - // Get the base URL. - baseUrl = urlString.substring(0, endOfDomainName); - } else { // There are no characters after the base URL. - // Set the base URL to be the entire URL string. - baseUrl = urlString; - } - - // Get the index of the last `.` in the domain. - int lastDotIndex = baseUrl.lastIndexOf("."); - - // Get the index of the penultimate `.` in the domain. - int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1); - - // Markup the beginning of the URL. - if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted. - urlEditText.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - // De-emphasize subdomains. - if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name. - urlEditText.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted. - if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name. - // De-emphasize the protocol and the additional subdomains. - urlEditText.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { // There is only one subdomain in the domain name. - // De-emphasize only the protocol. - urlEditText.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - } - - // De-emphasize the text after the domain name. - if (endOfDomainName > 0) { - urlEditText.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - } - } - } - private void loadBookmarksFolder() { // Update the bookmarks cursor with the contents of the bookmarks database for the current folder. bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder); @@ -4371,11 +4335,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private String sanitizeUrl(String url) { // Sanitize tracking queries. if (sanitizeTrackingQueries) - url = sanitizeUrlHelper.sanitizeTrackingQueries(url); + url = SanitizeUrlHelper.sanitizeTrackingQueries(url); // Sanitize AMP redirects. if (sanitizeAmpRedirects) - url = sanitizeUrlHelper.sanitizeAmpRedirects(url); + url = SanitizeUrlHelper.sanitizeAmpRedirects(url); // Return the sanitized URL. return url; @@ -4395,7 +4359,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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. + // Restore each tab. for (int i = 0; i < savedStateArrayList.size(); i++) { // Add a new tab. tabLayout.addTab(tabLayout.newTab()); @@ -4421,7 +4385,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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. + } else { // The first tab is not selected. // Move to the selected tab. webViewPager.setCurrentItem(savedTabPosition); } @@ -4864,8 +4828,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Display the current URL in the URL text box. urlEditText.setText(url); - // Highlight the URL text. - highlightUrlText(); + // Highlight the URL syntax. + UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan); } } else { // A new intent is being loaded. // Reset the loading new intent tracker. @@ -4880,7 +4844,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Remove any background on the URL relative layout. urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null)); } - } else { // The fragment has not been populated. Try again in 100 milliseconds. + } else if (pageNumber == savedTabPosition){ // The app is being restored but the saved tab position fragment has not been populated yet. Try again in 100 milliseconds. // Create a handler to set the current WebView. Handler setCurrentWebViewHandler = new Handler(); @@ -5140,10 +5104,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Get the file name from the content disposition. - String fileNameString = PrepareSaveDialogCoroutine.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl); + String fileNameString = UrlHelper.getFileName(this, contentDisposition, mimetype, downloadUrl); // Instantiate the save dialog. - DialogFragment saveDialogFragment = SaveDialog.saveUrl(downloadUrl, formattedFileSizeString, fileNameString, userAgent, + DialogFragment saveDialogFragment = SaveDialog.saveUrl(downloadUrl, fileNameString, formattedFileSizeString, userAgent, nestedScrollWebView.getAcceptCookies()); // Try to show the dialog. The download listener continues to function even when the WebView is paused. Attempting to display a dialog in that state leads to a crash. @@ -5232,10 +5196,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the favorite icon when it changes. @Override public void onReceivedIcon(WebView view, Bitmap icon) { - // Only update the favorite icon if the website has finished loading. - if (progressBar.getVisibility() == View.GONE) { + // Only update the favorite icon if the website has finished loading and the new favorite icon height is greater than the current favorite icon height. + // This prevents low resolution icons from replacing high resolution one. + // The check for the visibility of the progress bar can possibly be removed once https://redmine.stoutner.com/issues/747 is fixed. + if ((progressBar.getVisibility() == View.GONE) && (icon.getHeight() > nestedScrollWebView.getFavoriteIconHeight())) { // Store the new favorite icon. - nestedScrollWebView.setFavoriteOrDefaultIcon(icon); + nestedScrollWebView.setFavoriteIcon(icon); // Get the current page position. int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); @@ -5367,9 +5333,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.setWebViewClient(new WebViewClient() { // `shouldOverrideUrlLoading` makes this WebView the default handler for URLs inside the app, so that links are not kicked out to other apps. - // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24. @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest webResourceRequest) { + // Get the URL from the web resource request. + String url = webResourceRequest.getUrl().toString(); + // Sanitize the url. url = sanitizeUrl(url); @@ -5848,8 +5816,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Display the formatted URL text. urlEditText.setText(url); - // Apply text highlighting to the URL text box. - highlightUrlText(); + // Highlight the URL syntax. + UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan); // Hide the keyboard. inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0); @@ -5984,8 +5952,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Display the final URL. Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly. urlEditText.setText(sanitizedUrl); - // Apply text highlighting to the URL. - highlightUrlText(); + // Highlight the URL syntax. + UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan); } // Only populate the title text view if the tab has been fully created.