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=1ee31db40a03cb8dfe9996eacfeaa79140aa230e;hp=0fd451611a28312b03479e36194e921e89c08878;hb=ff3641c1e0edbe729f4e09b804081d35185976e3;hpb=e9c77e79c3c6f7612f051b7c111e029ad125817b 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 0fd45161..1ee31db4 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; @@ -78,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; @@ -97,6 +99,7 @@ import android.widget.RadioButton; import android.widget.RelativeLayout; import android.widget.TextView; +import androidx.activity.OnBackPressedCallback; import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -123,7 +126,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; @@ -132,7 +134,6 @@ import com.stoutner.privacybrowser.asynctasks.PrepareSaveDialog; import com.stoutner.privacybrowser.asynctasks.SaveUrl; import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage; import com.stoutner.privacybrowser.dataclasses.PendingDialog; -import com.stoutner.privacybrowser.dialogs.AdConsentDialog; import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog; import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog; import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog; @@ -148,11 +149,11 @@ 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; import com.stoutner.privacybrowser.helpers.ProxyHelper; +import com.stoutner.privacybrowser.helpers.SanitizeUrlHelper; import com.stoutner.privacybrowser.views.NestedScrollWebView; import java.io.ByteArrayInputStream; @@ -173,58 +174,47 @@ 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; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import kotlin.Pair; + public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener, PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener { - // The executor service handles background tasks. It is accessed from `ViewSourceActivity`. - public static ExecutorService executorService = Executors.newFixedThreadPool(4); - - // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxy()`. + // Define the public static variables. + public static final ExecutorService executorService = Executors.newFixedThreadPool(4); public static String orbotStatus = "unknown"; + public static final ArrayList pendingDialogsArrayList = new ArrayList<>(); + public static String proxyMode = ProxyHelper.NONE; - // 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()`. + // Declare the public static variables. + public static String currentBookmarksFolder = ""; public static boolean restartFromBookmarksActivity; + public static WebViewPagerAdapter webViewPagerAdapter; - // Define the public static variables. - public static ArrayList pendingDialogsArrayList = new ArrayList<>(); - - // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, - // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`. - public static String currentBookmarksFolder; + // 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 entry is accessed from `OpenDialog()`. private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 0; public final static int BROWSE_OPEN_REQUEST_CODE = 1; - // 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"; private final String SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST = "saved_nested_scroll_webview_state_array_list"; @@ -245,9 +235,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`. private NestedScrollWebView currentWebView; - // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`. - private final Map customHeaders = new HashMap<>(); - // The search URL is set in `applyAppSettings()` and used in `onNewIntent()`, `loadUrlFromTextBox()`, `initializeApp()`, and `initializeWebView()`. private String searchURL; @@ -267,10 +254,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; @@ -288,36 +271,39 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private int defaultProgressViewStartOffset; private int defaultProgressViewEndOffset; - // The URL sanitizers are set in `applyAppSettings()` and used in `sanitizeUrl()`. - private boolean sanitizeGoogleAnalytics; - private boolean sanitizeFacebookClickIds; - private boolean sanitizeTwitterAmpRedirects; + // Declare the helpers. + private BookmarksDatabaseHelper bookmarksDatabaseHelper; + private DomainsDatabaseHelper domainsDatabaseHelper; + private ProxyHelper proxyHelper; + private SanitizeUrlHelper sanitizeUrlHelper; // Declare the class variables - private BroadcastReceiver orbotStatusBroadcastReceiver; - private String webViewDefaultUserAgent; - private boolean incognitoModeEnabled; - private boolean fullScreenBrowsingModeEnabled; - private boolean inFullScreenBrowsingMode; + private boolean bottomAppBar; + private boolean displayAdditionalAppBarIcons; + 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 boolean reapplyAppSettingsOnRestart; - private boolean displayingFullScreenVideo; + private boolean reapplyDomainSettingsOnRestart; + private boolean sanitizeAmpRedirects; + private boolean sanitizeTrackingQueries; + 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; @@ -384,7 +370,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private Activity resultLauncherActivityHandle; // Define the save URL activity result launcher. It must be defined before `onCreate()` is run or the app will crash. - private final ActivityResultLauncher saveUrlActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument(), + private final ActivityResultLauncher saveUrlActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument("*/*"), new ActivityResultCallback() { @Override public void onActivityResult(Uri fileUri) { @@ -399,17 +385,36 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook }); // 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(), + private final ActivityResultLauncher saveWebpageArchiveActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument("multipart/related"), 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) { + // Initialize the file name string from the file URI last path segment. + String temporaryFileNameString = fileUri.getLastPathSegment(); + + // Query the exact file name if the API >= 26. + if (Build.VERSION.SDK_INT >= 26) { + // Get a cursor from the content resolver. + Cursor contentResolverCursor = resultLauncherActivityHandle.getContentResolver().query(fileUri, null, null, null); + + // Move to the fist row. + contentResolverCursor.moveToFirst(); + + // Get the file name from the cursor. + temporaryFileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)); + + // Close the cursor. + contentResolverCursor.close(); + } + + // Save the final file name string so it can be used inside the lambdas. This will no longer be needed once this activity has transitioned to Kotlin. + String finalFileNameString = temporaryFileNameString; + try { // Create a temporary MHT file. File temporaryMhtFile = File.createTempFile("temporary_mht_file", ".mht", getCacheDir()); - - // Save the temporary MHT file. currentWebView.saveWebArchive(temporaryMhtFile.toString(), false, callbackValue -> { if (callbackValue != null) { // The temporary MHT file was saved successfully. try { @@ -434,29 +439,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook mhtOutputStream.close(); temporaryMhtFileInputStream.close(); - // Initialize the file name string from the file URI last path segment. - String fileNameString = fileUri.getLastPathSegment(); - - // Query the exact file name if the API >= 26. - if (Build.VERSION.SDK_INT >= 26) { - // Get a cursor from the content resolver. - Cursor contentResolverCursor = resultLauncherActivityHandle.getContentResolver().query(fileUri, null, null, null); - - // Move to the fist row. - contentResolverCursor.moveToFirst(); - - // Get the file name from the cursor. - fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); - - // Close the cursor. - contentResolverCursor.close(); - } - // Display a snackbar. - Snackbar.make(currentWebView, getString(R.string.file_saved) + " " + fileNameString, Snackbar.LENGTH_SHORT).show(); + Snackbar.make(currentWebView, getString(R.string.saved, finalFileNameString), Snackbar.LENGTH_SHORT).show(); } catch (Exception exception) { // Display a snackbar with the exception. - Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show(); + Snackbar.make(currentWebView, getString(R.string.error_saving_file, finalFileNameString, exception), Snackbar.LENGTH_INDEFINITE).show(); } finally { // Delete the temporary MHT file. //noinspection ResultOfMethodCallIgnored @@ -464,19 +451,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } else { // There was an unspecified error while saving the temporary MHT file. // Display an error snackbar. - Snackbar.make(currentWebView, getString(R.string.error_saving_file), Snackbar.LENGTH_INDEFINITE).show(); + Snackbar.make(currentWebView, getString(R.string.error_saving_file, finalFileNameString, getString(R.string.unknown_error)), Snackbar.LENGTH_INDEFINITE).show(); } }); } catch (IOException ioException) { // Display a snackbar with the IO exception. - Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + ioException.toString(), Snackbar.LENGTH_INDEFINITE).show(); + Snackbar.make(currentWebView, getString(R.string.error_saving_file, finalFileNameString, 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(), + private final ActivityResultLauncher saveWebpageImageActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument("image/png"), new ActivityResultCallback() { @Override public void onActivityResult(Uri fileUri) { @@ -514,9 +501,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Get the preferences. - String appTheme = sharedPreferences.getString("app_theme", getString(R.string.app_theme_default_value)); + String appTheme = sharedPreferences.getString(getString(R.string.app_theme_key), getString(R.string.app_theme_default_value)); boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false); bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false); + displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false); // Get the theme entry values string array. String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values); @@ -544,24 +532,16 @@ 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(); - } - - // Set the theme. - setTheme(R.style.PrivacyBrowser); + WebView.enableSlowWholeDocumentDraw(); - // Set the content view. - if (bottomAppBar) { - setContentView(R.layout.main_framelayout_bottom_appbar); - } else { - setContentView(R.layout.main_framelayout_top_appbar); - } + // Set the content view according to the position of the app bar. + if (bottomAppBar) setContentView(R.layout.main_framelayout_bottom_appbar); + else setContentView(R.layout.main_framelayout_top_appbar); // Get 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); @@ -610,6 +590,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Initially disable the sliding drawers. They will be enabled once the blocklists are loaded. drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + // Initially hide the user interface so that only the blocklist loading screen is shown (if reloading). + drawerLayout.setVisibility(View.GONE); + // Initialize the web view pager adapter. webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager()); @@ -619,12 +602,60 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Store up to 100 tabs in memory. webViewPager.setOffscreenPageLimit(100); + // Instantiate the helpers. + bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this); + domainsDatabaseHelper = new DomainsDatabaseHelper(this); + proxyHelper = new ProxyHelper(); + sanitizeUrlHelper = new SanitizeUrlHelper(); + // Initialize the app. initializeApp(); // Apply the app settings from the shared preferences. applyAppSettings(); + // Control what the system back command does. + OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + // Process the different back options. + if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open. + // Close the navigation drawer. + drawerLayout.closeDrawer(GravityCompat.START); + } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open. + // close the bookmarks drawer. + drawerLayout.closeDrawer(GravityCompat.END); + } else if (displayingFullScreenVideo) { // A full screen video is shown. + // Exit the full screen video. + exitFullScreenVideo(); + } else if (currentWebView.canGoBack()) { // There is at least one item in the current WebView history. + // Get the current web back forward list. + WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList(); + + // Get the previous entry URL. + String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl(); + + // Apply the domain settings. + applyDomainSettings(currentWebView, previousUrl, false, false, false); + + // Go back. + currentWebView.goBack(); + } else if (tabLayout.getTabCount() > 1) { // There are at least two tabs. + // Close the current tab. + closeCurrentTab(); + } else { // There isn't anything to do in Privacy Browser. + // Close Privacy Browser. `finishAndRemoveTask()` also removes Privacy Browser from the recent app list. + finishAndRemoveTask(); + + // Manually kill Privacy Browser. Otherwise, it is glitchy when restarted. + System.exit(0); + } + } + }; + + // Register the on back pressed callback. + getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback); + // Populate the blocklists. populateBlocklists = new PopulateBlocklists(this, this).execute(); } @@ -634,9 +665,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Run the default commands. super.onNewIntent(intent); - // Replace the intent that started the app with this one. - setIntent(intent); - // Check to see if the app is being restarted from a saved state. if (savedStateArrayList == null || savedStateArrayList.size() == 0) { // The activity is not being restarted from a saved state. // Get the information from the intent. @@ -687,7 +715,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Add a new tab if specified in the preferences. - if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) { // Load the URL in a new tab. + if (sharedPreferences.getBoolean(getString(R.string.open_intents_in_new_tab_key), true)) { // Load the URL in a new tab. // Set the loading new intent flag. loadingNewIntent = true; @@ -708,6 +736,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook drawerLayout.closeDrawer(GravityCompat.END); } } + } else { // The app has been restarted. + // Replace the intent that started the app with this one. This will load the tab after the others have been restored. + setIntent(intent); } } @@ -804,7 +835,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. @@ -814,12 +845,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. @@ -862,15 +887,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 @@ -951,9 +967,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. @@ -995,10 +1012,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); @@ -1009,18 +1022,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); - - // Get the dark theme and app bar preferences. - boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false); - // Set the status of the additional app bar icons. Setting the refresh menu item to `SHOW_AS_ACTION_ALWAYS` makes it appear even on small devices like phones. if (displayAdditionalAppBarIcons) { optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); @@ -1037,17 +1038,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); } } @@ -1106,8 +1099,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Enable DOM Storage if JavaScript is enabled. optionsDomStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled()); + // Get the current theme status. + int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + + // Enable dark WebView if the API is < 33 or if night mode is enabled. + optionsDarkWebViewMenuItem.setEnabled((Build.VERSION.SDK_INT < 33) || (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES)); + // Set the checkbox status for dark WebView if the WebView supports it. - if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { + if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // The device is running API >= 33 and algorithmic darkening is supported. + optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView.getSettings())); + } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { // The device is running API < 33 and the WebView supports force dark. + //noinspection deprecation optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON); } } @@ -1413,12 +1415,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); } } }) @@ -1737,7 +1735,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; } else if (menuItemId == R.id.user_agent_custom) { // User Agent - Custom. // Update the user agent. - currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value))); + currentWebView.getSettings().setUserAgentString(sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value))); // Reload the current WebView. currentWebView.reload(); @@ -1791,13 +1789,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; } else if (menuItemId == R.id.dark_webview) { // Dark WebView. // Check to see if dark WebView is supported by this WebView. - if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { + if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // The device is running API >= 33 and algorithmic darkening is supported. + // Toggle algorithmic darkening. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(currentWebView.getSettings(), !WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView.getSettings())); + } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { // The device is running API < 33 and the WebView supports force dark. // Toggle the dark WebView setting. + //noinspection deprecation if (WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON) { // Dark WebView is currently enabled. // Turn off dark WebView. + //noinspection deprecation WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); } else { // Dark WebView is currently disabled. // Turn on dark WebView. + //noinspection deprecation WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); } } @@ -1845,7 +1849,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); @@ -1899,24 +1903,45 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Consume the event. return true; - } else if (menuItemId == R.id.share_url) { // Share URL. - // Setup the share string. + } else if (menuItemId == R.id.share_message) { // Share a message. + // Prepare the share string. String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl(); // Create the share intent. - Intent shareIntent = new Intent(Intent.ACTION_SEND); + Intent shareMessageIntent = new Intent(Intent.ACTION_SEND); // Add the share string to the intent. - shareIntent.putExtra(Intent.EXTRA_TEXT, shareString); + shareMessageIntent.putExtra(Intent.EXTRA_TEXT, shareString); // Set the MIME type. - shareIntent.setType("text/plain"); + shareMessageIntent.setType("text/plain"); // Set the intent to open in a new task. - shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + shareMessageIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // Make it so. - startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url))); + startActivity(Intent.createChooser(shareMessageIntent, getString(R.string.share_message))); + + // Consume the event. + return true; + } else if (menuItemId == R.id.share_url) { // Share URL. + // Create the share intent. + Intent shareUrlIntent = new Intent(Intent.ACTION_SEND); + + // Add the URL to the intent. + shareUrlIntent.putExtra(Intent.EXTRA_TEXT, currentWebView.getUrl()); + + // Add the title to the intent. + shareUrlIntent.putExtra(Intent.EXTRA_SUBJECT, currentWebView.getTitle()); + + // Set the MIME type. + shareUrlIntent.setType("text/plain"); + + // Set the intent to open in a new task. + shareUrlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + //Make it so. + startActivity(Intent.createChooser(shareUrlIntent, getString(R.string.share_url))); // Consume the event. return true; @@ -1933,19 +1958,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Consume the event. return true; } else if (menuItemId == R.id.add_or_edit_domain) { // Add or edit domain. + // Reapply the domain settings on returning to `MainWebViewActivity`. + reapplyDomainSettingsOnRestart = true; + // Check if domain settings currently exist. if (currentWebView.getDomainSettingsApplied()) { // Edit the current domain settings. - // Reapply the domain settings on returning to `MainWebViewActivity`. - reapplyDomainSettingsOnRestart = true; - // Create an intent to launch the domains activity. Intent domainsIntent = new Intent(this, DomainsActivity.class); // Add the extra information to the intent. - domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId()); - domainsIntent.putExtra("close_on_back", true); - domainsIntent.putExtra("current_url", currentWebView.getUrl()); - domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); + domainsIntent.putExtra(DomainsActivity.LOAD_DOMAIN, currentWebView.getDomainSettingsDatabaseId()); + domainsIntent.putExtra(DomainsActivity.CLOSE_ON_BACK, true); + domainsIntent.putExtra(DomainsActivity.CURRENT_URL, currentWebView.getUrl()); + domainsIntent.putExtra(DomainsActivity.CURRENT_IP_ADDRESSES, currentWebView.getCurrentIpAddresses()); // Get the current certificate. SslCertificate sslCertificate = currentWebView.getCertificate(); @@ -1963,28 +1988,28 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook long endDateLong = sslCertificate.getValidNotAfterDate().getTime(); // Add the certificate to the intent. - domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName); - domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName); - domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName); - domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName); - domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName); - domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName); - domainsIntent.putExtra("ssl_start_date", startDateLong); - domainsIntent.putExtra("ssl_end_date", endDateLong); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_CNAME, issuedToCName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_ONAME, issuedToOName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_UNAME, issuedToUName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_CNAME, issuedByCName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_ONAME, issuedByOName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_UNAME, issuedByUName); + domainsIntent.putExtra(DomainsActivity.SSL_START_DATE, startDateLong); + domainsIntent.putExtra(DomainsActivity.SSL_END_DATE, endDateLong); } // Make it so. startActivity(domainsIntent); } else { // Add a new domain. - // Apply the new domain settings on returning to `MainWebViewActivity`. - reapplyDomainSettingsOnRestart = true; - - // Get the current domain + // Get the current URI. Uri currentUri = Uri.parse(currentWebView.getUrl()); + + // Get the current domain from the URI. String currentDomain = currentUri.getHost(); - // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`. - DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0); + // Set an empty domain if it is null. + if (currentDomain == null) + currentDomain = ""; // Create the domain and store the database ID. int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain); @@ -1993,10 +2018,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook Intent domainsIntent = new Intent(this, DomainsActivity.class); // Add the extra information to the intent. - domainsIntent.putExtra("load_domain", newDomainDatabaseId); - domainsIntent.putExtra("close_on_back", true); - domainsIntent.putExtra("current_url", currentWebView.getUrl()); - domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); + domainsIntent.putExtra(DomainsActivity.LOAD_DOMAIN, newDomainDatabaseId); + domainsIntent.putExtra(DomainsActivity.CLOSE_ON_BACK, true); + domainsIntent.putExtra(DomainsActivity.CURRENT_URL, currentWebView.getUrl()); + domainsIntent.putExtra(DomainsActivity.CURRENT_IP_ADDRESSES, currentWebView.getCurrentIpAddresses()); // Get the current certificate. SslCertificate sslCertificate = currentWebView.getCertificate(); @@ -2014,29 +2039,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook long endDateLong = sslCertificate.getValidNotAfterDate().getTime(); // Add the certificate to the intent. - domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName); - domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName); - domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName); - domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName); - domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName); - domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName); - domainsIntent.putExtra("ssl_start_date", startDateLong); - domainsIntent.putExtra("ssl_end_date", endDateLong); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_CNAME, issuedToCName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_ONAME, issuedToOName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_UNAME, issuedToUName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_CNAME, issuedByCName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_ONAME, issuedByOName); + domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_UNAME, issuedByUName); + domainsIntent.putExtra(DomainsActivity.SSL_START_DATE, startDateLong); + domainsIntent.putExtra(DomainsActivity.SSL_END_DATE, endDateLong); } // 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. @@ -2168,8 +2184,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook Intent domainsIntent = new Intent(this, DomainsActivity.class); // Add the extra information to the intent. - domainsIntent.putExtra("current_url", currentWebView.getUrl()); - domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); + domainsIntent.putExtra(DomainsActivity.CURRENT_URL, currentWebView.getUrl()); + domainsIntent.putExtra(DomainsActivity.CURRENT_IP_ADDRESSES, currentWebView.getCurrentIpAddresses()); // Get the current certificate. SslCertificate sslCertificate = currentWebView.getCertificate(); @@ -2221,6 +2237,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Make it so. startActivity(logcatIntent); + } else if (menuItemId == R.id.webview_devtools) { // WebView Dev. + // Create a WebView DevTools intent. + Intent webViewDevToolsIntent = new Intent("com.android.webview.SHOW_DEV_UI"); + + // Launch as a new task so that the WebView DevTools and Privacy Browser show as a separate windows in the recent tasks list. + webViewDevToolsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // Make it so. + startActivity(webViewDevToolsIntent); } else if (menuItemId == R.id.guide) { // Guide. // Create an intent to launch the guide activity. Intent guideIntent = new Intent(this, GuideActivity.class); @@ -2256,26 +2281,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. @@ -2720,7 +2725,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Update the bookmarks cursor with the current contents of this folder. bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder); - // Update the `ListView`. + // Update the list view. bookmarksCursorAdapter.changeCursor(bookmarksCursor); // Scroll to the new folder. @@ -2814,51 +2819,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Update the bookmarks cursor with the current contents of this folder. bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder); - // Update the `ListView`. + // Update the list view. bookmarksCursorAdapter.changeCursor(bookmarksCursor); } - // Override `onBackPressed()` to handle the navigation drawer and and the WebViews. - @Override - public void onBackPressed() { - // Check the different options for processing `back`. - if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open. - // Close the navigation drawer. - drawerLayout.closeDrawer(GravityCompat.START); - } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open. - // close the bookmarks drawer. - drawerLayout.closeDrawer(GravityCompat.END); - } else if (displayingFullScreenVideo) { // A full screen video is shown. - // Exit the full screen video. - exitFullScreenVideo(); - } else if (currentWebView.canGoBack()) { // There is at least one item in the current WebView history. - // Get the current web back forward list. - WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList(); - - // Get the previous entry URL. - String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl(); - - // Apply the domain settings. - applyDomainSettings(currentWebView, previousUrl, false, false, false); - - // Go back. - currentWebView.goBack(); - } else if (tabLayout.getTabCount() > 1) { // There are at least two tabs. - // Close the current tab. - closeCurrentTab(); - } else { // There isn't anything to do in Privacy Browser. - // Close Privacy Browser. `finishAndRemoveTask()` also removes Privacy Browser from the recent app list. - if (Build.VERSION.SDK_INT >= 21) { - finishAndRemoveTask(); - } else { - finish(); - } - - // Manually kill Privacy Browser. Otherwise, it is glitchy when restarted. - System.exit(0); - } - } - // Process the results of a file browse. @Override public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) { @@ -2868,11 +2832,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: @@ -2916,21 +2877,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) { @@ -3104,7 +3065,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. @@ -3158,19 +3119,10 @@ 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)); - - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Set the red color span according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700)); - } else { - redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_900)); - } + // Initialize the color spans for highlighting the URLs. + initialGrayColorSpan = new ForegroundColorSpan(getColor(R.color.gray_500)); + finalGrayColorSpan = new ForegroundColorSpan(getColor(R.color.gray_500)); + redColorSpan = new ForegroundColorSpan(getColor(R.color.red_text)); // Remove the formatting from the URL edit text when the user is editing the text. urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> { @@ -3211,7 +3163,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; @@ -3422,17 +3374,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()`. @@ -3458,15 +3401,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer)); drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks)); - // Initialize the bookmarks database helper. The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`. - bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0); - - // Initialize `currentBookmarksFolder`. `""` is the home folder in the database. - currentBookmarksFolder = ""; - - // Load the home folder, which is `""` in the database. + // Load the bookmarks folder. loadBookmarksFolder(); + // Handle clicks on bookmarks. bookmarksListView.setOnItemClickListener((parent, view, position, id) -> { // Convert the id from long to int to match the format of the bookmarks database. int databaseId = (int) id; @@ -3478,15 +3416,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); @@ -3506,7 +3444,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()); @@ -3520,11 +3458,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Move the bookmark cursor to the first row. bookmarkCursor.moveToFirst(); - // Load the bookmark in a new tab but do not switch to the tab or close the drawer. - addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)), false); + // Load the bookmark in a new tab. + addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), true); - // Display a snackbar. - Snackbar.make(currentWebView, R.string.bookmark_opened_in_background, Snackbar.LENGTH_SHORT).show(); + // Close the bookmarks drawer. + drawerLayout.closeDrawer(GravityCompat.END); } // Consume the event. @@ -3573,9 +3511,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }); - // Replace the header that `WebView` creates for `X-Requested-With` with a null value. The default value is the application ID (com.stoutner.privacybrowser.standard). - customHeaders.put("X-Requested-With", ""); - // Inflate a bare WebView to get the default user agent. It is not used to render content on the screen. @SuppressLint("InflateParams") View webViewLayout = getLayoutInflater().inflate(R.layout.bare_webview, null, false); @@ -3594,15 +3529,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Store the values from the shared preferences in variables. - incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false); - sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true); - sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true); - sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true); - proxyMode = sharedPreferences.getString("proxy", getString(R.string.proxy_default_value)); - fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false); + incognitoModeEnabled = sharedPreferences.getBoolean(getString(R.string.incognito_mode_key), false); + sanitizeTrackingQueries = sharedPreferences.getBoolean(getString(R.string.tracking_queries_key), true); + sanitizeAmpRedirects = sharedPreferences.getBoolean(getString(R.string.amp_redirects_key), true); + proxyMode = sharedPreferences.getString(getString(R.string.proxy_key), getString(R.string.proxy_default_value)); + fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean(getString(R.string.full_screen_browsing_mode_key), false); + hideAppBar = sharedPreferences.getBoolean(getString(R.string.hide_app_bar_key), true); 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) { @@ -3614,20 +3548,37 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Get the search string. - String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value)); + String searchString = sharedPreferences.getString(getString(R.string.search_key), getString(R.string.search_default_value)); // Set the search string. - if (searchString.equals("Custom URL")) { // A custom search string is used. - searchURL = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value)); - } else { // A custom search string is not used. + if (searchString.equals(getString(R.string.custom_url_item))) + searchURL = sharedPreferences.getString(getString(R.string.search_custom_url_key), getString(R.string.search_custom_url_default_value)); + else searchURL = searchString; - } // Apply the proxy. applyProxy(false); - // Adjust the layout and scrolling parameters if the app bar is at the top of the screen. - if (!bottomAppBar) { + // Adjust the layout and scrolling parameters according to the position of the app bar. + if (bottomAppBar) { // The app bar is on the bottom. + // Adjust the UI. + if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) { // The app bar scrolls or full screen browsing mode is engaged with the app bar hidden. + // Reset the WebView padding to fill the available space. + swipeRefreshLayout.setPadding(0, 0, 0, 0); + } else { // The app bar doesn't scroll or full screen browsing mode is not engaged with the app bar hidden. + // Move the WebView above the app bar layout. + swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight); + + // Show the app bar if it is scrolled off the screen. + if (appBarLayout.getTranslationY() != 0) { + // Animate the bottom app bar onto the screen. + objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0); + + // Make it so. + objectAnimator.start(); + } + } + } else { // The app bar is on the top. // Get the current layout parameters. Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command. CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams(); AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams(); @@ -3688,15 +3639,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. @@ -3715,16 +3657,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); } @@ -3814,28 +3746,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`. - DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0); - - // Get a full cursor from `domainsDatabaseHelper`. + // Get a full domain name cursor. Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain(); // Initialize `domainSettingsSet`. 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`. + // Populate the domain settings set. 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)); } - // Close `domainNameCursor. + // Close the domain name cursor. domainNameCursor.close(); // Initialize the domain name in database variable. @@ -3854,7 +3783,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Check all the subdomains of the host name against wildcard domains in the domain cursor. - while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name. + while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the hostname. if (domainSettingsSet.contains("*." + newHostName)) { // Check the host name prepended by `*.`. // Set the domain settings applied tracker to true. nestedScrollWebView.setDomainSettingsApplied(true); @@ -3872,12 +3801,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Store the general preference information. - String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value)); - String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value)); - boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true); - String webViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value)); - boolean wideViewport = sharedPreferences.getBoolean("wide_viewport", true); - boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true); + String defaultFontSizeString = sharedPreferences.getString(getString(R.string.font_size_key), getString(R.string.font_size_default_value)); + String defaultUserAgentName = sharedPreferences.getString(getString(R.string.user_agent_key), getString(R.string.user_agent_default_value)); + boolean defaultSwipeToRefresh = sharedPreferences.getBoolean(getString(R.string.swipe_to_refresh_key), true); + String webViewTheme = sharedPreferences.getString(getString(R.string.webview_theme_key), getString(R.string.webview_theme_default_value)); + boolean wideViewport = sharedPreferences.getBoolean(getString(R.string.wide_viewport_key), true); + boolean displayWebpageImages = sharedPreferences.getBoolean(getString(R.string.display_webpage_images_key), true); // Get the WebView theme entry values string array. String[] webViewThemeEntryValuesStringArray = getResources().getStringArray(R.array.webview_theme_entry_values); @@ -3890,42 +3819,49 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook String[] userAgentDataArray = getResources().getStringArray(R.array.user_agent_data); if (nestedScrollWebView.getDomainSettingsApplied()) { // The url has custom domain settings. - // Get a cursor for the current host and move it to the first position. + // Remove the incorrect lint warning below that the domain name in database might be null. + assert domainNameInDatabase != null; + + // Get a cursor for the current host. Cursor currentDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase); + + // Move to the first position. 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.setEasyListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1); - nestedScrollWebView.setEasyPrivacyEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1); - nestedScrollWebView.setFanboysAnnoyanceListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1); - nestedScrollWebView.setFanboysSocialBlockingListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex( + 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.getColumnIndex(DomainsDatabaseHelper.ULTRALIST)) == 1); - nestedScrollWebView.setUltraPrivacyEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1); - nestedScrollWebView.setBlockAllThirdPartyRequests(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)); - Date pinnedSslStartDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE))); - Date pinnedSslEndDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE))); - boolean pinnedIpAddresses = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1); - String pinnedHostIpAddresses = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES)); + 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(); @@ -3981,7 +3917,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case SETTINGS_CUSTOM_USER_AGENT: // Set the default custom user agent. - nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value))); + nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value))); break; default: @@ -4038,19 +3974,52 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Disable swipe to refresh. swipeRefreshLayout.setEnabled(false); + break; } // Check to see if WebView themes are supported. - if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { + if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // The device is running API >= 33 and algorithmic darkening is supported. + // Set the WebView theme. + switch (webViewThemeInt) { + case DomainsDatabaseHelper.SYSTEM_DEFAULT: + // Set the WebView theme. A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant. + if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) { // The light theme is selected. + // Turn off algorithmic darkening. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false); + } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) { // The dark theme is selected. + // Turn on algorithmic darkening. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true); + } else { // The system default theme is selected. + // Get the current system theme status. + int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + + // Set the algorithmic darkening according to the current system theme status. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES)); + } + break; + + case DomainsDatabaseHelper.LIGHT_THEME: + // Turn off algorithmic darkening. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false); + break; + + case DomainsDatabaseHelper.DARK_THEME: + // Turn on algorithmic darkening. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true); + break; + } + } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { // The device is running API < 33 and the WebView supports force dark. // Set the WebView theme. switch (webViewThemeInt) { case DomainsDatabaseHelper.SYSTEM_DEFAULT: // Set the WebView theme. A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant. if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) { // The light theme is selected. // Turn off the WebView dark mode. + //noinspection deprecation WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) { // The dark theme is selected. // Turn on the WebView dark mode. + //noinspection deprecation WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); } else { // The system default theme is selected. // Get the current system theme status. @@ -4059,9 +4028,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the WebView theme according to the current system theme status. if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { // The system is in day mode. // Turn off the WebView dark mode. + //noinspection deprecation WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); } else { // The system is in night mode. // Turn on the WebView dark mode. + //noinspection deprecation WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); } } @@ -4069,11 +4040,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case DomainsDatabaseHelper.LIGHT_THEME: // Turn off the WebView dark mode. + //noinspection deprecation WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); break; case DomainsDatabaseHelper.DARK_THEME: // Turn on the WebView dark mode. + //noinspection deprecation WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); break; } @@ -4109,28 +4082,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.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean(getString(R.string.javascript_key), 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.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)); + nestedScrollWebView.getSettings().setDomStorageEnabled(sharedPreferences.getBoolean(getString(R.string.dom_storage_key), false)); + boolean saveFormData = sharedPreferences.getBoolean(getString(R.string.save_form_data_key), false); // Form data can be removed once the minimum API >= 26. + nestedScrollWebView.setEasyListEnabled(sharedPreferences.getBoolean(getString(R.string.easylist_key), true)); + nestedScrollWebView.setEasyPrivacyEnabled(sharedPreferences.getBoolean(getString(R.string.easyprivacy_key), true)); + nestedScrollWebView.setFanboysAnnoyanceListEnabled(sharedPreferences.getBoolean(getString(R.string.fanboys_annoyance_list_key), true)); + nestedScrollWebView.setFanboysSocialBlockingListEnabled(sharedPreferences.getBoolean(getString(R.string.fanboys_social_blocking_list_key), true)); + nestedScrollWebView.setUltraListEnabled(sharedPreferences.getBoolean(getString(R.string.ultralist_key), true)); + nestedScrollWebView.setUltraPrivacyEnabled(sharedPreferences.getBoolean(getString(R.string.ultraprivacy_key), true)); + nestedScrollWebView.setBlockAllThirdPartyRequests(sharedPreferences.getBoolean(getString(R.string.block_all_third_party_requests_key), false)); // Apply the default cookie setting. cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptCookies()); @@ -4181,7 +4147,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case SETTINGS_CUSTOM_USER_AGENT: // Set the default custom user agent. - nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value))); + nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value))); break; default: @@ -4190,13 +4156,30 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Apply the WebView theme if supported by the installed WebView. - if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { + if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // The device is running API >= 33 and algorithmic darkening is supported. + // Set the WebView theme. A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant. + if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) { // the light theme is selected. + // Turn off algorithmic darkening. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false); + } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) { // The dark theme is selected. + // Turn on algorithmic darkening. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true); + } else { // The system default theme is selected. + // Get the current system theme status. + int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + + // Set the algorithmic darkening according to the current system theme status. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), currentThemeStatus == Configuration.UI_MODE_NIGHT_YES); + } + } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { // The device is running API < 33 and the WebView supports force dark. // Set the WebView theme. A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant. if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) { // The light theme is selected. // Turn off the WebView dark mode. + //noinspection deprecation WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) { // The dark theme is selected. // Turn on the WebView dark mode. + //noinspection deprecation WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); } else { // The system default theme is selected. // Get the current system theme status. @@ -4205,9 +4188,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the WebView theme according to the current system theme status. if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { // The system is in day mode. // Turn off the WebView dark mode. + //noinspection deprecation WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); } else { // The system is in night mode. // Turn on the WebView dark mode. + //noinspection deprecation WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); } } @@ -4237,13 +4222,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Load the URL if directed. This makes sure that the domain settings are properly loaded before the URL. By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent. if (loadUrl) { - nestedScrollWebView.loadUrl(url, customHeaders); + nestedScrollWebView.loadUrl(url); } } 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; @@ -4284,7 +4269,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; @@ -4328,27 +4313,31 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } else { appBarLayout.setBackgroundResource(R.color.dark_blue_30); } + // Get the package manager. + PackageManager packageManager = getPackageManager(); // Check to see if I2P is installed. try { - // Get the package manager. - PackageManager packageManager = getPackageManager(); - - // Check to see if I2P is in the list. This will throw an error and drop to the catch section if it isn't installed. + // Check to see if the F-Droid flavor is installed. This will throw an error and drop to the catch section if it isn't installed. packageManager.getPackageInfo("net.i2p.android.router", 0); - } catch (PackageManager.NameNotFoundException exception) { // I2P is not installed. - // Sow the I2P not installed dialog if it is not already displayed. - if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) { - // Get a handle for the waiting for proxy alert dialog. - DialogFragment i2pNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode); + } catch (PackageManager.NameNotFoundException fdroidException) { // The F-Droid flavor is not installed. + try { + // Check to see if the Google Play flavor is installed. This will throw an error and drop to the catch section if it isn't installed. + packageManager.getPackageInfo("net.i2p.android", 0); + } catch (PackageManager.NameNotFoundException googlePlayException) { // The Google Play flavor is not installed. + // Sow the I2P not installed dialog if it is not already displayed. + if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) { + // Get a handle for the waiting for proxy alert dialog. + DialogFragment i2pNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode); - // Try to show the dialog. Sometimes the window is not yet active if returning from Settings. - try { - // Display the I2P not installed alert dialog. - i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog)); - } catch (Exception i2pNotInstalledException) { - // Add the dialog to the pending dialog array list. It will be displayed in `onStart()`. - pendingDialogsArrayList.add(new PendingDialog(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog))); + // Try to show the dialog. Sometimes the window is not yet active if returning from Settings. + try { + // Display the I2P not installed alert dialog. + i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog)); + } catch (Exception i2pNotInstalledException) { + // Add the dialog to the pending dialog array list. It will be displayed in `onStart()`. + pendingDialogsArrayList.add(new PendingDialog(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog))); + } } } } @@ -4398,35 +4387,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. @@ -4513,7 +4487,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); @@ -4522,11 +4496,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); @@ -4592,49 +4566,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } private String sanitizeUrl(String url) { - // Sanitize Google Analytics. - if (sanitizeGoogleAnalytics) { - // Remove `?utm_`. - if (url.contains("?utm_")) { - url = url.substring(0, url.indexOf("?utm_")); - } - - // Remove `&utm_`. - if (url.contains("&utm_")) { - url = url.substring(0, url.indexOf("&utm_")); - } - } - - // Sanitize Facebook Click IDs. - if (sanitizeFacebookClickIds) { - // Remove `?fbclid=`. - if (url.contains("?fbclid=")) { - url = url.substring(0, url.indexOf("?fbclid=")); - } - - // Remove `&fbclid=`. - if (url.contains("&fbclid=")) { - url = url.substring(0, url.indexOf("&fbclid=")); - } - - // Remove `?fbadid=`. - if (url.contains("?fbadid=")) { - url = url.substring(0, url.indexOf("?fbadid=")); - } + // Sanitize tracking queries. + if (sanitizeTrackingQueries) + url = sanitizeUrlHelper.sanitizeTrackingQueries(url); - // Remove `&fbadid=`. - if (url.contains("&fbadid=")) { - url = url.substring(0, url.indexOf("&fbadid=")); - } - } - - // Sanitize Twitter AMP redirects. - if (sanitizeTwitterAmpRedirects) { - // Remove `?amp=1`. - if (url.contains("?amp=1")) { - url = url.substring(0, url.indexOf("?amp=1")); - } - } + // Sanitize AMP redirects. + if (sanitizeAmpRedirects) + url = sanitizeUrlHelper.sanitizeAmpRedirects(url); // Return the sanitized URL. return url; @@ -4730,7 +4668,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Add a new tab if specified in the preferences. - if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) { // Load the URL in a new tab. + if (sharedPreferences.getBoolean(getString(R.string.open_intents_in_new_tab_key), true)) { // Load the URL in a new tab. // Set the loading new intent flag. loadingNewIntent = true; @@ -4772,8 +4710,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(); } } @@ -4817,8 +4759,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. @@ -4831,15 +4773,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. @@ -4852,15 +4785,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() { @@ -4872,7 +4796,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook bookmarksDatabaseHelper.close(); // Get the status of the clear everything preference. - boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true); + boolean clearEverything = sharedPreferences.getBoolean(getString(R.string.clear_everything_key), true); // Get a handle for the runtime. Runtime runtime = Runtime.getRuntime(); @@ -4882,13 +4806,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook String privateDataDirectoryString = getApplicationInfo().dataDir; // 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(); - } + if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_cookies_key), true)) { + // 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 { @@ -4905,7 +4825,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Clear DOM storage. - if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) { + if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_dom_storage_key), true)) { // Ask `WebStorage` to clear the DOM storage. WebStorage webStorage = WebStorage.getInstance(); webStorage.deleteAllData(); @@ -4933,7 +4853,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Clear form data if the API < 26. - if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) { + if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_form_data_key), true))) { WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this); webViewDatabase.clearFormData(); @@ -4965,7 +4885,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Clear the cache. - if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) { + if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_cache_key), true)) { // Clear the cache from each WebView. for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { // Get the WebView tab fragment. @@ -4991,7 +4911,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Delete the secondary `Service Worker` cache directory. // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly. - Process deleteServiceWorkerProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"}); + Process deleteServiceWorkerProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Default/Service Worker/"}); // Wait until the processes have finished. deleteCacheProcess.waitFor(); @@ -5028,9 +4948,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Clear the custom headers. - customHeaders.clear(); - // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache. // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`. if (clearEverything) { @@ -5046,11 +4963,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); @@ -5142,16 +5055,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. @@ -5171,21 +5078,51 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @SuppressLint("ClickableViewAccessibility") @Override - public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, String url, Boolean restoringState) { + public void initializeWebView(@NonNull NestedScrollWebView nestedScrollWebView, int pageNumber, @NonNull ProgressBar progressBar, @NonNull String url, boolean restoringState) { // Get a handle for the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Get the WebView theme. - String webViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value)); + String webViewTheme = sharedPreferences.getString(getString(R.string.webview_theme_key), getString(R.string.webview_theme_default_value)); // Get the WebView theme entry values string array. String[] webViewThemeEntryValuesStringArray = getResources().getStringArray(R.array.webview_theme_entry_values); // Apply the WebView theme if supported by the installed WebView. - if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { + if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // The device is running API >= 33 and algorithmic darkening is supported. + // Set the WebView them. A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant. + if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) { // The light theme is selected. + // Turn off algorithmic darkening. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false); + + // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode. + // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`. + nestedScrollWebView.setVisibility(View.VISIBLE); + } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) { // The dark theme is selected. + // Turn on algorithmic darkening. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true); + } else { + // The system default theme is selected. + int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + + // Set the algorithmic darkening according to the current system theme status. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { // The system is in day mode. + // Turn off algorithmic darkening. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false); + + // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode. + // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`. + nestedScrollWebView.setVisibility(View.VISIBLE); + } else { // The system is in night mode. + // Turn on algorithmic darkening. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true); + } + } + } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { // The device is running API < 33 and the WebView supports force dark. // Set the WebView theme. A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant. if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) { // The light theme is selected. // Turn off the WebView dark mode. + //noinspection deprecation WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode. @@ -5193,6 +5130,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.setVisibility(View.VISIBLE); } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) { // The dark theme is selected. // Turn on the WebView dark mode. + //noinspection deprecation WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); } else { // The system default theme is selected. // Get the current system theme status. @@ -5201,6 +5139,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the WebView theme according to the current system theme status. if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { // The system is in day mode. // Turn off the WebView dark mode. + //noinspection deprecation WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode. @@ -5208,6 +5147,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.setVisibility(View.VISIBLE); } else { // The system is in night mode. // Turn on the WebView dark mode. + //noinspection deprecation WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); } } @@ -5226,7 +5166,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook assert inputMethodManager != null; // Set the app bar scrolling. - nestedScrollWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true)); + nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar); // Allow pinch to zoom. nestedScrollWebView.getSettings().setBuiltInZoomControls(true); @@ -5235,9 +5175,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); @@ -5260,7 +5198,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Toggle the full screen browsing mode. if (inFullScreenBrowsingMode) { // Switch to full screen mode. // Hide the app bar if specified. - if (hideAppBar) { + if (hideAppBar) { // The app bar is hidden. // Close the find on page bar if it is visible. closeFindOnPage(null); @@ -5270,8 +5208,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Hide the action bar. actionBar.hide(); - // Set layout and scrolling parameters if the app bar is at the top of the screen. - if (!bottomAppBar) { + // Set layout and scrolling parameters according to the position of the app bar. + if (bottomAppBar) { // The app bar is at the bottom. + // Reset the WebView padding to fill the available space. + swipeRefreshLayout.setPadding(0, 0, 0, 0); + } else { // The app bar is at the top. // Check to see if the app bar is normally scrolled. if (scrollAppBar) { // The app bar is scrolled when it is displayed. // Get the swipe refresh layout parameters. @@ -5287,15 +5228,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook swipeRefreshLayout.setProgressViewOffset(false, -200, defaultProgressViewEndOffset); } } - } - - // 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); + } else { // The app bar is not hidden. + // Adjust the UI for the bottom app bar. + if (bottomAppBar) { + // Adjust the UI according to the scrolling of the app bar. + if (scrollAppBar) { + // Reset the WebView padding to fill the available space. + swipeRefreshLayout.setPadding(0, 0, 0, 0); + } else { + // Move the WebView above the app bar layout. + swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight); + } + } } /* Hide the system bars. @@ -5314,33 +5258,33 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show the action bar. actionBar.show(); - - // Set layout and scrolling parameters if the app bar is at the top of the screen. - if (!bottomAppBar) { - // Check to see if the app bar is normally scrolled. - if (scrollAppBar) { // The app bar is scrolled when it is displayed. - // Get the swipe refresh layout parameters. - CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams(); - - // Add the off-screen scrolling layout. - swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior()); - } else { // The app bar is not scrolled when it is displayed. - // The swipe refresh layout must be manually moved below the app bar layout. - swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0); - - // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels. - swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight); - } - } } - // 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)); + // Set layout and scrolling parameters according to the position of the app bar. + if (bottomAppBar) { // The app bar is at the bottom. + // Adjust the UI. + if (scrollAppBar) { + // Reset the WebView padding to fill the available space. + swipeRefreshLayout.setPadding(0, 0, 0, 0); + } else { + // Move the WebView above the app bar layout. + swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight); + } + } else { // The app bar is at the top. + // Check to see if the app bar is normally scrolled. + if (scrollAppBar) { // The app bar is scrolled when it is displayed. + // Get the swipe refresh layout parameters. + CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams(); + + // Add the off-screen scrolling layout. + swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior()); + } else { // The app bar is not scrolled when it is displayed. + // The swipe refresh layout must be manually moved below the app bar layout. + swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0); + + // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels. + swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight); + } } // Remove the `SYSTEM_UI` flags from the root frame layout. @@ -5353,6 +5297,30 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return false; } } + + @Override + public boolean onFling(MotionEvent motionEvent1, MotionEvent motionEvent2, float velocityX, float velocityY) { + // Scroll the bottom app bar if enabled. + if (bottomAppBar && scrollAppBar && !objectAnimator.isRunning()) { + // Calculate the Y change. + float motionY = motionEvent2.getY() - motionEvent1.getY(); + + // Scroll the app bar if the change is greater than 50 pixels. + if (motionY > 50) { + // Animate the bottom app bar onto the screen. + objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0); + } else if (motionY < -50) { + // Animate the bottom app bar off the screen. + objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", appBarLayout.getHeight()); + } + + // Make it so. + objectAnimator.start(); + } + + // Do not consume the event. + return false; + } }); // Pass all touch events on the WebView through the double-tap gesture detector. @@ -5426,68 +5394,30 @@ 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); - } - - // 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); - } - - // 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(); - } - - // 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); - } + // Process scroll changes. + 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); + } - // 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() { @@ -5581,20 +5511,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. @@ -5628,34 +5549,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; } @@ -5745,7 +5663,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. @@ -5765,9 +5686,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())); @@ -5783,31 +5701,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 (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. @@ -6105,8 +6017,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { - // Set the padding and layout settings if the app bar is at the top of the screen. - if (!bottomAppBar) { + // Get the app bar layout height. This can't be done in `applyAppSettings()` because the app bar is not yet populated there. + // This should only be populated if it is greater than 0 because otherwise it will be reset to 0 if the app bar is hidden in full screen browsing mode. + if (appBarLayout.getHeight() > 0) appBarHeight = appBarLayout.getHeight(); + + // Set the padding and layout settings according to the position of the app bar. + if (bottomAppBar) { // The app bar is on the bottom. + // Adjust the UI. + if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) { // The app bar scrolls or full screen browsing mode is engaged with the app bar hidden. + // Reset the WebView padding to fill the available space. + swipeRefreshLayout.setPadding(0, 0, 0, 0); + } else { // The app bar doesn't scroll or full screen browsing mode is not engaged with the app bar hidden. + // Move the WebView above the app bar layout. + swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight); + } + } else { // The app bar is on the top. // Set the top padding of the swipe refresh layout according to the app bar scrolling preference. This can't be done in `appAppSettings()` because the app bar is not yet populated there. if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) { // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior. @@ -6115,9 +6040,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels. swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10, defaultProgressViewEndOffset); } else { - // Get the app bar layout height. This can't be done in `applyAppSettings()` because the app bar is not yet populated there. - appBarHeight = appBarLayout.getHeight(); - // The swipe refresh layout must be manually moved below the app bar layout. swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0); @@ -6161,28 +6083,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the title. optionsRefreshMenuItem.setTitle(R.string.stop); - // 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. - 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); - } - } + // Set the icon if it is displayed in the AppBar. + if (displayAdditionalAppBarIcons) + optionsRefreshMenuItem.setIcon(R.drawable.close_blue); } } @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(); } @@ -6191,23 +6101,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reset the Refresh title. optionsRefreshMenuItem.setTitle(R.string.refresh); - // 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 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); - } - } + // Reset the icon if it is displayed in the app bar. + if (displayAdditionalAppBarIcons) + optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled); } + // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, + // which links to `/data/data/com.stoutner.privacybrowser.standard`. + String privateDataDirectoryString = getApplicationInfo().dataDir; + // Clear the cache, history, and logcat if Incognito Mode is enabled. if (incognitoModeEnabled) { // Clear the cache. `true` includes disk files. @@ -6218,16 +6120,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Manually delete cache folders. try { - // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, - // which links to `/data/data/com.stoutner.privacybrowser.standard`. - String privateDataDirectoryString = getApplicationInfo().dataDir; - // Delete the main cache directory. Runtime.getRuntime().exec("rm -rf " + privateDataDirectoryString + "/cache"); - - // Delete the secondary `Service Worker` cache directory. - // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise. - Runtime.getRuntime().exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"}); } catch (IOException exception) { // Do nothing if an error is thrown. } @@ -6241,6 +6135,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } + // Clear the `Service Worker` directory. + try { + // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise. + Runtime.getRuntime().exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Default/Service Worker/"}); + } catch (IOException exception) { + // Do nothing. + } + // Get the current page position. int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); @@ -6313,7 +6215,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. @@ -6332,11 +6235,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Proceed to the website if the current SSL website certificate matches the pinned domain certificate. if (nestedScrollWebView.hasPinnedSslCertificate()) { // Get the pinned SSL certificate. - ArrayList pinnedSslCertificateArrayList = nestedScrollWebView.getPinnedSslCertificate(); + Pair pinnedSslCertificatePair = nestedScrollWebView.getPinnedSslCertificate(); // Extract the arrays from the array list. - String[] pinnedSslCertificateStringArray = (String[]) pinnedSslCertificateArrayList.get(0); - Date[] pinnedSslCertificateDateArray = (Date[]) pinnedSslCertificateArrayList.get(1); + String[] pinnedSslCertificateStringArray = pinnedSslCertificatePair.getFirst(); + Date[] pinnedSslCertificateDateArray = pinnedSslCertificatePair.getSecond(); // Check if the current SSL certificate matches the pinned certificate. if (currentWebsiteIssuedToCName.equals(pinnedSslCertificateStringArray[0]) && currentWebsiteIssuedToOName.equals(pinnedSslCertificateStringArray[1]) && @@ -6449,4 +6352,4 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } } -} \ No newline at end of file +}