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=229ab3b6392233e6c9737fd797c88b967c81a3c5;hp=eff9c68d0f1820ccc59e7ad069394a44a38e32b9;hb=HEAD;hpb=b870c254c26d19749fef9e3dbe9894f8f40c32eb diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java deleted file mode 100644 index eff9c68d..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ /dev/null @@ -1,6513 +0,0 @@ -/* - * Copyright © 2015-2020 Soren Stoutner . - * - * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner . - * - * This file is part of Privacy Browser . - * - * Privacy Browser 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, - * 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 . - */ - -package com.stoutner.privacybrowser.activities; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.Dialog; -import android.app.DownloadManager; -import android.app.SearchManager; -import android.content.ActivityNotFoundException; -import android.content.BroadcastReceiver; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.net.http.SslCertificate; -import android.net.http.SslError; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.preference.PreferenceManager; -import android.print.PrintDocumentAdapter; -import android.print.PrintManager; -import android.text.Editable; -import android.text.Spanned; -import android.text.TextWatcher; -import android.text.style.ForegroundColorSpan; -import android.util.Patterns; -import android.util.TypedValue; -import android.view.ContextMenu; -import android.view.GestureDetector; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.inputmethod.InputMethodManager; -import android.webkit.CookieManager; -import android.webkit.HttpAuthHandler; -import android.webkit.SslErrorHandler; -import android.webkit.ValueCallback; -import android.webkit.WebBackForwardList; -import android.webkit.WebChromeClient; -import android.webkit.WebResourceResponse; -import android.webkit.WebSettings; -import android.webkit.WebStorage; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.webkit.WebViewDatabase; -import android.widget.ArrayAdapter; -import android.widget.CursorAdapter; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.ProgressBar; -import android.widget.RadioButton; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.ActionBarDrawerToggle; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.app.AppCompatDelegate; -import androidx.appcompat.widget.Toolbar; -import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import androidx.core.content.FileProvider; -import androidx.core.content.res.ResourcesCompat; -import androidx.core.view.GravityCompat; -import androidx.drawerlayout.widget.DrawerLayout; -import androidx.fragment.app.DialogFragment; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; -import androidx.viewpager.widget.ViewPager; -import androidx.webkit.WebSettingsCompat; -import androidx.webkit.WebViewFeature; - -import com.google.android.material.appbar.AppBarLayout; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -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; -import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists; -import com.stoutner.privacybrowser.asynctasks.PrepareSaveDialog; -import com.stoutner.privacybrowser.asynctasks.SaveUrl; -import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage; -import com.stoutner.privacybrowser.dialogs.AdConsentDialog; -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; -import com.stoutner.privacybrowser.dialogs.ProxyNotInstalledDialog; -import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog; -import com.stoutner.privacybrowser.dialogs.SaveWebpageDialog; -import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog; -import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog; -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.CheckPinnedMismatchHelper; -import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper; -import com.stoutner.privacybrowser.helpers.FileNameHelper; -import com.stoutner.privacybrowser.helpers.ProxyHelper; -import com.stoutner.privacybrowser.views.NestedScrollWebView; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLDecoder; -import java.net.URLEncoder; -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; -import java.util.concurrent.Executors; - -public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, - EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener, - PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, StoragePermissionDialog.StoragePermissionDialogListener, - 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()`. - public static String orbotStatus = "unknown"; - - // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`. It is also used in `onCreate()`, `onResume()`, and `addTab()`. - public static WebViewPagerAdapter webViewPagerAdapter; - - // The load URL on restart variables are public static so they can be accessed from `BookmarksActivity`. They are used in `onRestart()`. - public static boolean loadUrlOnRestart; - public static String urlToLoadOnRestart; - - // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`. - public static boolean restartFromBookmarksActivity; - - // `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; - - // 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 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; - - // Start activity for result request codes. The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`. - public final static int BROWSE_OPEN_REQUEST_CODE = 0; - public final static int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 1; - private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 2; - - // The proxy mode is public static so it can be accessed from `ProxyHelper()`. - // It is also used in `onRestart()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxy()`. - // It will be updated in `applyAppSettings()`, but it needs to be initialized here or the first run of `onPrepareOptionsMenu()` crashes. - public static String proxyMode = ProxyHelper.NONE; - - // Define the saved instance state constants. - private final String SAVED_STATE_ARRAY_LIST = "saved_state_array_list"; - 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; - private ArrayList savedNestedScrollWebViewStateArrayList; - private int savedTabPosition; - private String savedProxyMode; - - // Define the class variables. - @SuppressWarnings("rawtypes") - AsyncTask populateBlocklists; - - // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, - // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`. - private NestedScrollWebView currentWebView; - - // `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; - - // The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()`, `updatePrivacyIcons()`, and `initializeWebView()`. - private Menu optionsMenu; - - // The blocklists are populated in `finishedPopulatingBlocklists()` and accessed from `initializeWebView()`. - private ArrayList> easyList; - private ArrayList> easyPrivacy; - private ArrayList> fanboysAnnoyanceList; - private ArrayList> fanboysSocialList; - private ArrayList> ultraList; - private ArrayList> ultraPrivacy; - - // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`. - private String webViewDefaultUserAgent; - - // The incognito mode is set in `applyAppSettings()` and used in `initializeWebView()`. - private boolean incognitoModeEnabled; - - // The full screen browsing mode tracker is set it `applyAppSettings()` and used in `initializeWebView()`. - private boolean fullScreenBrowsingModeEnabled; - - // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`. - private boolean inFullScreenBrowsingMode; - - // The app bar trackers are set in `applyAppSettings()` and used in `initializeWebView()`. - private boolean hideAppBar; - private boolean scrollAppBar; - - // The loading new intent tracker is set in `onNewIntent()` and used in `setCurrentWebView()`. - private boolean loadingNewIntent; - - // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, . - private boolean reapplyDomainSettingsOnRestart; - - // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`. - private boolean reapplyAppSettingsOnRestart; - - // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`. - private boolean displayingFullScreenVideo; - - // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`. - private BroadcastReceiver orbotStatusBroadcastReceiver; - - // The waiting for proxy boolean is used in `onResume()`, `initializeApp()` and `applyProxy()`. - private boolean waitingForProxy = false; - - // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`. - private ActionBarDrawerToggle actionBarDrawerToggle; - - // The color spans are used in `onCreate()` and `highlightUrlText()`. - private ForegroundColorSpan redColorSpan; - private ForegroundColorSpan initialGrayColorSpan; - private ForegroundColorSpan finalGrayColorSpan; - - // `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; - - // `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; - - // The default progress view offsets are set in `onCreate()` and used in `initializeWebView()`. - private int appBarHeight; - 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; - - // The file path strings are used in `onSaveWebpage()` and `onRequestPermissionResult()` - private String openFilePath; - private String saveWebpageUrl; - private String saveWebpageFilePath; - - // Declare the class views. - private DrawerLayout drawerLayout; - private AppBarLayout appBarLayout; - private TabLayout tabLayout; - private ViewPager webViewPager; - - @Override - // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`. - @SuppressLint("ClickableViewAccessibility") - protected void onCreate(Bundle savedInstanceState) { - // Run the default commands. - super.onCreate(savedInstanceState); - - // Check to see if the activity has been restarted. - if (savedInstanceState != null) { - // Store the saved instance state variables. - savedStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_STATE_ARRAY_LIST); - savedNestedScrollWebViewStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST); - savedTabPosition = savedInstanceState.getInt(SAVED_TAB_POSITION); - savedProxyMode = savedInstanceState.getString(PROXY_MODE); - } - - // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default. - PreferenceManager.setDefaultValues(this, R.xml.preferences, false); - - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - // Get the screenshot preference. - String appTheme = sharedPreferences.getString("app_theme", getString(R.string.app_theme_default_value)); - boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false); - - // Get the theme entry values string array. - String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values); - - // Set the app theme according to the preference. A switch statement cannot be used because the theme entry values string array is not a compile time constant. - if (appTheme.equals(appThemeEntryValuesStringArray[1])) { // The light theme is selected. - // Apply the light theme. - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); - } else if (appTheme.equals(appThemeEntryValuesStringArray[2])) { // The dark theme is selected. - // Apply the dark theme. - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); - } else { // The system default theme is selected. - if (Build.VERSION.SDK_INT >= 28) { // The system default theme is supported. - // Follow the system default theme. - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); - } else { // The system default theme is not supported. - // Follow the battery saver mode. - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY); - } - } - - // Disable screenshots if not allowed. - if (!allowScreenshots) { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); - } - - // Enable the drawing of the entire webpage. This makes it possible to save a website image. This must be done before anything else happens with the WebView. - if (Build.VERSION.SDK_INT >= 21) { - WebView.enableSlowWholeDocumentDraw(); - } - - // Set the theme. - setTheme(R.style.PrivacyBrowser); - - // Set the content view. - setContentView(R.layout.main_framelayout); - - // Get handles for the views. - drawerLayout = findViewById(R.id.drawerlayout); - appBarLayout = findViewById(R.id.appbar_layout); - Toolbar toolbar = findViewById(R.id.toolbar); - tabLayout = findViewById(R.id.tablayout); - webViewPager = findViewById(R.id.webviewpager); - - // Get a handle for the app compat delegate. - AppCompatDelegate appCompatDelegate = getDelegate(); - - // Set the support action bar. - appCompatDelegate.setSupportActionBar(toolbar); - - // Get a handle for the action bar. - ActionBar actionBar = appCompatDelegate.getSupportActionBar(); - - // This is needed to get rid of the Android Studio warning that the action bar might be null. - assert actionBar != null; - - // Add the custom layout, which shows the URL text bar. - actionBar.setCustomView(R.layout.url_app_bar); - actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); - - // Create the hamburger icon at the start of the AppBar. - actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer); - - // Initially disable the sliding drawers. They will be enabled once the blocklists are loaded. - drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); - - // Initialize the web view pager adapter. - webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager()); - - // Set the pager adapter on the web view pager. - webViewPager.setAdapter(webViewPagerAdapter); - - // Store up to 100 tabs in memory. - webViewPager.setOffscreenPageLimit(100); - - // Initialize the app. - initializeApp(); - - // Apply the app settings from the shared preferences. - applyAppSettings(); - - // Populate the blocklists. - populateBlocklists = new PopulateBlocklists(this, this).execute(); - } - - @Override - protected void onNewIntent(Intent intent) { - // 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. - if (savedStateArrayList == null || savedStateArrayList.size() == 0) { // The activity is running for the first time. - // Get the information from the intent. - String intentAction = intent.getAction(); - Uri intentUriData = intent.getData(); - - // Determine if this is a web search. - boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)); - - // Only process the URI if it contains data or it is a web search. If the user pressed the desktop icon after the app was already running the URI will be null. - if (intentUriData != null || isWebSearch) { - // Get the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - // Create a URL string. - String url; - - // If the intent action is a web search, perform the search. - if (isWebSearch) { // The intent is a web search. - // Create an encoded URL string. - String encodedUrlString; - - // Sanitize the search input and convert it to a search. - try { - encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8"); - } catch (UnsupportedEncodingException exception) { - encodedUrlString = ""; - } - - // Add the base search URL. - url = searchURL + encodedUrlString; - } else { // The intent should contain a URL. - // Set the intent data as the URL. - url = intentUriData.toString(); - } - - // 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. - // Set the loading new intent flag. - loadingNewIntent = true; - - // Add a new tab. - addNewTab(url, true); - } else { // Load the URL in the current tab. - // Make it so. - loadUrl(currentWebView, url); - } - - // Close the navigation drawer if it is open. - if (drawerLayout.isDrawerVisible(GravityCompat.START)) { - drawerLayout.closeDrawer(GravityCompat.START); - } - - // Close the bookmarks drawer if it is open. - if (drawerLayout.isDrawerVisible(GravityCompat.END)) { - drawerLayout.closeDrawer(GravityCompat.END); - } - } - } - } - - @Override - public void onRestart() { - // Run the default commands. - super.onRestart(); - - // Apply the app settings if returning from the Settings activity. - if (reapplyAppSettingsOnRestart) { - // Reset the reapply app settings on restart tracker. - reapplyAppSettingsOnRestart = false; - - // Apply the app settings. - applyAppSettings(); - } - - // Apply the domain settings if returning from the settings or domains activity. - if (reapplyDomainSettingsOnRestart) { - // Reset the reapply domain settings on restart tracker. - reapplyDomainSettingsOnRestart = false; - - // Reapply the domain settings for each tab. - for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - // Only reload the WebViews if they exist. - if (fragmentView != null) { - // Get the nested scroll WebView from the tab fragment. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - - // Reset the current domain name so the domain settings will be reapplied. - nestedScrollWebView.resetCurrentDomainName(); - - // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings. - if (nestedScrollWebView.getUrl() != null) { - applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true); - } - } - } - } - - // Load the URL on restart (used when loading a bookmark). - if (loadUrlOnRestart) { - // Load the specified URL. - loadUrl(currentWebView, urlToLoadOnRestart); - - // Reset the load on restart tracker. - loadUrlOnRestart = false; - } - - // Update the bookmarks drawer if returning from the Bookmarks activity. - if (restartFromBookmarksActivity) { - // Close the bookmarks drawer. - drawerLayout.closeDrawer(GravityCompat.END); - - // Reload the bookmarks drawer. - loadBookmarksFolder(); - - // Reset `restartFromBookmarksActivity`. - restartFromBookmarksActivity = false; - } - - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated. - updatePrivacyIcons(true); - } - - // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`. - @Override - public void onResume() { - // Run the default commands. - super.onResume(); - - // Resume any WebViews. - for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - // Only resume the WebViews if they exist (they won't when the app is first created). - if (fragmentView != null) { - // Get the nested scroll WebView from the tab fragment. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - - // Resume the nested scroll WebView JavaScript timers. - nestedScrollWebView.resumeTimers(); - - // Resume the nested scroll WebView. - nestedScrollWebView.onResume(); - } - } - - // Reapply the proxy settings if the system is using a proxy. This redisplays the appropriate alert dialog. - if (!proxyMode.equals(ProxyHelper.NONE)) { - applyProxy(false); - } - - // Reapply any system UI flags and the ad in the free flavor. - if (displayingFullScreenVideo || inFullScreenBrowsingMode) { // The system is displaying a website or a video in full screen mode. - // Get a handle for the root frame layouts. - FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout); - - /* 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 if (BuildConfig.FLAVOR.contentEquals("free")) { // The system in not in full screen mode. - // Resume the ad. - AdHelper.resumeAd(findViewById(R.id.adview)); - } - } - - @Override - public void onPause() { - // Run the default commands. - super.onPause(); - - for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - // Only pause the WebViews if they exist (they won't when the app is first created). - if (fragmentView != null) { - // Get the nested scroll WebView from the tab fragment. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - - // Pause the nested scroll WebView. - nestedScrollWebView.onPause(); - - // Pause the nested scroll WebView JavaScript timers. - nestedScrollWebView.pauseTimers(); - } - } - - // Pause the ad or it will continue to consume resources in the background on the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - // Pause the ad. - AdHelper.pauseAd(findViewById(R.id.adview)); - } - } - - @Override - public void onSaveInstanceState(@NonNull Bundle savedInstanceState) { - // Run the default commands. - super.onSaveInstanceState(savedInstanceState); - - // Create the saved state array lists. - ArrayList savedStateArrayList = new ArrayList<>(); - ArrayList savedNestedScrollWebViewStateArrayList = new ArrayList<>(); - - // Get the URLs from each tab. - for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - if (fragmentView != null) { - // Get the nested scroll WebView from the tab fragment. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - - // Create saved state bundle. - Bundle savedStateBundle = new Bundle(); - - // Get the current states. - nestedScrollWebView.saveState(savedStateBundle); - Bundle savedNestedScrollWebViewStateBundle = nestedScrollWebView.saveNestedScrollWebViewState(); - - // Store the saved states in the array lists. - savedStateArrayList.add(savedStateBundle); - savedNestedScrollWebViewStateArrayList.add(savedNestedScrollWebViewStateBundle); - } - } - - // Get the current tab position. - int currentTabPosition = tabLayout.getSelectedTabPosition(); - - // Store the saved states in the bundle. - savedInstanceState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList); - savedInstanceState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList); - savedInstanceState.putInt(SAVED_TAB_POSITION, currentTabPosition); - savedInstanceState.putString(PROXY_MODE, proxyMode); - } - - @Override - public void onDestroy() { - // Unregister the orbot status broadcast receiver if it exists. - if (orbotStatusBroadcastReceiver != null) { - this.unregisterReceiver(orbotStatusBroadcastReceiver); - } - - // Close the bookmarks cursor if it exists. - if (bookmarksCursor != null) { - bookmarksCursor.close(); - } - - // Close the bookmarks database if it exists. - if (bookmarksDatabaseHelper != null) { - bookmarksDatabaseHelper.close(); - } - - // Stop populating the blocklists if the AsyncTask is running in the background. - if (populateBlocklists != null) { - populateBlocklists.cancel(true); - } - - // Run the default commands. - super.onDestroy(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu. This adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.webview_options_menu, menu); - - // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`. - optionsMenu = menu; - - // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step. - updatePrivacyIcons(false); - - // Get handles for the menu items. - MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies); - MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies); - MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage); - MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26. - MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26. - MenuItem refreshMenuItem = menu.findItem(R.id.refresh); - MenuItem darkWebViewMenuItem = menu.findItem(R.id.dark_webview); - MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent); - - // Only display third-party cookies if API >= 21 - toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21); - - // Only display the form data menu items if the API < 26. - toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26); - clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26); - - // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly. - clearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26); - - // Only display the dark WebView menu item if API >= 21. - darkWebViewMenuItem.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("display_additional_app_bar_icons", 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) { - toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - } else { //Do not display the additional icons. - toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - } - - // Replace Refresh with Stop if a URL is already loading. - if (currentWebView != null && currentWebView.getProgress() != 100) { - // Set the title. - refreshMenuItem.setTitle(R.string.stop); - - // Set the icon if it is displayed in the app bar. - 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) { - refreshMenuItem.setIcon(R.drawable.close_day); - } else { - refreshMenuItem.setIcon(R.drawable.close_night); - } - } - } - - // Done. - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - // Get handles for the menu items. - MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain); - MenuItem firstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies); - MenuItem thirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies); - MenuItem domStorageMenuItem = menu.findItem(R.id.toggle_dom_storage); - MenuItem saveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26. - MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data); - MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies); - MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage); - MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26. - MenuItem blocklistsMenuItem = menu.findItem(R.id.blocklists); - MenuItem easyListMenuItem = menu.findItem(R.id.easylist); - MenuItem easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy); - MenuItem fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list); - MenuItem fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list); - MenuItem ultraListMenuItem = menu.findItem(R.id.ultralist); - MenuItem ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy); - MenuItem blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests); - MenuItem proxyMenuItem = menu.findItem(R.id.proxy); - MenuItem userAgentMenuItem = menu.findItem(R.id.user_agent); - MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size); - MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh); - MenuItem wideViewportMenuItem = menu.findItem(R.id.wide_viewport); - MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images); - MenuItem darkWebViewMenuItem = menu.findItem(R.id.dark_webview); - - // Get a handle for the cookie manager. - CookieManager cookieManager = CookieManager.getInstance(); - - // Initialize the current user agent string and the font size. - String currentUserAgent = getString(R.string.user_agent_privacy_browser); - int fontSize = 100; - - // Set items that require the current web view to be populated. It will be null when the program is first opened, as `onPrepareOptionsMenu()` is called before the first WebView is initialized. - if (currentWebView != null) { - // Set the add or edit domain text. - if (currentWebView.getDomainSettingsApplied()) { - addOrEditDomain.setTitle(R.string.edit_domain_settings); - } else { - addOrEditDomain.setTitle(R.string.add_domain_settings); - } - - // Get the current user agent from the WebView. - currentUserAgent = currentWebView.getSettings().getUserAgentString(); - - // Get the current font size from the - fontSize = currentWebView.getSettings().getTextZoom(); - - // Set the status of the menu item checkboxes. - domStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled()); - saveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData()); // Form data can be removed once the minimum API >= 26. - easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)); - easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)); - fanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); - fanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)); - ultraListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)); - ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)); - blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); - swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh()); - wideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort()); - displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically()); - - // Initialize the display names for the blocklists with the number of blocked requests. - blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist)); - easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy)); - fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list)); - fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list)); - ultraListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist)); - ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy)); - blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests)); - - // Only modify third-party cookies if the API >= 21. - if (Build.VERSION.SDK_INT >= 21) { - // Set the status of the third-party cookies checkbox. - thirdPartyCookiesMenuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView)); - - // Enable third-party cookies if first-party cookies are enabled. - thirdPartyCookiesMenuItem.setEnabled(cookieManager.acceptCookie()); - } - - // Enable DOM Storage if JavaScript is enabled. - domStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled()); - - // Set the checkbox status for dark WebView if the WebView supports it. - if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { - darkWebViewMenuItem.setChecked(WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON); - } - } - - // Set the checked status of the first party cookies menu item. - firstPartyCookiesMenuItem.setChecked(cookieManager.acceptCookie()); - - // Enable Clear Cookies if there are any. - clearCookiesMenuItem.setEnabled(cookieManager.hasCookies()); - - // 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; - - // Get a count of the number of files in the Local Storage directory. - File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/"); - int localStorageDirectoryNumberOfFiles = 0; - if (localStorageDirectory.exists()) { - // `Objects.requireNonNull` removes a lint warning that `localStorageDirectory.list` might produce a null pointed exception if it is dereferenced. - localStorageDirectoryNumberOfFiles = Objects.requireNonNull(localStorageDirectory.list()).length; - } - - // Get a count of the number of files in the IndexedDB directory. - File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB"); - int indexedDBDirectoryNumberOfFiles = 0; - if (indexedDBDirectory.exists()) { - // `Objects.requireNonNull` removes a lint warning that `indexedDBDirectory.list` might produce a null pointed exception if it is dereferenced. - indexedDBDirectoryNumberOfFiles = Objects.requireNonNull(indexedDBDirectory.list()).length; - } - - // Enable Clear DOM Storage if there is any. - clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0); - - // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26. - if (Build.VERSION.SDK_INT < 26) { - // Get the WebView database. - WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this); - - // Enable the clear form data menu item if there is anything to clear. - clearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData()); - } - - // Enable Clear Data if any of the submenu items are enabled. - clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled()); - - // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked. - fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListMenuItem.isChecked()); - - // Set the proxy title and check the applied proxy. - switch (proxyMode) { - case ProxyHelper.NONE: - // Set the proxy title. - proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_none)); - - // Check the proxy None radio button. - menu.findItem(R.id.proxy_none).setChecked(true); - break; - - case ProxyHelper.TOR: - // Set the proxy title. - proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_tor)); - - // Check the proxy Tor radio button. - menu.findItem(R.id.proxy_tor).setChecked(true); - break; - - case ProxyHelper.I2P: - // Set the proxy title. - proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_i2p)); - - // Check the proxy I2P radio button. - menu.findItem(R.id.proxy_i2p).setChecked(true); - break; - - case ProxyHelper.CUSTOM: - // Set the proxy title. - proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_custom)); - - // Check the proxy Custom radio button. - menu.findItem(R.id.proxy_custom).setChecked(true); - break; - } - - // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants. - if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser. - // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_privacy_browser)); - - // Select the Privacy Browser radio box. - menu.findItem(R.id.user_agent_privacy_browser).setChecked(true); - } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default. - // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_webview_default)); - - // Select the WebView Default radio box. - menu.findItem(R.id.user_agent_webview_default).setChecked(true); - } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android. - // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_android)); - - // Select the Firefox on Android radio box. - menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true); - } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android. - // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_android)); - - // Select the Chrome on Android radio box. - menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true); - } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS. - // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_ios)); - - // Select the Safari on iOS radio box. - menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true); - } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux. - // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_linux)); - - // Select the Firefox on Linux radio box. - menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true); - } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux. - // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chromium_on_linux)); - - // Select the Chromium on Linux radio box. - menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true); - } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows. - // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_windows)); - - // Select the Firefox on Windows radio box. - menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true); - } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows. - // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_windows)); - - // Select the Chrome on Windows radio box. - menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true); - } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows. - // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_edge_on_windows)); - - // Select the Edge on Windows radio box. - menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true); - } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows. - // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_internet_explorer_on_windows)); - - // Select the Internet on Windows radio box. - menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true); - } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS. - // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_macos)); - - // Select the Safari on macOS radio box. - menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true); - } else { // Custom user agent. - // Update the user agent menu item title. - userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_custom)); - - // Select the Custom radio box. - menu.findItem(R.id.user_agent_custom).setChecked(true); - } - - // Set the font size title. - fontSizeMenuItem.setTitle(getString(R.string.font_size) + " - " + fontSize + "%"); - - // Run all the other default commands. - super.onPrepareOptionsMenu(menu); - - // Display the menu. - return true; - } - - @Override - // Remove Android Studio's warning about the dangers of enabling JavaScript. We know. Oh, how we know. - @SuppressLint("SetJavaScriptEnabled") - public boolean onOptionsItemSelected(MenuItem menuItem) { - // Get the selected menu item ID. - int menuItemId = menuItem.getItemId(); - - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - // Get a handle for the cookie manager. - CookieManager cookieManager = CookieManager.getInstance(); - - // Run the commands that correlate to the selected menu item. - switch (menuItemId) { - case R.id.toggle_javascript: - // Toggle the JavaScript status. - currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled()); - - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. - updatePrivacyIcons(true); - - // Display a `Snackbar`. - if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScrip is enabled. - Snackbar.make(webViewPager, R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show(); - } else if (cookieManager.acceptCookie()) { // JavaScript is disabled, but first-party cookies are enabled. - Snackbar.make(webViewPager, R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show(); - } else { // Privacy mode. - Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show(); - } - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.refresh: - if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed. - // Reload the current WebView. - currentWebView.reload(); - } else { // The stop button was pushed. - // Stop the loading of the WebView. - currentWebView.stopLoading(); - } - - // Consume the event. - return true; - - case R.id.bookmarks: - // Open the bookmarks drawer. - drawerLayout.openDrawer(GravityCompat.END); - - // Consume the event. - return true; - - case R.id.toggle_first_party_cookies: - // Switch the first-party cookie status. - cookieManager.setAcceptCookie(!cookieManager.acceptCookie()); - - // Store the first-party cookie status. - currentWebView.setAcceptFirstPartyCookies(cookieManager.acceptCookie()); - - // Update the menu checkbox. - menuItem.setChecked(cookieManager.acceptCookie()); - - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. - updatePrivacyIcons(true); - - // Display a snackbar. - if (cookieManager.acceptCookie()) { // First-party cookies are enabled. - Snackbar.make(webViewPager, R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show(); - } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is still enabled. - Snackbar.make(webViewPager, R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show(); - } else { // Privacy mode. - Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show(); - } - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.toggle_third_party_cookies: - if (Build.VERSION.SDK_INT >= 21) { - // Switch the status of thirdPartyCookiesEnabled. - cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView)); - - // Update the menu checkbox. - menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView)); - - // Display a snackbar. - if (cookieManager.acceptThirdPartyCookies(currentWebView)) { - Snackbar.make(webViewPager, R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(webViewPager, R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show(); - } - - // Reload the current WebView. - currentWebView.reload(); - } // Else do nothing because SDK < 21. - - // Consume the event. - return true; - - case R.id.toggle_dom_storage: - // Toggle the status of domStorageEnabled. - currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled()); - - // Update the menu checkbox. - menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled()); - - // Update the privacy icon. `true` refreshes the app bar icons. - updatePrivacyIcons(true); - - // Display a snackbar. - if (currentWebView.getSettings().getDomStorageEnabled()) { - Snackbar.make(webViewPager, R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(webViewPager, R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show(); - } - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - // Form data can be removed once the minimum API >= 26. - case R.id.toggle_save_form_data: - // Switch the status of saveFormDataEnabled. - currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData()); - - // Update the menu checkbox. - menuItem.setChecked(currentWebView.getSettings().getSaveFormData()); - - // Display a snackbar. - if (currentWebView.getSettings().getSaveFormData()) { - Snackbar.make(webViewPager, R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(webViewPager, R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show(); - } - - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. - updatePrivacyIcons(true); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.clear_cookies: - Snackbar.make(webViewPager, R.string.cookies_deleted, Snackbar.LENGTH_LONG) - .setAction(R.string.undo, v -> { - // Do nothing because everything will be handled by `onDismissed()` below. - }) - .addCallback(new Snackbar.Callback() { - @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`. - @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); - } - } - } - }) - .show(); - - // Consume the event. - return true; - - case R.id.clear_dom_storage: - Snackbar.make(webViewPager, R.string.dom_storage_deleted, Snackbar.LENGTH_LONG) - .setAction(R.string.undo, v -> { - // Do nothing because everything will be handled by `onDismissed()` below. - }) - .addCallback(new Snackbar.Callback() { - @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`. - @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 DOM Storage. - WebStorage webStorage = WebStorage.getInstance(); - webStorage.deleteAllData(); - - // Initialize a handler to manually delete the DOM storage files and directories. - Handler deleteDomStorageHandler = new Handler(); - - // Setup a runnable to manually delete the DOM storage files and directories. - Runnable deleteDomStorageRunnable = () -> { - try { - // Get a handle for the runtime. - Runtime runtime = Runtime.getRuntime(); - - // 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; - - // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly. - Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"}); - - // Multiple commands must be used because `Runtime.exec()` does not like `*`. - Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB"); - Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager"); - Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal"); - Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases"); - - // Wait for the processes to finish. - deleteLocalStorageProcess.waitFor(); - deleteIndexProcess.waitFor(); - deleteQuotaManagerProcess.waitFor(); - deleteQuotaManagerJournalProcess.waitFor(); - deleteDatabasesProcess.waitFor(); - } catch (Exception exception) { - // Do nothing if an error is thrown. - } - }; - - // Manually delete the DOM storage files after 200 milliseconds. - deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200); - } - } - }) - .show(); - - // Consume the event. - return true; - - // Form data can be remove once the minimum API >= 26. - case R.id.clear_form_data: - Snackbar.make(webViewPager, R.string.form_data_deleted, Snackbar.LENGTH_LONG) - .setAction(R.string.undo, v -> { - // Do nothing because everything will be handled by `onDismissed()` below. - }) - .addCallback(new Snackbar.Callback() { - @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`. - @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 form data. - WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext()); - mainWebViewDatabase.clearFormData(); - } - } - }) - .show(); - - // Consume the event. - return true; - - case R.id.easylist: - // Toggle the EasyList status. - currentWebView.enableBlocklist(NestedScrollWebView.EASYLIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)); - - // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.easyprivacy: - // Toggle the EasyPrivacy status. - currentWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)); - - // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.fanboys_annoyance_list: - // Toggle Fanboy's Annoyance List status. - currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); - - // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); - - // Update the staus of Fanboy's Social Blocking List. - MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list); - fanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.fanboys_social_blocking_list: - // Toggle Fanboy's Social Blocking List status. - currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)); - - // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.ultralist: - // Toggle the UltraList status. - currentWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)); - - // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.ultraprivacy: - // Toggle the UltraPrivacy status. - currentWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)); - - // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.block_all_third_party_requests: - //Toggle the third-party requests blocker status. - currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); - - // Update the menu checkbox. - menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.proxy_none: - // Update the proxy mode. - proxyMode = ProxyHelper.NONE; - - // Apply the proxy mode. - applyProxy(true); - - // Consume the event. - return true; - - case R.id.proxy_tor: - // Update the proxy mode. - proxyMode = ProxyHelper.TOR; - - // Apply the proxy mode. - applyProxy(true); - - // Consume the event. - return true; - - case R.id.proxy_i2p: - // Update the proxy mode. - proxyMode = ProxyHelper.I2P; - - // Apply the proxy mode. - applyProxy(true); - - // Consume the event. - return true; - - case R.id.proxy_custom: - // Update the proxy mode. - proxyMode = ProxyHelper.CUSTOM; - - // Apply the proxy mode. - applyProxy(true); - - // Consume the event. - return true; - - case R.id.user_agent_privacy_browser: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_webview_default: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(""); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_firefox_on_android: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_chrome_on_android: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_safari_on_ios: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_firefox_on_linux: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_chromium_on_linux: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_firefox_on_windows: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_chrome_on_windows: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_edge_on_windows: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_internet_explorer_on_windows: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_safari_on_macos: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.user_agent_custom: - // Update the user agent. - currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value))); - - // Reload the current WebView. - currentWebView.reload(); - - // Consume the event. - return true; - - case R.id.font_size: - // Instantiate the font size dialog. - DialogFragment fontSizeDialogFragment = FontSizeDialog.displayDialog(currentWebView.getSettings().getTextZoom()); - - // Show the font size dialog. - fontSizeDialogFragment.show(getSupportFragmentManager(), getString(R.string.font_size)); - - // Consume the event. - return true; - - case R.id.swipe_to_refresh: - // Toggle the stored status of swipe to refresh. - currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh()); - - // Get a handle for the swipe refresh layout. - SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); - - // Update the swipe refresh layout. - if (currentWebView.getSwipeToRefresh()) { // 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.getY() == 0); - } else { // Swipe to refresh is disabled. - // Disable the swipe refresh layout. - swipeRefreshLayout.setEnabled(false); - } - - // Consume the event. - return true; - - case R.id.wide_viewport: - // Toggle the viewport. - currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort()); - - // Consume the event. - return true; - - case R.id.display_images: - if (currentWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically. - // Disable loading of images. - currentWebView.getSettings().setLoadsImagesAutomatically(false); - - // Reload the website to remove existing images. - currentWebView.reload(); - } else { // Images are not currently loaded automatically. - // Enable loading of images. Missing images will be loaded without the need for a reload. - currentWebView.getSettings().setLoadsImagesAutomatically(true); - } - - // Consume the event. - return true; - - case R.id.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); - } - } - - // Consume the event. - return true; - - case R.id.find_on_page: - // Get a handle for the views. - Toolbar toolbar = findViewById(R.id.toolbar); - LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout); - EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext); - - // Set the minimum height of the find on page linear layout to match the toolbar. - findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight()); - - // Hide the toolbar. - toolbar.setVisibility(View.GONE); - - // Show the find on page linear layout. - findOnPageLinearLayout.setVisibility(View.VISIBLE); - - // Display the keyboard. The app must wait 200 ms before running the command to work around a bug in Android. - // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working - findOnPageEditText.postDelayed(() -> { - // Set the focus on `findOnPageEditText`. - findOnPageEditText.requestFocus(); - - // Get a handle for the input method manager. - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - - // Remove the lint warning below that the input method manager might be null. - assert inputMethodManager != null; - - // Display the keyboard. `0` sets no input flags. - inputMethodManager.showSoftInput(findOnPageEditText, 0); - }, 200); - - // Consume the event. - return true; - - case R.id.print: - // Get a print manager instance. - PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE); - - // Remove the lint error below that print manager might be null. - assert printManager != null; - - // Create a print document adapter from the current WebView. - PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter(); - - // Print the document. - printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null); - - // Consume the event. - return true; - - case R.id.save_url: - // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired. - new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(), - currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl()); - - // Consume the event. - return true; - - case R.id.save_archive: - // Instantiate the save dialog. - DialogFragment saveArchiveFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_ARCHIVE, null, null, getString(R.string.webpage_mht), null, - false); - - // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name. - saveArchiveFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog)); - - // Consume the event. - return true; - - case R.id.save_image: - // Instantiate the save dialog. - DialogFragment saveImageFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_IMAGE, null, null, getString(R.string.webpage_png), null, - false); - - // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name. - saveImageFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog)); - - // Consume the event. - return true; - - case R.id.add_to_homescreen: - // Instantiate the create home screen shortcut dialog. - DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(), - currentWebView.getFavoriteOrDefaultIcon()); - - // Show the create home screen shortcut dialog. - createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut)); - - // Consume the event. - return true; - - case R.id.view_source: - // Create an intent to launch the view source activity. - Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class); - - // Add the variables to the intent. - viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString()); - viewSourceIntent.putExtra("current_url", currentWebView.getUrl()); - - // Make it so. - startActivity(viewSourceIntent); - - // Consume the event. - return true; - - case R.id.share_url: - // Setup the share string. - String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl(); - - // Create the share intent. - Intent shareIntent = new Intent(Intent.ACTION_SEND); - - // Add the share string to the intent. - shareIntent.putExtra(Intent.EXTRA_TEXT, shareString); - - // Set the MIME type. - shareIntent.setType("text/plain"); - - // Set the intent to open in a new task. - shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - // Make it so. - startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url))); - - // Consume the event. - return true; - - case R.id.open_with_app: - // Open the URL with an outside app. - openWithApp(currentWebView.getUrl()); - - // Consume the event. - return true; - - case R.id.open_with_browser: - // Open the URL with an outside browser. - openWithBrowser(currentWebView.getUrl()); - - // Consume the event. - return true; - - case R.id.add_or_edit_domain: - 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()); - - // Get the current certificate. - SslCertificate sslCertificate = currentWebView.getCertificate(); - - // Check to see if the SSL certificate is populated. - if (sslCertificate != null) { - // Extract the certificate to strings. - String issuedToCName = sslCertificate.getIssuedTo().getCName(); - String issuedToOName = sslCertificate.getIssuedTo().getOName(); - String issuedToUName = sslCertificate.getIssuedTo().getUName(); - String issuedByCName = sslCertificate.getIssuedBy().getCName(); - String issuedByOName = sslCertificate.getIssuedBy().getOName(); - String issuedByUName = sslCertificate.getIssuedBy().getUName(); - long startDateLong = sslCertificate.getValidNotBeforeDate().getTime(); - 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); - } - - // Check to see if the current IP addresses have been received. - if (currentWebView.hasCurrentIpAddresses()) { - // Add the current IP addresses to the intent. - domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); - } - - // Make it so. - startActivity(domainsIntent); - } else { // Add a new domain. - // Apply the new domain settings on returning to `MainWebViewActivity`. - reapplyDomainSettingsOnRestart = true; - - // Get the current domain - Uri currentUri = Uri.parse(currentWebView.getUrl()); - 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); - - // Create the domain and store the database ID. - int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain); - - // 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", newDomainDatabaseId); - domainsIntent.putExtra("close_on_back", true); - domainsIntent.putExtra("current_url", currentWebView.getUrl()); - - // Get the current certificate. - SslCertificate sslCertificate = currentWebView.getCertificate(); - - // Check to see if the SSL certificate is populated. - if (sslCertificate != null) { - // Extract the certificate to strings. - String issuedToCName = sslCertificate.getIssuedTo().getCName(); - String issuedToOName = sslCertificate.getIssuedTo().getOName(); - String issuedToUName = sslCertificate.getIssuedTo().getUName(); - String issuedByCName = sslCertificate.getIssuedBy().getCName(); - String issuedByOName = sslCertificate.getIssuedBy().getOName(); - String issuedByUName = sslCertificate.getIssuedBy().getUName(); - long startDateLong = sslCertificate.getValidNotBeforeDate().getTime(); - 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); - } - - // Check to see if the current IP addresses have been received. - if (currentWebView.hasCurrentIpAddresses()) { - // Add the current IP addresses to the intent. - domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); - } - - // Make it so. - startActivity(domainsIntent); - } - - // Consume the event. - return true; - - case R.id.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; - - default: - // Don't consume the event. - return super.onOptionsItemSelected(menuItem); - } - } - - // removeAllCookies is deprecated, but it is required for API < 21. - @Override - public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) { - // Get the menu item ID. - int menuItemId = menuItem.getItemId(); - - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - // Run the commands that correspond to the selected menu item. - switch (menuItemId) { - case R.id.clear_and_exit: - // Clear and exit Privacy Browser. - clearAndExit(); - break; - - case R.id.home: - // Load the homepage. - loadUrl(currentWebView, sharedPreferences.getString("homepage", getString(R.string.homepage_default_value))); - break; - - case R.id.back: - if (currentWebView.canGoBack()) { - // 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); - - // Load the previous website in the history. - currentWebView.goBack(); - } - break; - - case R.id.forward: - if (currentWebView.canGoForward()) { - // Get the current web back forward list. - WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList(); - - // Get the next entry URL. - String nextUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() + 1).getUrl(); - - // Apply the domain settings. - applyDomainSettings(currentWebView, nextUrl, false, false); - - // Load the next website in the history. - currentWebView.goForward(); - } - break; - - case R.id.history: - // Instantiate the URL history dialog. - DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId()); - - // Show the URL history dialog. - urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history)); - break; - - case R.id.open: - // Instantiate the open file dialog. - DialogFragment openDialogFragment = new OpenDialog(); - - // Show the open file dialog. - openDialogFragment.show(getSupportFragmentManager(), getString(R.string.open)); - break; - - case R.id.requests: - // Populate the resource requests. - RequestsActivity.resourceRequests = currentWebView.getResourceRequests(); - - // Create an intent to launch the Requests activity. - Intent requestsIntent = new Intent(this, RequestsActivity.class); - - // Add the block third-party requests status to the intent. - requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); - - // Make it so. - startActivity(requestsIntent); - break; - - case R.id.downloads: - // Launch the system Download Manager. - Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS); - - // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list. - downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - // Make it so. - startActivity(downloadManagerIntent); - break; - - case R.id.domains: - // Set the flag to reapply the domain settings on restart when returning from Domain Settings. - reapplyDomainSettingsOnRestart = true; - - // Launch the domains activity. - Intent domainsIntent = new Intent(this, DomainsActivity.class); - - // Add the extra information to the intent. - domainsIntent.putExtra("current_url", currentWebView.getUrl()); - - // Get the current certificate. - SslCertificate sslCertificate = currentWebView.getCertificate(); - - // Check to see if the SSL certificate is populated. - if (sslCertificate != null) { - // Extract the certificate to strings. - String issuedToCName = sslCertificate.getIssuedTo().getCName(); - String issuedToOName = sslCertificate.getIssuedTo().getOName(); - String issuedToUName = sslCertificate.getIssuedTo().getUName(); - String issuedByCName = sslCertificate.getIssuedBy().getCName(); - String issuedByOName = sslCertificate.getIssuedBy().getOName(); - String issuedByUName = sslCertificate.getIssuedBy().getUName(); - long startDateLong = sslCertificate.getValidNotBeforeDate().getTime(); - 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); - } - - // Check to see if the current IP addresses have been received. - if (currentWebView.hasCurrentIpAddresses()) { - // Add the current IP addresses to the intent. - domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses()); - } - - // Make it so. - startActivity(domainsIntent); - break; - - case R.id.settings: - // Set the flag to reapply app settings on restart when returning from Settings. - reapplyAppSettingsOnRestart = true; - - // Set the flag to reapply the domain settings on restart when returning from Settings. - reapplyDomainSettingsOnRestart = true; - - // Launch the settings activity. - Intent settingsIntent = new Intent(this, SettingsActivity.class); - startActivity(settingsIntent); - break; - - case R.id.import_export: - // Launch the import/export activity. - Intent importExportIntent = new Intent (this, ImportExportActivity.class); - startActivity(importExportIntent); - break; - - case R.id.logcat: - // Launch the logcat activity. - Intent logcatIntent = new Intent(this, LogcatActivity.class); - startActivity(logcatIntent); - break; - - case R.id.guide: - // Launch `GuideActivity`. - Intent guideIntent = new Intent(this, GuideActivity.class); - startActivity(guideIntent); - break; - - case R.id.about: - // Create an intent to launch the about activity. - Intent aboutIntent = new Intent(this, AboutActivity.class); - - // Create a string array for the blocklist versions. - String[] blocklistVersions = new String[] {easyList.get(0).get(0)[0], easyPrivacy.get(0).get(0)[0], fanboysAnnoyanceList.get(0).get(0)[0], fanboysSocialList.get(0).get(0)[0], - ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]}; - - // Add the blocklist versions to the intent. - aboutIntent.putExtra("blocklist_versions", blocklistVersions); - - // Make it so. - startActivity(aboutIntent); - break; - } - - // Close the navigation drawer. - drawerLayout.closeDrawer(GravityCompat.START); - return true; - } - - @Override - public void onPostCreate(Bundle savedInstanceState) { - // Run the default commands. - super.onPostCreate(savedInstanceState); - - // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished. This creates the navigation drawer icon. - 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) { - // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations. - AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), 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) { - // Store the hit test result. - final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult(); - - // Define the URL strings. - final String imageUrl; - final String linkUrl; - - // Get handles for the system managers. - final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - - // Remove the lint errors below that the clipboard manager might be null. - assert clipboardManager != null; - - // Process the link according to the type. - switch (hitTestResult.getType()) { - // `SRC_ANCHOR_TYPE` is a link. - case WebView.HitTestResult.SRC_ANCHOR_TYPE: - // Get the target URL. - linkUrl = hitTestResult.getExtra(); - - // Set the target URL as the title of the `ContextMenu`. - menu.setHeaderTitle(linkUrl); - - // Add an Open in New Tab entry. - menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> { - // Load the link URL in a new tab and move to it. - addNewTab(linkUrl, true); - - // Consume the event. - return true; - }); - - // Add an Open in Background entry. - menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> { - // Load the link URL in a new tab but do not move to it. - addNewTab(linkUrl, false); - - // Consume the event. - return true; - }); - - // Add an Open with App entry. - menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> { - openWithApp(linkUrl); - - // Consume the event. - return true; - }); - - // Add an Open with Browser entry. - menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> { - openWithBrowser(linkUrl); - - // Consume the event. - return true; - }); - - // Add a Copy URL entry. - menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> { - // Save the link URL in a `ClipData`. - ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl); - - // Set the `ClipData` as the clipboard's primary clip. - clipboardManager.setPrimaryClip(srcAnchorTypeClipData); - - // Consume the event. - return true; - }); - - // Add a Save URL entry. - menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> { - // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired. - new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(), - currentWebView.getAcceptFirstPartyCookies()).execute(linkUrl); - - // Consume the event. - return true; - }); - - // Add an empty Cancel entry, which by default closes the context menu. - menu.add(R.string.cancel); - break; - - // `IMAGE_TYPE` is an image. - case WebView.HitTestResult.IMAGE_TYPE: - // Get the image URL. - imageUrl = hitTestResult.getExtra(); - - // Set the image URL as the title of the context menu. - menu.setHeaderTitle(imageUrl); - - // Add an Open in New Tab entry. - menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> { - // Load the image in a new tab. - addNewTab(imageUrl, true); - - // Consume the event. - return true; - }); - - // Add an Open with App entry. - menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> { - // Open the image URL with an external app. - openWithApp(imageUrl); - - // Consume the event. - return true; - }); - - // Add an Open with Browser entry. - menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> { - // Open the image URL with an external browser. - openWithBrowser(imageUrl); - - // Consume the event. - return true; - }); - - // Add a View Image entry. - menu.add(R.string.view_image).setOnMenuItemClickListener(item -> { - // Load the image in the current tab. - loadUrl(currentWebView, imageUrl); - - // Consume the event. - return true; - }); - - // Add a Save Image entry. - menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> { - // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired. - new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(), - currentWebView.getAcceptFirstPartyCookies()).execute(imageUrl); - - // Consume the event. - return true; - }); - - // Add a Copy URL entry. - menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> { - // Save the image URL in a clip data. - ClipData imageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl); - - // Set the clip data as the clipboard's primary clip. - clipboardManager.setPrimaryClip(imageTypeClipData); - - // Consume the event. - return true; - }); - - // Add an empty Cancel entry, which by default closes the context menu. - menu.add(R.string.cancel); - break; - - // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link. - case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: - // Get the image URL. - imageUrl = hitTestResult.getExtra(); - - // Instantiate a handler. - Handler handler = new Handler(); - - // Get a message from the handler. - Message message = handler.obtainMessage(); - - // Request the image details from the last touched node be returned in the message. - currentWebView.requestFocusNodeHref(message); - - // Get the link URL from the message data. - linkUrl = message.getData().getString("url"); - - // Set the link URL as the title of the context menu. - menu.setHeaderTitle(linkUrl); - - // Add an Open in New Tab entry. - menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> { - // Load the link URL in a new tab and move to it. - addNewTab(linkUrl, true); - - // Consume the event. - return true; - }); - - // Add an Open in Background entry. - menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> { - // Lod the link URL in a new tab but do not move to it. - addNewTab(linkUrl, false); - - // Consume the event. - return true; - }); - - // Add an Open Image in New Tab entry. - menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> { - // Load the image in a new tab and move to it. - addNewTab(imageUrl, true); - - // Consume the event. - return true; - }); - - // Add an Open with App entry. - menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> { - // Open the link URL with an external app. - openWithApp(linkUrl); - - // Consume the event. - return true; - }); - - // Add an Open with Browser entry. - menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> { - // Open the link URL with an external browser. - openWithBrowser(linkUrl); - - // Consume the event. - return true; - }); - - // Add a View Image entry. - menu.add(R.string.view_image).setOnMenuItemClickListener((MenuItem item) -> { - // View the image in the current tab. - loadUrl(currentWebView, imageUrl); - - // Consume the event. - return true; - }); - - // Add a Save Image entry. - menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> { - // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired. - new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(), - currentWebView.getAcceptFirstPartyCookies()).execute(imageUrl); - - // Consume the event. - return true; - }); - - // Add a Copy URL entry. - menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> { - // Save the link URL in a clip data. - ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl); - - // Set the clip data as the clipboard's primary clip. - clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData); - - // Consume the event. - return true; - }); - - // Add a Save URL entry. - menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> { - // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired. - new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(), - currentWebView.getAcceptFirstPartyCookies()).execute(linkUrl); - - // Consume the event. - return true; - }); - - // Add an empty Cancel entry, which by default closes the context menu. - menu.add(R.string.cancel); - break; - - case WebView.HitTestResult.EMAIL_TYPE: - // Get the target URL. - linkUrl = hitTestResult.getExtra(); - - // Set the target URL as the title of the `ContextMenu`. - menu.setHeaderTitle(linkUrl); - - // Add a Write Email entry. - menu.add(R.string.write_email).setOnMenuItemClickListener(item -> { - // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched. - Intent emailIntent = new Intent(Intent.ACTION_SENDTO); - - // Parse the url and set it as the data for the `Intent`. - emailIntent.setData(Uri.parse("mailto:" + linkUrl)); - - // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser. - emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - try { - // Make it so. - startActivity(emailIntent); - } catch (ActivityNotFoundException exception) { - // Display a snackbar. - Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show(); - } - - // Consume the event. - return true; - }); - - // Add a Copy Email Address entry. - menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> { - // Save the email address in a `ClipData`. - ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl); - - // Set the `ClipData` as the clipboard's primary clip. - clipboardManager.setPrimaryClip(srcEmailTypeClipData); - - // Consume the event. - return true; - }); - - // Add an empty Cancel entry, which by default closes the context menu. - menu.add(R.string.cancel); - break; - } - } - - @Override - public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) { - // Get a handle for the bookmarks list view. - ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview); - - // Get the dialog. - Dialog dialog = dialogFragment.getDialog(); - - // Remove the incorrect lint warning below that the dialog might be null. - assert dialog != null; - - // Get the views from the dialog fragment. - EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext); - EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext); - - // Extract the strings from the edit texts. - String bookmarkNameString = createBookmarkNameEditText.getText().toString(); - String bookmarkUrlString = createBookmarkUrlEditText.getText().toString(); - - // Create a favorite icon byte array output stream. - ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream(); - - // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG). - favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream); - - // Convert the favorite icon byte array stream to a byte array. - byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray(); - - // Display the new bookmark below the current items in the (0 indexed) list. - int newBookmarkDisplayOrder = bookmarksListView.getCount(); - - // Create the bookmark. - bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray); - - // Update the bookmarks cursor with the current contents of this folder. - bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder); - - // Update the list view. - bookmarksCursorAdapter.changeCursor(bookmarksCursor); - - // Scroll to the new bookmark. - bookmarksListView.setSelection(newBookmarkDisplayOrder); - } - - @Override - public void onCreateBookmarkFolder(DialogFragment dialogFragment, @NonNull Bitmap favoriteIconBitmap) { - // Get a handle for the bookmarks list view. - ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview); - - // 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 in the dialog fragment. - EditText createFolderNameEditText = dialog.findViewById(R.id.create_folder_name_edittext); - RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.create_folder_default_icon_radiobutton); - ImageView folderIconImageView = dialog.findViewById(R.id.create_folder_default_icon); - - // Get new folder name string. - String folderNameString = createFolderNameEditText.getText().toString(); - - // Create a folder icon bitmap. - Bitmap folderIconBitmap; - - // Set the folder icon bitmap according to the dialog. - if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon. - // Get the default folder icon drawable. - Drawable folderIconDrawable = folderIconImageView.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 folderIconByteArrayOutputStream = 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, folderIconByteArrayOutputStream); - - // Convert the folder icon byte array stream to a byte array. - byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray(); - - // Move all the bookmarks down one in the display order. - for (int i = 0; i < bookmarksListView.getCount(); i++) { - int databaseId = (int) bookmarksListView.getItemIdAtPosition(i); - bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1); - } - - // Create the folder, which will be placed at the top of the `ListView`. - bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray); - - // Update the bookmarks cursor with the current contents of this folder. - bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder); - - // Update the `ListView`. - bookmarksCursorAdapter.changeCursor(bookmarksCursor); - - // Scroll to the new folder. - bookmarksListView.setSelection(0); - } - - @Override - public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) { - // 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 `dialogFragment`. - EditText editFolderNameEditText = dialog.findViewById(R.id.edit_folder_name_edittext); - RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_current_icon_radiobutton); - RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_default_icon_radiobutton); - ImageView defaultFolderIconImageView = dialog.findViewById(R.id.edit_folder_default_icon_imageview); - - // 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. - // Get a handle for the layouts. - FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout); - RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout); - FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout); - - // Re-enable the screen timeout. - fullScreenVideoFrameLayout.setKeepScreenOn(false); - - // Unset the full screen video flag. - displayingFullScreenVideo = false; - - // Remove all the views from the full screen video frame layout. - fullScreenVideoFrameLayout.removeAllViews(); - - // Hide the full screen video frame layout. - fullScreenVideoFrameLayout.setVisibility(View.GONE); - - // Enable the sliding drawers. - drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); - - // Show the main content relative layout. - mainContentRelativeLayout.setVisibility(View.VISIBLE); - - // Apply the appropriate full screen mode flags. - if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode. - // Hide the banner ad in the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - AdHelper.hideAd(findViewById(R.id.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. - * 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); - - // Reload the website if the app bar is hidden. Otherwise, there is some bug in Android that causes the WebView to be entirely black. - if (hideAppBar) { - // Reload the WebView. - currentWebView.reload(); - } - } else { // Switch to normal viewing mode. - // 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) { - // Reload the ad. - AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id)); - } - } 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); - - // 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) { - // 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: - // 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)); - } - break; - - case BROWSE_SAVE_WEBPAGE_REQUEST_CODE: - // Don't do anything if the user pressed back from the file picker. - if (resultCode == Activity.RESULT_OK) { - // Get a handle for the save dialog fragment. - DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_dialog)); - - // Only update the file name if the dialog still exists. - if (saveWebpageDialogFragment != null) { - // Get a handle for the save webpage dialog. - Dialog saveWebpageDialog = saveWebpageDialogFragment.getDialog(); - - // Remove the incorrect lint warning below that the dialog might be null. - assert saveWebpageDialog != null; - - // Get a handle for the file name edit text. - EditText fileNameEditText = saveWebpageDialog.findViewById(R.id.file_name_edittext); - TextView fileExistsWarningTextView = saveWebpageDialog.findViewById(R.id.file_exists_warning_textview); - - // Instantiate the file name helper. - FileNameHelper fileNameHelper = new FileNameHelper(); - - // Get the file path if it isn't null. - if (returnedIntent.getData() != null) { - // Convert the file name URI to a file name path. - String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData()); - - // Set the file name path as the text of the file name edit text. - fileNameEditText.setText(fileNamePath); - - // Move the cursor to the end of the file name edit text. - fileNameEditText.setSelection(fileNamePath.length()); - - // Hide the file exists warning. - fileExistsWarningTextView.setVisibility(View.GONE); - } - } - } - 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); - - // Instantiate the file name helper. - FileNameHelper fileNameHelper = new FileNameHelper(); - - // Get the file path if it isn't null. - if (returnedIntent.getData() != null) { - // Convert the file name URI to a file name path. - String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData()); - - // Set the file name path as the text of the file name edit text. - fileNameEditText.setText(fileNamePath); - - // Move the cursor to the end of the file name edit text. - fileNameEditText.setSelection(fileNamePath.length()); - } - } - } - break; - } - } - - private void loadUrlFromTextBox() { - // Get a handle for the URL edit text. - EditText urlEditText = findViewById(R.id.url_edittext); - - // 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(); - - // Initialize the formatted URL string. - String url = ""; - - // Check to see if `unformattedUrlString` 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://")) { - unformattedUrlString = "https://" + unformattedUrlString; - } - - // Initialize `unformattedUrl`. - 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. - try { - unformattedUrl = new URL(unformattedUrlString); - } catch (MalformedURLException e) { - e.printStackTrace(); - } - - // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value. - String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null; - String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null; - String path = unformattedUrl != null ? unformattedUrl.getPath() : null; - String query = unformattedUrl != null ? unformattedUrl.getQuery() : null; - String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null; - - // Build the URI. - Uri.Builder uri = new Uri.Builder(); - uri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment); - - // Decode the URI as a UTF-8 string in. - try { - url = URLDecoder.decode(uri.build().toString(), "UTF-8"); - } catch (UnsupportedEncodingException exception) { - // Do nothing. The formatted URL string will remain blank. - } - } else if (!unformattedUrlString.isEmpty()){ // This is not a URL, but rather a search string. - // Create an encoded URL String. - String encodedUrlString; - - // Sanitize the search input. - try { - encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8"); - } catch (UnsupportedEncodingException exception) { - encodedUrlString = ""; - } - - // Add the base search URL. - url = searchURL + encodedUrlString; - } - - // Clear the focus from the URL edit text. Otherwise, proximate typing in the box will retain the colorized formatting instead of being reset during refocus. - urlEditText.clearFocus(); - - // Make it so. - loadUrl(currentWebView, url); - } - - private void loadUrl(NestedScrollWebView nestedScrollWebView, String url) { - // Sanitize the URL. - url = sanitizeUrl(url); - - // Apply the domain settings. - applyDomainSettings(nestedScrollWebView, url, true, false); - - // Load the URL. - nestedScrollWebView.loadUrl(url, customHeaders); - } - - public void findPreviousOnPage(View view) { - // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards. - currentWebView.findNext(false); - } - - public void findNextOnPage(View view) { - // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards. - currentWebView.findNext(true); - } - - public void closeFindOnPage(View view) { - // Get a handle for the views. - Toolbar toolbar = findViewById(R.id.toolbar); - LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout); - EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext); - - // Delete the contents of `find_on_page_edittext`. - findOnPageEditText.setText(null); - - // Clear the highlighted phrases if the WebView is not null. - if (currentWebView != null) { - currentWebView.clearMatches(); - } - - // Hide the find on page linear layout. - findOnPageLinearLayout.setVisibility(View.GONE); - - // Show the toolbar. - toolbar.setVisibility(View.VISIBLE); - - // Get a handle for the input method manager. - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - - // Remove the lint warning below that the input method manager might be null. - assert inputMethodManager != null; - - // Hide the keyboard. - inputMethodManager.hideSoftInputFromWindow(toolbar.getWindowToken(), 0); - } - - @Override - public void onApplyNewFontSize(DialogFragment dialogFragment) { - // Get the dialog. - Dialog dialog = dialogFragment.getDialog(); - - // Remove the incorrect lint warning below tha the dialog might be null. - assert dialog != null; - - // Get a handle for the font size edit text. - EditText fontSizeEditText = dialog.findViewById(R.id.font_size_edittext); - - // Initialize the new font size variable with the current font size. - int newFontSize = currentWebView.getSettings().getTextZoom(); - - // Get the font size from the edit text. - try { - newFontSize = Integer.parseInt(fontSizeEditText.getText().toString()); - } catch (Exception exception) { - // If the edit text does not contain a valid font size do nothing. - } - - // Apply the new font size. - currentWebView.getSettings().setTextZoom(newFontSize); - } - - @Override - public void onOpen(DialogFragment dialogFragment) { - // Get the dialog. - Dialog dialog = dialogFragment.getDialog(); - - // Remove the incorrect lint warning below that the dialog might be null. - assert dialog != null; - - // Get a handle for the file name edit text. - EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext); - - // Get the file path string. - openFilePath = fileNameEditText.getText().toString(); - - // Check to see if the storage permission is needed. - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted. - // Open the file. - currentWebView.loadUrl("file://" + openFilePath); - } else { // The storage permission has not been granted. - // Get the external private directory file. - File externalPrivateDirectoryFile = getExternalFilesDir(null); - - // Remove the incorrect lint error below that the file might be null. - assert externalPrivateDirectoryFile != null; - - // Get the external private directory string. - String externalPrivateDirectory = externalPrivateDirectoryFile.toString(); - - // Check to see if the file path is in the external private directory. - if (openFilePath.startsWith(externalPrivateDirectory)) { // the file path is in the external private directory. - // Open the file. - currentWebView.loadUrl("file://" + openFilePath); - } else { // The file path is in a public directory. - // Check if the user has previously denied the storage permission. - if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. - // Instantiate the storage permission alert dialog. - DialogFragment storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(StoragePermissionDialog.OPEN); - - // Show the storage permission alert dialog. The permission will be requested the the dialog is closed. - storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission)); - } else { // Show the permission request directly. - // Request the write external storage permission. The file will be opened when it finishes. - ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, StoragePermissionDialog.OPEN); - } - } - } - } - - @Override - public void onSaveWebpage(int saveType, DialogFragment dialogFragment) { - // Get the dialog. - Dialog dialog = dialogFragment.getDialog(); - - // Remove the incorrect lint warning below that the dialog might be null. - assert dialog != null; - - // Get a handle for the edit texts. - EditText urlEditText = dialog.findViewById(R.id.url_edittext); - EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext); - - // Get the strings from the edit texts. - saveWebpageUrl = urlEditText.getText().toString(); - saveWebpageFilePath = fileNameEditText.getText().toString(); - - // Check to see if the storage permission is needed. - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted. - //Save the webpage according to the save type. - switch (saveType) { - case StoragePermissionDialog.SAVE_URL: - // Save the URL. - new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); - break; - - case StoragePermissionDialog.SAVE_ARCHIVE: - // Save the webpage archive. - saveWebpageArchive(); - break; - - case StoragePermissionDialog.SAVE_IMAGE: - // Save the webpage image. - new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute(); - break; - } - } else { // The storage permission has not been granted. - // Get the external private directory file. - File externalPrivateDirectoryFile = getExternalFilesDir(null); - - // Remove the incorrect lint error below that the file might be null. - assert externalPrivateDirectoryFile != null; - - // Get the external private directory string. - String externalPrivateDirectory = externalPrivateDirectoryFile.toString(); - - // Check to see if the file path is in the external private directory. - if (saveWebpageFilePath.startsWith(externalPrivateDirectory)) { // The file path is in the external private directory. - // Save the webpage according to the save type. - switch (saveType) { - case StoragePermissionDialog.SAVE_URL: - // Save the URL. - new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); - break; - - case StoragePermissionDialog.SAVE_ARCHIVE: - // Save the webpage archive. - saveWebpageArchive(); - break; - - case StoragePermissionDialog.SAVE_IMAGE: - // Save the webpage image. - new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute(); - break; - } - } else { // The file path is in a public directory. - // Check if the user has previously denied the storage permission. - if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. - // Instantiate the storage permission alert dialog. - DialogFragment storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(saveType); - - // Show the storage permission alert dialog. The permission will be requested when the dialog is closed. - storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission)); - } else { // Show the permission request directly. - // Request the write external storage permission according to the save type. The URL will be saved when it finishes. - ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, saveType); - } - } - } - } - - @Override - public void onCloseStoragePermissionDialog(int requestType) { - // Request the write external storage permission according to the request type. The file will be opened when it finishes. - ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestType); - - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - //Only process the results if they exist (this method is triggered when a dialog is presented the first time for an app, but no grant results are included). - if (grantResults.length > 0) { - switch (requestCode) { - case StoragePermissionDialog.OPEN: - // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted. - // Load the file. - currentWebView.loadUrl("file://" + openFilePath); - } else { // The storage permission was not granted. - // Display an error snackbar. - Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); - } - - // Reset the open file path. - openFilePath = ""; - break; - - case StoragePermissionDialog.SAVE_URL: - // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted. - // Save the raw URL. - new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); - } else { // The storage permission was not granted. - // Display an error snackbar. - Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); - } - - // Reset the save strings. - saveWebpageUrl = ""; - saveWebpageFilePath = ""; - break; - - case StoragePermissionDialog.SAVE_ARCHIVE: - // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted. - // Save the webpage archive. - saveWebpageArchive(); - } else { // The storage permission was not granted. - // Display an error snackbar. - Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); - } - - // Reset the save webpage file path. - saveWebpageFilePath = ""; - break; - - case StoragePermissionDialog.SAVE_IMAGE: - // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted. - // Save the webpage image. - new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute(); - } else { // The storage permission was not granted. - // Display an error snackbar. - Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); - } - - // Reset the save webpage file path. - saveWebpageFilePath = ""; - break; - } - } - } - - private void initializeApp() { - // Get a handle for the input method. - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - - // 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)); - } - - // Get handles for the URL views. - EditText urlEditText = findViewById(R.id.url_edittext); - - // Remove the formatting from the URL edit text when the user is editing the text. - urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> { - if (hasFocus) { // The user is editing the URL text box. - // Remove the highlighting. - urlEditText.getText().removeSpan(redColorSpan); - urlEditText.getText().removeSpan(initialGrayColorSpan); - urlEditText.getText().removeSpan(finalGrayColorSpan); - } else { // The user has stopped editing the URL text box. - // Move to the beginning of the string. - urlEditText.setSelection(0); - - // Reapply the highlighting. - highlightUrlText(); - } - }); - - // Set the go button on the keyboard to load the URL in `urlTextBox`. - urlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> { - // If the event is a key-down event on the `enter` button, load the URL. - if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { - // Load the URL into the mainWebView and consume the event. - loadUrlFromTextBox(); - - // If the enter key was pressed, consume the event. - return true; - } else { - // If any other key was pressed, do not consume the event. - return false; - } - }); - - // Create an Orbot status broadcast receiver. - orbotStatusBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // Store the content of the status message in `orbotStatus`. - 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) { - // Reset the waiting for proxy status. - waitingForProxy = false; - - // Get a handle for the waiting for proxy dialog. - DialogFragment waitingForProxyDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.waiting_for_proxy_dialog)); - - // Dismiss the waiting for proxy dialog if it is displayed. - if (waitingForProxyDialogFragment != null) { - waitingForProxyDialogFragment.dismiss(); - } - - // Reload existing URLs and load any URLs that are waiting for the proxy. - for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - // Only process the WebViews if they exist. - if (fragmentView != null) { - // Get the nested scroll WebView from the tab fragment. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - - // Get the waiting for proxy URL string. - String waitingForProxyUrlString = nestedScrollWebView.getWaitingForProxyUrlString(); - - // Load the pending URL if it exists. - if (!waitingForProxyUrlString.isEmpty()) { // A URL is waiting to be loaded. - // Load the URL. - loadUrl(nestedScrollWebView, waitingForProxyUrlString); - - // Reset the waiting for proxy URL string. - nestedScrollWebView.resetWaitingForProxyUrlString(); - } else { // No URL is waiting to be loaded. - // Reload the existing URL. - nestedScrollWebView.reload(); - } - } - } - } - } - }; - - // Register the Orbot status broadcast receiver on `this` context. - this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS")); - - // Get handles for views that need to be modified. - NavigationView navigationView = findViewById(R.id.navigationview); - SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); - ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview); - FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab); - FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab); - FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab); - EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext); - - // Listen for touches on the navigation menu. - navigationView.setNavigationItemSelectedListener(this); - - // Get handles for the navigation menu and the back and forward menu items. - Menu navigationMenu = navigationView.getMenu(); - MenuItem navigationBackMenuItem = navigationMenu.findItem(R.id.back); - MenuItem navigationForwardMenuItem = navigationMenu.findItem(R.id.forward); - MenuItem navigationHistoryMenuItem = navigationMenu.findItem(R.id.history); - MenuItem navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests); - - // Update the web view pager every time a tab is modified. - webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - // Do nothing. - } - - @Override - public void onPageSelected(int position) { - // Close the find on page bar if it is open. - closeFindOnPage(null); - - // Set the current WebView. - setCurrentWebView(position); - - // Select the corresponding tab if it does not match the currently selected page. This will happen if the page was scrolled by creating a new tab. - if (tabLayout.getSelectedTabPosition() != position) { - // Create a handler to select the tab. - Handler selectTabHandler = new Handler(); - - // Create a runnable to select the tab. - Runnable selectTabRunnable = () -> { - // Get a handle for the tab. - TabLayout.Tab tab = tabLayout.getTabAt(position); - - // Assert that the tab is not null. - assert tab != null; - - // Select the tab. - tab.select(); - }; - - // Select the tab layout after 150 milliseconds, which leaves enough time for a new tab to be inflated. TODO. - selectTabHandler.postDelayed(selectTabRunnable, 150); - } - } - - @Override - public void onPageScrollStateChanged(int state) { - // Do nothing. - } - }); - - // Display the View SSL Certificate dialog when the currently selected tab is reselected. - tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { - @Override - public void onTabSelected(TabLayout.Tab tab) { - // Select the same page in the view pager. - webViewPager.setCurrentItem(tab.getPosition()); - } - - @Override - public void onTabUnselected(TabLayout.Tab tab) { - // Do nothing. - } - - @Override - public void onTabReselected(TabLayout.Tab tab) { - // Instantiate the View SSL Certificate dialog. - DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId()); - - // Display the View SSL Certificate dialog. - viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate)); - } - }); - - // Set the launch bookmarks activity FAB to launch the bookmarks activity. - launchBookmarksActivityFab.setOnClickListener(v -> { - // Get a copy of the favorite icon bitmap. - Bitmap favoriteIconBitmap = currentWebView.getFavoriteOrDefaultIcon(); - - // Create a favorite icon byte array output stream. - ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream(); - - // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG). - favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream); - - // Convert the favorite icon byte array stream to a byte array. - byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray(); - - // Create an intent to launch the bookmarks activity. - Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class); - - // Add the extra information to the intent. - bookmarksIntent.putExtra("current_url", currentWebView.getUrl()); - bookmarksIntent.putExtra("current_title", currentWebView.getTitle()); - bookmarksIntent.putExtra("current_folder", currentBookmarksFolder); - bookmarksIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray); - - // Make it so. - startActivity(bookmarksIntent); - }); - - // Set the create new bookmark folder FAB to display an alert dialog. - createBookmarkFolderFab.setOnClickListener(v -> { - // Create a create bookmark folder dialog. - DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteOrDefaultIcon()); - - // Show the create bookmark folder dialog. - createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder)); - }); - - // Set the create new bookmark FAB to display an alert dialog. - createBookmarkFab.setOnClickListener(view -> { - // Instantiate the create bookmark dialog. - DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteOrDefaultIcon()); - - // Display the create bookmark dialog. - createBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.create_bookmark)); - }); - - // Search for the string on the page whenever a character changes in the `findOnPageEditText`. - findOnPageEditText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // Do nothing. - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - // Do nothing. - } - - @Override - public void afterTextChanged(Editable s) { - // Search for the text in the WebView if it is not null. Sometimes on resume after a period of non-use the WebView will be null. - if (currentWebView != null) { - currentWebView.findAllAsync(findOnPageEditText.getText().toString()); - } - } - }); - - // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard. - findOnPageEditText.setOnKeyListener((v, keyCode, event) -> { - if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed. - // Hide the soft keyboard. - inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0); - - // Consume the event. - return true; - } else { // A different key was pressed. - // Do not consume the event. - return false; - } - }); - - // Implement swipe to refresh. - swipeRefreshLayout.setOnRefreshListener(() -> currentWebView.reload()); - - // Store the default progress view offsets for use later in `initializeWebView()`. - defaultProgressViewStartOffset = swipeRefreshLayout.getProgressViewStartOffset(); - defaultProgressViewEndOffset = swipeRefreshLayout.getProgressViewEndOffset(); - - // Set the refresh color scheme according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - swipeRefreshLayout.setColorSchemeResources(R.color.blue_700); - } else { - swipeRefreshLayout.setColorSchemeResources(R.color.violet_500); - } - - // Initialize a color background typed value. - TypedValue colorBackgroundTypedValue = new TypedValue(); - - // Get the color background from the theme. - getTheme().resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true); - - // Get the color background int from the typed value. - int colorBackgroundInt = colorBackgroundTypedValue.data; - - // Set the swipe refresh background color. - swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt); - - // The drawer titles identify the drawer layouts in accessibility mode. - 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. - loadBookmarksFolder(); - - 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; - - // Get the bookmark cursor for this ID. - Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId); - - // Move the bookmark cursor to the first row. - 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. - // Store the new folder name in `currentBookmarksFolder`. - currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(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))); - - // Close the bookmarks drawer. - drawerLayout.closeDrawer(GravityCompat.END); - } - - // Close the `Cursor`. - bookmarkCursor.close(); - }); - - bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> { - // Convert the database ID from `long` to `int`. - int databaseId = (int) id; - - // Find out if the selected bookmark is a folder. - boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId); - - if (isFolder) { - // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`. - oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); - - // Instantiate the edit folder bookmark dialog. - DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon()); - - // Show the edit folder bookmark dialog. - editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder)); - } else { - // Get the bookmark cursor for this ID. - Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId); - - // 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); - } - - // Consume the event. - return true; - }); - - // The drawer listener is used to update the navigation menu. - drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() { - @Override - public void onDrawerSlide(@NonNull View drawerView, float slideOffset) { - } - - @Override - public void onDrawerOpened(@NonNull View drawerView) { - } - - @Override - public void onDrawerClosed(@NonNull View drawerView) { - } - - @Override - public void onDrawerStateChanged(int newState) { - if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing. - // Update the navigation menu items if the WebView is not null. - if (currentWebView != null) { - navigationBackMenuItem.setEnabled(currentWebView.canGoBack()); - navigationForwardMenuItem.setEnabled(currentWebView.canGoForward()); - navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward())); - navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - - // Hide the keyboard (if displayed). - inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0); - } - - // Clear the focus from from the URL text box and the WebView. This removes any text selection markers and context menus, which otherwise draw above the open drawers. - urlEditText.clearFocus(); - currentWebView.clearFocus(); - } - } - }); - - // 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); - - // Get a handle for the WebView. - WebView bareWebView = webViewLayout.findViewById(R.id.bare_webview); - - // Store the default user agent. - webViewDefaultUserAgent = bareWebView.getSettings().getUserAgentString(); - - // Destroy the bare WebView. - bareWebView.destroy(); - } - - private void applyAppSettings() { - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - // Store the values from the shared preferences in variables. - incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false); - boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", 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); - hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true); - scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true); - - // Apply the saved proxy mode if the app has been restarted. - if (savedProxyMode != null) { - // Apply the saved proxy mode. - proxyMode = savedProxyMode; - - // Reset the saved proxy mode. - savedProxyMode = null; - } - - // Get the search string. - String searchString = sharedPreferences.getString("search", 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. - searchURL = searchString; - } - - // Get a handle for the app compat delegate. - AppCompatDelegate appCompatDelegate = getDelegate(); - - // Get handles for the views that need to be modified. - FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout); - ActionBar actionBar = appCompatDelegate.getSupportActionBar(); - Toolbar toolbar = findViewById(R.id.toolbar); - LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout); - LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout); - SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); - - // Remove the incorrect lint warning below that the action bar might be null. - assert actionBar != null; - - // Apply the proxy. - applyProxy(false); - - // Set Do Not Track status. - if (doNotTrackEnabled) { - customHeaders.put("DNT", "1"); - } else { - customHeaders.remove("DNT"); - } - - // 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(); - AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams(); - AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams(); - - // Add the scrolling behavior to the layout parameters. - if (scrollAppBar) { - // Enable scrolling of the app bar. - swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior()); - toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP); - findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP); - tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP); - } else { - // Disable scrolling of the app bar. - swipeRefreshLayoutParams.setBehavior(null); - toolbarLayoutParams.setScrollFlags(0); - findOnPageLayoutParams.setScrollFlags(0); - tabsLayoutParams.setScrollFlags(0); - - // Expand the app bar if it is currently collapsed. - appBarLayout.setExpanded(true); - } - - // Apply the modified layout parameters. - swipeRefreshLayout.setLayoutParams(swipeRefreshLayoutParams); - toolbar.setLayoutParams(toolbarLayoutParams); - findOnPageLinearLayout.setLayoutParams(findOnPageLayoutParams); - tabsLinearLayout.setLayoutParams(tabsLayoutParams); - - // Set the app bar scrolling for each WebView. - for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - // Only modify the WebViews if they exist. - if (fragmentView != null) { - // Get the nested scroll WebView from the tab fragment. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - - // Set the app bar scrolling. - nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar); - } - } - - // Update the full screen browsing mode settings. - if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode. - // Update the visibility of the app bar, which might have changed in the settings. - if (hideAppBar) { - // Hide the tab linear layout. - tabsLinearLayout.setVisibility(View.GONE); - - // Hide the action bar. - actionBar.hide(); - } else { - // Show the tab linear layout. - tabsLinearLayout.setVisibility(View.VISIBLE); - - // Show the action bar. - actionBar.show(); - } - - // Hide the banner ad in the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - AdHelper.hideAd(findViewById(R.id.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. - * 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 { // Privacy Browser is not in full screen browsing mode. - // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled. - inFullScreenBrowsingMode = false; - - // Show the tab linear layout. - tabsLinearLayout.setVisibility(View.VISIBLE); - - // Show the action bar. - actionBar.show(); - - // Show the banner ad in the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - // Initialize the ads. If this isn't the first run, `loadAd()` will be automatically called instead. - AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getSupportFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id)); - } - - // Remove the `SYSTEM_UI` flags from the root frame layout. - rootFrameLayout.setSystemUiVisibility(0); - } - } - - @Override - public void navigateHistory(String url, int steps) { - // Apply the domain settings. - applyDomainSettings(currentWebView, url, false, false); - - // Load the history entry. - currentWebView.goBackOrForward(steps); - } - - @Override - public void pinnedErrorGoBack() { - // 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); - - // Go back. - currentWebView.goBack(); - } - - // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled. - @SuppressLint("SetJavaScriptEnabled") - private void applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetTab, boolean reloadWebsite) { - // Store the current URL. - nestedScrollWebView.setCurrentUrl(url); - - // Parse the URL into a URI. - Uri uri = Uri.parse(url); - - // Extract the domain from `uri`. - String newHostName = uri.getHost(); - - // Strings don't like to be null. - if (newHostName == null) { - newHostName = ""; - } - - // Apply the domain settings if a new domain is being loaded or if the new domain is blank. This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc. - if (!nestedScrollWebView.getCurrentDomainName().equals(newHostName) || newHostName.equals("")) { - // Set the new host name as the current domain name. - nestedScrollWebView.setCurrentDomainName(newHostName); - - // Reset the ignoring of pinned domain information. - nestedScrollWebView.setIgnorePinnedDomainInformation(false); - - // Clear any pinned SSL certificate or IP addresses. - nestedScrollWebView.clearPinnedSslCertificate(); - nestedScrollWebView.clearPinnedIpAddresses(); - - // Reset the favorite icon if specified. - if (resetTab) { - // Initialize the favorite icon. - nestedScrollWebView.initializeFavoriteIcon(); - - // Get the current page position. - int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); - - // Get the corresponding tab. - TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition); - - // Update the tab if it isn't null, which sometimes happens when restarting from the background. - if (tab != null) { - // Get the tab custom view. - View tabCustomView = tab.getCustomView(); - - // Remove the warning below that the tab custom view might be null. - assert tabCustomView != null; - - // Get the tab views. - ImageView tabFavoriteIconImageView = tabCustomView.findViewById(R.id.favorite_icon_imageview); - TextView tabTitleTextView = tabCustomView.findViewById(R.id.title_textview); - - // Set the default favorite icon as the favorite icon for this tab. - tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteOrDefaultIcon(), 64, 64, true)); - - // Set the loading title text. - tabTitleTextView.setText(R.string.loading); - } - } - - // 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`. - Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain(); - - // Initialize `domainSettingsSet`. - Set domainSettingsSet = new HashSet<>(); - - // Get the domain name column index. - int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME); - - // Populate `domainSettingsSet`. - for (int i = 0; i < domainNameCursor.getCount(); i++) { - // Move `domainsCursor` to the current row. - domainNameCursor.moveToPosition(i); - - // Store the domain name in `domainSettingsSet`. - domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex)); - } - - // Close `domainNameCursor. - domainNameCursor.close(); - - // Initialize the domain name in database variable. - String domainNameInDatabase = null; - - // Check the hostname against the domain settings set. - if (domainSettingsSet.contains(newHostName)) { // The hostname is contained in the domain settings set. - // Record the domain name in the database. - domainNameInDatabase = newHostName; - - // Set the domain settings applied tracker to true. - nestedScrollWebView.setDomainSettingsApplied(true); - } else { // The hostname is not contained in the domain settings set. - // Set the domain settings applied tracker to false. - nestedScrollWebView.setDomainSettingsApplied(false); - } - - // 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. - if (domainSettingsSet.contains("*." + newHostName)) { // Check the host name prepended by `*.`. - // Set the domain settings applied tracker to true. - nestedScrollWebView.setDomainSettingsApplied(true); - - // Store the applied domain names as it appears in the database. - domainNameInDatabase = "*." + newHostName; - } - - // Strip out the lowest subdomain of of the host name. - newHostName = newHostName.substring(newHostName.indexOf(".") + 1); - } - - - // Get a handle for the shared preferences. - 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); - - // Get the WebView theme entry values string array. - String[] webViewThemeEntryValuesStringArray = getResources().getStringArray(R.array.webview_theme_entry_values); - - // Get a handle for the cookie manager. - CookieManager cookieManager = CookieManager.getInstance(); - - // Get handles for the views. - RelativeLayout urlRelativeLayout = findViewById(R.id.url_relativelayout); - SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); - - // Initialize the user agent array adapter and string array. - ArrayAdapter userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item); - 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. - Cursor currentDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase); - 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.setAcceptFirstPartyCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1); - boolean domainThirdPartyCookiesEnabled = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1); - nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1); - // Form data can be removed once the minimum API >= 26. - boolean saveFormData = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST, - currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, - currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, - currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, - currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ULTRALIST)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, - currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, - currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1); - String userAgentName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT)); - int fontSize = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE)); - int swipeToRefreshInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH)); - int webViewThemeInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.WEBVIEW_THEME)); - int wideViewportInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.WIDE_VIEWPORT)); - int displayWebpageImagesInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES)); - boolean pinnedSslCertificate = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1); - String pinnedSslIssuedToCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)); - String pinnedSslIssuedToOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)); - String pinnedSslIssuedToUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)); - String pinnedSslIssuedByCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)); - String pinnedSslIssuedByOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)); - String pinnedSslIssuedByUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)); - boolean pinnedIpAddresses = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1); - String pinnedHostIpAddresses = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES)); - - // Get the pinned SSL date longs. - long pinnedSslStartDateLong = currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)); - long pinnedSslEndDateLong = currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)); - - // Define the pinned SSL date variables. - Date pinnedSslStartDate; - Date pinnedSslEndDate; - - // Set the pinned SSL certificate start date to `null` if the saved date long is 0 because creating a new date results in an error if the input is 0. - if (pinnedSslStartDateLong == 0) { - pinnedSslStartDate = null; - } else { - pinnedSslStartDate = new Date(pinnedSslStartDateLong); - } - - // Set the pinned SSL certificate end date to `null` if the saved date long is 0 because creating a new date results in an error if the input is 0. - if (pinnedSslEndDateLong == 0) { - pinnedSslEndDate = null; - } else { - pinnedSslEndDate = new Date(pinnedSslEndDateLong); - } - - // Close the current host domain settings cursor. - currentDomainSettingsCursor.close(); - - // If there is a pinned SSL certificate, store it in the WebView. - if (pinnedSslCertificate) { - nestedScrollWebView.setPinnedSslCertificate(pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName, - pinnedSslStartDate, pinnedSslEndDate); - } - - // If there is a pinned IP address, store it in the WebView. - if (pinnedIpAddresses) { - nestedScrollWebView.setPinnedIpAddresses(pinnedHostIpAddresses); - } - - // Apply the cookie domain settings. - cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies()); - - // Set third-party cookies status if API >= 21. - if (Build.VERSION.SDK_INT >= 21) { - cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, domainThirdPartyCookiesEnabled); - } - - // Apply the form data setting if the API < 26. - if (Build.VERSION.SDK_INT < 26) { - nestedScrollWebView.getSettings().setSaveFormData(saveFormData); - } - - // Apply the font size. - try { // Try the specified font size to see if it is valid. - if (fontSize == 0) { // Apply the default font size. - // Try to set the font size from the value in the app settings. - nestedScrollWebView.getSettings().setTextZoom(Integer.parseInt(defaultFontSizeString)); - } else { // Apply the font size from domain settings. - nestedScrollWebView.getSettings().setTextZoom(fontSize); - } - } catch (Exception exception) { // The specified font size is invalid - // Set the font size to be 100% - nestedScrollWebView.getSettings().setTextZoom(100); - } - - // Set the user agent. - if (userAgentName.equals(getString(R.string.system_default_user_agent))) { // Use the system default user agent. - // Get the array position of the default user agent name. - int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName); - - // Set the user agent according to the system default. - switch (defaultUserAgentArrayPosition) { - case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list. - // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names. - nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName); - break; - - case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT: - // Set the user agent to `""`, which uses the default value. - nestedScrollWebView.getSettings().setUserAgentString(""); - break; - - 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))); - break; - - default: - // Get the user agent string from the user agent data array - nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]); - } - } else { // Set the user agent according to the stored name. - // Get the array position of the user agent name. - int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName); - - switch (userAgentArrayPosition) { - case UNRECOGNIZED_USER_AGENT: // The user agent name contains a custom user agent. - nestedScrollWebView.getSettings().setUserAgentString(userAgentName); - break; - - case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT: - // Set the user agent to `""`, which uses the default value. - nestedScrollWebView.getSettings().setUserAgentString(""); - break; - - default: - // Get the user agent string from the user agent data array. - nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]); - } - } - - // Set swipe to refresh. - switch (swipeToRefreshInt) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: - // Store the swipe to refresh status in the nested scroll WebView. - nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh); - - // Update the swipe refresh layout. - if (defaultSwipeToRefresh) { // Swipe to refresh is enabled. - // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes. - swipeRefreshLayout.setEnabled(currentWebView.getY() == 0); - } else { // Swipe to refresh is disabled. - // Disable the swipe refresh layout. - swipeRefreshLayout.setEnabled(false); - } - break; - - case DomainsDatabaseHelper.ENABLED: - // 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.getY() == 0); - break; - - case DomainsDatabaseHelper.DISABLED: - // Store the swipe to refresh status in the nested scroll WebView. - nestedScrollWebView.setSwipeToRefresh(false); - - // Disable swipe to refresh. - swipeRefreshLayout.setEnabled(false); - } - - // Check to see if WebView themes are supported. - if (WebViewFeature.isFeatureSupported(WebViewFeature.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. - 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. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - } else { // The system default theme is selected. - // Get the current system theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Set the WebView theme according to the current system theme status. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { // The system is in day mode. - // Turn off the WebView dark mode. - 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); - } - } - break; - - case DomainsDatabaseHelper.LIGHT_THEME: - // Turn off the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); - break; - - case DomainsDatabaseHelper.DARK_THEME: - // Turn on the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - break; - } - } - - // Set the viewport. - switch (wideViewportInt) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: - nestedScrollWebView.getSettings().setUseWideViewPort(wideViewport); - break; - - case DomainsDatabaseHelper.ENABLED: - nestedScrollWebView.getSettings().setUseWideViewPort(true); - break; - - case DomainsDatabaseHelper.DISABLED: - nestedScrollWebView.getSettings().setUseWideViewPort(false); - break; - } - - // Set the loading of webpage images. - switch (displayWebpageImagesInt) { - case DomainsDatabaseHelper.SYSTEM_DEFAULT: - nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages); - break; - - case DomainsDatabaseHelper.ENABLED: - nestedScrollWebView.getSettings().setLoadsImagesAutomatically(true); - break; - - case DomainsDatabaseHelper.DISABLED: - nestedScrollWebView.getSettings().setLoadsImagesAutomatically(false); - 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)); - } - } 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.setAcceptFirstPartyCookies(sharedPreferences.getBoolean("first_party_cookies", false)); - boolean defaultThirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false); - nestedScrollWebView.getSettings().setDomStorageEnabled(sharedPreferences.getBoolean("dom_storage", false)); - boolean saveFormData = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26. - nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST, sharedPreferences.getBoolean("easylist", true)); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, sharedPreferences.getBoolean("easyprivacy", true)); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean("fanboys_annoyance_list", true)); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean("fanboys_social_blocking_list", true)); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, sharedPreferences.getBoolean("ultralist", true)); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, sharedPreferences.getBoolean("ultraprivacy", true)); - nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false)); - - // Apply the default first-party cookie setting. - cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies()); - - // Apply the default font size setting. - try { - // Try to set the font size from the value in the app settings. - nestedScrollWebView.getSettings().setTextZoom(Integer.parseInt(defaultFontSizeString)); - } catch (Exception exception) { - // If the app settings value is invalid, set the font size to 100%. - nestedScrollWebView.getSettings().setTextZoom(100); - } - - // Apply the form data setting if the API < 26. - if (Build.VERSION.SDK_INT < 26) { - nestedScrollWebView.getSettings().setSaveFormData(saveFormData); - } - - // Store the swipe to refresh status in the nested scroll WebView. - nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh); - - // Update the swipe refresh layout. - if (defaultSwipeToRefresh) { // Swipe to refresh is enabled. - // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes. - swipeRefreshLayout.setEnabled(currentWebView.getY() == 0); - } else { // Swipe to refresh is disabled. - // Disable the swipe refresh layout. - swipeRefreshLayout.setEnabled(false); - } - - // Reset the pinned variables. - nestedScrollWebView.setDomainSettingsDatabaseId(-1); - - // Set third-party cookies status if API >= 21. - if (Build.VERSION.SDK_INT >= 21) { - cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, defaultThirdPartyCookiesEnabled); - } - - // Get the array position of the user agent name. - int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName); - - // Set the user agent. - switch (userAgentArrayPosition) { - case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list. - // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names. - nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName); - break; - - case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT: - // Set the user agent to `""`, which uses the default value. - nestedScrollWebView.getSettings().setUserAgentString(""); - break; - - 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))); - break; - - default: - // Get the user agent string from the user agent data array - 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. 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); - } 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. - 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 viewport. - nestedScrollWebView.getSettings().setUseWideViewPort(wideViewport); - - // Set the loading of webpage images. - nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages); - - // Set a transparent background on URL edit text. - urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null)); - } - - // Close the domains database helper. - domainsDatabaseHelper.close(); - - // Update the privacy icons. - updatePrivacyIcons(true); - } - - // Reload the website if returning from the Domains activity. - if (reloadWebsite) { - nestedScrollWebView.reload(); - } - } - - 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); - - // Reset the waiting for proxy tracker. - waitingForProxy = false; - - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Update the user interface and reload the WebViews if requested. - switch (proxyMode) { - case ProxyHelper.NONE: - // Initialize a color background typed value. - TypedValue colorBackgroundTypedValue = new TypedValue(); - - // Get the color background from the theme. - getTheme().resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true); - - // Get the color background int from the typed value. - int colorBackgroundInt = colorBackgroundTypedValue.data; - - // Set the default app bar layout background. - appBarLayout.setBackgroundColor(colorBackgroundInt); - break; - - case ProxyHelper.TOR: - // Set the app bar background to indicate proxying through Orbot is enabled. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - appBarLayout.setBackgroundResource(R.color.blue_50); - } else { - appBarLayout.setBackgroundResource(R.color.dark_blue_30); - } - - // Check to see if Orbot is installed. - try { - // Get the package manager. - PackageManager packageManager = getPackageManager(); - - // Check to see if Orbot is in the list. This will throw an error and drop to the catch section if it isn't installed. - packageManager.getPackageInfo("org.torproject.android", 0); - - // Check to see if the proxy is ready. - if (!orbotStatus.equals("ON")) { // Orbot is not ready. - // Set the waiting for proxy status. - waitingForProxy = true; - - // Show the waiting for proxy dialog if it isn't already displayed. - if (getSupportFragmentManager().findFragmentByTag(getString(R.string.waiting_for_proxy_dialog)) == null) { - // Get a handle for the waiting for proxy alert dialog. - DialogFragment waitingForProxyDialogFragment = new WaitingForProxyDialog(); - - // Display the waiting for proxy alert dialog. - waitingForProxyDialogFragment.show(getSupportFragmentManager(), getString(R.string.waiting_for_proxy_dialog)); - } - } - } catch (PackageManager.NameNotFoundException exception) { // Orbot is not installed. - // Show the Orbot 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 Orbot not installed alert dialog. - DialogFragment orbotNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode); - - // Display the Orbot not installed alert dialog. - orbotNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog)); - } - } - break; - - case ProxyHelper.I2P: - // Set the app bar background to indicate proxying through Orbot is enabled. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - appBarLayout.setBackgroundResource(R.color.blue_50); - } else { - appBarLayout.setBackgroundResource(R.color.dark_blue_30); - } - - // 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. - packageManager.getPackageInfo("org.torproject.android", 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); - - // Display the I2P not installed alert dialog. - i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog)); - } - } - break; - - case ProxyHelper.CUSTOM: - // Set the app bar background to indicate proxying through Orbot is enabled. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - appBarLayout.setBackgroundResource(R.color.blue_50); - } else { - appBarLayout.setBackgroundResource(R.color.dark_blue_30); - } - break; - } - - // Reload the WebViews if requested and not waiting for the proxy. - if (reloadWebViews && !waitingForProxy) { - // Reload the WebViews. - for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - // Only reload the WebViews if they exist. - if (fragmentView != null) { - // Get the nested scroll WebView from the tab fragment. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - - // Reload the WebView. - nestedScrollWebView.reload(); - } - } - } - } - - private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) { - // Only update the privacy icons if the options menu and the current WebView have already been populated. - if ((optionsMenu != null) && (currentWebView != null)) { - // Get handles for the menu items. - MenuItem privacyMenuItem = optionsMenu.findItem(R.id.toggle_javascript); - MenuItem firstPartyCookiesMenuItem = optionsMenu.findItem(R.id.toggle_first_party_cookies); - MenuItem domStorageMenuItem = optionsMenu.findItem(R.id.toggle_dom_storage); - MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh); - - // Update the privacy icon. - if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is enabled. - privacyMenuItem.setIcon(R.drawable.javascript_enabled); - } else if (currentWebView.getAcceptFirstPartyCookies()) { // JavaScript is disabled but cookies are enabled. - privacyMenuItem.setIcon(R.drawable.warning); - } else { // All the dangerous features are disabled. - privacyMenuItem.setIcon(R.drawable.privacy_mode); - } - - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Update the first-party cookies icon. - if (currentWebView.getAcceptFirstPartyCookies()) { // First-party cookies are enabled. - firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled); - } else { // First-party cookies are disabled. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_day); - } else { - firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_night); - } - } - - // Update the DOM storage icon. - if (currentWebView.getSettings().getJavaScriptEnabled() && currentWebView.getSettings().getDomStorageEnabled()) { // Both JavaScript and DOM storage are enabled. - domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled); - } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is enabled but DOM storage is disabled. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_day); - } else { - domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_night); - } - } else { // JavaScript is disabled, so DOM storage is ghosted. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_day); - } else { - domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_night); - } - } - - // Update the refresh icon. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - refreshMenuItem.setIcon(R.drawable.refresh_enabled_day); - } else { - refreshMenuItem.setIcon(R.drawable.refresh_enabled_night); - } - - // `invalidateOptionsMenu()` calls `onPrepareOptionsMenu()` and redraws the icons in the app bar. - if (runInvalidateOptionsMenu) { - invalidateOptionsMenu(); - } - } - } - - private void highlightUrlText() { - // Get a handle for the URL edit text. - EditText urlEditText = findViewById(R.id.url_edittext); - - // Only highlight the URL text if the box is not currently selected. - if (!urlEditText.hasFocus()) { - // Get the URL string. - String urlString = urlEditText.getText().toString(); - - // Highlight the URL according to the protocol. - if (urlString.startsWith("file://") || urlString.startsWith("content://")) { // This is a file or content URL. - // De-emphasize everything before the file name. - urlEditText.getText().setSpan(initialGrayColorSpan, 0, urlString.lastIndexOf("/") + 1,Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { // This is a web URL. - // Get the index of the `/` immediately after the domain name. - int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2)); - - // Create a base URL string. - String baseUrl; - - // Get the base URL. - if (endOfDomainName > 0) { // There is at least one character after the base URL. - // Get the base URL. - baseUrl = urlString.substring(0, endOfDomainName); - } else { // There are no characters after the base URL. - // Set the base URL to be the entire URL string. - baseUrl = urlString; - } - - // Get the index of the last `.` in the domain. - int lastDotIndex = baseUrl.lastIndexOf("."); - - // Get the index of the penultimate `.` in the domain. - int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1); - - // Markup the beginning of the URL. - if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted. - urlEditText.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - // De-emphasize subdomains. - if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name. - urlEditText.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted. - if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name. - // De-emphasize the protocol and the additional subdomains. - urlEditText.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { // There is only one subdomain in the domain name. - // De-emphasize only the protocol. - urlEditText.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - } - - // De-emphasize the text after the domain name. - if (endOfDomainName > 0) { - urlEditText.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - } - } - } - - private void loadBookmarksFolder() { - // Update the bookmarks cursor with the contents of the bookmarks database for the current folder. - bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder); - - // Populate the bookmarks cursor adapter. `this` specifies the `Context`. `false` disables `autoRequery`. - bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) { - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - // Inflate the individual item layout. `false` does not attach it to the root. - return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - // Get handles for the views. - ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon); - 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)); - - // 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); - - // Display the bitmap in `bookmarkFavoriteIcon`. - bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap); - - // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`. - String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); - bookmarkNameTextView.setText(bookmarkNameString); - - // Make the font bold for folders. - if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { - bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD); - } else { // Reset the font to default for normal bookmarks. - bookmarkNameTextView.setTypeface(Typeface.DEFAULT); - } - } - }; - - // Get a handle for the bookmarks list view. - ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview); - - // Populate the list view with the adapter. - bookmarksListView.setAdapter(bookmarksCursorAdapter); - - // Get a handle for the bookmarks title text view. - TextView bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview); - - // Set the bookmarks drawer title. - if (currentBookmarksFolder.isEmpty()) { - bookmarksTitleTextView.setText(R.string.bookmarks); - } else { - bookmarksTitleTextView.setText(currentBookmarksFolder); - } - } - - private void openWithApp(String url) { - // Create an open with app intent with `ACTION_VIEW`. - Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW); - - // Set the URI but not the MIME type. This should open all available apps. - openWithAppIntent.setData(Uri.parse(url)); - - // Flag the intent to open in a new task. - openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - // Try the intent. - try { - // Show the chooser. - startActivity(openWithAppIntent); - } catch (ActivityNotFoundException exception) { // There are no apps available to open the URL. - // Show a snackbar with the error. - Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show(); - } - } - - private void openWithBrowser(String url) { - // Create an open with browser intent with `ACTION_VIEW`. - Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW); - - // Set the URI and the MIME type. `"text/html"` should load browser options. - openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html"); - - // Flag the intent to open in a new task. - openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - // Try the intent. - try { - // Show the chooser. - startActivity(openWithBrowserIntent); - } catch (ActivityNotFoundException exception) { // There are no browsers available to open the URL. - // Show a snackbar with the error. - Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show(); - } - } - - 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=")); - } - - // 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")); - } - } - - // Return the sanitized URL. - return url; - } - - public void finishedPopulatingBlocklists(ArrayList>> combinedBlocklists) { - // Store the blocklists. - easyList = combinedBlocklists.get(0); - easyPrivacy = combinedBlocklists.get(1); - fanboysAnnoyanceList = combinedBlocklists.get(2); - fanboysSocialList = combinedBlocklists.get(3); - ultraList = combinedBlocklists.get(4); - ultraPrivacy = combinedBlocklists.get(5); - - // Check to see if the activity has been restarted. - if ((savedStateArrayList == null) || (savedStateArrayList.size() == 0)) { // The activity has not been restarted or it was restarted on start to force the night theme. - // Add the first tab. - addNewTab("", true); - } else { // The activity has been restarted. - // Restore each tab. Once the minimum API >= 24, a `forEach()` command can be used. - for (int i = 0; i < savedStateArrayList.size(); i++) { - // Add a new tab. - tabLayout.addTab(tabLayout.newTab()); - - // Get the new tab. - TabLayout.Tab newTab = tabLayout.getTabAt(i); - - // Remove the lint warning below that the current tab might be null. - assert newTab != null; - - // Set a custom view on the new tab. - newTab.setCustomView(R.layout.tab_custom_view); - - // Add the new page. - webViewPagerAdapter.restorePage(savedStateArrayList.get(i), savedNestedScrollWebViewStateArrayList.get(i)); - } - - // Reset the saved state variables. - savedStateArrayList = null; - savedNestedScrollWebViewStateArrayList = null; - - // Restore the selected tab position. - if (savedTabPosition == 0) { // The first tab is selected. - // Set the first page as the current WebView. - setCurrentWebView(0); - } else { // the first tab is not selected. - // Move to the selected tab. - webViewPager.setCurrentItem(savedTabPosition); - } - - // Get the intent that started the app. - Intent intent = getIntent(); - - // Get the information from the intent. - String intentAction = intent.getAction(); - Uri intentUriData = intent.getData(); - - // Determine if this is a web search. - boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)); - - // Only process the URI if it contains data or it is a web search. If the user pressed the desktop icon after the app was already running the URI will be null. - if (intentUriData != null || isWebSearch) { - // Get the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - // Create a URL string. - String url; - - // If the intent action is a web search, perform the search. - if (isWebSearch) { // The intent is a web search. - // Create an encoded URL string. - String encodedUrlString; - - // Sanitize the search input and convert it to a search. - try { - encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8"); - } catch (UnsupportedEncodingException exception) { - encodedUrlString = ""; - } - - // Add the base search URL. - url = searchURL + encodedUrlString; - } else { // The intent should contain a URL. - // Set the intent data as the url. - url = intentUriData.toString(); - } - - // 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. - // Set the loading new intent flag. - loadingNewIntent = true; - - // Add a new tab. - addNewTab(url, true); - } else { // Load the URL in the current tab. - // Make it so. - loadUrl(currentWebView, url); - } - } - } - } - - public void addTab(View view) { - // Add a new tab with a blank URL. - addNewTab("", true); - } - - private void addNewTab(String url, boolean moveToTab) { - // Get the new page number. The page numbers are 0 indexed, so the new page number will match the current count. - int newTabNumber = tabLayout.getTabCount(); - - // Add a new tab. - tabLayout.addTab(tabLayout.newTab()); - - // Get the new tab. - TabLayout.Tab newTab = tabLayout.getTabAt(newTabNumber); - - // Remove the lint warning below that the current tab might be null. - assert newTab != null; - - // Set a custom view on the new tab. - newTab.setCustomView(R.layout.tab_custom_view); - - // Add the new WebView page. - webViewPagerAdapter.addPage(newTabNumber, webViewPager, url, moveToTab); - } - - public void closeTab(View view) { - // Run the command according to the number of tabs. - if (tabLayout.getTabCount() > 1) { // There is more than one tab open. - // Close the current tab. - closeCurrentTab(); - } else { // There is only one tab open. - clearAndExit(); - } - } - - private void closeCurrentTab() { - // Get the current tab number. - int currentTabNumber = tabLayout.getSelectedTabPosition(); - - // Delete the current tab. - tabLayout.removeTabAt(currentTabNumber); - - // Delete the current page. If the selected page number did not change during the delete, it will return true, meaning that the current WebView must be reset. - if (webViewPagerAdapter.deletePage(currentTabNumber, webViewPager)) { - setCurrentWebView(currentTabNumber); - } - - // Expand the app bar if it is currently collapsed. - appBarLayout.setExpanded(true); - } - - private void saveWebpageArchive() { - // Save the webpage archive. - currentWebView.saveWebArchive(saveWebpageFilePath); - - // Display a snackbar. - Snackbar saveWebpageArchiveSnackbar = Snackbar.make(currentWebView, getString(R.string.file_saved) + " " + saveWebpageFilePath, Snackbar.LENGTH_SHORT); - - // Add an open option to the snackbar. - saveWebpageArchiveSnackbar.setAction(R.string.open, (View view) -> { - // Get a file for the file name string. - File file = new File(saveWebpageFilePath); - - // Declare a file URI variable. - Uri fileUri; - - // Get the URI for the file according to the Android version. - if (Build.VERSION.SDK_INT >= 24) { // Use a file provider. - fileUri = FileProvider.getUriForFile(this, getString(R.string.file_provider), file); - } else { // Get the raw file path URI. - fileUri = Uri.fromFile(file); - } - - // Get a handle for the content resolver. - ContentResolver contentResolver = getContentResolver(); - - // Create an open intent with `ACTION_VIEW`. - Intent openIntent = new Intent(Intent.ACTION_VIEW); - - // Set the URI and the MIME type. - openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri)); - - // Allow the app to read the file URI. - openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - - // Show the chooser. - startActivity(Intent.createChooser(openIntent, getString(R.string.open))); - }); - - // Show the snackbar. - saveWebpageArchiveSnackbar.show(); - - // Reset the save Webpage file path. - saveWebpageFilePath = ""; - } - - private void clearAndExit() { - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - // Close the bookmarks cursor and database. - bookmarksCursor.close(); - bookmarksDatabaseHelper.close(); - - // Get the status of the clear everything preference. - boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true); - - // Get a handle for the runtime. - Runtime runtime = Runtime.getRuntime(); - - // 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 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(); - } - - // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run. - try { - // Two commands must be used because `Runtime.exec()` does not like `*`. - Process deleteCookiesProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies"); - Process deleteCookiesJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal"); - - // Wait until the processes have finished. - deleteCookiesProcess.waitFor(); - deleteCookiesJournalProcess.waitFor(); - } catch (Exception exception) { - // Do nothing if an error is thrown. - } - } - - // Clear DOM storage. - if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) { - // Ask `WebStorage` to clear the DOM storage. - WebStorage webStorage = WebStorage.getInstance(); - webStorage.deleteAllData(); - - // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run. - try { - // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly. - Process deleteLocalStorageProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"}); - - // Multiple commands must be used because `Runtime.exec()` does not like `*`. - Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB"); - Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager"); - Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal"); - Process deleteDatabaseProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases"); - - // Wait until the processes have finished. - deleteLocalStorageProcess.waitFor(); - deleteIndexProcess.waitFor(); - deleteQuotaManagerProcess.waitFor(); - deleteQuotaManagerJournalProcess.waitFor(); - deleteDatabaseProcess.waitFor(); - } catch (Exception exception) { - // Do nothing if an error is thrown. - } - } - - // Clear form data if the API < 26. - if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) { - WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this); - webViewDatabase.clearFormData(); - - // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run. - try { - // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly. - Process deleteWebDataProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"}); - Process deleteWebDataJournalProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"}); - - // Wait until the processes have finished. - deleteWebDataProcess.waitFor(); - deleteWebDataJournalProcess.waitFor(); - } catch (Exception exception) { - // Do nothing if an error is thrown. - } - } - - // Clear the logcat. - if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_logcat_key), true)) { - try { - // Clear the logcat. `-c` clears the logcat. `-b all` clears all the buffers (instead of just crash, main, and system). - Process process = Runtime.getRuntime().exec("logcat -b all -c"); - - // Wait for the process to finish. - process.waitFor(); - } catch (IOException|InterruptedException exception) { - // Do nothing. - } - } - - // Clear the cache. - if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) { - // Clear the cache from each WebView. - for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - // Only clear the cache if the WebView exists. - if (fragmentView != null) { - // Get the nested scroll WebView from the tab fragment. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - - // Clear the cache for this WebView. - nestedScrollWebView.clearCache(true); - } - } - - // Manually delete the cache directories. - try { - // Delete the main cache directory. - Process deleteCacheProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/cache"); - - // 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/"}); - - // Wait until the processes have finished. - deleteCacheProcess.waitFor(); - deleteServiceWorkerProcess.waitFor(); - } catch (Exception exception) { - // Do nothing if an error is thrown. - } - } - - // Wipe out each WebView. - for (int i = 0; i < webViewPagerAdapter.getCount(); i++) { - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - // Only wipe out the WebView if it exists. - if (fragmentView != null) { - // Get the nested scroll WebView from the tab fragment. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - - // Clear SSL certificate preferences for this WebView. - nestedScrollWebView.clearSslPreferences(); - - // Clear the back/forward history for this WebView. - nestedScrollWebView.clearHistory(); - - // Destroy the internal state of the WebView. - nestedScrollWebView.destroy(); - } - } - - // 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) { - try { - // Delete the folder. - Process deleteAppWebviewProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview"); - - // Wait until the process has finished. - deleteAppWebviewProcess.waitFor(); - } catch (Exception exception) { - // Do nothing if an error is thrown. - } - } - - // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list. - if (Build.VERSION.SDK_INT >= 21) { - finishAndRemoveTask(); - } else { - finish(); - } - - // Remove the terminated program from RAM. The status code is `0`. - System.exit(0); - } - - public void bookmarksBack(View view) { - if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed. - // close the bookmarks drawer. - drawerLayout.closeDrawer(GravityCompat.END); - } else { // A subfolder is displayed. - // Place the former parent folder in `currentFolder`. - currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder); - - // Load the new folder. - loadBookmarksFolder(); - } - } - - private void setCurrentWebView(int pageNumber) { - // Get handles for the URL views. - RelativeLayout urlRelativeLayout = findViewById(R.id.url_relativelayout); - EditText urlEditText = findViewById(R.id.url_edittext); - SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); - - // Stop the swipe to refresh indicator if it is running - swipeRefreshLayout.setRefreshing(false); - - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(pageNumber); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - // Set the current WebView if the fragment view is not null. - if (fragmentView != null) { // The fragment has been populated. - // Store the current WebView. - currentWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - - // Update the status of swipe to refresh. - if (currentWebView.getSwipeToRefresh()) { // Swipe to refresh is enabled. - // Enable the swipe refresh layout if the WebView is scrolled all the way to the top. It is updated every time the scroll changes. - swipeRefreshLayout.setEnabled(currentWebView.getY() == 0); - } else { // Swipe to refresh is disabled. - // Disable the swipe refresh layout. - swipeRefreshLayout.setEnabled(false); - } - - // Get a handle for the cookie manager. - CookieManager cookieManager = CookieManager.getInstance(); - - // Set the first-party cookie status. - cookieManager.setAcceptCookie(currentWebView.getAcceptFirstPartyCookies()); - - // Update the privacy icons. `true` redraws the icons in the app bar. - updatePrivacyIcons(true); - - // Get a handle for the input method manager. - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - - // Remove the lint warning below that the input method manager might be null. - assert inputMethodManager != null; - - // Get the current URL. - String url = currentWebView.getUrl(); - - // Update the URL edit text if not loading a new intent. Otherwise, this will be handled by `onPageStarted()` (if called) and `onPageFinished()`. - if (!loadingNewIntent) { // A new intent is not being loaded. - if ((url == null) || url.equals("about:blank")) { // The WebView is blank. - // Display the hint in the URL edit text. - urlEditText.setText(""); - - // Request focus for the URL text box. - urlEditText.requestFocus(); - - // Display the keyboard. - inputMethodManager.showSoftInput(urlEditText, 0); - } else { // The WebView has a loaded URL. - // Clear the focus from the URL text box. - urlEditText.clearFocus(); - - // Hide the soft keyboard. - inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0); - - // Display the current URL in the URL text box. - urlEditText.setText(url); - - // Highlight the URL text. - highlightUrlText(); - } - } else { // A new intent is being loaded. - // Reset the loading new intent tracker. - loadingNewIntent = false; - } - - // 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)); - } - } else { - urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null)); - } - } else { // The fragment has not been populated. Try again in 100 milliseconds. - // Create a handler to set the current WebView. - Handler setCurrentWebViewHandler = new Handler(); - - // Create a runnable to set the current WebView. - Runnable setCurrentWebWebRunnable = () -> { - // Set the current WebView. - setCurrentWebView(pageNumber); - }; - - // Try setting the current WebView again after 100 milliseconds. - setCurrentWebViewHandler.postDelayed(setCurrentWebWebRunnable, 100); - } - } - - @Override - public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, 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)); - - // 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. - if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) { // The light theme is selected. - // Turn off the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); - - // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode. - // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`. - nestedScrollWebView.setVisibility(View.VISIBLE); - } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) { // The dark theme is selected. - // Turn on the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - } else { // The system default theme is selected. - // Get the current system theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Set the WebView theme according to the current system theme status. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { // The system is in day mode. - // Turn off the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF); - - // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode. - // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`. - nestedScrollWebView.setVisibility(View.VISIBLE); - } else { // The system is in night mode. - // Turn on the WebView dark mode. - WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON); - } - } - } - - // Get a handle for the app compat delegate. - AppCompatDelegate appCompatDelegate = getDelegate(); - - // Get handles for the activity views. - FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout); - RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout); - ActionBar actionBar = appCompatDelegate.getSupportActionBar(); - LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout); - EditText urlEditText = findViewById(R.id.url_edittext); - SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); - - // Remove the incorrect lint warning below that the action bar might be null. - assert actionBar != null; - - // Get a handle for the activity - Activity activity = this; - - // Get a handle for the input method manager. - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - - // Instantiate the blocklist helper. - BlocklistHelper blocklistHelper = new BlocklistHelper(); - - // Remove the lint warning below that the input method manager might be null. - assert inputMethodManager != null; - - // Initialize the favorite icon. - nestedScrollWebView.initializeFavoriteIcon(); - - // Set the app bar scrolling. - nestedScrollWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true)); - - // Allow pinch to zoom. - nestedScrollWebView.getSettings().setBuiltInZoomControls(true); - - // Hide zoom controls. - 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); - } - - // Set the WebView to load in overview mode (zoomed out to the maximum width). - nestedScrollWebView.getSettings().setLoadWithOverviewMode(true); - - // Explicitly disable geolocation. - nestedScrollWebView.getSettings().setGeolocationEnabled(false); - - // Create a double-tap gesture detector to toggle full-screen mode. - GestureDetector doubleTapGestureDetector = new GestureDetector(getApplicationContext(), new GestureDetector.SimpleOnGestureListener() { - // Override `onDoubleTap()`. All other events are handled using the default settings. - @Override - public boolean onDoubleTap(MotionEvent event) { - if (fullScreenBrowsingModeEnabled) { // Only process the double-tap if full screen browsing mode is enabled. - // Toggle the full screen browsing mode tracker. - inFullScreenBrowsingMode = !inFullScreenBrowsingMode; - - // Toggle the full screen browsing mode. - if (inFullScreenBrowsingMode) { // Switch to full screen mode. - // Hide the app bar if specified. - if (hideAppBar) { - // Close the find on page bar if it is visible. - closeFindOnPage(null); - - // Hide the tab linear layout. - tabsLinearLayout.setVisibility(View.GONE); - - // Hide the action bar. - actionBar.hide(); - - // If the app bar is not being scrolled, the swipe refresh layout needs to be adjusted. - if (!scrollAppBar) { - // Remove the padding from the top of the swipe refresh layout. - swipeRefreshLayout.setPadding(0, 0, 0, 0); - - // The swipe refresh circle must be moved above the now removed status bar location. - swipeRefreshLayout.setProgressViewOffset(false, -200, defaultProgressViewEndOffset); - } - } - - // Hide the banner ad in the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - AdHelper.hideAd(findViewById(R.id.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. - * 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 { // Switch to normal viewing mode. - // Show the app bar if it was hidden. - if (hideAppBar) { - // Show the tab linear layout. - tabsLinearLayout.setVisibility(View.VISIBLE); - - // Show the action bar. - actionBar.show(); - - // If the app bar is not being scrolled, the swipe refresh layout needs to be adjusted. - if (!scrollAppBar) { - // 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")) { - // Reload the ad. - AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id)); - } - - // Remove the `SYSTEM_UI` flags from the root frame layout. - rootFrameLayout.setSystemUiVisibility(0); - } - - // Consume the double-tap. - return true; - } else { // Do not consume the double-tap because full screen browsing mode is disabled. - return false; - } - } - }); - - // Pass all touch events on the WebView through the double-tap gesture detector. - nestedScrollWebView.setOnTouchListener((View view, MotionEvent event) -> { - // Call `performClick()` on the view, which is required for accessibility. - view.performClick(); - - // Send the event to the gesture detector. - return doubleTapGestureDetector.onTouchEvent(event); - }); - - // Register the WebView for a context menu. This is used to see link targets and download images. - registerForContextMenu(nestedScrollWebView); - - // Allow the downloading of files. - nestedScrollWebView.setDownloadListener((String downloadUrl, String userAgent, String contentDisposition, String mimetype, long contentLength) -> { - // Define a formatted file size string. - String formattedFileSizeString; - - // Process the content length if it contains data. - if (contentLength > 0) { // The content length is greater than 0. - // Format the content length as a string. - formattedFileSizeString = NumberFormat.getInstance().format(contentLength) + " " + getString(R.string.bytes); - } else { // The content length is not greater than 0. - // Set the formatted file size string to be `unknown size`. - formattedFileSizeString = getString(R.string.unknown_size); - } - - // Get the file name from the content disposition. - String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl); - - // Instantiate the save dialog. - DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent, - nestedScrollWebView.getAcceptFirstPartyCookies()); - - // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name. - saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog)); - }); - - // Update the find on page count. - nestedScrollWebView.setFindListener(new WebView.FindListener() { - // Get a handle for `findOnPageCountTextView`. - final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview); - - @Override - public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) { - if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches. - // Set `findOnPageCountTextView` to `0/0`. - findOnPageCountTextView.setText(R.string.zero_of_zero); - } else if (isDoneCounting) { // There are matches. - // `activeMatchOrdinal` is zero-based. - int activeMatch = activeMatchOrdinal + 1; - - // Build the match string. - String matchString = activeMatch + "/" + numberOfMatches; - - // Set `findOnPageCountTextView`. - findOnPageCountTextView.setText(matchString); - } - } - }); - - // 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, i, i1, i2, i3) -> { - 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); - } - }); - } 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); - } - - - // 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() { - // Update the progress bar when a page is loading. - @Override - public void onProgressChanged(WebView view, int progress) { - // Update the progress bar. - progressBar.setProgress(progress); - - // Set the visibility of the progress bar. - if (progress < 100) { - // Show the progress bar. - progressBar.setVisibility(View.VISIBLE); - } else { - // Hide the progress bar. - progressBar.setVisibility(View.GONE); - - //Stop the swipe to refresh indicator if it is running - swipeRefreshLayout.setRefreshing(false); - - // Make the current WebView visible. If this is a new tab, the current WebView would have been created invisible in `webview_framelayout` to prevent a white background splash in night mode. - nestedScrollWebView.setVisibility(View.VISIBLE); - } - } - - // Set the favorite icon when it changes. - @Override - public void onReceivedIcon(WebView view, Bitmap icon) { - // Only update the favorite icon if the website has finished loading. - if (progressBar.getVisibility() == View.GONE) { - // Store the new favorite icon. - nestedScrollWebView.setFavoriteOrDefaultIcon(icon); - - // Get the current page position. - int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); - - // Get the current tab. - TabLayout.Tab tab = tabLayout.getTabAt(currentPosition); - - // Check to see if the tab has been populated. - if (tab != null) { - // Get the custom view from the tab. - View tabView = tab.getCustomView(); - - // Check to see if the custom tab view has been populated. - if (tabView != null) { - // Get the favorite icon image view from the tab. - ImageView tabFavoriteIconImageView = tabView.findViewById(R.id.favorite_icon_imageview); - - // Display the favorite icon in the tab. - tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true)); - } - } - } - } - - // Save a copy of the title when it changes. - @Override - public void onReceivedTitle(WebView view, String title) { - // Get the current page position. - int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); - - // Get the current tab. - TabLayout.Tab tab = tabLayout.getTabAt(currentPosition); - - // Only populate the title text view if the tab has been fully created. - if (tab != null) { - // Get the custom view from the tab. - View tabView = tab.getCustomView(); - - // Only populate the title text view if the tab view has been fully populated. - if (tabView != null) { - // Get the title text view from the tab. - TextView tabTitleTextView = tabView.findViewById(R.id.title_textview); - - // Set the title according to the URL. - if (title.equals("about:blank")) { - // Set the title to indicate a new tab. - tabTitleTextView.setText(R.string.new_tab); - } else { - // Set the title as the tab text. - tabTitleTextView.setText(title); - } - } - } - } - - // Enter full screen video. - @Override - public void onShowCustomView(View video, CustomViewCallback callback) { - // Get a handle for the full screen video frame layout. - FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout); - - // Set the full screen video flag. - displayingFullScreenVideo = true; - - // Pause the ad if this is the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations. - AdHelper.pauseAd(findViewById(R.id.adview)); - } - - // Hide the keyboard. - inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0); - - // Hide the main content relative layout. - mainContentRelativeLayout.setVisibility(View.GONE); - - /* 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); - - // Disable the sliding drawers. - drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); - - // Add the video view to the full screen video frame layout. - fullScreenVideoFrameLayout.addView(video); - - // Show the full screen video frame layout. - fullScreenVideoFrameLayout.setVisibility(View.VISIBLE); - - // Disable the screen timeout while the video is playing. YouTube does this automatically, but not all other videos do. - fullScreenVideoFrameLayout.setKeepScreenOn(true); - } - - // Exit full screen video. - @Override - public void onHideCustomView() { - // Get a handle for the full screen video frame layout. - FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout); - - // Re-enable the screen timeout. - fullScreenVideoFrameLayout.setKeepScreenOn(false); - - // Unset the full screen video flag. - displayingFullScreenVideo = false; - - // Remove all the views from the full screen video frame layout. - fullScreenVideoFrameLayout.removeAllViews(); - - // Hide the full screen video frame layout. - fullScreenVideoFrameLayout.setVisibility(View.GONE); - - // Enable the sliding drawers. - drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); - - // Show the main content relative layout. - mainContentRelativeLayout.setVisibility(View.VISIBLE); - - // Apply the appropriate full screen mode flags. - if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode. - // Hide the app bar if specified. - if (hideAppBar) { - // Hide the tab linear layout. - tabsLinearLayout.setVisibility(View.GONE); - - // Hide the action bar. - actionBar.hide(); - } - - // Hide the banner ad in the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - AdHelper.hideAd(findViewById(R.id.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. - * 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 { // Switch to normal viewing mode. - // 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) { - // Reload the ad. - AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id)); - } - } - - // 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; - - // 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(); - - // 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); - - // Set the file type to everything. - genericFileChooserIntent.setType("*/*"); - - // Start the generic file chooser intent. - startActivityForResult(genericFileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE); - } - } - return true; - } - }); - - nestedScrollWebView.setWebViewClient(new WebViewClient() { - // `shouldOverrideUrlLoading` makes this WebView the default handler for URLs inside the app, so that links are not kicked out to other apps. - // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24. - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - // Sanitize the url. - url = sanitizeUrl(url); - - // Handle the URL according to the type. - if (url.startsWith("http")) { // Load the URL in Privacy Browser. - // Apply the domain settings for the new URL. This doesn't do anything if the domain has not changed. - applyDomainSettings(nestedScrollWebView, url, true, false); - - // Load the URL. By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent. - nestedScrollWebView.loadUrl(url, customHeaders); - - // Returning true indicates that Privacy Browser is manually handling the loading of the URL. - // Custom headers cannot be added if false is returned and the WebView handles the loading of the URL. - return true; - } else if (url.startsWith("mailto:")) { // Load the email address in an external email program. - // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched. - Intent emailIntent = new Intent(Intent.ACTION_SENDTO); - - // Parse the url and set it as the data for the intent. - emailIntent.setData(Uri.parse(url)); - - // Open the email program in a new task instead of as part of Privacy Browser. - emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - try { - // Make it so. - startActivity(emailIntent); - } catch (ActivityNotFoundException exception) { - // Display a snackbar. - Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show(); - } - - - // Returning true indicates Privacy Browser is handling the URL by creating an intent. - return true; - } else if (url.startsWith("tel:")) { // Load the phone number in the dialer. - // Open the dialer and load the phone number, but wait for the user to place the call. - Intent dialIntent = new Intent(Intent.ACTION_DIAL); - - // Add the phone number to the intent. - dialIntent.setData(Uri.parse(url)); - - // Open the dialer in a new task instead of as part of Privacy Browser. - dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - try { - // Make it so. - startActivity(dialIntent); - } catch (ActivityNotFoundException exception) { - // Display a snackbar. - Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show(); - } - - // Returning true indicates Privacy Browser is handling the URL by creating an intent. - return true; - } else { // Load a system chooser to select an app that can handle the URL. - // Open an app that can handle the URL. - Intent genericIntent = new Intent(Intent.ACTION_VIEW); - - // Add the URL to the intent. - genericIntent.setData(Uri.parse(url)); - - // List all apps that can handle the URL instead of just opening the first one. - genericIntent.addCategory(Intent.CATEGORY_BROWSABLE); - - // Open the app in a new task instead of as part of Privacy Browser. - genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - // Start the app or display a snackbar if no app is available to handle the URL. - try { - startActivity(genericIntent); - } catch (ActivityNotFoundException exception) { - Snackbar.make(nestedScrollWebView, getString(R.string.unrecognized_url) + " " + url, Snackbar.LENGTH_SHORT).show(); - } - - // Returning true indicates Privacy Browser is handling the URL by creating an intent. - return true; - } - } - - // Check requests against the block lists. The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21. - @Override - public WebResourceResponse shouldInterceptRequest(WebView view, String url) { - // 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. - return null; - } - - // Wait until the blocklists have been populated. When Privacy Browser is being resumed after having the process killed in the background it will try to load the URLs immediately. - while (ultraPrivacy == null) { - // The wait must be synchronized, which only lets one thread run on it at a time, or `java.lang.IllegalMonitorStateException` is thrown. - synchronized (this) { - try { - // Check to see if the blocklists have been populated after 100 ms. - wait(100); - } catch (InterruptedException exception) { - // Do nothing. - } - } - } - - // Sanitize the URL. - url = sanitizeUrl(url); - - // Get a handle for the navigation view. - NavigationView navigationView = findViewById(R.id.navigationview); - - // Get a handle for the navigation menu. - Menu navigationMenu = navigationView.getMenu(); - - // Get a handle for the navigation requests menu item. - MenuItem navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests); - - // 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())); - - // Reset the whitelist results tracker. - String[] whitelistResultStringArray = null; - - // Initialize the third party request tracker. - boolean isThirdPartyRequest = false; - - // Get the current URL. `.getUrl()` throws an error because operations on the WebView cannot be made from this thread. - String currentBaseDomain = nestedScrollWebView.getCurrentDomainName(); - - // Store a copy of the current domain for use in later requests. - String currentDomain = currentBaseDomain; - - // Nobody is happy when comparing null strings. - if ((currentBaseDomain != null) && (url != null)) { - // Convert the request URL to a URI. - Uri requestUri = Uri.parse(url); - - // Get the request host name. - String requestBaseDomain = requestUri.getHost(); - - // 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); - } - - // Update the third party request tracker. - isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain); - } - } - - // Get the current WebView page position. - int webViewPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); - - // Determine if the WebView is currently displayed. - boolean webViewDisplayed = (webViewPagePosition == tabLayout.getSelectedTabPosition()); - - // Block third-party requests if enabled. - if (isThirdPartyRequest && nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)) { - // Add the result to the resource requests. - nestedScrollWebView.addResourceRequest(new String[]{BlocklistHelper.REQUEST_THIRD_PARTY, url}); - - // Increment the blocked requests counters. - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS); - - // Update the titles of the blocklist menu items if the WebView is currently displayed. - if (webViewDisplayed) { - // Updating the UI must be run from the UI thread. - activity.runOnUiThread(() -> { - // Update the menu item titles. - navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - - // Update the options menu if it has been populated. - if (optionsMenu != null) { - optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - optionsMenu.findItem(R.id.block_all_third_party_requests).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + - getString(R.string.block_all_third_party_requests)); - } - }); - } - - // Return an empty web resource response. - return emptyWebResourceResponse; - } - - // Check UltraList if it is enabled. - if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)) { - // Check the URL against UltraList. - String[] ultraListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraList); - - // Process the UltraList results. - if (ultraListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched UltraLists's blacklist. - // Add the result to the resource requests. - nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]}); - - // Increment the blocked requests counters. - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRALIST); - - // Update the titles of the blocklist menu items if the WebView is currently displayed. - if (webViewDisplayed) { - // Updating the UI must be run from the UI thread. - activity.runOnUiThread(() -> { - // Update the menu item titles. - navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - - // Update the options menu if it has been populated. - if (optionsMenu != null) { - optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - optionsMenu.findItem(R.id.ultralist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist)); - } - }); - } - - // The resource request was blocked. Return an empty web resource response. - return emptyWebResourceResponse; - } else if (ultraListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched UltraList's whitelist. - // Add a whitelist entry to the resource requests array. - nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]}); - - // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource. - return null; - } - } - - // Check UltraPrivacy if it is enabled. - if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)) { - // Check the URL against UltraPrivacy. - String[] ultraPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy); - - // Process the UltraPrivacy results. - if (ultraPrivacyResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched UltraPrivacy's blacklist. - // Add the result to the resource requests. - nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4], - ultraPrivacyResults[5]}); - - // Increment the blocked requests counters. - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRAPRIVACY); - - // Update the titles of the blocklist menu items if the WebView is currently displayed. - if (webViewDisplayed) { - // Updating the UI must be run from the UI thread. - activity.runOnUiThread(() -> { - // Update the menu item titles. - navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - - // Update the options menu if it has been populated. - if (optionsMenu != null) { - optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - optionsMenu.findItem(R.id.ultraprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy)); - } - }); - } - - // The resource request was blocked. Return an empty web resource response. - return emptyWebResourceResponse; - } else if (ultraPrivacyResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched UltraPrivacy's whitelist. - // Add a whitelist entry to the resource requests array. - nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4], - ultraPrivacyResults[5]}); - - // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource. - return null; - } - } - - // Check EasyList if it is enabled. - if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)) { - // Check the URL against EasyList. - String[] easyListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList); - - // Process the EasyList results. - if (easyListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched EasyList's blacklist. - // Add the result to the resource requests. - nestedScrollWebView.addResourceRequest(new String[] {easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]}); - - // Increment the blocked requests counters. - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASYLIST); - - // Update the titles of the blocklist menu items if the WebView is currently displayed. - if (webViewDisplayed) { - // Updating the UI must be run from the UI thread. - activity.runOnUiThread(() -> { - // Update the menu item titles. - navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - - // Update the options menu if it has been populated. - if (optionsMenu != null) { - optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - optionsMenu.findItem(R.id.easylist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist)); - } - }); - } - - // The resource request was blocked. Return an empty web resource response. - return emptyWebResourceResponse; - } else if (easyListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched EasyList's whitelist. - // Update the whitelist result string array tracker. - whitelistResultStringArray = new String[] {easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]}; - } - } - - // Check EasyPrivacy if it is enabled. - if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)) { - // Check the URL against EasyPrivacy. - String[] easyPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyPrivacy); - - // Process the EasyPrivacy results. - if (easyPrivacyResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched EasyPrivacy's blacklist. - // Add the result to the resource requests. - nestedScrollWebView.addResourceRequest(new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4], - easyPrivacyResults[5]}); - - // Increment the blocked requests counters. - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASYPRIVACY); - - // Update the titles of the blocklist menu items if the WebView is currently displayed. - if (webViewDisplayed) { - // Updating the UI must be run from the UI thread. - activity.runOnUiThread(() -> { - // Update the menu item titles. - navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - - // Update the options menu if it has been populated. - if (optionsMenu != null) { - optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - optionsMenu.findItem(R.id.easyprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy)); - } - }); - } - - // The resource request was blocked. Return an empty web resource response. - return emptyWebResourceResponse; - } else if (easyPrivacyResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched EasyPrivacy's whitelist. - // Update the whitelist result string array tracker. - whitelistResultStringArray = new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4], easyPrivacyResults[5]}; - } - } - - // Check Fanboy’s Annoyance List if it is enabled. - if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)) { - // Check the URL against Fanboy's Annoyance List. - String[] fanboysAnnoyanceListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList); - - // Process the Fanboy's Annoyance List results. - if (fanboysAnnoyanceListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched Fanboy's Annoyance List's blacklist. - // Add the result to the resource requests. - nestedScrollWebView.addResourceRequest(new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3], - fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]}); - - // Increment the blocked requests counters. - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST); - - // Update the titles of the blocklist menu items if the WebView is currently displayed. - if (webViewDisplayed) { - // Updating the UI must be run from the UI thread. - activity.runOnUiThread(() -> { - // Update the menu item titles. - navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - - // Update the options menu if it has been populated. - if (optionsMenu != null) { - optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - optionsMenu.findItem(R.id.fanboys_annoyance_list).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + - getString(R.string.fanboys_annoyance_list)); - } - }); - } - - // The resource request was blocked. Return an empty web resource response. - return emptyWebResourceResponse; - } else if (fanboysAnnoyanceListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)){ // The resource request matched Fanboy's Annoyance List's whitelist. - // Update the whitelist result string array tracker. - whitelistResultStringArray = new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3], - fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]}; - } - } else if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)) { // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled. - // Check the URL against Fanboy's Annoyance List. - String[] fanboysSocialListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysSocialList); - - // Process the Fanboy's Social Blocking List results. - if (fanboysSocialListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched Fanboy's Social Blocking List's blacklist. - // Add the result to the resource requests. - nestedScrollWebView.addResourceRequest(new String[] {fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3], - fanboysSocialListResults[4], fanboysSocialListResults[5]}); - - // Increment the blocked requests counters. - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST); - - // Update the titles of the blocklist menu items if the WebView is currently displayed. - if (webViewDisplayed) { - // Updating the UI must be run from the UI thread. - activity.runOnUiThread(() -> { - // Update the menu item titles. - navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - - // Update the options menu if it has been populated. - if (optionsMenu != null) { - optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - optionsMenu.findItem(R.id.fanboys_social_blocking_list).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + - getString(R.string.fanboys_social_blocking_list)); - } - }); - } - - // The resource request was blocked. Return an empty web resource response. - return emptyWebResourceResponse; - } else if (fanboysSocialListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched Fanboy's Social Blocking List's whitelist. - // Update the whitelist result string array tracker. - whitelistResultStringArray = new String[] {fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3], - fanboysSocialListResults[4], fanboysSocialListResults[5]}; - } - } - - // Add the request to the log because it hasn't been processed by any of the previous checks. - if (whitelistResultStringArray != null) { // The request was processed by a whitelist. - nestedScrollWebView.addResourceRequest(whitelistResultStringArray); - } else { // The request didn't match any blocklist entry. Log it as a default request. - nestedScrollWebView.addResourceRequest(new String[]{BlocklistHelper.REQUEST_DEFAULT, url}); - } - - // The resource request has not been blocked. `return null` loads the requested resource. - return null; - } - - // Handle HTTP authentication requests. - @Override - public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { - // Store the handler. - nestedScrollWebView.setHttpAuthHandler(handler); - - // Instantiate an HTTP authentication dialog. - DialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm, nestedScrollWebView.getWebViewFragmentId()); - - // Show the HTTP authentication dialog. - httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication)); - } - - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - // Get the preferences. - boolean scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true); - - // 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. - swipeRefreshLayout.setPadding(0, 0, 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, 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); - - // 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); - } - - // Reset the list of resource requests. - nestedScrollWebView.clearResourceRequests(); - - // Reset the requests counters. - nestedScrollWebView.resetRequestsCounters(); - - // Hide the keyboard. - inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0); - - // Get the current page position. - int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); - - // Update the URL text bar if the page is currently selected. - if (tabLayout.getSelectedTabPosition() == currentPagePosition) { - // Clear the focus from the URL edit text. - urlEditText.clearFocus(); - - // Display the formatted URL text. - urlEditText.setText(url); - - // Apply text highlighting to `urlTextBox`. - highlightUrlText(); - } - - // Reset the list of host IP addresses. - nestedScrollWebView.clearCurrentIpAddresses(); - - // Get a URI for the current URL. - Uri currentUri = Uri.parse(url); - - // Get the IP addresses for the host. - new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost()); - - // Replace Refresh with Stop if the options menu has been created. (The first WebView typically begins loading before the menu items are instantiated.) - if (optionsMenu != null) { - // Get a handle for the refresh menu item. - MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh); - - // Set the title. - refreshMenuItem.setTitle(R.string.stop); - - // Get the app bar and theme preferences. - boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", 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) { - refreshMenuItem.setIcon(R.drawable.close_day); - } else { - refreshMenuItem.setIcon(R.drawable.close_night); - } - } - } - } - - @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.getAcceptFirstPartyCookies() && Build.VERSION.SDK_INT >= 21) { - CookieManager.getInstance().flush(); - } - - // Update the Refresh menu item if the options menu has been created. - if (optionsMenu != null) { - // Get a handle for the refresh menu item. - MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh); - - // Reset the Refresh title. - refreshMenuItem.setTitle(R.string.refresh); - - // Get the app bar and theme preferences. - boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", 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) { - refreshMenuItem.setIcon(R.drawable.refresh_enabled_day); - } else { - refreshMenuItem.setIcon(R.drawable.refresh_enabled_night); - } - } - } - - // Clear the cache and history if Incognito Mode is enabled. - if (incognitoModeEnabled) { - // Clear the cache. `true` includes disk files. - nestedScrollWebView.clearCache(true); - - // Clear the back/forward history. - nestedScrollWebView.clearHistory(); - - // 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 e) { - // Do nothing if an error is thrown. - } - } - - // Get the current page position. - int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); - - // Check the current website information against any pinned domain information if the current IP addresses have been loaded. - if ((nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses()) && nestedScrollWebView.hasCurrentIpAddresses() && - !nestedScrollWebView.ignorePinnedDomainInformation()) { - CheckPinnedMismatchHelper.checkPinnedMismatch(getSupportFragmentManager(), nestedScrollWebView); - } - - // Get the current URL from the nested scroll WebView. This is more accurate than using the URL passed into the method, which is sometimes not the final one. - String currentUrl = nestedScrollWebView.getUrl(); - - // Get the current tab. - TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition); - - // Update the URL text bar if the page is currently selected and the user is not currently typing in the URL edit text. - // Crash records show that, in some crazy way, it is possible for the current URL to be blank at this point. - // Probably some sort of race condition when Privacy Browser is being resumed. - if ((tabLayout.getSelectedTabPosition() == currentPagePosition) && !urlEditText.hasFocus() && (currentUrl != null)) { - // Check to see if the URL is `about:blank`. - if (currentUrl.equals("about:blank")) { // The WebView is blank. - // Display the hint in the URL edit text. - urlEditText.setText(""); - - // Request focus for the URL text box. - urlEditText.requestFocus(); - - // Display the keyboard. - inputMethodManager.showSoftInput(urlEditText, 0); - - // Apply the domain settings. This clears any settings from the previous domain. - applyDomainSettings(nestedScrollWebView, "", true, false); - - // Only populate the title text view if the tab has been fully created. - if (tab != null) { - // Get the custom view from the tab. - View tabView = tab.getCustomView(); - - // Remove the incorrect warning below that the current tab view might be null. - assert tabView != null; - - // Get the title text view from the tab. - TextView tabTitleTextView = tabView.findViewById(R.id.title_textview); - - // Set the title as the tab text. - tabTitleTextView.setText(R.string.new_tab); - } - } else { // The WebView has loaded a webpage. - // Update the URL edit text if it is not currently being edited. - if (!urlEditText.hasFocus()) { - // Sanitize the current URL. This removes unwanted URL elements that were added by redirects, so that they won't be included if the URL is shared. - String sanitizedUrl = sanitizeUrl(currentUrl); - - // Display the final URL. Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly. - urlEditText.setText(sanitizedUrl); - - // Apply text highlighting to the URL. - highlightUrlText(); - } - - // Only populate the title text view if the tab has been fully created. - if (tab != null) { - // Get the custom view from the tab. - View tabView = tab.getCustomView(); - - // Remove the incorrect warning below that the current tab view might be null. - assert tabView != null; - - // Get the title text view from the tab. - TextView tabTitleTextView = tabView.findViewById(R.id.title_textview); - - // Set the title as the tab text. Sometimes `onReceivedTitle()` is not called, especially when navigating history. - tabTitleTextView.setText(nestedScrollWebView.getTitle()); - } - } - } - } - - // Handle SSL Certificate errors. - @Override - public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { - // Get the current website SSL certificate. - SslCertificate currentWebsiteSslCertificate = error.getCertificate(); - - // Extract the individual pieces of information from the current website SSL certificate. - String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName(); - String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName(); - String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName(); - String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName(); - String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName(); - String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName(); - Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate(); - Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate(); - - // 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(); - - // Extract the arrays from the array list. - String[] pinnedSslCertificateStringArray = (String[]) pinnedSslCertificateArrayList.get(0); - Date[] pinnedSslCertificateDateArray = (Date[]) pinnedSslCertificateArrayList.get(1); - - // Check if the current SSL certificate matches the pinned certificate. - if (currentWebsiteIssuedToCName.equals(pinnedSslCertificateStringArray[0]) && currentWebsiteIssuedToOName.equals(pinnedSslCertificateStringArray[1]) && - currentWebsiteIssuedToUName.equals(pinnedSslCertificateStringArray[2]) && currentWebsiteIssuedByCName.equals(pinnedSslCertificateStringArray[3]) && - currentWebsiteIssuedByOName.equals(pinnedSslCertificateStringArray[4]) && currentWebsiteIssuedByUName.equals(pinnedSslCertificateStringArray[5]) && - currentWebsiteSslStartDate.equals(pinnedSslCertificateDateArray[0]) && currentWebsiteSslEndDate.equals(pinnedSslCertificateDateArray[1])) { - - // An SSL certificate is pinned and matches the current domain certificate. Proceed to the website without displaying an error. - handler.proceed(); - } - } else { // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate. - // Store the SSL error handler. - nestedScrollWebView.setSslErrorHandler(handler); - - // Instantiate an SSL certificate error alert dialog. - DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error, nestedScrollWebView.getWebViewFragmentId()); - - // Show the SSL certificate error dialog. - sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error)); - } - } - }); - - // Check to see if the state is being restored. - if (restoringState) { // The state is being restored. - // Resume the nested scroll WebView JavaScript timers. - nestedScrollWebView.resumeTimers(); - } else if (pageNumber == 0) { // The first page is being loaded. - // Set this nested scroll WebView as the current WebView. - currentWebView = nestedScrollWebView; - - // Initialize the URL to load string. - String urlToLoadString; - - // Get the intent that started the app. - Intent launchingIntent = getIntent(); - - // Get the information from the intent. - String launchingIntentAction = launchingIntent.getAction(); - Uri launchingIntentUriData = launchingIntent.getData(); - - // Parse the launching intent URL. - if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) { // The intent contains a search string. - // Create an encoded URL string. - String encodedUrlString; - - // Sanitize the search input and convert it to a search. - try { - encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8"); - } catch (UnsupportedEncodingException exception) { - encodedUrlString = ""; - } - - // Store the web search as the URL to load. - urlToLoadString = searchURL + encodedUrlString; - } else if (launchingIntentUriData != null){ // The intent contains a URL. - // Store the URL. - urlToLoadString = launchingIntentUriData.toString(); - } else if (!url.equals("")) { // The activity has been restarted. - // Load the saved URL. - urlToLoadString = url; - } else { // The is no URL in the intent. - // Store the homepage to be loaded. - urlToLoadString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)); - } - - // Load the website if not waiting for the proxy. - if (waitingForProxy) { // Store the URL to be loaded in the Nested Scroll WebView. - nestedScrollWebView.setWaitingForProxyUrlString(urlToLoadString); - } else { // Load the URL. - loadUrl(nestedScrollWebView, urlToLoadString); - } - } else { // This is not the first tab. - // Load the URL. - loadUrl(nestedScrollWebView, url); - - // Set the focus and display the keyboard if the URL is blank. - if (url.equals("")) { - // Request focus for the URL text box. - urlEditText.requestFocus(); - - // Create a display keyboard handler. - Handler displayKeyboardHandler = new Handler(); - - // Create a display keyboard runnable. - Runnable displayKeyboardRunnable = () -> { - // Display the keyboard. - inputMethodManager.showSoftInput(urlEditText, 0); - }; - - // Display the keyboard after 100 milliseconds, which leaves enough time for the tab to transition. - displayKeyboardHandler.postDelayed(displayKeyboardRunnable, 100); - } - } - } -} \ No newline at end of file