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=3d27a4cf61d0782be71bbe673cc29dad58082b41;hp=d2881d7ebe8887c6d299ca1ec9b1b55cdc6b7e44;hb=333ec579b52efbfbad89e0150c7c320822ba9ecf;hpb=cbf3ebe084eaf5004b74b7f1907b0a1e1a22791f 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 d2881d7e..3d27a4cf 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -1,26 +1,27 @@ /* - * Copyright © 2015-2021 Soren Stoutner . + * Copyright © 2015-2022 Soren Stoutner . * * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner . * - * This file is part of Privacy Browser . + * This file is part of Privacy Browser Android . * - * Privacy Browser is free software: you can redistribute it and/or modify + * Privacy Browser Android is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privacy Browser is distributed in the hope that it will be useful, + * Privacy Browser Android is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with Privacy Browser. If not, see . + * along with Privacy Browser Android. If not, see . */ package com.stoutner.privacybrowser.activities; +import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; import android.app.Dialog; @@ -55,6 +56,7 @@ 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; @@ -77,6 +79,7 @@ import android.webkit.SslErrorHandler; import android.webkit.ValueCallback; import android.webkit.WebBackForwardList; import android.webkit.WebChromeClient; +import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.webkit.WebSettings; import android.webkit.WebStorage; @@ -96,6 +99,9 @@ import android.widget.RadioButton; import android.widget.RelativeLayout; import android.widget.TextView; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBarDrawerToggle; @@ -119,7 +125,6 @@ import com.google.android.material.navigation.NavigationView; import com.google.android.material.snackbar.Snackbar; import com.google.android.material.tabs.TabLayout; -import com.stoutner.privacybrowser.BuildConfig; import com.stoutner.privacybrowser.R; import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter; import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses; @@ -127,8 +132,7 @@ 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.definitions.PendingDialog; -import com.stoutner.privacybrowser.dialogs.AdConsentDialog; +import com.stoutner.privacybrowser.dataclasses.PendingDialog; import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog; import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog; import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog; @@ -138,13 +142,12 @@ import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog; import com.stoutner.privacybrowser.dialogs.OpenDialog; import com.stoutner.privacybrowser.dialogs.ProxyNotInstalledDialog; import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog; -import com.stoutner.privacybrowser.dialogs.SaveWebpageDialog; +import com.stoutner.privacybrowser.dialogs.SaveDialog; import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog; import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog; import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog; import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog; import com.stoutner.privacybrowser.fragments.WebViewTabFragment; -import com.stoutner.privacybrowser.helpers.AdHelper; import com.stoutner.privacybrowser.helpers.BlocklistHelper; import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper; @@ -169,7 +172,6 @@ import java.net.URLEncoder; import java.text.NumberFormat; import java.util.ArrayList; -import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -182,45 +184,34 @@ import java.util.concurrent.Executors; public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener, - PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, UrlHistoryDialog.NavigateHistoryListener, + PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener { - // The executor service handles background tasks. It is accessed from `ViewSourceActivity`. + // Define the public static variables. public static ExecutorService executorService = Executors.newFixedThreadPool(4); - - // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxy()`. public static String orbotStatus = "unknown"; - - // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`. It is also used in `onCreate()`, `onResume()`, and `addTab()`. - public static WebViewPagerAdapter webViewPagerAdapter; - - // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`. - public static boolean restartFromBookmarksActivity; - - // Define the public static variables. public static ArrayList pendingDialogsArrayList = new ArrayList<>(); + public static String proxyMode = ProxyHelper.NONE; - // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, - // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`. + // Declare the public static variables. public static String currentBookmarksFolder; + public static boolean restartFromBookmarksActivity; + public static WebViewPagerAdapter webViewPagerAdapter; + + // Declare the public static views. + public static AppBarLayout appBarLayout; // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`. public final static int UNRECOGNIZED_USER_AGENT = -1; public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1; - public final static int SETTINGS_CUSTOM_USER_AGENT = 12; + public final static int SETTINGS_CUSTOM_USER_AGENT = 11; public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0; public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2; - public final static int DOMAINS_CUSTOM_USER_AGENT = 13; + public final static int DOMAINS_CUSTOM_USER_AGENT = 12; - // Define the start activity for result request codes. The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`. + // 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; - public final static int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 2; - - // The proxy mode is public static so it can be accessed from `ProxyHelper()`. - // It is also used in `onRestart()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxy()`. - // It will be updated in `applyAppSettings()`, but it needs to be initialized here or the first run of `onPrepareOptionsMenu()` crashes. - public static String proxyMode = ProxyHelper.NONE; // Define the saved instance state constants. private final String SAVED_STATE_ARRAY_LIST = "saved_state_array_list"; @@ -264,10 +255,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private ForegroundColorSpan initialGrayColorSpan; private ForegroundColorSpan finalGrayColorSpan; - // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, - // and `loadBookmarksFolder()`. - private BookmarksDatabaseHelper bookmarksDatabaseHelper; - // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`. private Cursor bookmarksCursor; @@ -291,29 +278,31 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private boolean sanitizeTwitterAmpRedirects; // Declare the class variables - private BroadcastReceiver orbotStatusBroadcastReceiver; - private String webViewDefaultUserAgent; - private boolean incognitoModeEnabled; - private boolean fullScreenBrowsingModeEnabled; - private boolean inFullScreenBrowsingMode; + private BookmarksDatabaseHelper bookmarksDatabaseHelper; + private boolean bottomAppBar; + private boolean displayingFullScreenVideo; private boolean downloadWithExternalApp; + private boolean fullScreenBrowsingModeEnabled; private boolean hideAppBar; - private boolean scrollAppBar; - private boolean bottomAppBar; + private boolean incognitoModeEnabled; + private boolean inFullScreenBrowsingMode; private boolean loadingNewIntent; - private boolean reapplyDomainSettingsOnRestart; + private BroadcastReceiver orbotStatusBroadcastReceiver; + private ProxyHelper proxyHelper; private boolean reapplyAppSettingsOnRestart; - private boolean displayingFullScreenVideo; + private boolean reapplyDomainSettingsOnRestart; + private boolean scrollAppBar; private boolean waitingForProxy; + private String webViewDefaultUserAgent; // Define the class variables. - private long lastScrollUpdate = 0; + private ObjectAnimator objectAnimator = new ObjectAnimator(); + private String saveUrlString = ""; // Declare the class views. private FrameLayout rootFrameLayout; private DrawerLayout drawerLayout; - private RelativeLayout mainContentRelativeLayout; - private AppBarLayout appBarLayout; + private CoordinatorLayout coordinatorLayout; private Toolbar toolbar; private RelativeLayout urlRelativeLayout; private EditText urlEditText; @@ -376,13 +365,124 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private MenuItem optionsFontSizeMenuItem; private MenuItem optionsAddOrEditDomainMenuItem; - @Override - // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`. + // This variable won't be needed once the class is migrated to Kotlin, as can be seen in LogcatActivity or AboutVersionFragment. + 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(), + new ActivityResultCallback() { + @Override + public void onActivityResult(Uri fileUri) { + // Only save the URL if the file URI is not null, which happens if the user exited the file picker by pressing back. + if (fileUri != null) { + new SaveUrl(getApplicationContext(), resultLauncherActivityHandle, fileUri, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()).execute(saveUrlString); + } + + // Reset the save URL string. + saveUrlString = ""; + } + }); + + // Define the save webpage archive activity result launcher. It must be defined before `onCreate()` is run or the app will crash. + private final ActivityResultLauncher saveWebpageArchiveActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument(), + new ActivityResultCallback() { + @Override + 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) { + 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 { + // Create a temporary MHT file input stream. + FileInputStream temporaryMhtFileInputStream = new FileInputStream(temporaryMhtFile); + + // Get an output stream for the save webpage file path. + OutputStream mhtOutputStream = getContentResolver().openOutputStream(fileUri); + + // Create a transfer byte array. + byte[] transferByteArray = new byte[1024]; + + // Create an integer to track the number of bytes read. + int bytesRead; + + // Copy the temporary MHT file input stream to the MHT output stream. + while ((bytesRead = temporaryMhtFileInputStream.read(transferByteArray)) > 0) { + mhtOutputStream.write(transferByteArray, 0, bytesRead); + } + + // Close the streams. + 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(); + } catch (Exception exception) { + // Display a snackbar with the exception. + Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + exception, Snackbar.LENGTH_INDEFINITE).show(); + } finally { + // Delete the temporary MHT file. + //noinspection ResultOfMethodCallIgnored + temporaryMhtFile.delete(); + } + } 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(); + } + }); + } catch (IOException ioException) { + // Display a snackbar with the IO exception. + Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + ioException, Snackbar.LENGTH_INDEFINITE).show(); + } + } + } + }); + + // Define the save webpage image activity result launcher. It must be defined before `onCreate()` is run or the app will crash. + private final ActivityResultLauncher saveWebpageImageActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument(), + new ActivityResultCallback() { + @Override + public void onActivityResult(Uri fileUri) { + // Only save the webpage image if the file URI is not null, which happens if the user exited the file picker by pressing back. + if (fileUri != null) { + // Save the webpage image. + new SaveWebpageImage(resultLauncherActivityHandle, fileUri, currentWebView).execute(); + } + } + }); + + // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with WebView. @SuppressLint("ClickableViewAccessibility") + @Override protected void onCreate(Bundle savedInstanceState) { // Run the default commands. super.onCreate(savedInstanceState); + // 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. @@ -429,9 +529,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // 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. - if (Build.VERSION.SDK_INT >= 21) { - WebView.enableSlowWholeDocumentDraw(); - } + //WebView.enableSlowWholeDocumentDraw(); Temporarily disabled due to . // Set the theme. setTheme(R.style.PrivacyBrowser); @@ -446,7 +544,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get handles for the views. rootFrameLayout = findViewById(R.id.root_framelayout); drawerLayout = findViewById(R.id.drawerlayout); - mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout); + coordinatorLayout = findViewById(R.id.coordinatorlayout); appBarLayout = findViewById(R.id.appbar_layout); toolbar = findViewById(R.id.toolbar); findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout); @@ -454,10 +552,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook tabLayout = findViewById(R.id.tablayout); swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); webViewPager = findViewById(R.id.webviewpager); - fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout); - - // Get a handle for the navigation view. NavigationView navigationView = findViewById(R.id.navigationview); + fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout); // Get a handle for the navigation menu. Menu navigationMenu = navigationView.getMenu(); @@ -506,6 +602,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Store up to 100 tabs in memory. webViewPager.setOffscreenPageLimit(100); + // Instantiate the proxy helper. + proxyHelper = new ProxyHelper(); + // Initialize the app. initializeApp(); @@ -631,7 +730,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); // Reset the current domain name so the domain settings will be reapplied. - nestedScrollWebView.resetCurrentDomainName(); + nestedScrollWebView.setCurrentDomainName(""); // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings. if (nestedScrollWebView.getUrl() != null) { @@ -691,7 +790,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook applyProxy(false); } - // Reapply any system UI flags and the ad in the free flavor. + // Reapply any system UI flags. if (displayingFullScreenVideo || inFullScreenBrowsingMode) { // The system is displaying a website or a video in full screen mode. /* Hide the system bars. * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. @@ -701,12 +800,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook */ rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } else if (BuildConfig.FLAVOR.contentEquals("free")) { // The system in not in full screen mode. - // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load. - View adView = findViewById(R.id.adview); - - // Resume the ad. - AdHelper.resumeAd(adView); } // Show any pending dialogs. @@ -749,15 +842,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook if (currentWebView != null) { currentWebView.pauseTimers(); } - - // Pause the ad or it will continue to consume resources in the background on the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load. - View adView = findViewById(R.id.adview); - - // Pause the ad. - AdHelper.pauseAd(adView); - } } @Override @@ -838,9 +922,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`. optionsMenu = menu; - // Get handles for the class menu items. + // Get handles for the menu items. optionsPrivacyMenuItem = menu.findItem(R.id.javascript); optionsRefreshMenuItem = menu.findItem(R.id.refresh); + MenuItem bookmarksMenuItem = menu.findItem(R.id.bookmarks); optionsCookiesMenuItem = menu.findItem(R.id.cookies); optionsDomStorageMenuItem = menu.findItem(R.id.dom_storage); optionsSaveFormDataMenuItem = menu.findItem(R.id.save_form_data); // Form data can be removed once the minimum API >= 26. @@ -882,10 +967,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook optionsFontSizeMenuItem = menu.findItem(R.id.font_size); optionsAddOrEditDomainMenuItem = menu.findItem(R.id.add_or_edit_domain); - // Get handles for the method menu items. - MenuItem bookmarksMenuItem = menu.findItem(R.id.bookmarks); - MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent); - // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step. updatePrivacyIcons(false); @@ -896,12 +977,6 @@ 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 API >= 21. - optionsDarkWebViewMenuItem.setVisible(Build.VERSION.SDK_INT >= 21); - - // Only show Ad Consent if this is the free flavor. - adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free")); - // Get the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); @@ -924,17 +999,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the title. optionsRefreshMenuItem.setTitle(R.string.stop); - // Set the icon if it is displayed in the app bar. + // Set the icon if it is displayed in the app bar. Once the minimum API is >= 26, the blue and black icons can be combined with a tint list. if (displayAdditionalAppBarIcons) { - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Set the icon according to the current theme status. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day); - } else { - optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night); - } + optionsRefreshMenuItem.setIcon(R.drawable.close_blue); } } @@ -969,13 +1036,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the status of the menu item checkboxes. optionsDomStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled()); optionsSaveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData()); // Form data can be removed once the minimum API >= 26. - optionsEasyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)); - optionsEasyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)); - optionsFanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); - optionsFanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)); - optionsUltraListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)); - optionsUltraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)); - optionsBlockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); + optionsEasyListMenuItem.setChecked(currentWebView.getEasyListEnabled()); + optionsEasyPrivacyMenuItem.setChecked(currentWebView.getEasyPrivacyEnabled()); + optionsFanboysAnnoyanceListMenuItem.setChecked(currentWebView.getFanboysAnnoyanceListEnabled()); + optionsFanboysSocialBlockingListMenuItem.setChecked(currentWebView.getFanboysSocialBlockingListEnabled()); + optionsUltraListMenuItem.setChecked(currentWebView.getUltraListEnabled()); + optionsUltraPrivacyMenuItem.setChecked(currentWebView.getUltraPrivacyEnabled()); + optionsBlockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.getBlockAllThirdPartyRequests()); optionsSwipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh()); optionsWideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort()); optionsDisplayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically()); @@ -1300,12 +1367,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onDismissed(Snackbar snackbar, int event) { if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed. - // Delete the cookies, which command varies by SDK. - if (Build.VERSION.SDK_INT < 21) { - cookieManager.removeAllCookie(); - } else { - cookieManager.removeAllCookies(null); - } + // Delete the cookies. + cookieManager.removeAllCookies(null); } } }) @@ -1393,10 +1456,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; } else if (menuItemId == R.id.easylist) { // EasyList. // Toggle the EasyList status. - currentWebView.enableBlocklist(NestedScrollWebView.EASYLIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)); + currentWebView.setEasyListEnabled(!currentWebView.getEasyListEnabled()); // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)); + menuItem.setChecked(currentWebView.getEasyListEnabled()); // Reload the current WebView. currentWebView.reload(); @@ -1405,10 +1468,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; } else if (menuItemId == R.id.easyprivacy) { // EasyPrivacy. // Toggle the EasyPrivacy status. - currentWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)); + currentWebView.setEasyPrivacyEnabled(!currentWebView.getEasyPrivacyEnabled()); // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)); + menuItem.setChecked(currentWebView.getEasyPrivacyEnabled()); // Reload the current WebView. currentWebView.reload(); @@ -1417,13 +1480,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; } else if (menuItemId == R.id.fanboys_annoyance_list) { // Fanboy's Annoyance List. // Toggle Fanboy's Annoyance List status. - currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); + currentWebView.setFanboysAnnoyanceListEnabled(!currentWebView.getFanboysAnnoyanceListEnabled()); // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); + menuItem.setChecked(currentWebView.getFanboysAnnoyanceListEnabled()); // Update the status of Fanboy's Social Blocking List. - optionsFanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); + optionsFanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.getFanboysAnnoyanceListEnabled()); // Reload the current WebView. currentWebView.reload(); @@ -1432,10 +1495,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; } else if (menuItemId == R.id.fanboys_social_blocking_list) { // Fanboy's Social Blocking List. // Toggle Fanboy's Social Blocking List status. - currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)); + currentWebView.setFanboysSocialBlockingListEnabled(!currentWebView.getFanboysSocialBlockingListEnabled()); // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)); + menuItem.setChecked(currentWebView.getFanboysSocialBlockingListEnabled()); // Reload the current WebView. currentWebView.reload(); @@ -1444,10 +1507,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; } else if (menuItemId == R.id.ultralist) { // UltraList. // Toggle the UltraList status. - currentWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)); + currentWebView.setUltraListEnabled(!currentWebView.getUltraListEnabled()); // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)); + menuItem.setChecked(currentWebView.getUltraListEnabled()); // Reload the current WebView. currentWebView.reload(); @@ -1456,10 +1519,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; } else if (menuItemId == R.id.ultraprivacy) { // UltraPrivacy. // Toggle the UltraPrivacy status. - currentWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)); + currentWebView.setUltraPrivacyEnabled(!currentWebView.getUltraPrivacyEnabled()); // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)); + menuItem.setChecked(currentWebView.getUltraPrivacyEnabled()); // Reload the current WebView. currentWebView.reload(); @@ -1468,10 +1531,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; } else if (menuItemId == R.id.block_all_third_party_requests) { // Block all third-party requests. //Toggle the third-party requests blocker status. - currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); + currentWebView.setBlockAllThirdPartyRequests(!currentWebView.getBlockAllThirdPartyRequests()); // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); + menuItem.setChecked(currentWebView.getBlockAllThirdPartyRequests()); // Reload the current WebView. currentWebView.reload(); @@ -1732,7 +1795,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook assert printManager != null; // Create a print document adapter from the current WebView. - PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter(); + PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter(getString(R.string.print)); // Print the document. printManager.print(getString(R.string.privacy_browser_webpage), printDocumentAdapter, null); @@ -1745,28 +1808,21 @@ 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(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(), + new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()).execute(currentWebView.getCurrentUrl()); } // Consume the event. return true; } else if (menuItemId == R.id.save_archive) { - // Instantiate the save dialog. - DialogFragment saveArchiveFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_ARCHIVE, currentWebView.getCurrentUrl(), null, null, null, - false); - - // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name. - saveArchiveFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog)); + // Open the file picker with a default file name built from the current domain name. + saveWebpageArchiveActivityResultLauncher.launch(currentWebView.getCurrentDomainName() + ".mht"); + // Consume the event. return true; } else if (menuItemId == R.id.save_image) { // Save image. - // Instantiate the save dialog. - DialogFragment saveImageFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_IMAGE, currentWebView.getCurrentUrl(), null, null, null, - false); - - // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name. - saveImageFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog)); + // Open the file picker with a default file name built from the current domain name. + saveWebpageImageActivityResultLauncher.launch(currentWebView.getCurrentDomainName() + ".png"); // Consume the event. return true; @@ -1839,6 +1895,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId()); domainsIntent.putExtra("close_on_back", true); domainsIntent.putExtra("current_url", currentWebView.getUrl()); + domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); // Get the current certificate. SslCertificate sslCertificate = currentWebView.getCertificate(); @@ -1866,12 +1923,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook domainsIntent.putExtra("ssl_end_date", endDateLong); } - // Check to see if the current IP addresses have been received. - if (currentWebView.hasCurrentIpAddresses()) { - // Add the current IP addresses to the intent. - domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); - } - // Make it so. startActivity(domainsIntent); } else { // Add a new domain. @@ -1895,6 +1946,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook domainsIntent.putExtra("load_domain", newDomainDatabaseId); domainsIntent.putExtra("close_on_back", true); domainsIntent.putExtra("current_url", currentWebView.getUrl()); + domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); // Get the current certificate. SslCertificate sslCertificate = currentWebView.getCertificate(); @@ -1922,25 +1974,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook domainsIntent.putExtra("ssl_end_date", endDateLong); } - // Check to see if the current IP addresses have been received. - if (currentWebView.hasCurrentIpAddresses()) { - // Add the current IP addresses to the intent. - domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); - } - // Make it so. startActivity(domainsIntent); } - // Consume the event. - return true; - } else if (menuItemId == R.id.ad_consent) { // Ad consent. - // Instantiate the ad consent dialog. - DialogFragment adConsentDialogFragment = new AdConsentDialog(); - - // Display the ad consent dialog. - adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent)); - // Consume the event. return true; } else { // There is no match with the options menu. Pass the event up to the parent method. @@ -2015,7 +2052,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook Intent requestsIntent = new Intent(this, RequestsActivity.class); // Add the block third-party requests status to the intent. - requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); + requestsIntent.putExtra("block_all_third_party_requests", currentWebView.getBlockAllThirdPartyRequests()); // Make it so. startActivity(requestsIntent); @@ -2073,6 +2110,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Add the extra information to the intent. domainsIntent.putExtra("current_url", currentWebView.getUrl()); + domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); // Get the current certificate. SslCertificate sslCertificate = currentWebView.getCertificate(); @@ -2100,12 +2138,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook domainsIntent.putExtra("ssl_end_date", endDateLong); } - // Check to see if the current IP addresses have been received. - if (currentWebView.hasCurrentIpAddresses()) { - // Add the current IP addresses to the intent. - domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); - } - // Make it so. startActivity(domainsIntent); } else if (menuItemId == R.id.settings) { // Settings. @@ -2165,26 +2197,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook actionBarDrawerToggle.syncState(); } - @Override - public void onConfigurationChanged(@NonNull Configuration newConfig) { - // Run the default commands. - super.onConfigurationChanged(newConfig); - - // Reload the ad for the free flavor if not in full screen mode. - if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) { - // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load. - View adView = findViewById(R.id.adview); - - // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations. - // `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23. - AdHelper.loadAd(adView, getApplicationContext(), this, getString(R.string.ad_unit_id)); - } - - // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug: - // https://code.google.com/p/android/issues/detail?id=20493#c8 - // ActivityCompat.invalidateOptionsMenu(this); - } - @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { // Get the hit test result. @@ -2263,7 +2275,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(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(), + new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()).execute(linkUrl); } @@ -2335,7 +2347,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(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(), + new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()).execute(imageUrl); } @@ -2440,7 +2452,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(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(), + new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()).execute(imageUrl); } @@ -2467,7 +2479,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(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(), + new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()).execute(linkUrl); } @@ -2757,11 +2769,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook closeCurrentTab(); } else { // There isn't anything to do in Privacy Browser. // Close Privacy Browser. `finishAndRemoveTask()` also removes Privacy Browser from the recent app list. - if (Build.VERSION.SDK_INT >= 21) { - finishAndRemoveTask(); - } else { - finish(); - } + finishAndRemoveTask(); // Manually kill Privacy Browser. Otherwise, it is glitchy when restarted. System.exit(0); @@ -2777,11 +2785,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Run the commands that correlate to the specified request code. switch (requestCode) { case BROWSE_FILE_UPLOAD_REQUEST_CODE: - // File uploads only work on API >= 21. - if (Build.VERSION.SDK_INT >= 21) { - // Pass the file to the WebView. - fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent)); - } + // Pass the file to the WebView. + fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent)); break; case BROWSE_OPEN_REQUEST_CODE: @@ -2815,38 +2820,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } break; - - case BROWSE_SAVE_WEBPAGE_REQUEST_CODE: - // Don't do anything if the user pressed back from the file picker. - if (resultCode == Activity.RESULT_OK) { - // Get a handle for the save dialog fragment. - DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_dialog)); - - // Only update the file name if the dialog still exists. - if (saveWebpageDialogFragment != null) { - // Get a handle for the save webpage dialog. - Dialog saveWebpageDialog = saveWebpageDialogFragment.getDialog(); - - // Remove the incorrect lint warning below that the dialog might be null. - assert saveWebpageDialog != null; - - // Get a handle for the file name edit text. - EditText fileNameEditText = saveWebpageDialog.findViewById(R.id.file_name_edittext); - - // 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; } } @@ -2857,21 +2830,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Initialize the formatted URL string. String url = ""; - // Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search. + // Check to see if the unformatted URL string is a valid URL. Otherwise, convert it into a search. if (unformattedUrlString.startsWith("content://")) { // This is a Content URL. // Load the entire content URL. url = unformattedUrlString; } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") || unformattedUrlString.startsWith("file://")) { // This is a standard URL. // Add `https://` at the beginning if there is no protocol. Otherwise the app will segfault. - if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) { + if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://")) { unformattedUrlString = "https://" + unformattedUrlString; } - // Initialize `unformattedUrl`. + // Initialize the unformatted URL. URL unformattedUrl = null; - // Convert `unformattedUrlString` to a `URL`, then to a `URI`, and then back to a `String`, which sanitizes the input and adds in any missing components. + // Convert the unformatted URL string to a URL, then to a URI, and then back to a string, which sanitizes the input and adds in any missing components. try { unformattedUrl = new URL(unformattedUrlString); } catch (MalformedURLException e) { @@ -3045,7 +3018,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook currentWebView.loadUrl(temporaryMhtFile.toString()); } catch (Exception exception) { // Display a snackbar. - Snackbar.make(currentWebView, getString(R.string.error) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show(); + Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show(); } } else { // Let the WebView handle opening of the file. // Open the file. @@ -3067,97 +3040,27 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook startActivity(Intent.createChooser(downloadIntent, getString(R.string.download_with_external_app))); } - public void onSaveWebpage(int saveType, @NonNull String originalUrlString, DialogFragment dialogFragment) { - // Get the dialog. - Dialog dialog = dialogFragment.getDialog(); - - // Remove the incorrect lint warning below that the dialog might be null. - assert dialog != null; - - // Get a handle for the file name edit text. - EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext); - - // Get the file path from the edit text. - String saveWebpageFilePath = fileNameEditText.getText().toString(); - - //Save the webpage according to the save type. - switch (saveType) { - case SaveWebpageDialog.SAVE_URL: - // Get a handle for the dialog URL edit text. - EditText dialogUrlEditText = dialog.findViewById(R.id.url_edittext); - - // Define the save webpage URL. - String saveWebpageUrl; - - // Store the URL. - if (originalUrlString.startsWith("data:")) { - // Save the original URL. - saveWebpageUrl = originalUrlString; - } else { - // Get the URL from the edit text, which may have been modified. - saveWebpageUrl = dialogUrlEditText.getText().toString(); - } - - // Save the URL. - new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()).execute(saveWebpageUrl); - break; - - case SaveWebpageDialog.SAVE_ARCHIVE: - 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 { - // Create a temporary MHT file input stream. - FileInputStream temporaryMhtFileInputStream = new FileInputStream(temporaryMhtFile); - - // Get an output stream for the save webpage file path. - OutputStream mhtOutputStream = getContentResolver().openOutputStream(Uri.parse(saveWebpageFilePath)); - - // Create a transfer byte array. - byte[] transferByteArray = new byte[1024]; + public void onSaveUrl(@NonNull String originalUrlString, @NonNull String fileNameString, @NonNull DialogFragment dialogFragment) { + // Store the URL. This will be used in the save URL activity result launcher. + if (originalUrlString.startsWith("data:")) { + // Save the original URL. + saveUrlString = originalUrlString; + } else { + // Get the dialog. + Dialog dialog = dialogFragment.getDialog(); - // Create an integer to track the number of bytes read. - int bytesRead; + // Remove the incorrect lint warning below that the dialog might be null. + assert dialog != null; - // Copy the temporary MHT file input stream to the MHT output stream. - while ((bytesRead = temporaryMhtFileInputStream.read(transferByteArray)) > 0) { - mhtOutputStream.write(transferByteArray, 0, bytesRead); - } - - // Close the streams. - mhtOutputStream.close(); - temporaryMhtFileInputStream.close(); - - // Display a snackbar. - Snackbar.make(currentWebView, getString(R.string.file_saved) + " " + currentWebView.getCurrentUrl(), Snackbar.LENGTH_SHORT).show(); - } catch (Exception exception) { - // Display a snackbar with the exception. - Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show(); - } finally { - // Delete the temporary MHT file. - //noinspection ResultOfMethodCallIgnored - temporaryMhtFile.delete(); - } - } 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(); - } - }); - } catch (IOException ioException) { - // Display a snackbar with the IO exception. - Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + ioException.toString(), Snackbar.LENGTH_INDEFINITE).show(); - } - break; + // Get a handle for the dialog URL edit text. + EditText dialogUrlEditText = dialog.findViewById(R.id.url_edittext); - case SaveWebpageDialog.SAVE_IMAGE: - // Save the webpage image. - new SaveWebpageImage(this, saveWebpageFilePath, currentWebView).execute(); - break; + // Get the URL from the edit text, which may have been modified. + saveUrlString = dialogUrlEditText.getText().toString(); } + + // Open the file picker. + saveUrlActivityResultLauncher.launch(fileNameString); } // Remove the warning that `OnTouchListener()` needs to override `performClick()`, as the only purpose of setting the `OnTouchListener()` is to make it do nothing. @@ -3169,18 +3072,18 @@ 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. The deprecated `getResources()` must be used until API >= 23. - initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500)); - finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500)); + // Initialize the gray foreground 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(getResources().getColor(R.color.red_a700)); + redColorSpan = new ForegroundColorSpan(getColor(R.color.red_a700)); } else { - redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_900)); + redColorSpan = new ForegroundColorSpan(getColor(R.color.red_900)); } // Remove the formatting from the URL edit text when the user is editing the text. @@ -3222,7 +3125,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS"); // If Privacy Browser is waiting on the proxy, load the website now that Orbot is connected. - if ((orbotStatus != null) && orbotStatus.equals("ON") && waitingForProxy) { + if ((orbotStatus != null) && orbotStatus.equals(ProxyHelper.ORBOT_STATUS_ON) && waitingForProxy) { // Reset the waiting for proxy status. waitingForProxy = false; @@ -3263,7 +3166,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook loadUrl(nestedScrollWebView, waitingForProxyUrlString); // Reset the waiting for proxy URL string. - nestedScrollWebView.resetWaitingForProxyUrlString(); + nestedScrollWebView.setWaitingForProxyUrlString(""); } else { // No URL is waiting to be loaded. // Reload the existing URL. nestedScrollWebView.reload(); @@ -3338,7 +3241,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()); + DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId(), currentWebView.getFavoriteOrDefaultIcon()); // Display the View SSL Certificate dialog. viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate)); @@ -3433,17 +3336,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Implement swipe to refresh. swipeRefreshLayout.setOnRefreshListener(() -> { - // Check the visibility of the bottom app bar. Sometimes it is hidden if the WebView is the same size as the visible screen. - if (bottomAppBar && scrollAppBar && (appBarLayout.getVisibility() == View.GONE)) { // The bottom app bar is currently hidden. - // Show the app bar. - appBarLayout.setVisibility(View.VISIBLE); - - // Disable the refreshing animation. - swipeRefreshLayout.setRefreshing(false); - } else { // A bottom app bar is not currently hidden. - // Reload the website. - currentWebView.reload(); - } + // Reload the website. + currentWebView.reload(); }); // Store the default progress view offsets for use later in `initializeWebView()`. @@ -3451,11 +3345,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook defaultProgressViewEndOffset = swipeRefreshLayout.getProgressViewEndOffset(); // Set the refresh color scheme according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - swipeRefreshLayout.setColorSchemeResources(R.color.blue_700); - } else { - swipeRefreshLayout.setColorSchemeResources(R.color.violet_500); - } + swipeRefreshLayout.setColorSchemeResources(R.color.blue_text); // Initialize a color background typed value. TypedValue colorBackgroundTypedValue = new TypedValue(); @@ -3493,15 +3383,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook bookmarkCursor.moveToFirst(); // Act upon the bookmark according to the type. - if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder. + if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder. // Store the new folder name in `currentBookmarksFolder`. - currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); + currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME)); // Load the new folder. loadBookmarksFolder(); } else { // The selected bookmark is not a folder. // Load the bookmark URL. - loadUrl(currentWebView, bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL))); + loadUrl(currentWebView, bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL))); // Close the bookmarks drawer. drawerLayout.closeDrawer(GravityCompat.END); @@ -3521,7 +3411,7 @@ 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.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); + oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME)); // Instantiate the edit folder bookmark dialog. DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon()); @@ -3536,7 +3426,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook bookmarkCursor.moveToFirst(); // Load the bookmark in a new tab but do not switch to the tab or close the drawer. - addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)), false); + addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), false); // Display a snackbar. Snackbar.make(currentWebView, R.string.bookmark_opened_in_background, Snackbar.LENGTH_SHORT).show(); @@ -3576,9 +3466,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0); } - // Clear the focus from from the URL text box and the WebView. This removes any text selection markers and context menus, which otherwise draw above the open drawers. + // Clear the focus from from the URL text box. This removes any text selection markers and context menus, which otherwise draw above the open drawers. urlEditText.clearFocus(); - currentWebView.clearFocus(); + + // Clear the focus from from the WebView if it is not null, which can happen if a user opens a drawer while the browser is being resumed. + if (currentWebView != null) { + // Clearing the focus from the WebView removes any text selection markers and context menus, which otherwise draw above the open drawers. + currentWebView.clearFocus(); + } } } }); @@ -3612,7 +3507,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false); downloadWithExternalApp = sharedPreferences.getBoolean(getString(R.string.download_with_external_app_key), false); hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true); - scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true); + scrollAppBar = sharedPreferences.getBoolean(getString(R.string.scroll_app_bar_key), true); // Apply the saved proxy mode if the app has been restarted. if (savedProxyMode != null) { @@ -3698,15 +3593,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook actionBar.show(); } - // Hide the banner ad in the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load. - View adView = findViewById(R.id.adview); - - // Hide the banner ad. - AdHelper.hideAd(adView); - } - /* Hide the system bars. * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar. @@ -3725,16 +3611,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show the action bar. actionBar.show(); - // Show the banner ad in the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load. - View adView = findViewById(R.id.adview); - - // Initialize the ads. If this isn't the first run, `loadAd()` will be automatically called instead. - // `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23. - AdHelper.initializeAds(adView, getApplicationContext(), this, getSupportFragmentManager(), getString(R.string.ad_unit_id)); - } - // Remove the `SYSTEM_UI` flags from the root frame layout. rootFrameLayout.setSystemUiVisibility(0); } @@ -3791,7 +3667,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Clear any pinned SSL certificate or IP addresses. nestedScrollWebView.clearPinnedSslCertificate(); - nestedScrollWebView.clearPinnedIpAddresses(); + nestedScrollWebView.setPinnedIpAddresses(""); // Reset the favorite icon if specified. if (resetTab) { @@ -3834,14 +3710,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook Set domainSettingsSet = new HashSet<>(); // Get the domain name column index. - int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME); + int domainNameColumnIndex = domainNameCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME); // Populate `domainSettingsSet`. for (int i = 0; i < domainNameCursor.getCount(); i++) { - // Move `domainsCursor` to the current row. + // Move the domains cursor to the current row. domainNameCursor.moveToPosition(i); - // Store the domain name in `domainSettingsSet`. + // Store the domain name in the domain settings set. domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex)); } @@ -3905,62 +3781,37 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook currentDomainSettingsCursor.moveToFirst(); // Get the settings from the cursor. - nestedScrollWebView.setDomainSettingsDatabaseId(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID))); - nestedScrollWebView.getSettings().setJavaScriptEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1); - nestedScrollWebView.setAcceptCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.COOKIES)) == 1); - nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1); + nestedScrollWebView.setDomainSettingsDatabaseId(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper._ID))); + nestedScrollWebView.getSettings().setJavaScriptEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1); + nestedScrollWebView.setAcceptCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.COOKIES)) == 1); + nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1); // Form data can be removed once the minimum API >= 26. - boolean saveFormData = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST, - currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, - currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, - currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, - currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ULTRALIST)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, - currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, - currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1); - String userAgentName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT)); - int fontSize = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE)); - int swipeToRefreshInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH)); - int webViewThemeInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.WEBVIEW_THEME)); - int wideViewportInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.WIDE_VIEWPORT)); - int displayWebpageImagesInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES)); - boolean pinnedSslCertificate = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1); - String pinnedSslIssuedToCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)); - String pinnedSslIssuedToOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)); - String pinnedSslIssuedToUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)); - String pinnedSslIssuedByCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)); - String pinnedSslIssuedByOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)); - String pinnedSslIssuedByUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)); - boolean pinnedIpAddresses = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1); - String pinnedHostIpAddresses = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES)); - - // Get the pinned SSL date longs. - long pinnedSslStartDateLong = currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)); - long pinnedSslEndDateLong = currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)); - - // Define the pinned SSL date variables. - Date pinnedSslStartDate; - Date pinnedSslEndDate; - - // Set the pinned SSL certificate start date to `null` if the saved date long is 0 because creating a new date results in an error if the input is 0. - if (pinnedSslStartDateLong == 0) { - pinnedSslStartDate = null; - } else { - pinnedSslStartDate = new Date(pinnedSslStartDateLong); - } - - // Set the pinned SSL certificate end date to `null` if the saved date long is 0 because creating a new date results in an error if the input is 0. - if (pinnedSslEndDateLong == 0) { - pinnedSslEndDate = null; - } else { - pinnedSslEndDate = new Date(pinnedSslEndDateLong); - } + boolean saveFormData = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1); + nestedScrollWebView.setEasyListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1); + nestedScrollWebView.setEasyPrivacyEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1); + nestedScrollWebView.setFanboysAnnoyanceListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1); + nestedScrollWebView.setFanboysSocialBlockingListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow( + DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1); + nestedScrollWebView.setUltraListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ULTRALIST)) == 1); + nestedScrollWebView.setUltraPrivacyEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1); + nestedScrollWebView.setBlockAllThirdPartyRequests(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1); + String userAgentName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.USER_AGENT)); + 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)); + int wideViewportInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WIDE_VIEWPORT)); + int displayWebpageImagesInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DISPLAY_IMAGES)); + boolean pinnedSslCertificate = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1); + String pinnedSslIssuedToCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)); + String pinnedSslIssuedToOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)); + String pinnedSslIssuedToUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)); + String pinnedSslIssuedByCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)); + String pinnedSslIssuedByOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)); + String pinnedSslIssuedByUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)); + Date pinnedSslStartDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_START_DATE))); + Date pinnedSslEndDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_END_DATE))); + boolean pinnedIpAddresses = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1); + String pinnedHostIpAddresses = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.IP_ADDRESSES)); // Close the current host domain settings cursor. currentDomainSettingsCursor.close(); @@ -4144,28 +3995,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook break; } - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - // Set a background on the URL relative layout to indicate that custom domain settings are being used. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_light_green, null)); - } else { - urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_dark_blue, null)); - } + urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.domain_settings_url_background, null)); } else { // The new URL does not have custom domain settings. Load the defaults. // Store the values from the shared preferences. nestedScrollWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false)); nestedScrollWebView.setAcceptCookies(sharedPreferences.getBoolean(getString(R.string.cookies_key), false)); nestedScrollWebView.getSettings().setDomStorageEnabled(sharedPreferences.getBoolean("dom_storage", false)); boolean saveFormData = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26. - nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST, sharedPreferences.getBoolean("easylist", true)); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, sharedPreferences.getBoolean("easyprivacy", true)); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean("fanboys_annoyance_list", true)); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean("fanboys_social_blocking_list", true)); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, sharedPreferences.getBoolean("ultralist", true)); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, sharedPreferences.getBoolean("ultraprivacy", true)); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false)); + nestedScrollWebView.setEasyListEnabled(sharedPreferences.getBoolean("easylist", true)); + nestedScrollWebView.setEasyPrivacyEnabled(sharedPreferences.getBoolean("easyprivacy", true)); + nestedScrollWebView.setFanboysAnnoyanceListEnabled(sharedPreferences.getBoolean("fanboys_annoyance_list", true)); + nestedScrollWebView.setFanboysSocialBlockingListEnabled(sharedPreferences.getBoolean("fanboys_social_blocking_list", true)); + nestedScrollWebView.setUltraListEnabled(sharedPreferences.getBoolean("ultralist", true)); + nestedScrollWebView.setUltraPrivacyEnabled(sharedPreferences.getBoolean("ultraprivacy", true)); + nestedScrollWebView.setBlockAllThirdPartyRequests(sharedPreferences.getBoolean("block_all_third_party_requests", false)); // Apply the default cookie setting. cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptCookies()); @@ -4277,8 +4121,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } private void applyProxy(boolean reloadWebViews) { - // Set the proxy according to the mode. `this` refers to the current activity where an alert dialog might be displayed. - ProxyHelper.setProxy(getApplicationContext(), appBarLayout, proxyMode); + // Set the proxy according to the mode. + proxyHelper.setProxy(getApplicationContext(), appBarLayout, proxyMode); // Reset the waiting for proxy tracker. waitingForProxy = false; @@ -4319,7 +4163,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook packageManager.getPackageInfo("org.torproject.android", 0); // Check to see if the proxy is ready. - if (!orbotStatus.equals("ON")) { // Orbot is not ready. + if (!orbotStatus.equals(ProxyHelper.ORBOT_STATUS_ON)) { // Orbot is not ready. // Set the waiting for proxy status. waitingForProxy = true; @@ -4433,35 +4277,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook optionsPrivacyMenuItem.setIcon(R.drawable.privacy_mode); } - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - // Update the cookies icon. - if (currentWebView.getAcceptCookies()) { // Cookies are enabled. + if (currentWebView.getAcceptCookies()) { optionsCookiesMenuItem.setIcon(R.drawable.cookies_enabled); - } else { // Cookies are disabled. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled_day); - } else { - optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled_night); - } + } else { + optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled); } // Update the refresh icon. if (optionsRefreshMenuItem.getTitle() == getString(R.string.refresh)) { // The refresh icon is displayed. - // Set the icon according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_day); - } else { - optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_night); - } + // Set the icon. Once the minimum API is >= 26, the blue and black icons can be combined with a tint list. + optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled); } else { // The stop icon is displayed. - // Set the icon according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day); - } else { - optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night); - } + // Set the icon. Once the minimum API is >= 26, the blue and black icons can be combined with a tint list. + optionsRefreshMenuItem.setIcon(R.drawable.close_blue); } // `invalidateOptionsMenu()` calls `onPrepareOptionsMenu()` and redraws the icons in the app bar. @@ -4548,7 +4377,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name); // Get the favorite icon byte array from the cursor. - byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); + byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON)); // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last. Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length); @@ -4557,11 +4386,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap); // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`. - String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); + String bookmarkNameString = cursor.getString(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME)); bookmarkNameTextView.setText(bookmarkNameString); // Make the font bold for folders. - if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { + if (cursor.getInt(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD); } else { // Reset the font to default for normal bookmarks. bookmarkNameTextView.setTypeface(Typeface.DEFAULT); @@ -4807,8 +4636,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook webViewPagerAdapter.addPage(newTabNumber, webViewPager, url, moveToTab); // Show the app bar if it is at the bottom of the screen and the new tab is taking focus. - if (bottomAppBar && moveToTab) { - appBarLayout.setVisibility(View.VISIBLE); + if (bottomAppBar && moveToTab && (appBarLayout.getTranslationY() != 0)) { + // Animate the bottom app bar onto the screen. + objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0); + + // Make it so. + objectAnimator.start(); } } @@ -4852,8 +4685,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Enable the sliding drawers. drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); - // Show the main content relative layout. - mainContentRelativeLayout.setVisibility(View.VISIBLE); + // Show the coordinator layout. + coordinatorLayout.setVisibility(View.VISIBLE); // Apply the appropriate full screen mode flags. if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode. @@ -4866,15 +4699,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook actionBar.hide(); } - // Hide the banner ad in the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load. - View adView = findViewById(R.id.adview); - - // Hide the banner ad. - AdHelper.hideAd(adView); - } - /* Hide the system bars. * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar. @@ -4887,15 +4711,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Remove the `SYSTEM_UI` flags from the root frame layout. rootFrameLayout.setSystemUiVisibility(0); } - - // Reload the ad for the free flavor if not in full screen mode. - if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) { - // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load. - View adView = findViewById(R.id.adview); - - // Reload the ad. - AdHelper.loadAd(adView, this, this, getString(R.string.ad_unit_id)); - } } private void clearAndExit() { @@ -4918,12 +4733,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Clear cookies. if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) { - // The command to remove cookies changed slightly in API 21. - if (Build.VERSION.SDK_INT >= 21) { - CookieManager.getInstance().removeAllCookies(null); - } else { - CookieManager.getInstance().removeAllCookie(); - } + // Request the cookies be deleted. + CookieManager.getInstance().removeAllCookies(null); // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run. try { @@ -5081,11 +4892,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list. - if (Build.VERSION.SDK_INT >= 21) { - finishAndRemoveTask(); - } else { - finish(); - } + finishAndRemoveTask(); // Remove the terminated program from RAM. The status code is `0`. System.exit(0); @@ -5177,16 +4984,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the background to indicate the domain settings status. if (currentWebView.getDomainSettingsApplied()) { - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Set a green background on the URL relative layout to indicate that custom domain settings are being used. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_light_green, null)); - } else { - urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_dark_blue, null)); - } + // Set a background on the URL relative layout to indicate that custom domain settings are being used. + urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.domain_settings_url_background, null)); } else { + // 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. @@ -5204,6 +5005,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } + @SuppressLint("ClickableViewAccessibility") @Override public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, String url, Boolean restoringState) { // Get a handle for the shared preferences. @@ -5259,11 +5061,8 @@ 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 favorite icon. - nestedScrollWebView.initializeFavoriteIcon(); - // Set the app bar scrolling. - nestedScrollWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true)); + nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar); // Allow pinch to zoom. nestedScrollWebView.getSettings().setBuiltInZoomControls(true); @@ -5272,9 +5071,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.getSettings().setDisplayZoomControls(false); // Don't allow mixed content (HTTP and HTTPS) on the same website. - if (Build.VERSION.SDK_INT >= 21) { - nestedScrollWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW); - } + nestedScrollWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW); // Set the WebView to load in overview mode (zoomed out to the maximum width). nestedScrollWebView.getSettings().setLoadWithOverviewMode(true); @@ -5326,15 +5123,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Hide the banner ad in the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load. - View adView = findViewById(R.id.adview); - - // Hide the banner ad. - AdHelper.hideAd(adView); - } - /* Hide the system bars. * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar. @@ -5371,15 +5159,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Show the banner ad in the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load. - View adView = findViewById(R.id.adview); - - // Reload the ad. `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23. - AdHelper.loadAd(adView, getApplicationContext(), activity, getString(R.string.ad_unit_id)); - } - // Remove the `SYSTEM_UI` flags from the root frame layout. rootFrameLayout.setSystemUiVisibility(0); } @@ -5426,7 +5205,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl); // Instantiate the save dialog. - DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent, + DialogFragment saveDialogFragment = SaveDialog.saveUrl(downloadUrl, formattedFileSizeString, fileNameString, 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. @@ -5464,67 +5243,46 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook }); // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView. Also reinforce full screen browsing mode. - // On API < 23, `getViewTreeObserver().addOnScrollChangedListener()` must be used, but it is a little bit buggy and appears to get garbage collected from time to time. - if (Build.VERSION.SDK_INT >= 23) { - nestedScrollWebView.setOnScrollChangeListener((view, scrollX, scrollY, oldScrollX, oldScrollY) -> { - // Set the swipe to refresh status. - if (nestedScrollWebView.getSwipeToRefresh()) { - // Only enable swipe to refresh if the WebView is scrolled to the top. - swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0); - } else { - // Disable swipe to refresh. - swipeRefreshLayout.setEnabled(false); - } + nestedScrollWebView.setOnScrollChangeListener((view, scrollX, scrollY, oldScrollX, oldScrollY) -> { + // Set the swipe to refresh status. + if (nestedScrollWebView.getSwipeToRefresh()) { + // Only enable swipe to refresh if the WebView is scrolled to the top. + swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0); + } else { + // Disable swipe to refresh. + swipeRefreshLayout.setEnabled(false); + } - // Set the visibility of the bottom app bar. - if (bottomAppBar && scrollAppBar && (Calendar.getInstance().getTimeInMillis() - lastScrollUpdate > 100)) { - if (scrollY - oldScrollY > 25) { // The WebView was scrolled down. - appBarLayout.setVisibility(View.GONE); - } else if (scrollY - oldScrollY < -25) { // The WebView was scrolled up. - appBarLayout.setVisibility(View.VISIBLE); - } + // Scroll the bottom app bar if enabled. + if (bottomAppBar && scrollAppBar && !objectAnimator.isRunning()) { + if (scrollY < oldScrollY) { // The WebView was scrolled down. + // Animate the bottom app bar onto the screen. + objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0); - // Update the last scroll update variable. This prevents the app bar from flashing on and off at the bottom of the screen. - lastScrollUpdate = Calendar.getInstance().getTimeInMillis(); - } + // Make it so. + objectAnimator.start(); + } else if (scrollY > oldScrollY) { // The WebView was scrolled up. + // Animate the bottom app bar off the screen. + objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", appBarLayout.getHeight()); - // Reinforce the system UI visibility flags if in full screen browsing mode. - // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard. - if (inFullScreenBrowsingMode) { - /* Hide the system bars. - * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. - * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar. - * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen. - * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown. - */ - rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } - }); - } else { - nestedScrollWebView.getViewTreeObserver().addOnScrollChangedListener(() -> { - if (nestedScrollWebView.getSwipeToRefresh()) { - // Only enable swipe to refresh if the WebView is scrolled to the top. - swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0); - } else { - // Disable swipe to refresh. - swipeRefreshLayout.setEnabled(false); + // Make it so. + objectAnimator.start(); } + } - // Reinforce the system UI visibility flags if in full screen browsing mode. - // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard. - if (inFullScreenBrowsingMode) { - /* Hide the system bars. - * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. - * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar. - * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen. - * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown. - */ - rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } - }); - } + // Reinforce the system UI visibility flags if in full screen browsing mode. + // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard. + if (inFullScreenBrowsingMode) { + /* Hide the system bars. + * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. + * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar. + * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen. + * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown. + */ + rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + }); // Set the web chrome client. nestedScrollWebView.setWebChromeClient(new WebChromeClient() { @@ -5618,20 +5376,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the full screen video flag. displayingFullScreenVideo = true; - // Pause the ad if this is the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load. - View adView = findViewById(R.id.adview); - - // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations. - AdHelper.pauseAd(adView); - } - // Hide the keyboard. inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0); - // Hide the main content relative layout. - mainContentRelativeLayout.setVisibility(View.GONE); + // Hide the coordinator layout. + coordinatorLayout.setVisibility(View.GONE); /* Hide the system bars. * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. @@ -5665,34 +5414,31 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Upload files. @Override public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { - // Show the file chooser if the device is running API >= 21. - if (Build.VERSION.SDK_INT >= 21) { - // Store the file path callback. - fileChooserCallback = filePathCallback; + // Store the file path callback. + fileChooserCallback = filePathCallback; - // Create an intent to open a chooser based on the file chooser parameters. - Intent fileChooserIntent = fileChooserParams.createIntent(); + // Create an intent to open a chooser based on the file chooser parameters. + Intent fileChooserIntent = fileChooserParams.createIntent(); - // Get a handle for the package manager. - PackageManager packageManager = getPackageManager(); + // Get a handle for the package manager. + PackageManager packageManager = getPackageManager(); - // 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); - } 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); + // 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); + } 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); - // Request an openable file. - genericFileChooserIntent.addCategory(Intent.CATEGORY_OPENABLE); + // Request an openable file. + genericFileChooserIntent.addCategory(Intent.CATEGORY_OPENABLE); - // Set the file type to everything. - genericFileChooserIntent.setType("*/*"); + // Set the file type to everything. + genericFileChooserIntent.setType("*/*"); - // Start the generic file chooser intent. - startActivityForResult(genericFileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE); - } + // Start the generic file chooser intent. + startActivityForResult(genericFileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE); } return true; } @@ -5782,7 +5528,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Check requests against the block lists. The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21. @Override - public WebResourceResponse shouldInterceptRequest(WebView view, String url) { + public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest webResourceRequest) { + // Get the URL. + String url = webResourceRequest.getUrl().toString(); + // Check to see if the resource request is for the main URL. if (url.equals(nestedScrollWebView.getCurrentUrl())) { // `return null` loads the resource request, which should never be blocked if it is the main URL. @@ -5802,9 +5551,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Sanitize the URL. - url = sanitizeUrl(url); - // Create an empty web resource response to be used if the resource request is blocked. WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes())); @@ -5820,31 +5566,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Store a copy of the current domain for use in later requests. String currentDomain = currentBaseDomain; - // Nobody is happy when comparing null strings. - if ((currentBaseDomain != null) && (url != null)) { - // Convert the request URL to a URI. - Uri requestUri = Uri.parse(url); - - // Get the request host name. - String requestBaseDomain = requestUri.getHost(); + // Get the request host name. + String requestBaseDomain = webResourceRequest.getUrl().getHost(); - // Only check for third-party requests if the current base domain is not empty and the request domain is not null. - if (!currentBaseDomain.isEmpty() && (requestBaseDomain != null)) { - // Determine the current base domain. - while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain. - // Remove the first subdomain. - currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1); - } - - // Determine the request base domain. - while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain. - // Remove the first subdomain. - requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1); - } + // Only check for third-party requests if the current base domain is not empty and the request domain is not null. + if (!currentBaseDomain.isEmpty() && (requestBaseDomain != null)) { + // Determine the current base domain. + while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain. + // Remove the first subdomain. + currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1); + } - // Update the third party request tracker. - isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain); + // Determine the request base domain. + while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain. + // Remove the first subdomain. + requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1); } + + // Update the third party request tracker. + isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain); } // Get the current WebView page position. @@ -5854,7 +5594,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook boolean webViewDisplayed = (webViewPagePosition == tabLayout.getSelectedTabPosition()); // Block third-party requests if enabled. - if (isThirdPartyRequest && nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)) { + if (isThirdPartyRequest && nestedScrollWebView.getBlockAllThirdPartyRequests()) { // Add the result to the resource requests. nestedScrollWebView.addResourceRequest(new String[]{BlocklistHelper.REQUEST_THIRD_PARTY, url}); @@ -5883,7 +5623,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Check UltraList if it is enabled. - if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)) { + if (nestedScrollWebView.getUltraListEnabled()) { // Check the URL against UltraList. String[] ultraListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraList); @@ -5923,7 +5663,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Check UltraPrivacy if it is enabled. - if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)) { + if (nestedScrollWebView.getUltraPrivacyEnabled()) { // Check the URL against UltraPrivacy. String[] ultraPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy); @@ -5965,7 +5705,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Check EasyList if it is enabled. - if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)) { + if (nestedScrollWebView.getEasyListEnabled()) { // Check the URL against EasyList. String[] easyListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList); @@ -6002,7 +5742,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Check EasyPrivacy if it is enabled. - if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)) { + if (nestedScrollWebView.getEasyPrivacyEnabled()) { // Check the URL against EasyPrivacy. String[] easyPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyPrivacy); @@ -6040,7 +5780,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Check Fanboy’s Annoyance List if it is enabled. - if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)) { + if (nestedScrollWebView.getFanboysAnnoyanceListEnabled()) { // Check the URL against Fanboy's Annoyance List. String[] fanboysAnnoyanceListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList); @@ -6077,7 +5817,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook whitelistResultStringArray = new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3], fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]}; } - } else if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)) { // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled. + } else if (nestedScrollWebView.getFanboysSocialBlockingListEnabled()) { // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled. // Check the URL against Fanboy's Annoyance List. String[] fanboysSocialListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysSocialList); @@ -6185,7 +5925,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Reset the list of host IP addresses. - nestedScrollWebView.clearCurrentIpAddresses(); + nestedScrollWebView.setCurrentIpAddresses(""); // Get a URI for the current URL. Uri currentUri = Uri.parse(url); @@ -6201,17 +5941,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the app bar and theme preferences. boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false); - // If the icon is displayed in the AppBar, set it according to the theme. + // Set the icon if it is displayed in the AppBar. Once the minimum API is >= 26, the blue and black icons can be combined with a tint list. if (displayAdditionalAppBarIcons) { - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Set the stop icon according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day); - } else { - optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night); - } + optionsRefreshMenuItem.setIcon(R.drawable.close_blue); } } } @@ -6219,7 +5951,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onPageFinished(WebView view, String url) { // Flush any cookies to persistent storage. The cookie manager has become very lazy about flushing cookies in recent versions. - if (nestedScrollWebView.getAcceptCookies() && Build.VERSION.SDK_INT >= 21) { + if (nestedScrollWebView.getAcceptCookies()) { CookieManager.getInstance().flush(); } @@ -6233,15 +5965,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // If the icon is displayed in the app bar, reset it according to the theme. if (displayAdditionalAppBarIcons) { - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Set the icon according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_day); - } else { - optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_night); - } + // Set the icon. + optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled); } } @@ -6350,7 +6075,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Handle SSL Certificate errors. + // Handle SSL Certificate errors. Suppress the lint warning that ignoring the error might be dangerous. + @SuppressLint("WebViewClientOnReceivedSslError") @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { // Get the current website SSL certificate.