private boolean hideAppBar;
private boolean scrollAppBar;
+ // The loading new intent tracker is set in `onNewIntent()` and used in `setCurrentWebView()`.
+ private boolean loadingNewIntent;
+
// `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
private boolean reapplyDomainSettingsOnRestart;
String intentAction = intent.getAction();
Uri intentUriData = intent.getData();
- // 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);
+ // Determine if this is a web search.
+ boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
+ // Only process the URI if it contains data or it is a web search. If the user pressed the desktop icon after the app was already running the URI will be null.
+ if (intentUriData != null || isWebSearch) {
// Get the shared preferences.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+ // Add a new tab if specified in the preferences.
if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {
+ // Set the loading new intent flag.
+ loadingNewIntent = true;
+
// Add a new tab.
addTab(null);
}
String url;
// If the intent action is a web search, perform the search.
- if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) {
+ if (isWebSearch) {
// Create an encoded URL string.
String encodedUrlString;
// Run the commands that correspond to the selected menu item.
switch (menuItemId) {
case R.id.close_tab:
- // 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();
-
- // Delete the current tab.
- tabLayout.removeTabAt(currentTabNumber);
-
- // Delete the current page. If the selected page number did not change during the delete, it will return true, meaning that the current WebView must be reset.
- if (webViewPagerAdapter.deletePage(currentTabNumber, webViewPager)) {
- setCurrentWebView(currentTabNumber);
- }
+ // Close the current tab.
+ closeCurrentTab();
break;
case R.id.clear_and_exit:
// Set the target URL as the title of the `ContextMenu`.
menu.setHeaderTitle(linkUrl);
- // Add a Load URL entry.
+ // Add an Open in New Tab entry.
menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
// Add a new tab.
addTab(null);
menu.add(R.string.cancel);
break;
- // `IMAGE_TYPE` is an image.
+ // `IMAGE_TYPE` is an image. `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link. Privacy Browser processes them the same.
case WebView.HitTestResult.IMAGE_TYPE:
+ case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
// Get the image URL.
imageUrl = hitTestResult.getExtra();
- // Set the image URL as the title of the `ContextMenu`.
+ // Set the image URL as the title of the context menu.
menu.setHeaderTitle(imageUrl);
- // Add a View Image entry.
- menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
- loadUrl(imageUrl);
- return false;
- });
-
- // 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 (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.
- if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
- // Store the image URL for use by `onRequestPermissionResult()`.
- downloadImageUrl = imageUrl;
-
- // Show a dialog if the user has previously denied the permission.
- if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
- // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
- DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
-
- // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
- downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
- } else { // Show the permission request directly.
- // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
- ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
- }
- } else { // The storage permission has already been granted.
- // Get a handle for the download image alert dialog.
- DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
-
- // Show the download image alert dialog.
- downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
- }
- }
- return false;
- });
-
- // Add a Copy URL entry.
- menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
- // Save the image URL in a `ClipData`.
- ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
-
- // Set the `ClipData` as the clipboard's primary clip.
- clipboardManager.setPrimaryClip(srcImageTypeClipData);
- return false;
- });
-
- // Add an Open with App entry.
- menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
- openWithApp(imageUrl);
- return false;
- });
+ // Add an Open in New Tab entry.
+ menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
+ // Add a new tab.
+ addTab(null);
- // Add an Open with Browser entry.
- menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
- openWithBrowser(imageUrl);
+ // Load the URL.
+ loadUrl(imageUrl);
return false;
});
- // Add a `Cancel` entry, which by default closes the `ContextMenu`.
- menu.add(R.string.cancel);
- break;
-
-
- // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
- case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
- // Get the image URL.
- imageUrl = hitTestResult.getExtra();
-
- // Set the image URL as the title of the `ContextMenu`.
- menu.setHeaderTitle(imageUrl);
-
- // Add a `View Image` entry.
+ // Add a View Image entry.
menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
loadUrl(imageUrl);
return false;
// Override `onBackPressed` to handle the navigation drawer and and the WebView.
@Override
public void onBackPressed() {
- // Get a handle for the drawer layout.
+ // Get a handle for the drawer layout and the tab layout.
DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+ TabLayout tabLayout = findViewById(R.id.tablayout);
if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open.
// Close the navigation drawer.
// Go back.
currentWebView.goBack();
- } else { // There is nothing else to do.
- // Load a blank website.
- loadUrl("");
+ } else if (tabLayout.getTabCount() > 1) { // There are at least two tabs.
+ // Close the current tab.
+ closeCurrentTab();
+ } else { // There isn't anything to do in Privacy Browser.
+ // Run the default commands.
+ super.onBackPressed();
+
+ // Manually kill Privacy Browser. Otherwise, it is glitchy when restarted.
+ System.exit(0);
}
}
// Delete the contents of `find_on_page_edittext`.
findOnPageEditText.setText(null);
- // Clear the highlighted phrases.
- currentWebView.clearMatches();
+ // Clear the highlighted phrases if the WebView is not null.
+ if (currentWebView != null) {
+ currentWebView.clearMatches();
+ }
// Hide the find on page linear layout.
findOnPageLinearLayout.setVisibility(View.GONE);
FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
ActionBar actionBar = getSupportActionBar();
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
customHeaders.remove("DNT");
}
- // Get the current layout parameters. Using coordinator layout parameters allows the `setBehavior()` command.
- CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+ // Get the current layout parameters. Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
+ CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+ AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
+ AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
+ AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
// Add the scrolling behavior to the layout parameters.
if (scrollAppBar) {
// Enable scrolling of the app bar.
- layoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
+ swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
+ toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+ findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+ tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
} else {
// Disable scrolling of the app bar.
- layoutParams.setBehavior(null);
+ swipeRefreshLayoutParams.setBehavior(null);
+ toolbarLayoutParams.setScrollFlags(0);
+ findOnPageLayoutParams.setScrollFlags(0);
+ tabsLayoutParams.setScrollFlags(0);
// Expand the app bar if it is currently collapsed.
appBarLayout.setExpanded(true);
}
- // Apply the modified layout parameters to the swipe refresh layout.
- swipeRefreshLayout.setLayoutParams(layoutParams);
+ // Apply the modified layout parameters.
+ swipeRefreshLayout.setLayoutParams(swipeRefreshLayoutParams);
+ toolbar.setLayoutParams(toolbarLayoutParams);
+ findOnPageLinearLayout.setLayoutParams(findOnPageLayoutParams);
+ tabsLinearLayout.setLayoutParams(tabsLayoutParams);
// Set the app bar scrolling for each WebView.
for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
// Get the corresponding tab.
TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
- // Remove the warning below that the tab might be null.
- assert tab != null;
-
- // Get the tab custom view.
- View tabCustomView = tab.getCustomView();
+ // Update the tab if it isn't null, which sometimes happens when restarting from the background.
+ if (tab != null) {
+ // Get the tab custom view.
+ View tabCustomView = tab.getCustomView();
- // Remove the warning below that the tab custom view might be null.
- assert tabCustomView != null;
+ // Remove the warning below that the tab custom view might be null.
+ assert tabCustomView != null;
- // Get the tab views.
- ImageView tabFavoriteIconImageView = tabCustomView.findViewById(R.id.favorite_icon_imageview);
- TextView tabTitleTextView = tabCustomView.findViewById(R.id.title_textview);
+ // Get the tab views.
+ ImageView tabFavoriteIconImageView = tabCustomView.findViewById(R.id.favorite_icon_imageview);
+ TextView tabTitleTextView = tabCustomView.findViewById(R.id.title_textview);
- // Set the default favorite icon as the favorite icon for this tab.
- tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteOrDefaultIcon(), 64, 64, true));
+ // Set the default favorite icon as the favorite icon for this tab.
+ tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteOrDefaultIcon(), 64, 64, true));
- // Set the loading title text.
- tabTitleTextView.setText(R.string.loading);
+ // 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`.
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();
-
- // Remove the incorrect lint warning later that the action bar might be null.
- assert actionBar != null;
+ // Get a handle for the app bar layout.
+ AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
// Set the homepage, search, and proxy options.
if (proxyThroughOrbot) { // Set the Tor options.
// 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.
+ // Set the app bar background to indicate proxying through Orbot is enabled.
if (darkTheme) {
- actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30));
+ appBarLayout.setBackgroundResource(R.color.dark_blue_30);
} else {
- actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
+ appBarLayout.setBackgroundResource(R.color.blue_50);
}
// Check to see if Orbot is ready.
// Reset the proxy to default. The host is `""` and the port is `"0"`.
OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
- // Set the default `appBar` background.
+ // Set the default app bar layout background.
if (darkTheme) {
- actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900));
+ appBarLayout.setBackgroundResource(R.color.gray_900);
} else {
- actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100));
+ appBarLayout.setBackgroundResource(R.color.gray_100);
}
// Reset `waitingForOrbot.
webViewPagerAdapter.addPage(newTabNumber, webViewPager);
}
+ private void closeCurrentTab() {
+ // Get handles for the views.
+ AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
+ TabLayout tabLayout = findViewById(R.id.tablayout);
+ ViewPager webViewPager = findViewById(R.id.webviewpager);
+
+ // Get the current tab number.
+ int currentTabNumber = tabLayout.getSelectedTabPosition();
+
+ // Delete the current tab.
+ tabLayout.removeTabAt(currentTabNumber);
+
+ // Delete the current page. If the selected page number did not change during the delete, it will return true, meaning that the current WebView must be reset.
+ if (webViewPagerAdapter.deletePage(currentTabNumber, webViewPager)) {
+ setCurrentWebView(currentTabNumber);
+ }
+
+ // Expand the app bar if it is currently collapsed.
+ appBarLayout.setExpanded(true);
+ }
+
private void setCurrentWebView(int pageNumber) {
// Get a handle for the shared preferences.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
// Get the current URL.
String url = currentWebView.getUrl();
- if ((url == null) || url.equals("about:blank")) { // The WebView is blank.
- // Display the hint in the URL edit text.
- urlEditText.setText("");
+ // Update the URL edit text if not loading a new intent. Otherwise, this will be handled by `onPageStarted()` (if called) and `onPageFinished()`.
+ if (!loadingNewIntent) { // A new intent is not being loaded.
+ if ((url == null) || url.equals("about:blank")) { // The WebView is blank.
+ // Display the hint in the URL edit text.
+ urlEditText.setText("");
- // Request focus for the URL text box.
- urlEditText.requestFocus();
+ // Request focus for the URL text box.
+ urlEditText.requestFocus();
- // Display the keyboard.
- inputMethodManager.showSoftInput(urlEditText, 0);
- } else { // The WebView has a loaded URL.
- // Clear the focus from the URL text box.
- urlEditText.clearFocus();
+ // Display the keyboard.
+ inputMethodManager.showSoftInput(urlEditText, 0);
+ } else { // The WebView has a loaded URL.
+ // Clear the focus from the URL text box.
+ urlEditText.clearFocus();
- // Hide the soft keyboard.
- inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
+ // Hide the soft keyboard.
+ inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
- // Display the current URL in the URL text box.
- urlEditText.setText(url);
+ // Display the current URL in the URL text box.
+ urlEditText.setText(url);
- // Highlight the URL text.
- highlightUrlText();
+ // Highlight the URL text.
+ highlightUrlText();
+ }
+ } else { // A new intent is being loaded.
+ // Reset the loading new intent tracker.
+ loadingNewIntent = false;
}
// Set the background to indicate the domain settings status.
// Update the URL text bar if the page is currently selected.
if (tabLayout.getSelectedTabPosition() == currentPagePosition) {
+ // Clear the focus from the URL edit text.
+ urlEditText.clearFocus();
+
// Display the formatted URL text.
urlEditText.setText(url);
// 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 the current website information against any pinned domain information if the current IP addresses have been loaded.
+ if ((nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses()) && nestedScrollWebView.hasCurrentIpAddresses() &&
+ !nestedScrollWebView.ignorePinnedDomainInformation()) {
+ CheckPinnedMismatchHelper.checkPinnedMismatch(getSupportFragmentManager(), nestedScrollWebView);
+ }
+
+ // Get the current URL from the nested scroll WebView. This is more accurate than using the URL passed into the method, which is sometimes not the final one.
+ String currentUrl = nestedScrollWebView.getUrl();
+
+ // Update the URL text bar if the page is currently selected and the user is not currently typing in the URL edit text.
+ // Crash records show that, in some crazy way, it is possible for the current URL to be blank at this point.
+ // Probably some sort of race condition when Privacy Browser is being resumed.
+ if ((tabLayout.getSelectedTabPosition() == currentPagePosition) && !urlEditText.hasFocus() && (currentUrl != null)) {
// Check to see if the URL is `about:blank`.
- if (url.equals("about:blank")) { // The WebView is blank.
+ if (currentUrl.equals("about:blank")) { // The WebView is blank.
// Display the hint in the URL edit text.
urlEditText.setText("");
// 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());
+ // 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(currentUrl);
- // Apply text highlighting to the URL.
- highlightUrlText();
- }
+ // Apply text highlighting to the URL.
+ highlightUrlText();
}
}
- // Check the current website information against any pinned domain information if the current IP addresses have been loaded.
- if ((nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses()) && nestedScrollWebView.hasCurrentIpAddresses() &&
- !nestedScrollWebView.ignorePinnedDomainInformation()) {
- CheckPinnedMismatchHelper.checkPinnedMismatch(getSupportFragmentManager(), nestedScrollWebView);
+ // Get the current tab.
+ TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
+
+ // Only populate the title text view if the tab has been fully created.
+ if (tab != null) {
+ // Get the custom view from the tab.
+ View tabView = tab.getCustomView();
+
+ // Remove the incorrect warning below that the current tab view might be null.
+ assert tabView != null;
+
+ // Get the title text view from the tab.
+ TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
+
+ // Set the title as the tab text. Sometimes `onReceivedTitle()` is not called, especially when navigating history.
+ tabTitleTextView.setText(nestedScrollWebView.getTitle());
}
}
}