X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FMainWebViewActivity.java;h=00a457db3394d8d583164f74175e41cebcf3f441;hp=db9538141593b79d894428c64bc3ba062c3dd8ea;hb=6790b16072ffa307636462579ed1476668a57e6e;hpb=9e11e18f4775c6bdd41f752f3baa290a0b50621d 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 db953814..00a457db 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -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; @@ -99,6 +96,8 @@ import android.widget.RadioButton; 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; @@ -114,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; @@ -127,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; @@ -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; @@ -184,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 ExecutorService executorService = Executors.newFixedThreadPool(4); + public static final ExecutorService executorService = Executors.newFixedThreadPool(4); public static String orbotStatus = "unknown"; - public static ArrayList pendingDialogsArrayList = new ArrayList<>(); + public static final ArrayList pendingDialogsArrayList = new ArrayList<>(); public static String proxyMode = ProxyHelper.NONE; // Declare the public static variables. @@ -210,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; @@ -226,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; @@ -248,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; @@ -274,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; @@ -300,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; @@ -369,7 +357,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private Activity resultLauncherActivityHandle; // Define the save URL activity result launcher. It must be defined before `onCreate()` is run or the app will crash. - private final ActivityResultLauncher saveUrlActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument("text/*"), + private final ActivityResultLauncher saveUrlActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument("*/*"), new ActivityResultCallback() { @Override public void onActivityResult(Uri fileUri) { @@ -390,11 +378,30 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook public void onActivityResult(Uri fileUri) { // Only save the webpage archive if the file URI is not null, which happens if the user exited the file picker by pressing back. if (fileUri != null) { + // Initialize the file name string from the file URI last path segment. + String temporaryFileNameString = fileUri.getLastPathSegment(); + + // Query the exact file name if the API >= 26. + if (Build.VERSION.SDK_INT >= 26) { + // Get a cursor from the content resolver. + Cursor contentResolverCursor = resultLauncherActivityHandle.getContentResolver().query(fileUri, null, null, null); + + // Move to the fist row. + contentResolverCursor.moveToFirst(); + + // Get the file name from the cursor. + temporaryFileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)); + + // Close the cursor. + contentResolverCursor.close(); + } + + // Save the final file name string so it can be used inside the lambdas. This will no longer be needed once this activity has transitioned to Kotlin. + String finalFileNameString = temporaryFileNameString; + try { // Create a temporary MHT file. File temporaryMhtFile = File.createTempFile("temporary_mht_file", ".mht", getCacheDir()); - - // Save the temporary MHT file. currentWebView.saveWebArchive(temporaryMhtFile.toString(), false, callbackValue -> { if (callbackValue != null) { // The temporary MHT file was saved successfully. try { @@ -419,29 +426,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook mhtOutputStream.close(); temporaryMhtFileInputStream.close(); - // Initialize the file name string from the file URI last path segment. - String fileNameString = fileUri.getLastPathSegment(); - - // Query the exact file name if the API >= 26. - if (Build.VERSION.SDK_INT >= 26) { - // Get a cursor from the content resolver. - Cursor contentResolverCursor = resultLauncherActivityHandle.getContentResolver().query(fileUri, null, null, null); - - // Move to the fist row. - contentResolverCursor.moveToFirst(); - - // Get the file name from the cursor. - fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)); - - // Close the cursor. - contentResolverCursor.close(); - } - // Display a snackbar. - Snackbar.make(currentWebView, getString(R.string.file_saved) + " " + fileNameString, Snackbar.LENGTH_SHORT).show(); + Snackbar.make(currentWebView, getString(R.string.saved, finalFileNameString), Snackbar.LENGTH_SHORT).show(); } catch (Exception exception) { // Display a snackbar with the exception. - Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + exception, Snackbar.LENGTH_INDEFINITE).show(); + Snackbar.make(currentWebView, getString(R.string.error_saving_file, finalFileNameString, exception), Snackbar.LENGTH_INDEFINITE).show(); } finally { // Delete the temporary MHT file. //noinspection ResultOfMethodCallIgnored @@ -449,12 +438,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } else { // There was an unspecified error while saving the temporary MHT file. // Display an error snackbar. - Snackbar.make(currentWebView, getString(R.string.error_saving_file), Snackbar.LENGTH_INDEFINITE).show(); + Snackbar.make(currentWebView, getString(R.string.error_saving_file, finalFileNameString, getString(R.string.unknown_error)), Snackbar.LENGTH_INDEFINITE).show(); } }); } catch (IOException ioException) { // Display a snackbar with the IO exception. - Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + ioException, Snackbar.LENGTH_INDEFINITE).show(); + Snackbar.make(currentWebView, getString(R.string.error_saving_file, finalFileNameString, ioException), Snackbar.LENGTH_INDEFINITE).show(); } } } @@ -473,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 @@ -486,6 +485,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); @@ -548,6 +548,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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 a handle for the navigation menu. @@ -588,6 +589,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Initially disable the sliding drawers. They will be enabled once the blocklists are loaded. drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + // Initially hide the user interface so that only the blocklist loading screen is shown (if reloading). + drawerLayout.setVisibility(View.GONE); + // Initialize the web view pager adapter. webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager()); @@ -601,7 +605,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this); domainsDatabaseHelper = new DomainsDatabaseHelper(this); proxyHelper = new ProxyHelper(); - sanitizeUrlHelper = new SanitizeUrlHelper(); + + // Update the bookmarks drawer pinned image view. + updateBookmarksDrawerPinnedImageView(); // Initialize the app. initializeApp(); @@ -609,8 +615,51 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); + + // Instantiate the populate blocklists coroutine. + PopulateBlocklistsCoroutine populateBlocklistsCoroutine = new PopulateBlocklistsCoroutine(this); + // Populate the blocklists. - populateBlocklists = new PopulateBlocklists(this, this).execute(); + populateBlocklistsCoroutine.populateBlocklists(this); } @Override @@ -803,10 +852,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. @@ -880,10 +929,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); - savedInstanceState.putString(PROXY_MODE, proxyMode); } @Override @@ -903,11 +953,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(); } @@ -975,6 +1020,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); @@ -1055,16 +1103,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. @@ -1741,23 +1785,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; @@ -1815,8 +1845,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. @@ -1836,7 +1866,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)); @@ -2190,6 +2220,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Make it so. startActivity(logcatIntent); + } else if (menuItemId == R.id.webview_devtools) { // WebView Dev. + // Create a WebView DevTools intent. + Intent webViewDevToolsIntent = new Intent("com.android.webview.SHOW_DEV_UI"); + + // Launch as a new task so that the WebView DevTools and Privacy Browser show as a separate windows in the recent tasks list. + webViewDevToolsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // Make it so. + startActivity(webViewDevToolsIntent); } else if (menuItemId == R.id.guide) { // Guide. // Create an intent to launch the guide activity. Intent guideIntent = new Intent(this, GuideActivity.class); @@ -2303,8 +2342,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. @@ -2375,8 +2413,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. @@ -2480,8 +2517,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. @@ -2507,8 +2543,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. @@ -2669,188 +2704,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Update the bookmarks cursor with the current contents of this folder. bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder); - // Update the `ListView`. + // Update the list view. bookmarksCursorAdapter.changeCursor(bookmarksCursor); // Scroll to the new folder. 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 `ListView`. - bookmarksCursorAdapter.changeCursor(bookmarksCursor); - } - - // Override `onBackPressed()` to handle the navigation drawer and and the WebViews. - @Override - public void onBackPressed() { - // Check the different options for processing `back`. - if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open. - // Close the navigation drawer. - drawerLayout.closeDrawer(GravityCompat.START); - } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open. - // 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); - } - } - - // 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(); @@ -3100,24 +2960,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Remove the lint warning below that the input method manager might be null. assert inputMethodManager != null; - // Initialize the gray foreground color spans for highlighting the URLs. + // Initialize the color spans for highlighting the URLs. initialGrayColorSpan = new ForegroundColorSpan(getColor(R.color.gray_500)); finalGrayColorSpan = new ForegroundColorSpan(getColor(R.color.gray_500)); - - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Set the red color span according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - redColorSpan = new ForegroundColorSpan(getColor(R.color.red_a700)); - } else { - redColorSpan = new ForegroundColorSpan(getColor(R.color.red_900)); - } + redColorSpan = new ForegroundColorSpan(getColor(R.color.red_text)); // 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); @@ -3125,8 +2976,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); } }); @@ -3269,7 +3120,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)); @@ -3285,7 +3136,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(); @@ -3312,7 +3163,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)); @@ -3321,7 +3172,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)); @@ -3416,14 +3267,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; @@ -3433,14 +3286,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); @@ -3448,13 +3310,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 but do not switch to the tab or close the drawer. - addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), false); + // 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); - // Display a snackbar. - Snackbar.make(drawerLayout, R.string.bookmark_opened_in_background, Snackbar.LENGTH_SHORT).show(); + // 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; }); @@ -3729,7 +3595,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); @@ -3791,7 +3657,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Store the general preference information. - boolean defaultXRequestedWithHeader = sharedPreferences.getBoolean(getString(R.string.x_requested_with_header_key), true); String defaultFontSizeString = sharedPreferences.getString(getString(R.string.font_size_key), getString(R.string.font_size_default_value)); String defaultUserAgentName = sharedPreferences.getString(getString(R.string.user_agent_key), getString(R.string.user_agent_default_value)); boolean defaultSwipeToRefresh = sharedPreferences.getBoolean(getString(R.string.swipe_to_refresh_key), true); @@ -3837,7 +3702,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.setBlockAllThirdPartyRequests(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow( DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1); String userAgentName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.USER_AGENT)); - int xRequestedWithHeaderInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.X_REQUESTED_WITH_HEADER)); int fontSize = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.FONT_SIZE)); int swipeToRefreshInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SWIPE_TO_REFRESH)); int webViewThemeInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WEBVIEW_THEME)); @@ -3877,24 +3741,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.getSettings().setSaveFormData(saveFormData); } - // Set the X-Requested-With header. - switch (xRequestedWithHeaderInt) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: - if (defaultXRequestedWithHeader) - nestedScrollWebView.setXRequestedWithHeader(); - else - nestedScrollWebView.resetXRequestedWithHeader(); - break; - - case DomainsDatabaseHelper.ENABLED: - nestedScrollWebView.setXRequestedWithHeader(); - break; - - case DomainsDatabaseHelper.DISABLED: - nestedScrollWebView.resetXRequestedWithHeader(); - break; - } - // Apply the font size. try { // Try the specified font size to see if it is valid. if (fontSize == 0) { // Apply the default font size. @@ -3962,8 +3808,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); @@ -3974,8 +3823,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: @@ -3987,8 +3840,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: @@ -4018,48 +3871,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. @@ -4125,19 +3936,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.getSettings().setSaveFormData(saveFormData); } - // Store the X-Requested-With header status in the nested scroll WebView. - if (defaultXRequestedWithHeader) - nestedScrollWebView.setXRequestedWithHeader(); - else - nestedScrollWebView.resetXRequestedWithHeader(); - // Store the swipe to refresh status in the nested scroll WebView. nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh); // 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); @@ -4171,8 +3979,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. @@ -4187,31 +3995,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. @@ -4238,7 +4021,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Load the URL if directed. This makes sure that the domain settings are properly loaded before the URL. By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent. if (loadUrl) { - nestedScrollWebView.loadUrl(url, nestedScrollWebView.getXRequestedWithHeader()); + nestedScrollWebView.loadUrl(url); } } @@ -4300,7 +4083,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))); } } } @@ -4316,7 +4099,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))); } } } @@ -4329,27 +4112,31 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } else { appBarLayout.setBackgroundResource(R.color.dark_blue_30); } + // Get the package manager. + PackageManager packageManager = getPackageManager(); // Check to see if I2P is installed. try { - // Get the package manager. - PackageManager packageManager = getPackageManager(); - - // Check to see if I2P is in the list. This will throw an error and drop to the catch section if it isn't installed. + // Check to see if the F-Droid flavor is installed. This will throw an error and drop to the catch section if it isn't installed. packageManager.getPackageInfo("net.i2p.android.router", 0); - } catch (PackageManager.NameNotFoundException exception) { // I2P is not installed. - // Sow the I2P not installed dialog if it is not already displayed. - if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) { - // Get a handle for the waiting for proxy alert dialog. - DialogFragment i2pNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode); + } catch (PackageManager.NameNotFoundException fdroidException) { // The F-Droid flavor is not installed. + try { + // Check to see if the Google Play flavor is installed. This will throw an error and drop to the catch section if it isn't installed. + packageManager.getPackageInfo("net.i2p.android", 0); + } catch (PackageManager.NameNotFoundException googlePlayException) { // The Google Play flavor is not installed. + // Sow the I2P not installed dialog if it is not already displayed. + if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) { + // Get a handle for the waiting for proxy alert dialog. + DialogFragment i2pNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode); - // Try to show the dialog. Sometimes the window is not yet active if returning from Settings. - try { - // Display the I2P not installed alert dialog. - 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))); + // Try to show the dialog. Sometimes the window is not yet active if returning from Settings. + try { + // Display the I2P not installed alert dialog. + 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 PendingDialogDataClass(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog))); + } } } } @@ -4422,64 +4209,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); @@ -4580,11 +4309,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; @@ -4994,6 +4723,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); @@ -5057,8 +4802,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. @@ -5090,7 +4835,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @SuppressLint("ClickableViewAccessibility") @Override - public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, String url, Boolean restoringState) { + public void initializeWebView(@NonNull NestedScrollWebView nestedScrollWebView, int pageNumber, @NonNull ProgressBar progressBar, @NonNull String url, boolean restoringState) { // Get a handle for the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); @@ -5100,8 +4845,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. @@ -5130,39 +4875,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 @@ -5366,10 +5078,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. @@ -5378,7 +5090,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))); } } }); @@ -5458,10 +5170,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()); @@ -5572,8 +5286,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); @@ -5584,8 +5298,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; } @@ -6074,8 +5788,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); @@ -6087,8 +5801,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) { @@ -6205,8 +5924,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. @@ -6275,7 +5994,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))); } } }