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=1ee31db40a03cb8dfe9996eacfeaa79140aa230e;hpb=ff3641c1e0edbe729f4e09b804081d35185976e3;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 1ee31db4..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 . * @@ -46,19 +46,16 @@ 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.Environment; import android.os.Handler; import android.os.Message; -import android.preference.PreferenceManager; import android.print.PrintDocumentAdapter; 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; @@ -100,6 +97,7 @@ import android.widget.RelativeLayout; import android.widget.TextView; import androidx.activity.OnBackPressedCallback; +import androidx.activity.result.ActivityResult; import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -115,6 +113,7 @@ import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.viewpager.widget.ViewPager; import androidx.webkit.WebSettingsCompat; @@ -128,16 +127,15 @@ import com.google.android.material.tabs.TabLayout; import com.stoutner.privacybrowser.R; import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter; -import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses; -import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists; -import com.stoutner.privacybrowser.asynctasks.PrepareSaveDialog; import com.stoutner.privacybrowser.asynctasks.SaveUrl; import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage; -import com.stoutner.privacybrowser.dataclasses.PendingDialog; +import com.stoutner.privacybrowser.coroutines.GetHostIpAddressesCoroutine; +import com.stoutner.privacybrowser.coroutines.PopulateBlocklistsCoroutine; +import com.stoutner.privacybrowser.coroutines.PrepareSaveDialogCoroutine; +import com.stoutner.privacybrowser.dataclasses.PendingDialogDataClass; import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog; import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog; import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog; -import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog; import com.stoutner.privacybrowser.dialogs.FontSizeDialog; import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog; import com.stoutner.privacybrowser.dialogs.OpenDialog; @@ -154,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; @@ -185,14 +184,13 @@ import java.util.concurrent.Executors; import kotlin.Pair; public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, - EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener, - PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener, - WebViewTabFragment.NewTabListener { + FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener, PinnedMismatchDialog.PinnedMismatchListener, + PopulateBlocklistsCoroutine.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener { // Define the public static variables. public static final ExecutorService executorService = Executors.newFixedThreadPool(4); public static String orbotStatus = "unknown"; - public static final ArrayList pendingDialogsArrayList = new ArrayList<>(); + public static final ArrayList pendingDialogsArrayList = new ArrayList<>(); public static String proxyMode = ProxyHelper.NONE; // Declare the public static variables. @@ -211,15 +209,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2; public final static int DOMAINS_CUSTOM_USER_AGENT = 12; - // Define the start activity for result request codes. The public static entry is accessed from `OpenDialog()`. - private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 0; - public final static int BROWSE_OPEN_REQUEST_CODE = 1; - // Define the saved instance state constants. + private final String BOOKMARKS_DRAWER_PINNED = "bookmarks_drawer_pinned"; + private final String PROXY_MODE = "proxy_mode"; 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"; // Define the saved instance state variables. private ArrayList savedStateArrayList; @@ -227,10 +222,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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()`. private NestedScrollWebView currentWebView; @@ -249,20 +240,12 @@ 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; // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`. private CursorAdapter bookmarksCursorAdapter; - // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`. - private String oldFolderNameString; - // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`. private ValueCallback fileChooserCallback; @@ -275,21 +258,24 @@ 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; private boolean bottomAppBar; 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; @@ -301,19 +287,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private String saveUrlString = ""; // Declare the class views. - private FrameLayout rootFrameLayout; - private DrawerLayout drawerLayout; - private CoordinatorLayout coordinatorLayout; - private Toolbar toolbar; - private RelativeLayout urlRelativeLayout; - private EditText urlEditText; private ActionBar actionBar; + private CoordinatorLayout coordinatorLayout; + private ImageView bookmarksDrawerPinnedImageView; + private DrawerLayout drawerLayout; private LinearLayout findOnPageLinearLayout; + private FrameLayout fullScreenVideoFrameLayout; + private FrameLayout rootFrameLayout; + private SwipeRefreshLayout swipeRefreshLayout; private LinearLayout tabsLinearLayout; private TabLayout tabLayout; - private SwipeRefreshLayout swipeRefreshLayout; + private Toolbar toolbar; + private EditText urlEditText; + private RelativeLayout urlRelativeLayout; private ViewPager webViewPager; - private FrameLayout fullScreenVideoFrameLayout; // Declare the class menus. private Menu optionsMenu; @@ -475,6 +462,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }); + // Define the save webpage image activity result launcher. It must be defined before `onCreate()` is run or the app will crash. + private final ActivityResultLauncher browseFileUploadActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), + new ActivityResultCallback() { + @Override + public void onActivityResult(ActivityResult activityResult) { + // Pass the file to the WebView. + fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(activityResult.getResultCode(), activityResult.getData())); + } + }); + // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with WebView. @SuppressLint("ClickableViewAccessibility") @Override @@ -485,15 +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. - 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); @@ -509,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. @@ -526,138 +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); - 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); - // Initialize the app. - initializeApp(); + // Instantiate the helpers. + bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this); + domainsDatabaseHelper = new DomainsDatabaseHelper(this); + proxyHelper = new ProxyHelper(); - // Apply the app settings from the shared preferences. - applyAppSettings(); + // Update the bookmarks drawer pinned image view. + updateBookmarksDrawerPinnedImageView(); - // 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(); - } else if (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. - // Close Privacy Browser. `finishAndRemoveTask()` also removes Privacy Browser from the recent app list. - finishAndRemoveTask(); - - // Manually kill Privacy Browser. Otherwise, it is glitchy when restarted. - System.exit(0); + // Initialize the app. + initializeApp(); + + // 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); - // Populate the blocklists. - populateBlocklists = new PopulateBlocklists(this, this).execute(); + // Instantiate the populate blocklists coroutine. + PopulateBlocklistsCoroutine populateBlocklistsCoroutine = new PopulateBlocklistsCoroutine(this); + + // Populate the blocklists. + populateBlocklistsCoroutine.populateBlocklists(this); + } } @Override @@ -737,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); } @@ -807,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(); + } } } @@ -850,10 +869,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show any pending dialogs. for (int i = 0; i < pendingDialogsArrayList.size(); i++) { // Get the pending dialog from the array list. - PendingDialog pendingDialog = pendingDialogsArrayList.get(i); + PendingDialogDataClass pendingDialogDataClass = pendingDialogsArrayList.get(i); // Show the pending dialog. - pendingDialog.dialogFragment.show(getSupportFragmentManager(), pendingDialog.tag); + pendingDialogDataClass.dialogFragment.show(getSupportFragmentManager(), pendingDialogDataClass.tag); } // Clear the pending dialogs array list. @@ -866,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(); + } } } @@ -894,43 +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.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); + // 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 @@ -950,11 +977,6 @@ 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(); } @@ -1022,6 +1044,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly. optionsClearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26); + // Only display the dark WebView menu item if the API >= 29. + optionsDarkWebViewMenuItem.setVisible(Build.VERSION.SDK_INT >= 29); + // Set the status of the additional app bar icons. Setting the refresh menu item to `SHOW_AS_ACTION_ALWAYS` makes it appear even on small devices like phones. if (displayAdditionalAppBarIcons) { optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); @@ -1102,16 +1127,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the current theme status. int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - // Enable dark WebView if the API is < 33 or if night mode is enabled. - optionsDarkWebViewMenuItem.setEnabled((Build.VERSION.SDK_INT < 33) || (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES)); + // Enable dark WebView if night mode is enabled. + optionsDarkWebViewMenuItem.setEnabled(currentThemeStatus == Configuration.UI_MODE_NIGHT_YES); - // Set the checkbox status for dark WebView if the WebView supports it. - if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // The device is running API >= 33 and algorithmic darkening is supported. + // Set the checkbox status for dark WebView if the device is running API >= 29 and algorithmic darkening is supported. + if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView.getSettings())); - } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { // The device is running API < 33 and the WebView supports force dark. - //noinspection deprecation - optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON); - } } // Set the cookies menu item checked status. @@ -1788,23 +1809,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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 ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // The device is running API >= 33 and algorithmic darkening is supported. - // Toggle algorithmic darkening. + // Toggle dark WebView if supported. + if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) WebSettingsCompat.setAlgorithmicDarkeningAllowed(currentWebView.getSettings(), !WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView.getSettings())); - } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { // The device is running API < 33 and the WebView supports force dark. - // Toggle the dark WebView setting. - //noinspection deprecation - if (WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON) { // Dark WebView is currently enabled. - // Turn off dark WebView. - //noinspection deprecation - WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); - } else { // Dark WebView is currently disabled. - // Turn on dark WebView. - //noinspection deprecation - WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - } - } // Consume the event. return true; @@ -1862,8 +1869,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook downloadUrlWithExternalApp(currentWebView.getCurrentUrl()); } else { // Handle the download inside of Privacy Browser. // 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(), currentWebView.getSettings().getUserAgentString(), - currentWebView.getAcceptCookies()).execute(currentWebView.getCurrentUrl()); + PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), currentWebView.getCurrentUrl(), currentWebView.getSettings().getUserAgentString(), + currentWebView.getAcceptCookies()); } // Consume the event. @@ -1883,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)); @@ -2278,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 @@ -2359,8 +2368,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook downloadUrlWithExternalApp(linkUrl); } else { // Handle the download inside of Privacy Browser. // 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(), currentWebView.getSettings().getUserAgentString(), - currentWebView.getAcceptCookies()).execute(linkUrl); + PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), linkUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()); } // Consume the event. @@ -2431,8 +2439,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook downloadUrlWithExternalApp(imageUrl); } else { // Handle the download inside of Privacy Browser. // 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(), currentWebView.getSettings().getUserAgentString(), - currentWebView.getAcceptCookies()).execute(imageUrl); + PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), imageUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()); } // Consume the event. @@ -2536,8 +2543,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook downloadUrlWithExternalApp(imageUrl); } else { // Handle the download inside of Privacy Browser. // 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(), currentWebView.getSettings().getUserAgentString(), - currentWebView.getAcceptCookies()).execute(imageUrl); + PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), imageUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()); } // Consume the event. @@ -2563,8 +2569,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook downloadUrlWithExternalApp(linkUrl); } else { // Handle the download inside of Privacy Browser. // 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(), currentWebView.getSettings().getUserAgentString(), - currentWebView.getAcceptCookies()).execute(linkUrl); + PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), linkUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()); } // Consume the event. @@ -2732,144 +2737,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook bookmarksListView.setSelection(0); } - @Override - public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, @NonNull 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 the dialog. - RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton); - RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton); - ImageView defaultFolderIconImageView = dialog.findViewById(R.id.default_icon_imageview); - EditText editFolderNameEditText = dialog.findViewById(R.id.folder_name_edittext); - - // Get the new folder name. - String newFolderNameString = editFolderNameEditText.getText().toString(); - - // Check if the favorite icon has changed. - if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed. - // Update the name in the database. - bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString); - } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed. - // Create the new folder icon Bitmap. - Bitmap folderIconBitmap; - - // Populate the new folder icon bitmap. - if (defaultFolderIconRadioButton.isChecked()) { - // Get the default folder icon drawable. - Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable(); - - // Convert the folder icon drawable to a bitmap drawable. - BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable; - - // Convert the folder icon bitmap drawable to a bitmap. - folderIconBitmap = folderIconBitmapDrawable.getBitmap(); - } else { // Use the `WebView` favorite icon. - // Copy the favorite icon bitmap to the folder icon bitmap. - folderIconBitmap = favoriteIconBitmap; - } - - // Create a folder icon byte array output stream. - ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream(); - - // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG). - folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream); - - // Convert the folder icon byte array stream to a byte array. - byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray(); - - // Update the folder icon in the database. - bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray); - } else { // The folder icon and the name have changed. - // Get the new folder icon bitmap. - Bitmap folderIconBitmap; - if (defaultFolderIconRadioButton.isChecked()) { - // Get the default folder icon drawable. - Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable(); - - // Convert the folder icon drawable to a bitmap drawable. - BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable; - - // Convert the folder icon bitmap drawable to a bitmap. - folderIconBitmap = folderIconBitmapDrawable.getBitmap(); - } else { // Use the `WebView` favorite icon. - // Copy the favorite icon bitmap to the folder icon bitmap. - folderIconBitmap = favoriteIconBitmap; - } - - // Create a folder icon byte array output stream. - ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream(); - - // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG). - folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream); - - // Convert the folder icon byte array stream to a byte array. - byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray(); - - // Update the folder name and icon in the database. - bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray); - } - - // Update the bookmarks cursor with the current contents of this folder. - bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder); - - // Update the list view. - bookmarksCursorAdapter.changeCursor(bookmarksCursor); - } - - // Process the results of a file browse. - @Override - public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) { - // Run the default commands. - super.onActivityResult(requestCode, resultCode, returnedIntent); - - // Run the commands that correlate to the specified request code. - switch (requestCode) { - case BROWSE_FILE_UPLOAD_REQUEST_CODE: - // Pass the file to the WebView. - fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent)); - break; - - case BROWSE_OPEN_REQUEST_CODE: - // Don't do anything if the user pressed back from the file picker. - if (resultCode == Activity.RESULT_OK) { - // Get a handle for the open dialog fragment. - DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open)); - - // Only update the file name if the dialog still exists. - if (openDialogFragment != null) { - // Get a handle for the open dialog. - Dialog openDialog = openDialogFragment.getDialog(); - - // Remove the incorrect lint warning below that the dialog might be null. - assert openDialog != null; - - // Get a handle for the file name edit text. - EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext); - - // Get the file name URI from the intent. - Uri fileNameUri = returnedIntent.getData(); - - // Get the file name string from the URI. - String fileNameString = fileNameUri.toString(); - - // Set the file name text. - fileNameEditText.setText(fileNameString); - - // Move the cursor to the end of the file name edit text. - fileNameEditText.setSelection(fileNameString.length()); - } - } - break; - } - } - private void loadUrlFromTextBox() { // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string. String unformattedUrlString = urlEditText.getText().toString().trim(); @@ -3127,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); @@ -3135,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); } }); @@ -3279,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)); @@ -3295,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(); @@ -3322,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)); @@ -3331,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)); @@ -3426,14 +3293,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Load the bookmark URL. loadUrl(currentWebView, bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL))); - // Close the bookmarks drawer. - drawerLayout.closeDrawer(GravityCompat.END); + // Close the bookmarks drawer if it is not pinned. + if (!bookmarksDrawerPinned) + drawerLayout.closeDrawer(GravityCompat.END); } - // Close the `Cursor`. + // Close the cursor. bookmarkCursor.close(); }); + // Handle long-presses on bookmarks. bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> { // Convert the database ID from `long` to `int`. int databaseId = (int) id; @@ -3443,14 +3312,23 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Check to see if the bookmark is a folder. if (isFolder) { // The bookmark is a folder. - // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`. - oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME)); + // Get a cursor of all the bookmarks in the folder. + Cursor bookmarksCursor = bookmarksDatabaseHelper.getFolderBookmarks(databaseId); - // Instantiate the edit folder bookmark dialog. - DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon()); + // Move to the first entry in the cursor. + bookmarksCursor.moveToFirst(); - // Show the edit folder bookmark dialog. - editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder)); + // Open each bookmark + for (int i = 0; i < bookmarksCursor.getCount(); i++) { + // Load the bookmark in a new tab, moving to the tab for the first bookmark if the drawer is not pinned. + addNewTab(bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), (!bookmarksDrawerPinned && (i == 0))); + + // Move to the next bookmark. + bookmarksCursor.moveToNext(); + } + + // Close the cursor. + bookmarksCursor.close(); } else { // The bookmark is not a folder. // Get the bookmark cursor for this ID. Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId); @@ -3458,13 +3336,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Move the bookmark cursor to the first row. bookmarkCursor.moveToFirst(); - // Load the bookmark in a new tab. - addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), true); + // Load the bookmark in a new tab and move to the tab if the drawer is not pinned. + addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), !bookmarksDrawerPinned); - // Close the bookmarks drawer. - drawerLayout.closeDrawer(GravityCompat.END); + // Close the cursor. + bookmarkCursor.close(); } + // Close the bookmarks drawer if it is not pinned. + if (!bookmarksDrawerPinned) + drawerLayout.closeDrawer(GravityCompat.END); + // Consume the event. return true; }); @@ -3739,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); @@ -3952,8 +3834,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Update the swipe refresh layout. if (defaultSwipeToRefresh) { // 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.getScrollY() == 0); + // Update the status of the swipe refresh layout if the current WebView is not null (crash reports indicate that in some unexpected way it sometimes is null). + if (currentWebView != null) { + // 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.getScrollY() == 0); + } } else { // Swipe to refresh is disabled. // Disable the swipe refresh layout. swipeRefreshLayout.setEnabled(false); @@ -3964,8 +3849,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Store the swipe to refresh status in the nested scroll WebView. nestedScrollWebView.setSwipeToRefresh(true); - // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes. - swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0); + + // Update the status of the swipe refresh layout if the current WebView is not null (crash reports indicate that in some unexpected way it sometimes is null). + if (currentWebView != null) { + // 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.getScrollY() == 0); + } break; case DomainsDatabaseHelper.DISABLED: @@ -3977,8 +3866,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook break; } - // Check to see if WebView themes are supported. - if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // The device is running API >= 33 and algorithmic darkening is supported. + // Set the WebView theme if device is running API >= 29 and algorithmic darkening is supported. + if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // Set the WebView theme. switch (webViewThemeInt) { case DomainsDatabaseHelper.SYSTEM_DEFAULT: @@ -4008,48 +3897,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true); break; } - } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { // The device is running API < 33 and the WebView supports force dark. - // Set the WebView theme. - switch (webViewThemeInt) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: - // 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. - //noinspection deprecation - 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. - //noinspection deprecation - 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. - //noinspection deprecation - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); - } else { // The system is in night mode. - // Turn on the WebView dark mode. - //noinspection deprecation - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - } - } - break; - - case DomainsDatabaseHelper.LIGHT_THEME: - // Turn off the WebView dark mode. - //noinspection deprecation - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); - break; - - case DomainsDatabaseHelper.DARK_THEME: - // Turn on the WebView dark mode. - //noinspection deprecation - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - break; - } } // Set the viewport. @@ -4120,8 +3967,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Update the swipe refresh layout. if (defaultSwipeToRefresh) { // 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.getScrollY() == 0); + // Update the status of the swipe refresh layout if the current WebView is not null (crash reports indicate that in some unexpected way it sometimes is null). + if (currentWebView != null) { + // 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.getScrollY() == 0); + } } else { // Swipe to refresh is disabled. // Disable the swipe refresh layout. swipeRefreshLayout.setEnabled(false); @@ -4155,8 +4005,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]); } - // Apply the WebView theme if supported by the installed WebView. - if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // The device is running API >= 33 and algorithmic darkening is supported. + // Set the WebView theme if device is running API >= 29 and algorithmic darkening is supported. + if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // 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 algorithmic darkening. @@ -4171,31 +4021,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the algorithmic darkening according to the current system theme status. WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), currentThemeStatus == Configuration.UI_MODE_NIGHT_YES); } - } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { // The device is running API < 33 and the WebView supports 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. - if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) { // The light theme is selected. - // Turn off the WebView dark mode. - //noinspection deprecation - 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. - //noinspection deprecation - 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. - //noinspection deprecation - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); - } else { // The system is in night mode. - // Turn on the WebView dark mode. - //noinspection deprecation - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - } - } } // Set the viewport. @@ -4284,7 +4109,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook waitingForProxyDialogFragment.show(getSupportFragmentManager(), getString(R.string.waiting_for_proxy_dialog)); } catch (Exception waitingForTorException) { // Add the dialog to the pending dialog array list. It will be displayed in `onStart()`. - pendingDialogsArrayList.add(new PendingDialog(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog))); + pendingDialogsArrayList.add(new PendingDialogDataClass(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog))); } } } @@ -4300,7 +4125,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook orbotNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog)); } catch (Exception orbotNotInstalledException) { // Add the dialog to the pending dialog array list. It will be displayed in `onStart()`. - pendingDialogsArrayList.add(new PendingDialog(orbotNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog))); + pendingDialogsArrayList.add(new PendingDialogDataClass(orbotNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog))); } } } @@ -4336,7 +4161,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog)); } catch (Exception i2pNotInstalledException) { // Add the dialog to the pending dialog array list. It will be displayed in `onStart()`. - pendingDialogsArrayList.add(new PendingDialog(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog))); + pendingDialogsArrayList.add(new PendingDialogDataClass(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog))); } } } @@ -4410,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); @@ -4568,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; @@ -4592,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()); @@ -4618,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); } @@ -4982,6 +4749,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } + public void toggleBookmarksDrawerPinned(View view) { + // Toggle the bookmarks drawer pinned tracker. + bookmarksDrawerPinned = !bookmarksDrawerPinned; + + // Update the bookmarks drawer pinned image view. + updateBookmarksDrawerPinnedImageView(); + } + + private void updateBookmarksDrawerPinnedImageView() { + // Set the current icon. + if (bookmarksDrawerPinned) + bookmarksDrawerPinnedImageView.setImageResource(R.drawable.pin_selected); + else + bookmarksDrawerPinnedImageView.setImageResource(R.drawable.pin); + } + private void setCurrentWebView(int pageNumber) { // Stop the swipe to refresh indicator if it is running swipeRefreshLayout.setRefreshing(false); @@ -5045,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. @@ -5061,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(); @@ -5088,8 +4871,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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 ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // The device is running API >= 33 and algorithmic darkening is supported. + // Set the WebView theme if device is running API >= 29 and algorithmic darkening is supported. + if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // Set the WebView them. 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 algorithmic darkening. @@ -5118,39 +4901,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true); } } - } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { // The device is running API < 33 and the WebView supports 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. - if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) { // The light theme is selected. - // Turn off the WebView dark mode. - //noinspection deprecation - 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. - //noinspection deprecation - 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. - //noinspection deprecation - 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. - //noinspection deprecation - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - } - } } // Get a handle for the activity @@ -5354,10 +5104,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Get the file name from the content disposition. - String fileNameString = PrepareSaveDialog.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. @@ -5366,7 +5116,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog)); } catch (Exception exception) { // The dialog could not be shown. // Add the dialog to the pending dialog array list. It will be displayed in `onStart()`. - pendingDialogsArrayList.add(new PendingDialog(saveDialogFragment, getString(R.string.save_dialog))); + pendingDialogsArrayList.add(new PendingDialogDataClass(saveDialogFragment, getString(R.string.save_dialog))); } } }); @@ -5446,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()); @@ -5560,8 +5312,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Check to see if the file chooser intent resolves to an installed package. if (fileChooserIntent.resolveActivity(packageManager) != null) { // The file chooser intent is fine. - // Start the file chooser intent. - startActivityForResult(fileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE); + // Launch the file chooser intent. + browseFileUploadActivityResultLauncher.launch(fileChooserIntent); } else { // The file chooser intent will cause a crash. // Create a generic intent to open a chooser. Intent genericFileChooserIntent = new Intent(Intent.ACTION_GET_CONTENT); @@ -5572,8 +5324,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the file type to everything. genericFileChooserIntent.setType("*/*"); - // Start the generic file chooser intent. - startActivityForResult(genericFileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE); + // Launch the generic file chooser intent. + browseFileUploadActivityResultLauncher.launch(genericFileChooserIntent); } return true; } @@ -5581,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); @@ -6062,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); @@ -6075,8 +5829,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get a URI for the current URL. Uri currentUri = Uri.parse(url); - // Get the IP addresses for the host. - new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost()); + // Get the current domain name. + String currentDomainName = currentUri.getHost(); + + if ((currentDomainName != null) && !currentDomainName.isEmpty()) { + // Get the IP addresses for the current URI. + GetHostIpAddressesCoroutine.getAddresses(currentDomainName, nestedScrollWebView, getSupportFragmentManager(), getString(R.string.pinned_mismatch)); + } // Replace Refresh with Stop if the options menu has been created. (The first WebView typically begins loading before the menu items are instantiated.) if (optionsMenu != null) { @@ -6193,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. @@ -6263,7 +6022,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error)); } catch (Exception exception) { // Add the dialog to the pending dialog array list. It will be displayed in `onStart()`. - pendingDialogsArrayList.add(new PendingDialog(sslCertificateErrorDialogFragment, getString(R.string.ssl_certificate_error))); + pendingDialogsArrayList.add(new PendingDialogDataClass(sslCertificateErrorDialogFragment, getString(R.string.ssl_certificate_error))); } } }