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=4193364de5c0041abc3b31948a7272f19b0ef80c;hp=e0a310c8f63dd734eb228e095d949be66a1b03e3;hb=fd2c21ea8241dce9c86a3213b9b9e325d5e7ce4a;hpb=729652a6a06a8c1bf6244c56089a9c0db84e283e diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index e0a310c8..4193364d 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -71,7 +71,6 @@ 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; @@ -124,7 +123,6 @@ import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog; import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog; import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog; import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog; -import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog; import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog; import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog; import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog; @@ -154,59 +152,27 @@ import java.util.List; import java.util.Map; import java.util.Set; -// TODO. The swipe refresh indicator needs to be enabled/disabled when switching tabs. +// TODO. New tabs are white in dark mode. +// TODO. Find on page. // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21. public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener, - EditBookmarkFolderDialog.EditBookmarkFolderListener, HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, WebViewTabFragment.NewTabListener, - PinnedMismatchDialog.PinnedMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener { - - // `darkTheme` is public static so it can be accessed from everywhere. - public static boolean darkTheme; - - // `allowScreenshots` is public static so it can be accessed from everywhere. It is also used in `onCreate()`. - public static boolean allowScreenshots; - - // `favoriteIconBitmap` is public static so it can be accessed from `BookmarksActivity`, `BookmarksDatabaseViewActivity`, `CreateBookmarkFolderDialog`, - // `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `EditBookmarkDatabaseViewDialog`, and `ViewSslCertificateDialog`. It is also used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, - // `onCreateHomeScreenShortcut()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`. - public static Bitmap favoriteIconBitmap; - - // `favoriteIconDefaultBitmap` public static so it can be accessed from `PinnedMismatchDialog`. It is also used in `onCreate()` and `applyDomainSettings`. - public static Bitmap favoriteIconDefaultBitmap; - - // TODO Remove. - // `formattedUrlString` is public static so it can be accessed from `AddDomainDialog`, `BookmarksActivity`, `DomainSettingsFragment`, and `PinnedMismatchDialog`. - // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`. - public static String formattedUrlString; + EditBookmarkFolderDialog.EditBookmarkFolderListener, NavigationView.OnNavigationItemSelectedListener, WebViewTabFragment.NewTabListener { // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`. public static String orbotStatus; - // The WebView pager adapter is accessed from `PinnedMismatchDialog`. It is also used in `onCreate()`, `onResume()`, and `addTab()`. + // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`. It is also used in `onCreate()`, `onResume()`, and `addTab()`. public static WebViewPagerAdapter webViewPagerAdapter; - // `reloadOnRestart` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()` - public static boolean reloadOnRestart; - - // `reloadUrlOnRestart` is public static so it can be accessed from `SettingsFragment` and `BookmarksActivity`. It is also used in `onRestart()`. + // 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; - // The blocklist versions are public static so they can be accessed from `AboutTabFragment`. They are also used in `onCreate()`. - public static String easyListVersion; - public static String easyPrivacyVersion; - public static String fanboysAnnoyanceVersion; - public static String fanboysSocialVersion; - public static String ultraPrivacyVersion; - - // `blockAllThirdPartyRequests` is public static so it can be accessed from `RequestsActivity`. - // It is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()` - public static boolean blockAllThirdPartyRequests; - // `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; @@ -221,100 +187,42 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook - // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`. - private boolean navigatingHistory; - // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxyThroughOrbot()`, and `applyDomainSettings()`. private NestedScrollWebView currentWebView; - // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`. - private FrameLayout fullScreenVideoFrameLayout; - - // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, `loadUrlFromTextBox()`, `onDownloadImage()`, `onDownloadFile()`, and `onRestart()`. - private CookieManager cookieManager; - // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`. private final Map customHeaders = new HashMap<>(); - // `firstPartyCookiesEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyDomainSettings()`. - private boolean firstPartyCookiesEnabled; - - // `thirdPartyCookiesEnabled` used in `onCreate()`, `onPrepareOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. - private boolean thirdPartyCookiesEnabled; - - // `domStorageEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. - private boolean domStorageEnabled; - - // `saveFormDataEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. It can be removed once the minimum API >= 26. - private boolean saveFormDataEnabled; - - // TODO Move to NestedScrollWebView. - // `nightMode` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. - private boolean nightMode; - - // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applyProxyThroughOrbot()`. - private String homepage; - - // `searchURL` is used in `loadURLFromTextBox()` and `applyProxyThroughOrbot()`. + // The search URL is set in `applyProxyThroughOrbot()` and used in `onCreate()`, `onNewIntent()`, `loadURLFromTextBox()`, and `initializeWebView()`. private String searchURL; - // The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()` and `updatePrivacyIcons()`. + // The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()`, `updatePrivacyIcons()`, and `initializeWebView()`. private Menu optionsMenu; - // The refresh menu item is set in `onCreateOptionsMenu()` and accessed from `initializeWebView()`. - // It must be this way because `initializeWebView()` runs before the menu is created but doesn't actually modify the menu until later. - private MenuItem refreshMenuItem; - - // The navigation requests menu item is used in `onCreate()` and accessed from `WebViewPagerAdapter`. - private MenuItem navigationRequestsMenuItem; // TODO. - - // TODO. This could probably be removed. - // The blocklist helper is used in `onCreate()` and `WebViewPagerAdapter`. - private BlockListHelper blockListHelper; - - // The blocklists are populated in `onCreate()` and accessed from `WebViewPagerAdapter`. + // The blocklists are populated in `onCreate()` and accessed from `initializeWebView()`. private ArrayList> easyList; private ArrayList> easyPrivacy; private ArrayList> fanboysAnnoyanceList; private ArrayList> fanboysSocialList; private ArrayList> ultraPrivacy; - // The blocklist menu items are used in `onCreate()`, `onCreateOptionsMenu()`, and `onPrepareOptionsMenu()`. - private MenuItem blocklistsMenuItem; - private MenuItem easyListMenuItem; - private MenuItem easyPrivacyMenuItem; - private MenuItem fanboysAnnoyanceListMenuItem; - private MenuItem fanboysSocialBlockingListMenuItem; - private MenuItem ultraPrivacyMenuItem; - private MenuItem blockAllThirdPartyRequestsMenuItem; - - // The blocklist variables are used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`. - private boolean easyListEnabled; - private boolean easyPrivacyEnabled; - private boolean fanboysAnnoyanceListEnabled; - private boolean fanboysSocialBlockingListEnabled; - private boolean ultraPrivacyEnabled; - // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`. private String webViewDefaultUserAgent; - // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`. - private Runtime privacyBrowserRuntime; - // `proxyThroughOrbot` is used in `onRestart()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`. private boolean proxyThroughOrbot; - // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`. + // The incognito mode is set in `applyAppSettings()` and used in `initializeWebView()`. private boolean incognitoModeEnabled; - // `fullScreenBrowsingModeEnabled` is used in `onCreate()` and `applyAppSettings()`. + // 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; - // Hide app bar is used in `onCreate()` and `applyAppSettings()`. + // The hide app bar tracker is used in `applyAppSettings()` and `initializeWebView()`. private boolean hideAppBar; // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, . @@ -326,30 +234,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`. private boolean displayingFullScreenVideo; - // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`. - private boolean downloadWithExternalApp; - // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`. private BroadcastReceiver orbotStatusBroadcastReceiver; // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`. private boolean waitingForOrbot; - // `domainSettingsJavaScriptEnabled` is used in `onOptionsItemSelected()` and `applyDomainSettings()`. - private Boolean domainSettingsJavaScriptEnabled; - - // `waitingForOrbotHtmlString` is used in `onCreate()` and `applyProxyThroughOrbot()`. - private String waitingForOrbotHtmlString; - - // `privateDataDirectoryString` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`. - private String privateDataDirectoryString; - - // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`. - private EditText findOnPageEditText; - - // `displayAdditionalAppBarIcons` is used in `onCreate()` and `onCreateOptionsMenu()`. - private boolean displayAdditionalAppBarIcons; - // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`. private ActionBarDrawerToggle actionBarDrawerToggle; @@ -363,25 +253,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private int drawerHeaderPaddingTop; private int drawerHeaderPaddingBottom; - // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`. - private SslErrorHandler sslErrorHandler; - - // `httpAuthHandler` is used in `onCreate()`, `onHttpAuthenticationCancel()`, and `onHttpAuthenticationProceed()`. - private static HttpAuthHandler httpAuthHandler; - - // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`. - private InputMethodManager inputMethodManager; - // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, // and `loadBookmarksFolder()`. private BookmarksDatabaseHelper bookmarksDatabaseHelper; - // `bookmarksListView` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, and `loadBookmarksFolder()`. - private ListView bookmarksListView; - - // `bookmarksTitleTextView` is used in `onCreate()` and `loadBookmarksFolder()`. - private TextView bookmarksTitleTextView; - // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`. private Cursor bookmarksCursor; @@ -402,10 +277,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`. private String downloadImageUrl; - // The user agent variables are used in `onCreate()` and `applyDomainSettings()`. - private ArrayAdapter userAgentNamesArray; - private String[] userAgentDataArray; - // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, `onRequestPermissionResult()`, and `initializeWebView()`. private final int DOWNLOAD_FILE_REQUEST_CODE = 1; private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2; @@ -418,8 +289,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Get the theme and screenshot preferences. - darkTheme = sharedPreferences.getBoolean("dark_theme", false); - allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false); + boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false); + boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false); // Disable screenshots if not allowed. if (!allowScreenshots) { @@ -439,12 +310,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the content view. setContentView(R.layout.main_framelayout); - // Get handles for the input method manager and toolbar. - inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + // 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; + + // Get a handle for the toolbar. Toolbar toolbar = findViewById(R.id.toolbar); // Set the action bar. `SupportActionBar` must be used until the minimum API is >= 21. setSupportActionBar(toolbar); + + // Get a handle for the action bar. ActionBar actionBar = getSupportActionBar(); // This is needed to get rid of the Android Studio warning that the action bar might be null. @@ -459,7 +337,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500)); finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500)); - // Get a handle for `urlTextBox`. + // Get handles for the URL views. EditText urlEditText = findViewById(R.id.url_edittext); // Remove the formatting from `urlTextBar` when the user is editing the text. @@ -493,9 +371,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }); - // Set `waitingForOrbotHTMLString`. - waitingForOrbotHtmlString = "

" + getString(R.string.waiting_for_orbot) + "

"; - // Initialize the Orbot status and the waiting for Orbot trackers. orbotStatus = "unknown"; waitingForOrbot = false; @@ -509,11 +384,43 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected. if (orbotStatus.equals("ON") && waitingForOrbot) { - // Reset `waitingForOrbot`. + // Reset the waiting for Orbot status. waitingForOrbot = false; - // Load `formattedUrlString - loadUrl(formattedUrlString); + // Get the intent that started the app. + Intent launchingIntent = getIntent(); + + // Get the information from the intent. + String launchingIntentAction = launchingIntent.getAction(); + Uri launchingIntentUriData = launchingIntent.getData(); + + // If the intent action is a web search, perform the search. + if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) { + // 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 = ""; + } + + // Load the completed search URL. + loadUrl(searchURL + encodedUrlString); + } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL. + // Load the URL from the intent. + loadUrl(launchingIntentUriData.toString()); + } else { // The is no URL in the intent. + // Select the homepage based on the proxy through Orbot status. + if (proxyThroughOrbot) { + // Load the Tor homepage. + loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value))); + } else { + // Load the normal homepage. + loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value))); + } + } } } }; @@ -521,8 +428,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Register `orbotStatusBroadcastReceiver` on `this` context. this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS")); - // Instantiate the block list helper. - blockListHelper = new BlockListHelper(); + // Instantiate the blocklist helper. + BlockListHelper blockListHelper = new BlockListHelper(); // Parse the block lists. easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt"); @@ -531,37 +438,28 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt"); ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt"); - // Store the list versions. - easyListVersion = easyList.get(0).get(0)[0]; - easyPrivacyVersion = easyPrivacy.get(0).get(0)[0]; - fanboysAnnoyanceVersion = fanboysAnnoyanceList.get(0).get(0)[0]; - fanboysSocialVersion = fanboysSocialList.get(0).get(0)[0]; - ultraPrivacyVersion = ultraPrivacy.get(0).get(0)[0]; - // Get handles for views that need to be modified. DrawerLayout drawerLayout = findViewById(R.id.drawerlayout); - final NavigationView navigationView = findViewById(R.id.navigationview); + NavigationView navigationView = findViewById(R.id.navigationview); TabLayout tabLayout = findViewById(R.id.tablayout); SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); ViewPager webViewPager = findViewById(R.id.webviewpager); - bookmarksListView = findViewById(R.id.bookmarks_drawer_listview); - bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview); + 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); - findOnPageEditText = findViewById(R.id.find_on_page_edittext); - fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout); + 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. The menu is zero-based. - final Menu navigationMenu = navigationView.getMenu(); - final MenuItem navigationCloseTabMenuItem = navigationMenu.getItem(0); - final MenuItem navigationBackMenuItem = navigationMenu.getItem(3); - final MenuItem navigationForwardMenuItem = navigationMenu.getItem(4); - final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(5); - navigationRequestsMenuItem = navigationMenu.getItem(6); + Menu navigationMenu = navigationView.getMenu(); + MenuItem navigationCloseTabMenuItem = navigationMenu.getItem(0); + MenuItem navigationBackMenuItem = navigationMenu.getItem(3); + MenuItem navigationForwardMenuItem = navigationMenu.getItem(4); + MenuItem navigationHistoryMenuItem = navigationMenu.getItem(5); + MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6); // Initialize the web view pager adapter. webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager()); @@ -581,58 +479,28 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onPageSelected(int position) { - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(position); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - // Remove the incorrect lint warning below that the fragment view might be null. - assert fragmentView != null; - - // Store the current WebView. - currentWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - - // Update the privacy icons. `true` redraws the icons in the app bar. - updatePrivacyIcons(true); - - // Store the current formatted URL string. - formattedUrlString = currentWebView.getUrl(); - - // Clear the focus from the URL text box. - urlEditText.clearFocus(); - - // Hide the soft keyboard. - inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0); + // Set the current WebView. + setCurrentWebView(position); - // Display the current URL in the URL text box. - urlEditText.setText(formattedUrlString); + // Select the corresponding tab if it does not match the currently selected page. This will happen if the page was scrolled via swiping in the view pager or by creating a new tab. + if (tabLayout.getSelectedTabPosition() != position) { + // Create a handler to select the tab. + Handler selectTabHandler = new Handler(); - // Highlight the URL text. - highlightUrlText(); + // Create a runnable select the new tab. + Runnable selectTabRunnable = () -> { + // Get a handle for the tab. + TabLayout.Tab tab = tabLayout.getTabAt(position); - // Set the background to indicate the domain settings status. - if (currentWebView.getDomainSettingsApplied()) { - // Set a green background on `urlTextBox` to indicate that custom domain settings are being used. The deprecated `.getDrawable()` must be used until the minimum API >= 21. - if (darkTheme) { - urlEditText.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue)); - } else { - urlEditText.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green)); - } - } else { - urlEditText.setBackground(getResources().getDrawable(R.color.transparent)); - } + // Assert that the tab is not null. + assert tab != null; - // Select the corresponding tab if it does not match the currently selected page. This will happen if the page was scrolled via swiping in the view pager. - if (tabLayout.getSelectedTabPosition() != position) { - // Get a handle for the corresponding tab. - TabLayout.Tab correspondingTab = tabLayout.getTabAt(position); + // Select the tab. + tab.select(); + }; - // Assert that the corresponding tab is not null. - assert correspondingTab != null; - - // Select the corresponding tab. - correspondingTab.select(); + // Select the tab layout after 100 milliseconds, which leaves enough time for a new tab to be created. + selectTabHandler.postDelayed(selectTabRunnable, 100); } } @@ -665,8 +533,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }); - // Add the first tab. (It doesn't matter what view is passed. That is just required as part of the ImageView `onClick()` syntax). - addTab(webViewPager); + // Add the first tab. + addTab(null); // Set the bookmarks drawer resources according to the theme. This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget. // The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21 and and `getResources().getColor()` must be used until the minimum API >= 23. @@ -684,15 +552,26 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the launch bookmarks activity FAB to launch the bookmarks activity. launchBookmarksActivityFab.setOnClickListener(v -> { - // Store the current WebView url and title in the bookmarks activity. - BookmarksActivity.currentWebViewUrl = currentWebView.getUrl(); - BookmarksActivity.currentWebViewTitle = currentWebView.getTitle(); + // 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); - // Include the current folder with the `Intent`. - bookmarksIntent.putExtra("Current Folder", currentBookmarksFolder); + // 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); @@ -700,15 +579,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the create new bookmark folder FAB to display an alert dialog. createBookmarkFolderFab.setOnClickListener(v -> { - // Show the create bookmark folder dialog and name the instance `@string/create_folder`. - DialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog(); + // 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(), favoriteIconBitmap); + DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteOrDefaultIcon()); // Display the create bookmark dialog. createBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.create_bookmark)); @@ -813,11 +694,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`. - DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId); + DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon()); editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder)); } else { // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`. - DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId); + DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon()); editBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.edit_bookmark)); } @@ -888,32 +769,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); - // Initialize cookieManager. - cookieManager = CookieManager.getInstance(); - // 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", ""); // 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 `Runtime`. - privacyBrowserRuntime = Runtime.getRuntime(); - - // Store the application's private data directory. - privateDataDirectoryString = getApplicationInfo().dataDir; - // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`. - - // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode. - inFullScreenBrowsingMode = false; - - // Initialize the privacy settings variables. - firstPartyCookiesEnabled = false; - thirdPartyCookiesEnabled = false; - domStorageEnabled = false; - saveFormDataEnabled = false; // Form data can be removed once the minimum API >= 26. - nightMode = false; - // 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); @@ -925,95 +786,63 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Destroy the bare WebView. bareWebView.destroy(); - - // Initialize the favorite icon bitmap. `ContextCompat` must be used until API >= 21. - Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world); - BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable; - assert favoriteIconBitmapDrawable != null; - favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap(); - - // If the favorite icon is null, load the default. - if (favoriteIconBitmap == null) { - favoriteIconBitmap = favoriteIconDefaultBitmap; - } - - // Initialize the user agent array adapter and string array. - userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item); - userAgentDataArray = getResources().getStringArray(R.array.user_agent_data); - - // Get the intent that started the app. - Intent launchingIntent = getIntent(); - - // Get the information from the intent. - String launchingIntentAction = launchingIntent.getAction(); - Uri launchingIntentUriData = launchingIntent.getData(); - - // If the intent action is a web search, perform the search. - if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) { - // 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 = ""; - } - - // Add the base search URL. - formattedUrlString = searchURL + encodedUrlString; - } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL. - // Set the formatted URL string. - formattedUrlString = launchingIntentUriData.toString(); - } } @Override protected void onNewIntent(Intent intent) { - // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity. - setIntent(intent); - // Get the information from the intent. String intentAction = intent.getAction(); Uri intentUriData = intent.getData(); - // If the intent action is a web search, perform the search. - if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) { - // Create an encoded URL string. - String encodedUrlString; + // Only process the URI if it contains data. If the user pressed the desktop icon after the app was already running the URI will be null. + if (intentUriData != null) { + // Sets the new intent as the activity intent, which replaces the one that originally started the app. + setIntent(intent); - // 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 a new tab. + addTab(null); + + // Create a URL string. + String url; + + // If the intent action is a web search, perform the search. + if ((intentAction != null) && intentAction.equals(Intent.ACTION_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 the base search URL. - formattedUrlString = searchURL + encodedUrlString; - } else if (intentUriData != null){ // Check to see if the intent contains a new URL. - // Set the formatted URL string. - formattedUrlString = intentUriData.toString(); - } + // Load the URL. + loadUrl(url); - // Load the URL. - loadUrl(formattedUrlString); + // Get a handle for the drawer layout. + DrawerLayout drawerLayout = findViewById(R.id.drawerlayout); - // Get a handle for the drawer layout. - DrawerLayout drawerLayout = findViewById(R.id.drawerlayout); + // Close the navigation drawer if it is open. + if (drawerLayout.isDrawerVisible(GravityCompat.START)) { + drawerLayout.closeDrawer(GravityCompat.START); + } - // 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); + } - // Close the bookmarks drawer if it is open. - if (drawerLayout.isDrawerVisible(GravityCompat.END)) { - drawerLayout.closeDrawer(GravityCompat.END); + // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it. + currentWebView.requestFocus(); } - - // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it. - currentWebView.requestFocus(); } @Override @@ -1035,53 +864,46 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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(); + } - // Reload the webpage if displaying of images has been disabled in the Settings activity. - if (reloadOnRestart) { - // 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(); + // Apply the domain settings if returning from the settings or domains activity. + if (reapplyDomainSettingsOnRestart) { + // Reset the reapply domain settings on restart tracker. + reapplyDomainSettingsOnRestart = false; - // 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); + // 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); - // Reload the WebView. This doesn't seem to work if for WebViews that aren't visible. - nestedScrollWebView.reload(); - } - } + // Get the fragment view. + View fragmentView = webViewTabFragment.getView(); - // Reset `reloadOnRestartBoolean`. - reloadOnRestart = false; - } + // 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 return from settings flag. - reapplyAppSettingsOnRestart = false; - } + // Reset the current domain name so the domain settings will be reapplied. + nestedScrollWebView.resetCurrentDomainName(); - // TODO apply to all the tabs. - // Apply the domain settings if returning from the Domains activity. - if (reapplyDomainSettingsOnRestart) { - // Reapply the domain settings. - applyDomainSettings(currentWebView, formattedUrlString, false, true); - - // Reset `reapplyDomainSettingsOnRestart`. - reapplyDomainSettingsOnRestart = false; + // Reapply the domain settings. + applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true); + } + } } - // Load the URL on restart to apply changes to night mode. + // Load the URL on restart (used when loading a bookmark). if (loadUrlOnRestart) { - // Load the current `formattedUrlString`. - loadUrl(formattedUrlString); + // Load the specified URL. + loadUrl(urlToLoadOnRestart); - // Reset `loadUrlOnRestart. + // Reset the load on restart tracker. loadUrlOnRestart = false; } @@ -1136,7 +958,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook currentWebView.getSettings().setUseWideViewPort(false); // Load a waiting page. `null` specifies no encoding, which defaults to ASCII. - currentWebView.loadData(waitingForOrbotHtmlString, "text/html", null); + currentWebView.loadData("

" + getString(R.string.waiting_for_orbot) + "

", "text/html", null); } if (displayingFullScreenVideo || inFullScreenBrowsingMode) { @@ -1210,7 +1032,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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`. + // 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. @@ -1222,14 +1044,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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. - refreshMenuItem = menu.findItem(R.id.refresh); - blocklistsMenuItem = menu.findItem(R.id.blocklists); - easyListMenuItem = menu.findItem(R.id.easylist); - easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy); - fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list); - fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list); - ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy); - blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests); + MenuItem refreshMenuItem = menu.findItem(R.id.refresh); MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent); // Only display third-party cookies if API >= 21 @@ -1239,14 +1054,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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 show Ad Consent if this is the free flavor. adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free")); // Get the shared preference values. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - // Get the status of the additional AppBar icons. - displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false); + // Get the dark theme and app bar preferences.. + boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false); + boolean darkTheme = sharedPreferences.getBoolean("dark_theme", 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) { @@ -1279,25 +1098,32 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public boolean onPrepareOptionsMenu(Menu menu) { - // Get a handle for the swipe refresh layout. - SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); - // Get handles for the menu items. MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain); - 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 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 ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy); + MenuItem blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests); MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size); MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh); MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images); MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode); MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot); + // 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; @@ -1317,44 +1143,51 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the current font size from the fontSize = currentWebView.getSettings().getTextZoom(); - // Set the status of the display images menu item. + // 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.EASY_LIST)); + easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY)); + fanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); + fanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)); + ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY)); + blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); + swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh()); displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically()); + nightModeMenuItem.setChecked(currentWebView.getNightMode()); // 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.EASY_LIST_BLOCKED_REQUESTS) + " - " + getString(R.string.easylist)); - easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY_BLOCKED_REQUESTS) + " - " + getString(R.string.easyprivacy)); - fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS) + " - " + getString(R.string.fanboys_annoyance_list)); - fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS) + " - " + - getString(R.string.fanboys_social_blocking_list)); - ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY_BLOCKED_REQUESTS) + " - " + getString(R.string.ultraprivacy)); - blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_BLOCKED_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests)); + easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_LIST) + " - " + getString(R.string.easylist)); + easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY) + " - " + 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)); + ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY) + " - " + 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 status of the menu item checkboxes. - toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled); - toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled); - toggleDomStorageMenuItem.setChecked(domStorageEnabled); - toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled); // Form data can be removed once the minimum API >= 26. - easyListMenuItem.setChecked(easyListEnabled); - easyPrivacyMenuItem.setChecked(easyPrivacyEnabled); - fanboysAnnoyanceListMenuItem.setChecked(fanboysAnnoyanceListEnabled); - fanboysSocialBlockingListMenuItem.setChecked(fanboysSocialBlockingListEnabled); - ultraPrivacyMenuItem.setChecked(ultraPrivacyEnabled); - blockAllThirdPartyRequestsMenuItem.setChecked(blockAllThirdPartyRequests); - swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled()); - nightModeMenuItem.setChecked(nightMode); + firstPartyCookiesMenuItem.setChecked(cookieManager.acceptCookie()); proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot); - // Enable third-party cookies if first-party cookies are enabled. - toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled); - - // Enable DOM Storage if JavaScript is enabled. - toggleDomStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled()); - // 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; @@ -1374,18 +1207,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26. if (Build.VERSION.SDK_INT < 26) { - WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this); - clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData()); - } else { - // Disable clear form data because it is not supported on current version of Android. - clearFormDataMenuItem.setEnabled(false); + // 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 if Fanboy's Annoyance List is checked. - fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled); + // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked. + fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListMenuItem.isChecked()); // 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. @@ -1482,8 +1315,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. @SuppressLint("SetJavaScriptEnabled") - // removeAllCookies is deprecated, but it is required for API < 21. - @SuppressWarnings("deprecation") public boolean onOptionsItemSelected(MenuItem menuItem) { // Reenter full screen browsing mode if it was interrupted by the options menu. if (inFullScreenBrowsingMode) { @@ -1508,6 +1339,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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: @@ -1520,7 +1354,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Display a `Snackbar`. if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScrip is enabled. Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show(); - } else if (firstPartyCookiesEnabled) { // JavaScript is disabled, but first-party cookies are enabled. + } else if (cookieManager.acceptCookie()) { // JavaScript is disabled, but first-party cookies are enabled. Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show(); } else { // Privacy mode. Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show(); @@ -1534,29 +1368,55 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook if (currentWebView.getDomainSettingsApplied()) { // Edit the current domain settings. // Reapply the domain settings on returning to `MainWebViewActivity`. reapplyDomainSettingsOnRestart = true; - currentWebView.resetCurrentDomainName(); - - // TODO. Move these to `putExtra`. The certificate can be stored as strings. - // Store the current SSL certificate and IP addresses in the domains activity. - DomainsActivity.currentSslCertificate = currentWebView.getCertificate(); - DomainsActivity.currentIpAddresses = currentWebView.getCurrentIpAddresses(); // Create an intent to launch the domains activity. Intent domainsIntent = new Intent(this, DomainsActivity.class); - // Put extra information instructing the domains activity to directly load the current domain and close on back instead of returning to the domains list. + // 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; - currentWebView.resetCurrentDomainName(); // Get the current domain - Uri currentUri = Uri.parse(formattedUrlString); + 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`. @@ -1565,17 +1425,45 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Create the domain and store the database ID. int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain); - // TODO. Move these to `putExtra`. The certificate can be stored as strings. - // Store the current SSL certificate and IP addresses in the domains activity. - DomainsActivity.currentSslCertificate = currentWebView.getCertificate(); - DomainsActivity.currentIpAddresses = currentWebView.getCurrentIpAddresses(); - // Create an intent to launch the domains activity. Intent domainsIntent = new Intent(this, DomainsActivity.class); - // Put extra information instructing the domains activity to directly load the new domain and close on back instead of returning to the domains list. + // 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); @@ -1583,20 +1471,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; case R.id.toggle_first_party_cookies: - // Switch the status of firstPartyCookiesEnabled. - firstPartyCookiesEnabled = !firstPartyCookiesEnabled; + // Switch the first-party cookie status. + cookieManager.setAcceptCookie(!cookieManager.acceptCookie()); - // Update the menu checkbox. - menuItem.setChecked(firstPartyCookiesEnabled); + // Store the first-party cookie status. + currentWebView.setAcceptFirstPartyCookies(cookieManager.acceptCookie()); - // Apply the new cookie status. - cookieManager.setAcceptCookie(firstPartyCookiesEnabled); + // 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 (firstPartyCookiesEnabled) { // First-party cookies are enabled. + // Display a snackbar. + if (cookieManager.acceptCookie()) { // First-party cookies are enabled. Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show(); } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is still enabled. Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show(); @@ -1611,16 +1499,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case R.id.toggle_third_party_cookies: if (Build.VERSION.SDK_INT >= 21) { // Switch the status of thirdPartyCookiesEnabled. - thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled; + cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView)); // Update the menu checkbox. - menuItem.setChecked(thirdPartyCookiesEnabled); - - // Apply the new cookie status. - cookieManager.setAcceptThirdPartyCookies(currentWebView, thirdPartyCookiesEnabled); + menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView)); - // Display a `Snackbar`. - if (thirdPartyCookiesEnabled) { + // Display a snackbar. + if (cookieManager.acceptThirdPartyCookies(currentWebView)) { Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show(); } else { Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show(); @@ -1632,20 +1517,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; case R.id.toggle_dom_storage: - // Switch the status of domStorageEnabled. - domStorageEnabled = !domStorageEnabled; + // Toggle the status of domStorageEnabled. + currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled()); // Update the menu checkbox. - menuItem.setChecked(domStorageEnabled); + menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled()); - // Apply the new DOM Storage status. - currentWebView.getSettings().setDomStorageEnabled(domStorageEnabled); - - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. + // Update the privacy icon. `true` refreshes the app bar icons. updatePrivacyIcons(true); - // Display a `Snackbar`. - if (domStorageEnabled) { + // Display a snackbar. + if (currentWebView.getSettings().getDomStorageEnabled()) { Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show(); } else { Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show(); @@ -1658,16 +1540,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Form data can be removed once the minimum API >= 26. case R.id.toggle_save_form_data: // Switch the status of saveFormDataEnabled. - saveFormDataEnabled = !saveFormDataEnabled; + currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData()); // Update the menu checkbox. - menuItem.setChecked(saveFormDataEnabled); - - // Apply the new form data status. - currentWebView.getSettings().setSaveFormData(saveFormDataEnabled); + menuItem.setChecked(currentWebView.getSettings().getSaveFormData()); - // Display a `Snackbar`. - if (saveFormDataEnabled) { + // Display a snackbar. + if (currentWebView.getSettings().getSaveFormData()) { Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show(); } else { Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show(); @@ -1689,20 +1568,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @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) { - switch (event) { - // The user pushed the undo button. - case Snackbar.Callback.DISMISS_EVENT_ACTION: - // Do nothing. - break; - - // The snackbar was dismissed without the undo button being pushed. - default: - // `cookieManager.removeAllCookie()` varies by SDK. - if (Build.VERSION.SDK_INT < 21) { - cookieManager.removeAllCookie(); - } else { - cookieManager.removeAllCookies(null); - } + 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); + } } } }) @@ -1718,46 +1590,46 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @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) { - switch (event) { - // The user pushed the undo button. - case Snackbar.Callback.DISMISS_EVENT_ACTION: - // Do nothing. - break; - - // The snackbar was dismissed without the undo button being pushed. - default: - // 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 { - // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly. - Process deleteLocalStorageProcess = privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"}); - - // Multiple commands must be used because `Runtime.exec()` does not like `*`. - Process deleteIndexProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB"); - Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager"); - Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal"); - Process deleteDatabasesProcess = privacyBrowserRuntime.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); + 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); } } }) @@ -1774,17 +1646,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @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) { - switch (event) { - // The user pushed the undo button. - case Snackbar.Callback.DISMISS_EVENT_ACTION: - // Do nothing. - break; - - // The snackbar was dismissed without the `Undo` button being pushed. - default: - // Delete the form data. - WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext()); - mainWebViewDatabase.clearFormData(); + 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(); } } }) @@ -1793,10 +1658,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case R.id.easylist: // Toggle the EasyList status. - easyListEnabled = !easyListEnabled; + currentWebView.enableBlocklist(NestedScrollWebView.EASY_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST)); // Update the menu checkbox. - menuItem.setChecked(easyListEnabled); + menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST)); // Reload the current WebView. currentWebView.reload(); @@ -1804,10 +1669,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case R.id.easyprivacy: // Toggle the EasyPrivacy status. - easyPrivacyEnabled = !easyPrivacyEnabled; + currentWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY)); // Update the menu checkbox. - menuItem.setChecked(easyPrivacyEnabled); + menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY)); // Reload the current WebView. currentWebView.reload(); @@ -1815,14 +1680,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case R.id.fanboys_annoyance_list: // Toggle Fanboy's Annoyance List status. - fanboysAnnoyanceListEnabled = !fanboysAnnoyanceListEnabled; + currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); // Update the menu checkbox. - menuItem.setChecked(fanboysAnnoyanceListEnabled); + 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(!fanboysAnnoyanceListEnabled); + fanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)); // Reload the current WebView. currentWebView.reload(); @@ -1830,10 +1695,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case R.id.fanboys_social_blocking_list: // Toggle Fanboy's Social Blocking List status. - fanboysSocialBlockingListEnabled = !fanboysSocialBlockingListEnabled; + currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)); // Update the menu checkbox. - menuItem.setChecked(fanboysSocialBlockingListEnabled); + menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)); // Reload the current WebView. currentWebView.reload(); @@ -1841,10 +1706,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case R.id.ultraprivacy: // Toggle the UltraPrivacy status. - ultraPrivacyEnabled = !ultraPrivacyEnabled; + currentWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY)); // Update the menu checkbox. - menuItem.setChecked(ultraPrivacyEnabled); + menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY)); // Reload the current WebView. currentWebView.reload(); @@ -1852,10 +1717,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case R.id.block_all_third_party_requests: //Toggle the third-party requests blocker status. - blockAllThirdPartyRequests = !blockAllThirdPartyRequests; + currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); // Update the menu checkbox. - menuItem.setChecked(blockAllThirdPartyRequests); + menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)); // Reload the current WebView. currentWebView.reload(); @@ -1998,11 +1863,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); - // Toggle swipe to refresh. - swipeRefreshLayout.setEnabled(!swipeRefreshLayout.isEnabled()); + // Update the swipe refresh layout. + if (currentWebView.getSwipeToRefresh()) { // Swipe to refresh is enabled. + if (Build.VERSION.SDK_INT >= 23) { // For API >= 23, the status of the scroll refresh listener is continuously updated by the on scroll change listener. + // Only enable the swipe refresh layout if the WebView is scrolled to the top. + swipeRefreshLayout.setEnabled(currentWebView.getY() == 0); + } else { // For API < 23, the swipe refresh layout is always enabled. + // Enable the swipe refresh layout. + swipeRefreshLayout.setEnabled(true); + } + } else { // Swipe to refresh is disabled. + // Disable the swipe refresh layout. + swipeRefreshLayout.setEnabled(false); + } return true; case R.id.display_images: @@ -2020,15 +1899,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case R.id.night_mode: // Toggle night mode. - nightMode = !nightMode; + currentWebView.setNightMode(!currentWebView.getNightMode()); // Enable or disable JavaScript according to night mode, the global preference, and any domain settings. - if (nightMode) { // Night mode is enabled, which requires JavaScript. + if (currentWebView.getNightMode()) { // Night mode is enabled, which requires JavaScript. // Enable JavaScript. currentWebView.getSettings().setJavaScriptEnabled(true); } else if (currentWebView.getDomainSettingsApplied()) { // Night mode is disabled and domain settings are applied. Set JavaScript according to the domain settings. // Apply the JavaScript preference that was stored the last time domain settings were loaded. - currentWebView.getSettings().setJavaScriptEnabled(domainSettingsJavaScriptEnabled); + currentWebView.getSettings().setJavaScriptEnabled(currentWebView.getDomainSettingsJavaScriptEnabled()); } else { // Night mode is disabled and domain settings are not applied. Set JavaScript according to the global preference. // Apply the JavaScript preference. currentWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false)); @@ -2045,6 +1924,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); // Hide the toolbar. toolbar.setVisibility(View.GONE); @@ -2058,6 +1938,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); @@ -2067,8 +1953,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Create an intent to launch the view source activity. Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class); - // Add the user agent as an extra to the intent. + // Add the variables to the intent. viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString()); + viewSourceIntent.putExtra("current_url", currentWebView.getUrl()); // Make it so. startActivity(viewSourceIntent); @@ -2076,7 +1963,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case R.id.share_url: // Setup the share string. - String shareString = currentWebView.getTitle() + " – " + formattedUrlString; + String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl(); // Create the share intent. Intent shareIntent = new Intent(Intent.ACTION_SEND); @@ -2088,30 +1975,31 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; case R.id.print: - // Get a `PrintManager` instance. + // Get a print manager instance. PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE); - // Create a print document adapter form the current WebView. - PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter(); - - // Remove the lint error below that `printManager` might be `null`. + // Remove the lint error below that print manager might be null. assert printManager != null; - // Print the document. The print attributes are `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); return true; case R.id.open_with_app: - openWithApp(formattedUrlString); + openWithApp(currentWebView.getUrl()); return true; case R.id.open_with_browser: - openWithBrowser(formattedUrlString); + openWithBrowser(currentWebView.getUrl()); return true; case R.id.add_to_homescreen: // Instantiate the create home screen shortcut dialog. - DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), formattedUrlString, favoriteIconBitmap); + 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)); @@ -2148,17 +2036,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // removeAllCookies is deprecated, but it is required for API < 21. - @SuppressWarnings("deprecation") @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.close_tab: - // Get a handle for the tab layout. + // Get a handle for the tab layout and the view pager. TabLayout tabLayout = findViewById(R.id.tablayout); + ViewPager webViewPager = findViewById(R.id.webviewpager); // Get the current tab number. int currentTabNumber = tabLayout.getSelectedTabPosition(); @@ -2166,8 +2057,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Delete the current tab. tabLayout.removeTabAt(currentTabNumber); - // Delete the current page. - webViewPagerAdapter.deletePage(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); + } break; case R.id.clear_and_exit: @@ -2175,26 +2068,30 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook bookmarksCursor.close(); bookmarksDatabaseHelper.close(); - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - // 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.removeAllCookies(null); + CookieManager.getInstance().removeAllCookies(null); } else { - cookieManager.removeAllCookie(); + 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 = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies"); - Process deleteCookiesJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal"); + 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(); @@ -2213,13 +2110,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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 = privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"}); + 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 = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB"); - Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager"); - Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal"); - Process deleteDatabaseProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases"); + 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(); @@ -2240,8 +2137,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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 = privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"}); - Process deleteWebDataJournalProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"}); + 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(); @@ -2274,11 +2171,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Manually delete the cache directories. try { // Delete the main cache directory. - Process deleteCacheProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache"); + 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 = privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"}); + Process deleteServiceWorkerProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"}); // Wait until the processes have finished. deleteCacheProcess.waitFor(); @@ -2312,9 +2209,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Clear the formatted URL string. - formattedUrlString = null; - // Clear the custom headers. customHeaders.clear(); @@ -2323,7 +2217,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook if (clearEverything) { try { // Delete the folder. - Process deleteAppWebviewProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview"); + Process deleteAppWebviewProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview"); // Wait until the process has finished. deleteAppWebviewProcess.waitFor(); @@ -2344,16 +2238,23 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook break; case R.id.home: - loadUrl(homepage); + // Select the homepage based on the proxy through Orbot status. + if (proxyThroughOrbot) { + // Load the Tor homepage. + loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value))); + } else { + // Load the normal homepage. + loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value))); + } break; case R.id.back: if (currentWebView.canGoBack()) { - // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled. - formattedUrlString = ""; + // Reset the current domain name so that navigation works if third-party requests are blocked. + currentWebView.resetCurrentDomainName(); - // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded. - navigatingHistory = true; + // Set navigating history so that the domain settings are applied when the new URL is loaded. + currentWebView.setNavigatingHistory(true); // Load the previous website in the history. currentWebView.goBack(); @@ -2362,11 +2263,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case R.id.forward: if (currentWebView.canGoForward()) { - // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled. - formattedUrlString = ""; + // Reset the current domain name so that navigation works if third-party requests are blocked. + currentWebView.resetCurrentDomainName(); - // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded. - navigatingHistory = true; + // Set navigating history so that the domain settings are applied when the new URL is loaded. + currentWebView.setNavigatingHistory(true); // Load the next website in the history. currentWebView.goForward(); @@ -2374,11 +2275,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook break; case R.id.history: - // Get the `WebBackForwardList`. - WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList(); + // Instantiate the URL history dialog. + DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId()); - // Show the URL history dialog and name this instance `R.string.history`. - DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList); + // Show the URL history dialog. urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history)); break; @@ -2389,6 +2289,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; @@ -2406,15 +2309,46 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case R.id.domains: // Set the flag to reapply the domain settings on restart when returning from Domain Settings. reapplyDomainSettingsOnRestart = true; - currentWebView.resetCurrentDomainName(); // TODO. Do this for all tabs. - - // TODO. Move these to `putExtra`. The certificate can be stored as strings. - // Store the current SSL certificate and IP addresses in the domains activity. - DomainsActivity.currentSslCertificate = currentWebView.getCertificate(); - DomainsActivity.currentIpAddresses = currentWebView.getCurrentIpAddresses(); // 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; @@ -2424,7 +2358,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the flag to reapply the domain settings on restart when returning from Settings. reapplyDomainSettingsOnRestart = true; - currentWebView.resetCurrentDomainName(); // TODO. Do this for all tabs. // Launch the settings activity. Intent settingsIntent = new Intent(this, SettingsActivity.class); @@ -2450,8 +2383,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook break; case R.id.about: - // Launch `AboutActivity`. + // 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], + ultraPrivacy.get(0).get(0)[0]}; + + // Add the blocklist versions to the intent. + aboutIntent.putExtra("blocklist_versions", blocklistVersions); + + // Make it so. startActivity(aboutIntent); break; } @@ -2503,20 +2445,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { - // Store the `HitTestResult`. + // Store the hit test result. final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult(); - // Create strings. + // Create the URL strings. final String imageUrl; final String linkUrl; - // Get a handle for the the clipboard and fragment managers. + // Get handles for the system managers. final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); FragmentManager fragmentManager = getSupportFragmentManager(); + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - // Remove the lint errors below that `clipboardManager` might be `null`. + // 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: @@ -2527,11 +2471,27 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook menu.setHeaderTitle(linkUrl); // Add a Load URL entry. - menu.add(R.string.load_url).setOnMenuItemClickListener((MenuItem item) -> { + menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> { + // Add a new tab. + addTab(null); + + // Load the URL. loadUrl(linkUrl); return false; }); + // Add an Open with App entry. + menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> { + openWithApp(linkUrl); + return false; + }); + + // Add an Open with Browser entry. + menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> { + openWithBrowser(linkUrl); + return false; + }); + // Add a Copy URL entry. menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> { // Save the link URL in a `ClipData`. @@ -2545,7 +2505,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Add a Download URL entry. menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> { // Check if the download should be processed by an external app. - if (downloadWithExternalApp) { // Download with an external app. + if (sharedPreferences.getBoolean("download_with_external_app", false)) { // Download with an external app. openUrlWithExternalApp(linkUrl); } else { // Download with Android's download manager. // Check to see if the storage permission has already been granted. @@ -2577,18 +2537,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return false; }); - // Add an Open with App entry. - menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> { - openWithApp(linkUrl); - return false; - }); - - // Add an Open with Browser entry. - menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> { - openWithBrowser(linkUrl); - return false; - }); - // Add a Cancel entry, which by default closes the context menu. menu.add(R.string.cancel); break; @@ -2647,7 +2595,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Add a Download Image entry. menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> { // Check if the download should be processed by an external app. - if (downloadWithExternalApp) { // Download with an external app. + if (sharedPreferences.getBoolean("download_with_external_app", false)) { // Download with an external app. openUrlWithExternalApp(imageUrl); } else { // Download with Android's download manager. // Check to see if the storage permission has already been granted. @@ -2721,7 +2669,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Add a `Download Image` entry. menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> { // Check if the download should be processed by an external app. - if (downloadWithExternalApp) { // Download with an external app. + if (sharedPreferences.getBoolean("download_with_external_app", false)) { // Download with an external app. openUrlWithExternalApp(imageUrl); } else { // Download with Android's download manager. // Check to see if the storage permission has already been granted. @@ -2780,7 +2728,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @Override - public void onCreateBookmark(DialogFragment dialogFragment) { + 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 views from the dialog fragment. EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext); EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext); @@ -2789,19 +2740,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook String bookmarkNameString = createBookmarkNameEditText.getText().toString(); String bookmarkUrlString = createBookmarkUrlEditText.getText().toString(); - // Get a copy of the favorite icon bitmap. - Bitmap favoriteIcon = favoriteIconBitmap; - - // Scale the favorite icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation. - if ((favoriteIcon.getHeight() > 256) || (favoriteIcon.getWidth() > 256)) { - favoriteIcon = Bitmap.createScaledBitmap(favoriteIcon, 256, 256, true); - } - // 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). - favoriteIcon.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream); + favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream); // Convert the favorite icon byte array stream to a byte array. byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray(); @@ -2823,7 +2766,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @Override - public void onCreateBookmarkFolder(DialogFragment dialogFragment) { + public void onCreateBookmarkFolder(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) { + // Get a handle for the bookmarks list view. + ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview); + // Get handles for the views in the dialog fragment. EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext); RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton); @@ -2846,13 +2792,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Convert the folder icon bitmap drawable to a bitmap. folderIconBitmap = folderIconBitmapDrawable.getBitmap(); } else { // Use the WebView favorite icon. - // Get a copy of the favorite icon bitmap. + // Copy the favorite icon bitmap to the folder icon bitmap. folderIconBitmap = favoriteIconBitmap; - - // Scale the folder icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation. - if ((folderIconBitmap.getHeight() > 256) || (folderIconBitmap.getWidth() > 256)) { - folderIconBitmap = Bitmap.createScaledBitmap(folderIconBitmap, 256, 256, true); - } } // Create a folder icon byte array output stream. @@ -2884,7 +2825,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @Override - public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId) { + public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId, Bitmap favoriteIconBitmap) { // Get handles for the views from `dialogFragment`. EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext); EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext); @@ -2898,19 +2839,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon. bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString); } else { // Update the bookmark using the `WebView` favorite icon. - // Get a copy of the favorite icon bitmap. - Bitmap favoriteIcon = favoriteIconBitmap; - - // Scale the favorite icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation. - if ((favoriteIcon.getHeight() > 256) || (favoriteIcon.getWidth() > 256)) { - favoriteIcon = Bitmap.createScaledBitmap(favoriteIcon, 256, 256, true); - } - // Create a favorite icon byte array output stream. ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream(); // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG). - favoriteIcon.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream); + favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream); // Convert the favorite icon byte array stream to a byte array. byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray(); @@ -2927,7 +2860,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @Override - public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId) { + public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) { // Get handles for the views from `dialogFragment`. EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext); RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton); @@ -2956,13 +2889,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Convert the folder icon bitmap drawable to a bitmap. folderIconBitmap = folderIconBitmapDrawable.getBitmap(); } else { // Use the `WebView` favorite icon. - // Get a copy of the favorite icon bitmap. - folderIconBitmap = MainWebViewActivity.favoriteIconBitmap; - - // Scale the folder icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation. - if ((folderIconBitmap.getHeight() > 256) || (folderIconBitmap.getWidth() > 256)) { - folderIconBitmap = Bitmap.createScaledBitmap(folderIconBitmap, 256, 256, true); - } + // Copy the favorite icon bitmap to the folder icon bitmap. + folderIconBitmap = favoriteIconBitmap; } // Create a folder icon byte array output stream. @@ -2989,13 +2917,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Convert the folder icon bitmap drawable to a bitmap. folderIconBitmap = folderIconBitmapDrawable.getBitmap(); } else { // Use the `WebView` favorite icon. - // Get a copy of the favorite icon bitmap. - folderIconBitmap = MainWebViewActivity.favoriteIconBitmap; - - // Scale the folder icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation. - if ((folderIconBitmap.getHeight() > 256) || (folderIconBitmap.getWidth() > 256)) { - folderIconBitmap = Bitmap.createScaledBitmap(folderIconBitmap, 256, 256, true); - } + // Copy the favorite icon bitmap to the folder icon bitmap. + folderIconBitmap = favoriteIconBitmap; } // Create a folder icon byte array output stream. @@ -3083,9 +3006,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Parse `imageUrl`. DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl)); + // Get a handle for the cookie manager. + CookieManager cookieManager = CookieManager.getInstance(); + // Pass cookies to download manager if cookies are enabled. This is required to download images from websites that require a login. // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner . - if (firstPartyCookiesEnabled) { + if (cookieManager.acceptCookie()) { // Get the cookies for `imageUrl`. String cookies = cookieManager.getCookie(imageUrl); @@ -3135,9 +3061,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Parse `downloadUrl`. DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl)); + // Get a handle for the cookie manager. + CookieManager cookieManager = CookieManager.getInstance(); + // Pass cookies to download manager if cookies are enabled. This is required to download files from websites that require a login. // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner . - if (firstPartyCookiesEnabled) { + if (cookieManager.acceptCookie()) { // Get the cookies for `downloadUrl`. String cookies = cookieManager.getCookie(downloadUrl); @@ -3177,74 +3106,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - @Override - public void onHttpAuthenticationCancel() { - // Cancel the `HttpAuthHandler`. - httpAuthHandler.cancel(); - } - - @Override - public void onHttpAuthenticationProceed(DialogFragment dialogFragment) { - // Get handles for the `EditTexts`. - EditText usernameEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_username); - EditText passwordEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_password); - - // Proceed with the HTTP authentication. - httpAuthHandler.proceed(usernameEditText.getText().toString(), passwordEditText.getText().toString()); - } - - @Override - public void onSslErrorCancel() { // TODO. How to handle this with multiple tabs? There could be multiple errors at once. - sslErrorHandler.cancel(); - } - - @Override - public void onSslErrorProceed() { // TODO. How to handle this with multiple tabs? There could be multiple errors at once. - sslErrorHandler.proceed(); - } - - @Override - public void onPinnedMismatchBack() { // TODO. Move this logic to the dialog. - if (currentWebView.canGoBack()) { // There is a back page in the history. - // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled. - formattedUrlString = ""; // TODO. - - // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded. - navigatingHistory = true; // TODO. - - // Go back. - currentWebView.goBack(); - } else { // There are no pages to go back to. - // Load a blank page - loadUrl(""); - } - } - - @Override - public void onPinnedMismatchProceed() { // TODO. Move this logic to the dialog. - // Do not check the pinned information for this domain again until the domain changes. - currentWebView.setIgnorePinnedDomainInformation(true); - } - - @Override - public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) { - // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled. - formattedUrlString = ""; - - // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded. - navigatingHistory = true; - - // Load the history entry. - currentWebView.goBackOrForward(moveBackOrForwardSteps); - } - - @Override - public void onClearHistory() { - // Clear the history. - currentWebView.clearHistory(); - } - - // Override `onBackPressed` to handle the navigation drawer and `mainWebView`. + // Override `onBackPressed` to handle the navigation drawer and and the WebView. @Override public void onBackPressed() { // Get a handle for the drawer layout. @@ -3264,19 +3126,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Load the new folder. loadBookmarksFolder(); } - } else if (currentWebView.canGoBack()) { // There is at least one item in the current WebView history. - // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled. - formattedUrlString = ""; + // Reset the current domain name so that navigation works if third-party requests are blocked. + currentWebView.resetCurrentDomainName(); - // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded. - navigatingHistory = true; + // Set navigating history so that the domain settings are applied when the new URL is loaded. + currentWebView.setNavigatingHistory(true); // Go back. currentWebView.goBack(); - } else { // There isn't anything to do in Privacy Browser. - // Pass `onBackPressed()` to the system. - super.onBackPressed(); + } else { // There is nothing else to do. + // Load a blank website. + loadUrl(""); } } @@ -3297,12 +3158,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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://")) { + if (unformattedUrlString.startsWith("content://")) { // This is a Content URL. // Load the entire content URL. - formattedUrlString = unformattedUrlString; - } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") - || unformattedUrlString.startsWith("file://")) { + 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; @@ -3326,20 +3190,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null; // Build the URI. - Uri.Builder formattedUri = new Uri.Builder(); - formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment); + Uri.Builder uri = new Uri.Builder(); + uri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment); - // Decode `formattedUri` as a `String` in `UTF-8`. + // Decode the URI as a UTF-8 string in. try { - formattedUrlString = URLDecoder.decode(formattedUri.build().toString(), "UTF-8"); + url = URLDecoder.decode(uri.build().toString(), "UTF-8"); } catch (UnsupportedEncodingException exception) { - // Load a blank string. - formattedUrlString = ""; + // Do nothing. The formatted URL string will remain blank. } - } else if (unformattedUrlString.isEmpty()){ // Load a blank web site. - // Load a blank string. - formattedUrlString = ""; - } else { // Search for the contents of the URL box. + } else if (!unformattedUrlString.isEmpty()){ // This is not a URL, but rather a search string. // Create an encoded URL String. String encodedUrlString; @@ -3351,20 +3211,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Add the base search URL. - formattedUrlString = searchURL + encodedUrlString; + 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(formattedUrlString); + loadUrl(url); } - private void loadUrl(String url) {// Apply any custom domain settings. - // Set the URL as the formatted URL string so that checking third-party requests works correctly. - formattedUrlString = url; - + private void loadUrl(String url) { // Apply the domain settings. applyDomainSettings(currentWebView, url, true, false); @@ -3386,6 +3243,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); @@ -3399,6 +3257,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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(currentWebView.getWindowToken(), 0); } @@ -3413,13 +3277,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false); fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false); hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true); - downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false); - // Get handles for the views that need to be modified. `getSupportActionBar()` must be used until the minimum API >= 21. + // Get handles for the views that need to be modified. FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout); ActionBar actionBar = getSupportActionBar(); + LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout); - // Remove the incorrect lint warnings below that the action bar might be null. + // Remove the incorrect lint warning below that the action bar might be null. assert actionBar != null; // Apply the proxy through Orbot settings. @@ -3454,8 +3318,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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(); } @@ -3479,7 +3351,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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 app bar. + // Show the tab linear layout. + tabsLinearLayout.setVisibility(View.VISIBLE); + + // Show the action bar. actionBar.show(); // Show the banner ad in the free flavor. @@ -3499,10 +3374,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled. @SuppressLint("SetJavaScriptEnabled") - private boolean applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetFavoriteIcon, boolean reloadWebsite) { - // Get a handle for the URL edit text. - EditText urlEditText = findViewById(R.id.url_edittext); - + private boolean applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetTab, boolean reloadWebsite) { // Store a copy of the current user agent to track changes for the return boolean. String initialUserAgent = nestedScrollWebView.getSettings().getUserAgentString(); @@ -3530,34 +3402,38 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.clearPinnedIpAddresses(); // Reset the favorite icon if specified. - if (resetFavoriteIcon) { - // Store the favorite icon bitmap. - favoriteIconBitmap = favoriteIconDefaultBitmap; // TODO. + if (resetTab) { + // Initialize the favorite icon. + nestedScrollWebView.initializeFavoriteIcon(); + + // Get the current page position. + int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); // Get a handle for the tab layout. TabLayout tabLayout = findViewById(R.id.tablayout); - // Get the current tab. - TabLayout.Tab currentTab = tabLayout.getTabAt(tabLayout.getSelectedTabPosition()); // TODO. We need to get the tab for this WebView, which might not be the current tab. + // Get the corresponding tab. + TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition); - // Remove the warning below that the current tab might be null. - assert currentTab != null; + // Remove the warning below that the tab might be null. + assert tab != null; - // Get the current tab custom view. - View currentTabCustomView = currentTab.getCustomView(); + // Get the tab custom view. + View tabCustomView = tab.getCustomView(); - // Remove the warning below that the current tab custom view might be null. - assert currentTabCustomView != null; + // Remove the warning below that the tab custom view might be null. + assert tabCustomView != null; - // Get the current tab favorite icon image view. - ImageView currentTabFavoriteIconImageView = currentTabCustomView.findViewById(R.id.favorite_icon_imageview); + // 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. - currentTabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true)); - } + tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteOrDefaultIcon(), 64, 64, true)); - // Get a handle for the swipe refresh layout. - SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); + // 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); @@ -3620,62 +3496,76 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); - nightMode = sharedPreferences.getBoolean("night_mode", false); // TODO. boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true); + boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false); + + // Get a handle for the cookie manager. + CookieManager cookieManager = CookieManager.getInstance(); - // Declare the JavaScript tracker. - boolean javaScriptEnabled; + // 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 currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase); - currentHostDomainSettingsCursor.moveToFirst(); + Cursor currentDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase); + currentDomainSettingsCursor.moveToFirst(); // Get the settings from the cursor. - nestedScrollWebView.setDomainSettingsDatabaseId(currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID))); - javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1); // TODO. Rename to domainSettingsJavaScriptEnabled after the global variable is removed. - firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1); // TODO. - thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1); // TODO. - domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1); // TODO. + nestedScrollWebView.setDomainSettingsDatabaseId(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID))); + nestedScrollWebView.setDomainSettingsJavaScriptEnabled(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. - saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1); // TODO. - easyListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1); // TODO. - easyPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1); // TODO. - fanboysAnnoyanceListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1); // TODO. - fanboysSocialBlockingListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1); // TODO. - ultraPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1); // TODO. - blockAllThirdPartyRequests = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1); // TODO. - String userAgentName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT)); - int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE)); - int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH)); - int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE)); - int displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES)); - boolean pinnedSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1); - String pinnedSslIssuedToCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)); - String pinnedSslIssuedToOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)); - String pinnedSslIssuedToUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)); - String pinnedSslIssuedByCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)); - String pinnedSslIssuedByOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)); - String pinnedSslIssuedByUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)); - boolean pinnedIpAddresses = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1); - String pinnedHostIpAddresses = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES)); + boolean saveFormData = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1); + nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_LIST, + currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1); + nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, + 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.ULTRA_PRIVACY, + 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 nightModeInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE)); + 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)); // Create 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 (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) { + if (currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) { pinnedSslStartDate = null; } else { - pinnedSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE))); + pinnedSslStartDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE))); } // 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 (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) { + if (currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) { pinnedSslEndDate = null; } else { - pinnedSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE))); + pinnedSslEndDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE))); } // If there is a pinned SSL certificate, store it in the WebView. @@ -3689,40 +3579,47 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.setPinnedIpAddresses(pinnedHostIpAddresses); } - // Set `nightMode` according to `nightModeInt`. If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used. + // Set night mode according to the night mode int. switch (nightModeInt) { + case DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT: + // Set night mode according to the current default. + nestedScrollWebView.setNightMode(sharedPreferences.getBoolean("night_mode", false)); + break; + case DomainsDatabaseHelper.NIGHT_MODE_ENABLED: - nightMode = true; // TODO. + // Enable night mode. + nestedScrollWebView.setNightMode(true); break; case DomainsDatabaseHelper.NIGHT_MODE_DISABLED: - nightMode = false; // TODO. + // Disable night mode. + nestedScrollWebView.setNightMode(false); break; } - // TODO. - // Store the domain JavaScript status. This is used by the options menu night mode toggle. - domainSettingsJavaScriptEnabled = javaScriptEnabled; - // Enable JavaScript if night mode is enabled. - if (nightMode) { + if (nestedScrollWebView.getNightMode()) { // Enable JavaScript. nestedScrollWebView.getSettings().setJavaScriptEnabled(true); } else { // Set JavaScript according to the domain settings. - nestedScrollWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); + nestedScrollWebView.getSettings().setJavaScriptEnabled(nestedScrollWebView.getDomainSettingsJavaScriptEnabled()); } - // Close `currentHostDomainSettingsCursor`. - currentHostDomainSettingsCursor.close(); + // Close the current host domain settings cursor. + currentDomainSettingsCursor.close(); // Apply the domain settings. - cookieManager.setAcceptCookie(firstPartyCookiesEnabled); //TODO This could be bad. - nestedScrollWebView.getSettings().setDomStorageEnabled(domStorageEnabled); // TODO. + 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(saveFormDataEnabled); + nestedScrollWebView.getSettings().setSaveFormData(saveFormData); } // Apply the font size. @@ -3732,18 +3629,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.getSettings().setTextZoom(fontSize); } - // Set third-party cookies status if API >= 21. - if (Build.VERSION.SDK_INT >= 21) { - cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, thirdPartyCookiesEnabled); // TODO. - } - // Only set the user agent if the webpage is not currently loading. Otherwise, changing the user agent on redirects can cause the original website to reload. // if (nestedScrollWebView.getProgress() == 100) { // A URL is not loading. // 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); // TODO Could this be local. + int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName); // Set the user agent according to the system default. switch (defaultUserAgentArrayPosition) { @@ -3788,19 +3680,28 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Set swipe to refresh. - switch (swipeToRefreshInt) { // TODO. This needs to be set and updated by tab. + switch (swipeToRefreshInt) { case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT: - // Set swipe to refresh according to the default. + // Store the swipe to refresh status in the nested scroll WebView. + nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh); + + // Apply swipe to refresh according to the default. This can be removed once the minimum API >= 23 because it is continuously set by an on scroll change listener. swipeRefreshLayout.setEnabled(defaultSwipeToRefresh); break; case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED: - // Enable swipe to refresh. + // Store the swipe to refresh status in the nested scroll WebView. + nestedScrollWebView.setSwipeToRefresh(true); + + // Enable swipe to refresh. This can be removed once the minimum API >= 23 because it is continuously set by an on scroll change listener. swipeRefreshLayout.setEnabled(true); break; case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED: - // Disable swipe to refresh. + // Store the swipe to refresh status in the nested scroll WebView. + nestedScrollWebView.setSwipeToRefresh(false); + + // Disable swipe to refresh. This can be removed once the minimum API >= 23 because it is continuously set by an on scroll change listener. swipeRefreshLayout.setEnabled(false); } @@ -3819,52 +3720,57 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook break; } - // Set a green background on URL edit text to indicate that custom domain settings are being used. The deprecated `.getDrawable()` must be used until the minimum API >= 21. + // Set a green background on the URL relative layout to indicate that custom domain settings are being used. The deprecated `.getDrawable()` must be used until the minimum API >= 21. if (darkTheme) { - urlEditText.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue)); + urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue)); } else { - urlEditText.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green)); + urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green)); } } else { // The new URL does not have custom domain settings. Load the defaults. - // Store the values from `sharedPreferences` in variables. - javaScriptEnabled = sharedPreferences.getBoolean("javascript", false); // TODO. - firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies", false); // TODO. - thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false); // TODO. - domStorageEnabled = sharedPreferences.getBoolean("dom_storage", false); // TODO. - saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26. // TODO. - easyListEnabled = sharedPreferences.getBoolean("easylist", true); // TODO. - easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true); // TODO. - fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboys_annoyance_list", true); // TODO. - fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboys_social_blocking_list", true); // TODO. - ultraPrivacyEnabled = sharedPreferences.getBoolean("ultraprivacy", true); // TODO. - blockAllThirdPartyRequests = sharedPreferences.getBoolean("block_all_third_party_requests", false); // TODO. - - // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`. - if (nightMode) { + // Store the values from the shared preferences. + boolean defaultJavaScriptEnabled = 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.EASY_LIST, sharedPreferences.getBoolean("easylist", true)); + nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, 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.ULTRA_PRIVACY, sharedPreferences.getBoolean("ultraprivacy", true)); + nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false)); + nestedScrollWebView.setNightMode(sharedPreferences.getBoolean("night_mode", false)); + + // Enable JavaScript if night mode is enabled. + if (nestedScrollWebView.getNightMode()) { // Enable JavaScript. nestedScrollWebView.getSettings().setJavaScriptEnabled(true); } else { // Set JavaScript according to the domain settings. - nestedScrollWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); + nestedScrollWebView.getSettings().setJavaScriptEnabled(defaultJavaScriptEnabled); } // Apply the default settings. - cookieManager.setAcceptCookie(firstPartyCookiesEnabled); // TODO. - nestedScrollWebView.getSettings().setDomStorageEnabled(domStorageEnabled); // TODO. + cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies()); nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); - swipeRefreshLayout.setEnabled(defaultSwipeToRefresh); // Apply the form data setting if the API < 26. if (Build.VERSION.SDK_INT < 26) { - currentWebView.getSettings().setSaveFormData(saveFormDataEnabled); + nestedScrollWebView.getSettings().setSaveFormData(saveFormData); } + // Store the swipe to refresh status in the nested scroll WebView. + nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh); + + // Apply swipe to refresh according to the default. + swipeRefreshLayout.setEnabled(defaultSwipeToRefresh); + // Reset the pinned variables. nestedScrollWebView.setDomainSettingsDatabaseId(-1); // Set third-party cookies status if API >= 21. if (Build.VERSION.SDK_INT >= 21) { - cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, thirdPartyCookiesEnabled); + cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, defaultThirdPartyCookiesEnabled); } // Only set the user agent if the webpage is not currently loading. Otherwise, changing the user agent on redirects can cause the original website to reload. @@ -3900,7 +3806,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages); // Set a transparent background on URL edit text. The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21. - urlEditText.setBackground(getResources().getDrawable(R.color.transparent)); + urlRelativeLayout.setBackground(getResources().getDrawable(R.color.transparent)); } // Close the domains database helper. @@ -3923,13 +3829,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get a handle for the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - // Get the search preferences. - String homepageString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)); - String torHomepageString = sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)); + // Get the search and theme preferences. String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value)); String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value)); String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value)); String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value)); + boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false); // Get a handle for the action bar. `getSupportActionBar()` must be used until the minimum API >= 21. ActionBar actionBar = getSupportActionBar(); @@ -3939,14 +3844,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the homepage, search, and proxy options. if (proxyThroughOrbot) { // Set the Tor options. - // Set `torHomepageString` as `homepage`. - homepage = torHomepageString; - - // If formattedUrlString is null assign the homepage to it. - if (formattedUrlString == null) { - formattedUrlString = homepage; - } - // Set the search URL. if (torSearchString.equals("Custom URL")) { // Get the custom URL string. searchURL = torSearchCustomUrlString; @@ -3957,7 +3854,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed. OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118"); - // Set the `appBar` background to indicate proxying through Orbot is enabled. `this` refers to the context. + // Set the `appBar` background to indicate proxying through Orbot is enabled. if (darkTheme) { actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30)); } else { @@ -3973,20 +3870,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook currentWebView.getSettings().setUseWideViewPort(false); // Load a waiting page. `null` specifies no encoding, which defaults to ASCII. - currentWebView.loadData(waitingForOrbotHtmlString, "text/html", null); + currentWebView.loadData("

" + getString(R.string.waiting_for_orbot) + "

", "text/html", null); } else if (reloadWebsite) { // Orbot is ready and the website should be reloaded. // Reload the website. currentWebView.reload(); } } else { // Set the non-Tor options. - // Set `homepageString` as `homepage`. - homepage = homepageString; - - // If formattedUrlString is null assign the homepage to it. - if (formattedUrlString == null) { - formattedUrlString = homepage; - } - // Set the search URL. if (searchString.equals("Custom URL")) { // Get the custom URL string. searchURL = searchCustomUrlString; @@ -3997,7 +3886,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reset the proxy to default. The host is `""` and the port is `"0"`. OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0"); - // Set the default `appBar` background. `this` refers to the context. + // Set the default `appBar` background. if (darkTheme) { actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900)); } else { @@ -4031,8 +3920,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) { - // Only update the privacy icons if the options menu has already been populated. - if (optionsMenu != null) { + // Only update the privacy icons if the options menu and the current WebView have already been populated. + if ((optionsMenu != null) && (currentWebView != null)) { + // Get a handle for the shared preferences. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + + // Get the theme and screenshot preferences. + boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false); + // Get handles for the menu items. MenuItem privacyMenuItem = optionsMenu.findItem(R.id.toggle_javascript); MenuItem firstPartyCookiesMenuItem = optionsMenu.findItem(R.id.toggle_first_party_cookies); @@ -4042,14 +3937,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Update the privacy icon. if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is enabled. privacyMenuItem.setIcon(R.drawable.javascript_enabled); - } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are 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); } // Update the first-party cookies icon. - if (firstPartyCookiesEnabled) { // First-party cookies are enabled. + if (currentWebView.getAcceptFirstPartyCookies()) { // First-party cookies are enabled. firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled); } else { // First-party cookies are disabled. if (darkTheme) { @@ -4060,7 +3955,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Update the DOM storage icon. - if (currentWebView.getSettings().getJavaScriptEnabled() && domStorageEnabled) { // Both JavaScript and DOM storage are enabled. + 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 (darkTheme) { @@ -4208,9 +4103,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }; - // Populate the `ListView` with the adapter. + // 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); @@ -4248,8 +4149,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } public void addTab(View view) { - // Get a handle for the tab layout. + // Get a handle for the tab layout and the view pager. TabLayout tabLayout = findViewById(R.id.tablayout); + ViewPager webViewPager = findViewById(R.id.webviewpager); // 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(); @@ -4263,15 +4165,91 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Remove the lint warning below that the current tab might be null. assert newTab != null; - // Set a custom view on the current tab. - newTab.setCustomView(R.layout.custom_tab_view); + // Set a custom view on the new tab. + newTab.setCustomView(R.layout.tab_custom_view); // Add the new WebView page. - webViewPagerAdapter.addPage(newTabNumber); + webViewPagerAdapter.addPage(newTabNumber, webViewPager); + } + + private void setCurrentWebView(int pageNumber) { + // Get a handle for the shared preferences. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + + // Get the theme preference. + boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false); + + // 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) { + // 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. + if (Build.VERSION.SDK_INT >= 23) { // For API >= 23, swipe refresh layout is continuously updated with an on scroll change listener and only enabled if the WebView is scrolled to the top. + // Enable the swipe refresh layout if the WebView is scrolled all the way to the top. + swipeRefreshLayout.setEnabled(currentWebView.getY() == 0); + } else { + // Enable the swipe refresh layout. + swipeRefreshLayout.setEnabled(true); + } + } 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); + + // Clear the focus from the URL text box. + urlEditText.clearFocus(); + + // 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 soft keyboard. + inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0); - if (newTabNumber > 0) { - // Move to the new tab. - newTab.select(); + // Display the current URL in the URL text box. + urlEditText.setText(currentWebView.getUrl()); + + // Highlight the URL text. + highlightUrlText(); + + // Set the background to indicate the domain settings status. + if (currentWebView.getDomainSettingsApplied()) { + // Set a green background on the URL relative layout to indicate that custom domain settings are being used. The deprecated `.getDrawable()` must be used until the minimum API >= 21. + if (darkTheme) { + urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue)); + } else { + urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green)); + } + } else { + urlRelativeLayout.setBackground(getResources().getDrawable(R.color.transparent)); + } } } @@ -4282,22 +4260,35 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook DrawerLayout drawerLayout = findViewById(R.id.drawerlayout); RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout); ActionBar actionBar = getSupportActionBar(); + LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout); EditText urlEditText = findViewById(R.id.url_edittext); TabLayout tabLayout = findViewById(R.id.tablayout); SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout); - // Remove the incorrect lint warnings below that the some of the views might be null. + // 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; + // Get a handle for the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Get the relevant preferences. boolean downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false); + // Initialize the favorite icon. + nestedScrollWebView.initializeFavoriteIcon(); + // Set the app bar scrolling. nestedScrollWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true)); @@ -4334,6 +4325,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook if (inFullScreenBrowsingMode) { // Switch to full screen mode. // Hide the app bar if specified. if (hideAppBar) { + // Hide the tab linear layout. + tabsLinearLayout.setVisibility(View.GONE); + + // Hide the action bar. actionBar.hide(); } @@ -4354,7 +4349,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); } else { // Switch to normal viewing mode. - // Show the app bar. + // Show the tab linear layout. + tabsLinearLayout.setVisibility(View.VISIBLE); + + // Show the action bar. actionBar.show(); // Show the banner ad in the free flavor. @@ -4459,13 +4457,23 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }); + if (Build.VERSION.SDK_INT >= 23) { + nestedScrollWebView.setOnScrollChangeListener((View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) -> { + // Update the status of swipe to refresh if it is enabled. + if (nestedScrollWebView.getSwipeToRefresh()) { + // Only enable swipe to refresh if the WebView is scrolled to the top. + swipeRefreshLayout.setEnabled(scrollY == 0); + } + }); + } + // 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) { // Inject the night mode CSS if night mode is enabled. - if (nightMode) { + if (nestedScrollWebView.getNightMode()) { // `background-color: #212121` sets the background to be dark gray. `color: #BDBDBD` sets the text color to be light gray. `box-shadow: none` removes a lower underline on links // used by WordPress. `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color. // `border: none` removes all borders, which can also be used to underline text. `a {color: #1565C0}` sets links to be a dark blue. @@ -4484,7 +4492,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }; - // Displaying of `mainWebView` after 500 milliseconds. + // Display the WebView after 500 milliseconds. displayWebViewHandler.postDelayed(displayWebViewRunnable, 500); }); } @@ -4500,10 +4508,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Hide the progress bar. progressBar.setVisibility(View.GONE); - // Display `mainWebView` if night mode is disabled. - // Because of a race condition between `applyDomainSettings` and `onPageStarted`, when night mode is set by domain settings the `WebView` may be hidden even if night mode is not - // currently enabled. - if (!nightMode) { + // Display the nested scroll WebView if night mode is disabled. + // Because of a race condition between `applyDomainSettings` and `onPageStarted`, + // when night mode is set by domain settings the WebView may be hidden even if night mode is not currently enabled. + if (!nestedScrollWebView.getNightMode()) { nestedScrollWebView.setVisibility(View.VISIBLE); } @@ -4517,8 +4525,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook public void onReceivedIcon(WebView view, Bitmap icon) { // Only update the favorite icon if the website has finished loading. if (progressBar.getVisibility() == View.GONE) { - // Save a copy of the favorite icon. - favoriteIconBitmap = icon; + // Store the new favorite icon. + nestedScrollWebView.setFavoriteOrDefaultIcon(icon); // Get the current page position. int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); @@ -4526,20 +4534,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the current tab. TabLayout.Tab tab = tabLayout.getTabAt(currentPosition); - // Remove the lint warning below that the current tab might be null. - assert 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; + // Check to see if the tab has been populated. + if (tab != null) { + // Get the custom view from the tab. + View tabView = tab.getCustomView(); - // Get the favorite icon image view from the tab. - ImageView tabFavoriteIconImageView = tabView.findViewById(R.id.favorite_icon_imageview); + // 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)); + // Display the favorite icon in the tab. + tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true)); + } + } } } @@ -4552,25 +4560,28 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the current tab. TabLayout.Tab tab = tabLayout.getTabAt(currentPosition); - // Remove the lint warning below that the current tab might be null. - assert tab != null; - - // Get the custom view from the tab. - View tabView = tab.getCustomView(); + // 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; + // 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); + // 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(title); + // 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; @@ -4614,6 +4625,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); + // Unset the full screen video flag. displayingFullScreenVideo = false; @@ -4629,10 +4643,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show the main content relative layout. mainContentRelativeLayout.setVisibility(View.VISIBLE); - // Apply the appropriate full screen mode the `SYSTEM_UI` flags. + // 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(); } @@ -4688,14 +4706,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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. - @SuppressWarnings("deprecation") @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (url.startsWith("http")) { // Load the URL in Privacy Browser. - // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled. - formattedUrlString = ""; - - // Apply the domain settings for the new URL. `applyDomainSettings` doesn't do anything if the domain has not changed. + // Apply the domain settings for the new URL. This doesn't do anything if the domain has not changed. boolean userAgentChanged = applyDomainSettings(nestedScrollWebView, url, true, false); // Check if the user agent has changed. @@ -4765,9 +4779,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Check requests against the block lists. The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21. - @SuppressWarnings("deprecation") @Override public WebResourceResponse shouldInterceptRequest(WebView view, String 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. The menu is 0 based. + MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6); + // 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())); @@ -4777,24 +4799,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Initialize the third party request tracker. boolean isThirdPartyRequest = false; - // Initialize the current domain string. - String currentDomain = ""; + // Get the current URL. `.getUrl()` throws an error because operations on the WebView cannot be made from this thread. + String currentBaseDomain = nestedScrollWebView.getCurrentDomainName(); - // Nobody is happy when comparing null strings. - if (!(formattedUrlString == null) && !(url == null)) { - // Get the domain strings to URIs. - Uri currentDomainUri = Uri.parse(formattedUrlString); - Uri requestDomainUri = Uri.parse(url); + // Store a copy of the current domain for use in later requests. + String currentDomain = currentBaseDomain; - // Get the domain host names. - String currentBaseDomain = currentDomainUri.getHost(); - String requestBaseDomain = requestDomainUri.getHost(); + // Nobody is happy when comparing null strings. + if ((currentBaseDomain != null) && (url != null)) { + // Convert the request URL to a URI. + Uri requestUri = Uri.parse(url); - // Update the current domain variable. - currentDomain = currentBaseDomain; + // Get the request host name. + String requestBaseDomain = requestUri.getHost(); - // Only compare the current base domain and the request base domain if neither is null. - if (!(currentBaseDomain == null) && !(requestBaseDomain == null)) { + // 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. @@ -4819,13 +4839,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook boolean webViewDisplayed = (webViewPagePosition == tabLayout.getSelectedTabPosition()); // Block third-party requests if enabled. - if (isThirdPartyRequest && blockAllThirdPartyRequests) { + 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_BLOCKED_REQUESTS); + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS); // Update the titles of the blocklist menu items if the WebView is currently displayed. if (webViewDisplayed) { @@ -4833,9 +4853,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook activity.runOnUiThread(() -> { // Update the menu item titles. navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - blockAllThirdPartyRequestsMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_BLOCKED_REQUESTS) + " - " + - getString(R.string.block_all_third_party_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)); + } }); } @@ -4844,7 +4868,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Check UltraPrivacy if it is enabled. - if (ultraPrivacyEnabled) { + if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY)) { // Check the URL against UltraPrivacy. String[] ultraPrivacyResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy); @@ -4856,7 +4880,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Increment the blocked requests counters. nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRA_PRIVACY_BLOCKED_REQUESTS); + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRA_PRIVACY); // Update the titles of the blocklist menu items if the WebView is currently displayed. if (webViewDisplayed) { @@ -4864,8 +4888,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook activity.runOnUiThread(() -> { // Update the menu item titles. navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - ultraPrivacyMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY_BLOCKED_REQUESTS) + " - " + getString(R.string.ultraprivacy)); + + // 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.ULTRA_PRIVACY) + " - " + getString(R.string.ultraprivacy)); + } }); } @@ -4882,7 +4910,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Check EasyList if it is enabled. - if (easyListEnabled) { + if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST)) { // Check the URL against EasyList. String[] easyListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList); @@ -4893,7 +4921,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Increment the blocked requests counters. nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_LIST_BLOCKED_REQUESTS); + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_LIST); // Update the titles of the blocklist menu items if the WebView is currently displayed. if (webViewDisplayed) { @@ -4901,8 +4929,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook activity.runOnUiThread(() -> { // Update the menu item titles. navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - easyListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_LIST_BLOCKED_REQUESTS) + " - " + getString(R.string.easylist)); + + // 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.EASY_LIST) + " - " + getString(R.string.easylist)); + } }); } @@ -4915,19 +4947,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Check EasyPrivacy if it is enabled. - if (easyPrivacyEnabled) { + if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY)) { // 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[5], + 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.EASY_PRIVACY_BLOCKED_REQUESTS); + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_PRIVACY); // Update the titles of the blocklist menu items if the WebView is currently displayed. if (webViewDisplayed) { @@ -4935,8 +4967,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook activity.runOnUiThread(() -> { // Update the menu item titles. navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - easyPrivacyMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY_BLOCKED_REQUESTS) + " - " + getString(R.string.easyprivacy)); + + // 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.EASY_PRIVACY) + " - " + getString(R.string.easyprivacy)); + } }); } @@ -4949,7 +4985,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Check Fanboy’s Annoyance List if it is enabled. - if (fanboysAnnoyanceListEnabled) { + if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)) { // Check the URL against Fanboy's Annoyance List. String[] fanboysAnnoyanceListResults = blockListHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList); @@ -4961,7 +4997,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Increment the blocked requests counters. nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS); + nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST); // Update the titles of the blocklist menu items if the WebView is currently displayed. if (webViewDisplayed) { @@ -4969,9 +5005,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook activity.runOnUiThread(() -> { // Update the menu item titles. navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - fanboysAnnoyanceListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS) + " - " + - getString(R.string.fanboys_annoyance_list)); + + // 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)); + } }); } @@ -4982,7 +5022,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook whitelistResultStringArray = new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3], fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]}; } - } else if (fanboysSocialBlockingListEnabled) { // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled. + } 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); @@ -4994,7 +5034,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Increment the blocked requests counters. nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); - nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST_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) { @@ -5002,9 +5042,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook activity.runOnUiThread(() -> { // Update the menu item titles. navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS)); - fanboysSocialBlockingListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS) + " - " + - getString(R.string.fanboys_social_blocking_list)); + + // 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)); + } }); } @@ -5031,30 +5075,29 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Handle HTTP authentication requests. @Override public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { - // Store `handler` so it can be accessed from `onHttpAuthenticationCancel()` and `onHttpAuthenticationProceed()`. - httpAuthHandler = handler; + // Store the handler. + nestedScrollWebView.setHttpAuthHandler(handler); - // Display the HTTP authentication dialog. - DialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm); + // 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 theme preference. + boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false); + // Reset the list of resource requests. nestedScrollWebView.clearResourceRequests(); - // Initialize the counters for requests blocked by each blocklist. - nestedScrollWebView.resetRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS); - nestedScrollWebView.resetRequestsCount(NestedScrollWebView.EASY_LIST_BLOCKED_REQUESTS); - nestedScrollWebView.resetRequestsCount(NestedScrollWebView.EASY_PRIVACY_BLOCKED_REQUESTS); - nestedScrollWebView.resetRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS); - nestedScrollWebView.resetRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS); - nestedScrollWebView.resetRequestsCount(NestedScrollWebView.ULTRA_PRIVACY_BLOCKED_REQUESTS); - nestedScrollWebView.resetRequestsCount(NestedScrollWebView.THIRD_PARTY_BLOCKED_REQUESTS); + // Reset the requests counters. + nestedScrollWebView.resetRequestsCounters(); // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied. - if (nightMode) { + if (nestedScrollWebView.getNightMode()) { nestedScrollWebView.setVisibility(View.INVISIBLE); } @@ -5063,43 +5106,52 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Check to see if Privacy Browser is waiting on Orbot. if (!waitingForOrbot) { // Process the URL. - // The formatted URL string must be updated at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded. - formattedUrlString = url; - - // Display the formatted URL text. - urlEditText.setText(formattedUrlString); + // Get the current page position. + int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId()); - // Apply text highlighting to `urlTextBox`. - highlightUrlText(); + // Update the URL text bar if the page is currently selected. + if (tabLayout.getSelectedTabPosition() == currentPagePosition) { + // Display the formatted URL text. + urlEditText.setText(url); - // Get a URI for the current URL. - Uri currentUri = Uri.parse(formattedUrlString); + // 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()); // Apply any custom domain settings if the URL was loaded by navigating history. - if (navigatingHistory) { + if (nestedScrollWebView.getNavigatingHistory()) { + // Reset navigating history. + nestedScrollWebView.setNavigatingHistory(false); + // Apply the domain settings. boolean userAgentChanged = applyDomainSettings(nestedScrollWebView, url, true, false); - // Reset `navigatingHistory`. - navigatingHistory = false; - // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded. if (userAgentChanged) { - loadUrl(formattedUrlString); + loadUrl(url); } } - // Replace Refresh with Stop if the menu item has been created. (The WebView typically begins loading before the menu items are instantiated.) - if (refreshMenuItem != null) { + // Replace Refresh with Stop if the options menu has been created. (The 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) { if (darkTheme) { @@ -5112,7 +5164,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load. @Override public void onPageFinished(WebView view, String url) { // Reset the wide view port if it has been turned off by the waiting for Orbot message. @@ -5121,16 +5172,23 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.getSettings().setUseWideViewPort(url.startsWith("http")); } - // Flush any cookies to persistent storage. `CookieManager` has become very lazy about flushing cookies in recent versions. - if (firstPartyCookiesEnabled && Build.VERSION.SDK_INT >= 21) { - cookieManager.flush(); + // 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 it has been created. - if (refreshMenuItem != null) { + // 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); + boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false); + // If the icon is displayed in the AppBar, reset it according to the theme. if (displayAdditionalAppBarIcons) { if (darkTheme) { @@ -5141,7 +5199,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Clear the cache and history if Incognito Mode is enabled. if (incognitoModeEnabled) { // Clear the cache. `true` includes disk files. @@ -5152,12 +5209,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Manually delete cache folders. try { + // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, + // which links to `/data/data/com.stoutner.privacybrowser.standard`. + String privateDataDirectoryString = getApplicationInfo().dataDir; + // Delete the main cache directory. - privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache"); + 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. - privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"}); + Runtime.getRuntime().exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"}); } catch (IOException e) { // Do nothing if an error is thrown. } @@ -5165,32 +5226,36 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Update the URL text box and apply domain settings if not waiting on Orbot. if (!waitingForOrbot) { - // Check to see if `WebView` has set `url` to be `about:blank`. - if (url.equals("about:blank")) { // `WebView` is blank, so `formattedUrlString` should be `""` and `urlTextBox` should display a hint. - // Set `formattedUrlString` to `""`. - formattedUrlString = ""; - - urlEditText.setText(formattedUrlString); - - // Request focus for `urlTextBox`. - urlEditText.requestFocus(); - - // Display the keyboard. - inputMethodManager.showSoftInput(urlEditText, 0); - - // Apply the domain settings. This clears any settings from the previous domain. - applyDomainSettings(nestedScrollWebView, formattedUrlString, true, false); - } else { // `WebView` has loaded a webpage. - // Set the formatted URL string. Getting the URL from the WebView instead of using the one provided by `onPageFinished` makes websites like YouTube function correctly. - formattedUrlString = nestedScrollWebView.getUrl(); - - // Only update the URL text box if the user is not typing in it. - if (!urlEditText.hasFocus()) { - // Display the formatted URL text. - urlEditText.setText(formattedUrlString); - - // Apply text highlighting to `urlTextBox`. - highlightUrlText(); + // 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) { + // Check to see if the URL is `about:blank`. + if (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); + + // Hide the WebView, which causes the default background color to be displayed according to the theme. // TODO + nestedScrollWebView.setVisibility(View.GONE); + + // Apply the domain settings. This clears any settings from the previous domain. + applyDomainSettings(nestedScrollWebView, "", true, false); + } else { // The WebView has loaded a webpage. + // Only update the URL text box if the user is not typing in it. + if (!urlEditText.hasFocus()) { + // 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(nestedScrollWebView.getUrl()); + + // Apply text highlighting to the URL. + highlightUrlText(); + } } } @@ -5237,11 +5302,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook handler.proceed(); } } else { // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate. - // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`. - sslErrorHandler = handler; // TODO. We need to pass this in instead of using a static variable. Because multiple could be displayed at once from different tabs. + // Store the SSL error handler. + nestedScrollWebView.setSslErrorHandler(handler); - // Display the SSL error `AlertDialog`. - DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error); + // 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)); } } @@ -5257,7 +5324,40 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Load the website if not waiting for Orbot to connect. if (!waitingForOrbot) { - loadUrl(formattedUrlString); + // Get the intent that started the app. + Intent launchingIntent = getIntent(); + + // Get the information from the intent. + String launchingIntentAction = launchingIntent.getAction(); + Uri launchingIntentUriData = launchingIntent.getData(); + + // If the intent action is a web search, perform the search. + if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) { + // 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 = ""; + } + + // Load the completed search URL. + loadUrl(searchURL + encodedUrlString); + } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL. + // Load the URL from the intent. + loadUrl(launchingIntentUriData.toString()); + } else { // The is no URL in the intent. + // Select the homepage based on the proxy through Orbot status. + if (proxyThroughOrbot) { + // Load the Tor homepage. + loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value))); + } else { + // Load the normal homepage. + loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value))); + } + } } } }