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=461c7405e59c0e9dd6c76e2a7e2420f3f14c1263;hp=cd5a3d8444e6ac6cae3de63abe226dae5fd71066;hb=153a29723774971e62fa0102db87393f1afef193;hpb=f052d92fe6d33b659038d46ce10b6bc4b743dd20 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 cd5a3d84..461c7405 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -1,5 +1,5 @@ /* - * Copyright © 2015-2022 Soren Stoutner . + * Copyright 2015-2022 Soren Stoutner . * * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner . * @@ -52,7 +52,6 @@ import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; -import android.preference.PreferenceManager; import android.print.PrintDocumentAdapter; import android.print.PrintManager; import android.provider.DocumentsContract; @@ -99,6 +98,8 @@ import android.widget.RadioButton; import android.widget.RelativeLayout; import android.widget.TextView; +import androidx.activity.OnBackPressedCallback; +import androidx.activity.result.ActivityResult; import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -114,6 +115,7 @@ import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.viewpager.widget.ViewPager; import androidx.webkit.WebSettingsCompat; @@ -136,7 +138,6 @@ import com.stoutner.privacybrowser.dataclasses.PendingDialog; import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog; import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog; import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog; -import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog; import com.stoutner.privacybrowser.dialogs.FontSizeDialog; import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog; import com.stoutner.privacybrowser.dialogs.OpenDialog; @@ -174,10 +175,8 @@ import java.text.NumberFormat; import java.util.ArrayList; 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; @@ -186,14 +185,13 @@ import java.util.concurrent.Executors; import kotlin.Pair; public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, - EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener, - PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener, - WebViewTabFragment.NewTabListener { + FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener, PinnedMismatchDialog.PinnedMismatchListener, + PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener { // Define the public static variables. - public static ExecutorService executorService = Executors.newFixedThreadPool(4); + public static final ExecutorService executorService = Executors.newFixedThreadPool(4); public static String orbotStatus = "unknown"; - public static ArrayList pendingDialogsArrayList = new ArrayList<>(); + public static final ArrayList pendingDialogsArrayList = new ArrayList<>(); public static String proxyMode = ProxyHelper.NONE; // Declare the public static variables. @@ -212,15 +210,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2; public final static int DOMAINS_CUSTOM_USER_AGENT = 12; - // Define the start activity for result request codes. The public static entry is accessed from `OpenDialog()`. - private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 0; - public final static int BROWSE_OPEN_REQUEST_CODE = 1; - // Define the saved instance state constants. + private final String BOOKMARKS_DRAWER_PINNED = "bookmarks_drawer_pinned"; + private final String PROXY_MODE = "proxy_mode"; private final String SAVED_STATE_ARRAY_LIST = "saved_state_array_list"; private final String SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST = "saved_nested_scroll_webview_state_array_list"; private final String SAVED_TAB_POSITION = "saved_tab_position"; - private final String PROXY_MODE = "proxy_mode"; // Define the saved instance state variables. private ArrayList savedStateArrayList; @@ -236,9 +231,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; @@ -264,9 +256,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`. private CursorAdapter bookmarksCursorAdapter; - // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`. - private String oldFolderNameString; - // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`. private ValueCallback fileChooserCallback; @@ -282,7 +271,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private SanitizeUrlHelper sanitizeUrlHelper; // Declare the class variables + private boolean bookmarksDrawerPinned; private boolean bottomAppBar; + private boolean displayAdditionalAppBarIcons; private boolean displayingFullScreenVideo; private boolean downloadWithExternalApp; private boolean fullScreenBrowsingModeEnabled; @@ -304,19 +295,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private String saveUrlString = ""; // Declare the class views. - private FrameLayout rootFrameLayout; - private DrawerLayout drawerLayout; - private CoordinatorLayout coordinatorLayout; - private Toolbar toolbar; - private RelativeLayout urlRelativeLayout; - private EditText urlEditText; private ActionBar actionBar; + private CoordinatorLayout coordinatorLayout; + private ImageView bookmarksDrawerPinnedImageView; + private DrawerLayout drawerLayout; private LinearLayout findOnPageLinearLayout; + private FrameLayout fullScreenVideoFrameLayout; + private FrameLayout rootFrameLayout; + private SwipeRefreshLayout swipeRefreshLayout; private LinearLayout tabsLinearLayout; private TabLayout tabLayout; - private SwipeRefreshLayout swipeRefreshLayout; + private Toolbar toolbar; + private EditText urlEditText; + private RelativeLayout urlRelativeLayout; private ViewPager webViewPager; - private FrameLayout fullScreenVideoFrameLayout; // Declare the class menus. private Menu optionsMenu; @@ -373,7 +365,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) { @@ -388,17 +380,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 { @@ -423,29 +434,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook mhtOutputStream.close(); temporaryMhtFileInputStream.close(); - // Initialize the file name string from the file URI last path segment. - String fileNameString = fileUri.getLastPathSegment(); - - // Query the exact file name if the API >= 26. - if (Build.VERSION.SDK_INT >= 26) { - // Get a cursor from the content resolver. - Cursor contentResolverCursor = resultLauncherActivityHandle.getContentResolver().query(fileUri, null, null, null); - - // Move to the fist row. - contentResolverCursor.moveToFirst(); - - // Get the file name from the cursor. - fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)); - - // Close the cursor. - contentResolverCursor.close(); - } - // Display a snackbar. - Snackbar.make(currentWebView, getString(R.string.file_saved) + " " + fileNameString, Snackbar.LENGTH_SHORT).show(); + Snackbar.make(currentWebView, getString(R.string.saved, finalFileNameString), Snackbar.LENGTH_SHORT).show(); } catch (Exception exception) { // Display a snackbar with the exception. - Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + exception, Snackbar.LENGTH_INDEFINITE).show(); + Snackbar.make(currentWebView, getString(R.string.error_saving_file, finalFileNameString, exception), Snackbar.LENGTH_INDEFINITE).show(); } finally { // Delete the temporary MHT file. //noinspection ResultOfMethodCallIgnored @@ -453,19 +446,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, 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) { @@ -477,6 +470,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }); + // Define the save webpage image activity result launcher. It must be defined before `onCreate()` is run or the app will crash. + private final ActivityResultLauncher browseFileUploadActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), + new ActivityResultCallback() { + @Override + public void onActivityResult(ActivityResult activityResult) { + // Pass the file to the WebView. + fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(activityResult.getResultCode(), activityResult.getData())); + } + }); + // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with WebView. @SuppressLint("ClickableViewAccessibility") @Override @@ -490,6 +493,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Check to see if the activity has been restarted. if (savedInstanceState != null) { // Store the saved instance state variables. + bookmarksDrawerPinned = savedInstanceState.getBoolean(BOOKMARKS_DRAWER_PINNED); savedStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_STATE_ARRAY_LIST); savedNestedScrollWebViewStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST); savedTabPosition = savedInstanceState.getInt(SAVED_TAB_POSITION); @@ -503,9 +507,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); @@ -551,6 +556,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); webViewPager = findViewById(R.id.webviewpager); NavigationView navigationView = findViewById(R.id.navigationview); + bookmarksDrawerPinnedImageView = findViewById(R.id.bookmarks_drawer_pinned_imageview); fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout); // Get a handle for the navigation menu. @@ -591,6 +597,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()); @@ -606,12 +615,54 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook proxyHelper = new ProxyHelper(); sanitizeUrlHelper = new SanitizeUrlHelper(); + // Update the bookmarks drawer pinned image view. + updateBookmarksDrawerPinnedImageView(); + // 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. + // Run clear and exit. + clearAndExit(); + } + } + }; + + // Register the on back pressed callback. + getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback); + // Populate the blocklists. populateBlocklists = new PopulateBlocklists(this, this).execute(); } @@ -671,7 +722,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; @@ -883,10 +934,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook int currentTabPosition = tabLayout.getSelectedTabPosition(); // Store the saved states in the bundle. + savedInstanceState.putBoolean(BOOKMARKS_DRAWER_PINNED, bookmarksDrawerPinned); + savedInstanceState.putString(PROXY_MODE, proxyMode); savedInstanceState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList); savedInstanceState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList); savedInstanceState.putInt(SAVED_TAB_POSITION, currentTabPosition); - savedInstanceState.putString(PROXY_MODE, proxyMode); } @Override @@ -978,11 +1030,8 @@ 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); - // 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); + // Only display the dark WebView menu item if the API >= 29. + optionsDarkWebViewMenuItem.setVisible(Build.VERSION.SDK_INT >= 29); // Set the status of the additional app bar icons. Setting the refresh menu item to `SHOW_AS_ACTION_ALWAYS` makes it appear even on small devices like phones. if (displayAdditionalAppBarIcons) { @@ -1061,10 +1110,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Enable DOM Storage if JavaScript is enabled. optionsDomStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled()); - // Set the checkbox status for dark WebView if the WebView supports it. - if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { - optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON); - } + // Get the current theme status. + int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + + // Enable dark WebView if night mode is enabled. + optionsDarkWebViewMenuItem.setEnabled(currentThemeStatus == Configuration.UI_MODE_NIGHT_YES); + + // Set the checkbox status for dark WebView if the device is running API >= 29 and algorithmic darkening is supported. + if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) + optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView.getSettings())); } // Set the cookies menu item checked status. @@ -1688,7 +1742,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(); @@ -1741,17 +1795,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Consume the event. return true; } else if (menuItemId == R.id.dark_webview) { // Dark WebView. - // Check to see if dark WebView is supported by this WebView. - if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { - // Toggle the dark WebView setting. - if (WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON) { // Dark WebView is currently enabled. - // Turn off dark WebView. - WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); - } else { // Dark WebView is currently disabled. - // Turn on dark WebView. - WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - } - } + // Toggle dark WebView if supported. + if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) + WebSettingsCompat.setAlgorithmicDarkeningAllowed(currentWebView.getSettings(), !WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView.getSettings())); // Consume the event. return true; @@ -1905,19 +1951,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(); @@ -1935,26 +1981,29 @@ 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(); + // 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); @@ -1962,10 +2011,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(); @@ -1983,14 +2032,14 @@ 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. @@ -2128,8 +2177,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(); @@ -2181,6 +2230,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); @@ -2660,188 +2718,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Update the bookmarks cursor with the current contents of this folder. bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder); - // Update the `ListView`. + // Update the list view. bookmarksCursorAdapter.changeCursor(bookmarksCursor); // Scroll to the new folder. bookmarksListView.setSelection(0); } - @Override - public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, @NonNull Bitmap favoriteIconBitmap) { - // Remove the incorrect lint warning below that the dialog fragment might be null. - assert dialogFragment != null; - - // Get the dialog. - Dialog dialog = dialogFragment.getDialog(); - - // Remove the incorrect lint warning below that the dialog might be null. - assert dialog != null; - - // Get handles for the views from the dialog. - RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton); - RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton); - ImageView defaultFolderIconImageView = dialog.findViewById(R.id.default_icon_imageview); - EditText editFolderNameEditText = dialog.findViewById(R.id.folder_name_edittext); - - // Get the new folder name. - String newFolderNameString = editFolderNameEditText.getText().toString(); - - // Check if the favorite icon has changed. - if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed. - // Update the name in the database. - bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString); - } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed. - // Create the new folder icon Bitmap. - Bitmap folderIconBitmap; - - // Populate the new folder icon bitmap. - if (defaultFolderIconRadioButton.isChecked()) { - // Get the default folder icon drawable. - Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable(); - - // Convert the folder icon drawable to a bitmap drawable. - BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable; - - // Convert the folder icon bitmap drawable to a bitmap. - folderIconBitmap = folderIconBitmapDrawable.getBitmap(); - } else { // Use the `WebView` favorite icon. - // Copy the favorite icon bitmap to the folder icon bitmap. - folderIconBitmap = favoriteIconBitmap; - } - - // Create a folder icon byte array output stream. - ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream(); - - // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG). - folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream); - - // Convert the folder icon byte array stream to a byte array. - byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray(); - - // Update the folder icon in the database. - bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray); - } else { // The folder icon and the name have changed. - // Get the new folder icon bitmap. - Bitmap folderIconBitmap; - if (defaultFolderIconRadioButton.isChecked()) { - // Get the default folder icon drawable. - Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable(); - - // Convert the folder icon drawable to a bitmap drawable. - BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable; - - // Convert the folder icon bitmap drawable to a bitmap. - folderIconBitmap = folderIconBitmapDrawable.getBitmap(); - } else { // Use the `WebView` favorite icon. - // Copy the favorite icon bitmap to the folder icon bitmap. - folderIconBitmap = favoriteIconBitmap; - } - - // Create a folder icon byte array output stream. - ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream(); - - // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG). - folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream); - - // Convert the folder icon byte array stream to a byte array. - byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray(); - - // Update the folder name and icon in the database. - bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray); - } - - // Update the bookmarks cursor with the current contents of this folder. - bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder); - - // Update the `ListView`. - bookmarksCursorAdapter.changeCursor(bookmarksCursor); - } - - // Override `onBackPressed()` to handle the navigation drawer and and the WebViews. - @Override - public void onBackPressed() { - // Check the different options for processing `back`. - if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open. - // Close the navigation drawer. - drawerLayout.closeDrawer(GravityCompat.START); - } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open. - // close the bookmarks drawer. - drawerLayout.closeDrawer(GravityCompat.END); - } else if (displayingFullScreenVideo) { // A full screen video is shown. - // Exit the full screen video. - exitFullScreenVideo(); - } else if (currentWebView.canGoBack()) { // There is at least one item in the current WebView history. - // Get the current web back forward list. - WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList(); - - // Get the previous entry URL. - String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl(); - - // Apply the domain settings. - applyDomainSettings(currentWebView, previousUrl, false, false, false); - - // Go back. - currentWebView.goBack(); - } else if (tabLayout.getTabCount() > 1) { // There are at least two tabs. - // Close the current tab. - closeCurrentTab(); - } else { // There isn't anything to do in Privacy Browser. - // Close Privacy Browser. `finishAndRemoveTask()` also removes Privacy Browser from the recent app list. - finishAndRemoveTask(); - - // Manually kill Privacy Browser. Otherwise, it is glitchy when restarted. - System.exit(0); - } - } - - // Process the results of a file browse. - @Override - public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) { - // Run the default commands. - super.onActivityResult(requestCode, resultCode, returnedIntent); - - // Run the commands that correlate to the specified request code. - switch (requestCode) { - case BROWSE_FILE_UPLOAD_REQUEST_CODE: - // Pass the file to the WebView. - fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent)); - break; - - case BROWSE_OPEN_REQUEST_CODE: - // Don't do anything if the user pressed back from the file picker. - if (resultCode == Activity.RESULT_OK) { - // Get a handle for the open dialog fragment. - DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open)); - - // Only update the file name if the dialog still exists. - if (openDialogFragment != null) { - // Get a handle for the open dialog. - Dialog openDialog = openDialogFragment.getDialog(); - - // Remove the incorrect lint warning below that the dialog might be null. - assert openDialog != null; - - // Get a handle for the file name edit text. - EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext); - - // Get the file name URI from the intent. - Uri fileNameUri = returnedIntent.getData(); - - // Get the file name string from the URI. - String fileNameString = fileNameUri.toString(); - - // Set the file name text. - fileNameEditText.setText(fileNameString); - - // Move the cursor to the end of the file name edit text. - fileNameEditText.setSelection(fileNameString.length()); - } - } - break; - } - } - private void loadUrlFromTextBox() { // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string. String unformattedUrlString = urlEditText.getText().toString().trim(); @@ -3091,19 +2974,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. + // Initialize the color spans for highlighting the URLs. initialGrayColorSpan = new ForegroundColorSpan(getColor(R.color.gray_500)); finalGrayColorSpan = new ForegroundColorSpan(getColor(R.color.gray_500)); - - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Set the red color span according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - redColorSpan = new ForegroundColorSpan(getColor(R.color.red_a700)); - } else { - redColorSpan = new ForegroundColorSpan(getColor(R.color.red_900)); - } + redColorSpan = new ForegroundColorSpan(getColor(R.color.red_text)); // Remove the formatting from the URL edit text when the user is editing the text. urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> { @@ -3407,14 +3281,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Load the bookmark URL. loadUrl(currentWebView, bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL))); - // Close the bookmarks drawer. - drawerLayout.closeDrawer(GravityCompat.END); + // Close the bookmarks drawer if it is not pinned. + if (!bookmarksDrawerPinned) + drawerLayout.closeDrawer(GravityCompat.END); } - // Close the `Cursor`. + // Close the cursor. bookmarkCursor.close(); }); + // Handle long-presses on bookmarks. bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> { // Convert the database ID from `long` to `int`. int databaseId = (int) id; @@ -3424,14 +3300,23 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Check to see if the bookmark is a folder. if (isFolder) { // The bookmark is a folder. - // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`. - oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME)); + // Get a cursor of all the bookmarks in the folder. + Cursor bookmarksCursor = bookmarksDatabaseHelper.getFolderBookmarks(databaseId); + + // Move to the first entry in the cursor. + bookmarksCursor.moveToFirst(); + + // Open each bookmark + for (int i = 0; i < bookmarksCursor.getCount(); i++) { + // Load the bookmark in a new tab, moving to the tab for the first bookmark if the drawer is not pinned. + addNewTab(bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), (!bookmarksDrawerPinned && (i == 0))); - // Instantiate the edit folder bookmark dialog. - DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon()); + // Move to the next bookmark. + bookmarksCursor.moveToNext(); + } - // Show the edit folder bookmark dialog. - editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder)); + // Close the cursor. + bookmarksCursor.close(); } else { // The bookmark is not a folder. // Get the bookmark cursor for this ID. Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId); @@ -3439,13 +3324,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Move the bookmark cursor to the first row. bookmarkCursor.moveToFirst(); - // Load the bookmark in a new tab but do not switch to the tab or close the drawer. - addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), false); + // Load the bookmark in a new tab and move to the tab if the drawer is not pinned. + addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), !bookmarksDrawerPinned); - // Display a snackbar. - Snackbar.make(drawerLayout, R.string.bookmark_opened_in_background, Snackbar.LENGTH_SHORT).show(); + // Close the cursor. + bookmarkCursor.close(); } + // Close the bookmarks drawer if it is not pinned. + if (!bookmarksDrawerPinned) + drawerLayout.closeDrawer(GravityCompat.END); + // Consume the event. return true; }); @@ -3492,9 +3381,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); @@ -3513,13 +3399,13 @@ 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); + 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("proxy", getString(R.string.proxy_default_value)); - fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false); + 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(getString(R.string.scroll_app_bar_key), true); // Apply the saved proxy mode if the app has been restarted. @@ -3532,14 +3418,13 @@ 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); @@ -3786,12 +3671,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); @@ -3822,12 +3707,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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.setFanboysAnnoyanceListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow( + DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1); nestedScrollWebView.setFanboysSocialBlockingListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow( DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1); nestedScrollWebView.setUltraListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ULTRALIST)) == 1); nestedScrollWebView.setUltraPrivacyEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1); - nestedScrollWebView.setBlockAllThirdPartyRequests(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1); + 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)); @@ -3900,7 +3787,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: @@ -3935,8 +3822,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Update the swipe refresh layout. if (defaultSwipeToRefresh) { // Swipe to refresh is enabled. - // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes. - swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0); + // Update the status of the swipe refresh layout if the current WebView is not null (crash reports indicate that in some unexpected way it sometimes is null). + if (currentWebView != null) { + // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes. + swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0); + } } else { // Swipe to refresh is disabled. // Disable the swipe refresh layout. swipeRefreshLayout.setEnabled(false); @@ -3947,8 +3837,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Store the swipe to refresh status in the nested scroll WebView. nestedScrollWebView.setSwipeToRefresh(true); - // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes. - swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0); + + // Update the status of the swipe refresh layout if the current WebView is not null (crash reports indicate that in some unexpected way it sometimes is null). + if (currentWebView != null) { + // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes. + swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0); + } break; case DomainsDatabaseHelper.DISABLED: @@ -3957,43 +3851,38 @@ 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)) { + // Set the WebView theme if device is running API >= 29 and algorithmic darkening is supported. + if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // Set the WebView theme. switch (webViewThemeInt) { case DomainsDatabaseHelper.SYSTEM_DEFAULT: // 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. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); + // Turn off algorithmic darkening. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false); } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) { // The dark theme is selected. - // Turn on the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); + // 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 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. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); - } else { // The system is in night mode. - // Turn on the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - } + // 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 the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); + // Turn off algorithmic darkening. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false); break; case DomainsDatabaseHelper.DARK_THEME: - // Turn on the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); + // Turn on algorithmic darkening. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true); break; } } @@ -4032,17 +3921,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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()); @@ -4066,8 +3955,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Update the swipe refresh layout. if (defaultSwipeToRefresh) { // Swipe to refresh is enabled. - // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes. - swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0); + // Update the status of the swipe refresh layout if the current WebView is not null (crash reports indicate that in some unexpected way it sometimes is null). + if (currentWebView != null) { + // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes. + swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0); + } } else { // Swipe to refresh is disabled. // Disable the swipe refresh layout. swipeRefreshLayout.setEnabled(false); @@ -4093,7 +3985,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: @@ -4101,27 +3993,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]); } - // Apply the WebView theme if supported by the installed WebView. - if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { + // Set the WebView theme if device is running API >= 29 and algorithmic darkening is supported. + if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { // Set the WebView theme. A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant. - if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) { // The light theme is selected. - // Turn off the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); + 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 the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); + // 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 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. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); - } else { // The system is in night mode. - // Turn on the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - } + // Set the algorithmic darkening according to the current system theme status. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), currentThemeStatus == Configuration.UI_MODE_NIGHT_YES); } } @@ -4149,7 +4035,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Load the URL if directed. This makes sure that the domain settings are properly loaded before the URL. By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent. if (loadUrl) { - nestedScrollWebView.loadUrl(url, customHeaders); + nestedScrollWebView.loadUrl(url); } } @@ -4240,27 +4126,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))); + } } } } @@ -4591,7 +4481,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; @@ -4719,7 +4609,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(); @@ -4729,7 +4619,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook String privateDataDirectoryString = getApplicationInfo().dataDir; // Clear cookies. - if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) { + if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_cookies_key), true)) { // Request the cookies be deleted. CookieManager.getInstance().removeAllCookies(null); @@ -4748,7 +4638,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(); @@ -4776,7 +4666,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(); @@ -4808,7 +4698,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. @@ -4834,7 +4724,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(); @@ -4871,9 +4761,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) { @@ -4908,6 +4795,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } + public void toggleBookmarksDrawerPinned(View view) { + // Toggle the bookmarks drawer pinned tracker. + bookmarksDrawerPinned = !bookmarksDrawerPinned; + + // Update the bookmarks drawer pinned image view. + updateBookmarksDrawerPinnedImageView(); + } + + private void updateBookmarksDrawerPinnedImageView() { + // Set the current icon. + if (bookmarksDrawerPinned) + bookmarksDrawerPinnedImageView.setImageResource(R.drawable.pin_selected); + else + bookmarksDrawerPinnedImageView.setImageResource(R.drawable.pin); + } + private void setCurrentWebView(int pageNumber) { // Stop the swipe to refresh indicator if it is running swipeRefreshLayout.setRefreshing(false); @@ -5004,44 +4907,44 @@ 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)) { - // Set the WebView theme. A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant. + // Set the WebView theme if device is running API >= 29 and algorithmic darkening is supported. + if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { + // Set the WebView them. A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant. if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) { // The light theme is selected. - // Turn off the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); + // 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 the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - } else { // The system default theme is selected. - // Get the current system theme status. + // 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 WebView theme according to the current system theme status. + // 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 the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); + // 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 the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); + // Turn on algorithmic darkening. + WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true); } } } @@ -5198,7 +5101,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Calculate the Y change. float motionY = motionEvent2.getY() - motionEvent1.getY(); - // Scroll the app bar if the change is greater than 100 pixels. + // 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); @@ -5453,8 +5356,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Check to see if the file chooser intent resolves to an installed package. if (fileChooserIntent.resolveActivity(packageManager) != null) { // The file chooser intent is fine. - // Start the file chooser intent. - startActivityForResult(fileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE); + // Launch the file chooser intent. + browseFileUploadActivityResultLauncher.launch(fileChooserIntent); } else { // The file chooser intent will cause a crash. // Create a generic intent to open a chooser. Intent genericFileChooserIntent = new Intent(Intent.ACTION_GET_CONTENT); @@ -5465,8 +5368,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the file type to everything. genericFileChooserIntent.setType("*/*"); - // Start the generic file chooser intent. - startActivityForResult(genericFileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE); + // Launch the generic file chooser intent. + browseFileUploadActivityResultLauncher.launch(genericFileChooserIntent); } return true; } @@ -5976,13 +5879,9 @@ 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); - - // Set the icon if it is displayed in the AppBar. Once the minimum API is >= 26, the blue and black icons can be combined with a tint list. - if (displayAdditionalAppBarIcons) { + // Set the icon if it is displayed in the AppBar. + if (displayAdditionalAppBarIcons) optionsRefreshMenuItem.setIcon(R.drawable.close_blue); - } } } @@ -5998,16 +5897,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) { - // Set the icon. + // 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. @@ -6018,16 +5916,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. } @@ -6041,6 +5931,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()); @@ -6250,4 +6148,4 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } } -} \ No newline at end of file +}