import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
import com.stoutner.privacybrowser.helpers.ProxyHelper;
+import com.stoutner.privacybrowser.helpers.SanitizeUrlHelper;
import com.stoutner.privacybrowser.views.NestedScrollWebView;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Date;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import kotlin.Pair;
+
public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener,
PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener,
public static String proxyMode = ProxyHelper.NONE;
// Declare the public static variables.
- public static String currentBookmarksFolder;
+ public static String currentBookmarksFolder = "";
public static boolean restartFromBookmarksActivity;
public static WebViewPagerAdapter webViewPagerAdapter;
// The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
public final static int UNRECOGNIZED_USER_AGENT = -1;
public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
- public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
+ public final static int SETTINGS_CUSTOM_USER_AGENT = 11;
public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
- public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
+ public final static int DOMAINS_CUSTOM_USER_AGENT = 12;
// Define the start activity for result request codes. The public static entry is accessed from `OpenDialog()`.
private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 0;
// `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
private NestedScrollWebView currentWebView;
- // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
- private final Map<String, String> customHeaders = new HashMap<>();
-
// The search URL is set in `applyAppSettings()` and used in `onNewIntent()`, `loadUrlFromTextBox()`, `initializeApp()`, and `initializeWebView()`.
private String searchURL;
private int defaultProgressViewStartOffset;
private int defaultProgressViewEndOffset;
- // The URL sanitizers are set in `applyAppSettings()` and used in `sanitizeUrl()`.
- private boolean sanitizeGoogleAnalytics;
- private boolean sanitizeFacebookClickIds;
- private boolean sanitizeTwitterAmpRedirects;
+ // Declare the helpers.
+ private BookmarksDatabaseHelper bookmarksDatabaseHelper;
+ private DomainsDatabaseHelper domainsDatabaseHelper;
+ private ProxyHelper proxyHelper;
+ private SanitizeUrlHelper sanitizeUrlHelper;
// Declare the class variables
- private BookmarksDatabaseHelper bookmarksDatabaseHelper;
private boolean bottomAppBar;
private boolean displayingFullScreenVideo;
private boolean downloadWithExternalApp;
private boolean inFullScreenBrowsingMode;
private boolean loadingNewIntent;
private BroadcastReceiver orbotStatusBroadcastReceiver;
- private ProxyHelper proxyHelper;
private boolean reapplyAppSettingsOnRestart;
private boolean reapplyDomainSettingsOnRestart;
+ private boolean sanitizeAmpRedirects;
+ private boolean sanitizeTrackingQueries;
private boolean scrollAppBar;
private boolean waitingForProxy;
private String webViewDefaultUserAgent;
Snackbar.make(currentWebView, getString(R.string.file_saved) + " " + fileNameString, Snackbar.LENGTH_SHORT).show();
} catch (Exception exception) {
// Display a snackbar with the exception.
- Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
+ Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + exception, Snackbar.LENGTH_INDEFINITE).show();
} finally {
// Delete the temporary MHT file.
//noinspection ResultOfMethodCallIgnored
});
} catch (IOException ioException) {
// Display a snackbar with the IO exception.
- Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + ioException.toString(), Snackbar.LENGTH_INDEFINITE).show();
+ Snackbar.make(currentWebView, getString(R.string.error_saving_file) + " " + ioException, Snackbar.LENGTH_INDEFINITE).show();
}
}
}
// Enable the drawing of the entire webpage. This makes it possible to save a website image. This must be done before anything else happens with the WebView.
WebView.enableSlowWholeDocumentDraw();
- // Set the theme.
- setTheme(R.style.PrivacyBrowser);
-
- // Set the content view.
- if (bottomAppBar) {
- setContentView(R.layout.main_framelayout_bottom_appbar);
- } else {
- setContentView(R.layout.main_framelayout_top_appbar);
- }
+ // Set the content view according to the position of the app bar.
+ if (bottomAppBar) setContentView(R.layout.main_framelayout_bottom_appbar);
+ else setContentView(R.layout.main_framelayout_top_appbar);
// Get handles for the views.
rootFrameLayout = findViewById(R.id.root_framelayout);
// Store up to 100 tabs in memory.
webViewPager.setOffscreenPageLimit(100);
- // Instantiate the proxy helper.
+ // Instantiate the helpers.
+ bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this);
+ domainsDatabaseHelper = new DomainsDatabaseHelper(this);
proxyHelper = new ProxyHelper();
+ sanitizeUrlHelper = new SanitizeUrlHelper();
// Initialize the app.
initializeApp();
// Run the default commands.
super.onNewIntent(intent);
- // Replace the intent that started the app with this one.
- setIntent(intent);
-
// Check to see if the app is being restarted from a saved state.
if (savedStateArrayList == null || savedStateArrayList.size() == 0) { // The activity is not being restarted from a saved state.
// Get the information from the intent.
drawerLayout.closeDrawer(GravityCompat.END);
}
}
+ } else { // The app has been restarted.
+ // Replace the intent that started the app with this one. This will load the tab after the others have been restored.
+ setIntent(intent);
}
}
// Set the title.
optionsRefreshMenuItem.setTitle(R.string.stop);
- // Set the icon if it is displayed in the app bar.
+ // Set the icon if it is displayed in the app bar. Once the minimum API is >= 26, the blue and black icons can be combined with a tint list.
if (displayAdditionalAppBarIcons) {
- // Get the current theme status.
- int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
- // Set the icon according to the current theme status.
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
- optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
- } else {
- optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
- }
+ optionsRefreshMenuItem.setIcon(R.drawable.close_blue);
}
}
// Consume the event.
return true;
- } else if (menuItemId == R.id.share_url) { // Share URL.
- // Setup the share string.
+ } else if (menuItemId == R.id.share_message) { // Share a message.
+ // Prepare the share string.
String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
// Create the share intent.
- Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ Intent shareMessageIntent = new Intent(Intent.ACTION_SEND);
// Add the share string to the intent.
- shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
+ shareMessageIntent.putExtra(Intent.EXTRA_TEXT, shareString);
// Set the MIME type.
- shareIntent.setType("text/plain");
+ shareMessageIntent.setType("text/plain");
// Set the intent to open in a new task.
- shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ shareMessageIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// Make it so.
- startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
+ startActivity(Intent.createChooser(shareMessageIntent, getString(R.string.share_message)));
+
+ // Consume the event.
+ return true;
+ } else if (menuItemId == R.id.share_url) { // Share URL.
+ // Create the share intent.
+ Intent shareUrlIntent = new Intent(Intent.ACTION_SEND);
+
+ // Add the URL to the intent.
+ shareUrlIntent.putExtra(Intent.EXTRA_TEXT, currentWebView.getUrl());
+
+ // Add the title to the intent.
+ shareUrlIntent.putExtra(Intent.EXTRA_SUBJECT, currentWebView.getTitle());
+
+ // Set the MIME type.
+ shareUrlIntent.setType("text/plain");
+
+ // Set the intent to open in a new task.
+ shareUrlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ //Make it so.
+ startActivity(Intent.createChooser(shareUrlIntent, getString(R.string.share_url)));
// Consume the event.
return true;
Uri currentUri = Uri.parse(currentWebView.getUrl());
String currentDomain = currentUri.getHost();
- // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
- DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
-
// Create the domain and store the database ID.
int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
// Initialize the formatted URL string.
String url = "";
- // Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search.
+ // Check to see if the unformatted URL string is a valid URL. Otherwise, convert it into a search.
if (unformattedUrlString.startsWith("content://")) { // This is a Content URL.
// Load the entire content URL.
url = unformattedUrlString;
} else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
unformattedUrlString.startsWith("file://")) { // This is a standard URL.
// Add `https://` at the beginning if there is no protocol. Otherwise the app will segfault.
- if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
+ if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://")) {
unformattedUrlString = "https://" + unformattedUrlString;
}
- // Initialize `unformattedUrl`.
+ // Initialize the unformatted URL.
URL unformattedUrl = null;
- // Convert `unformattedUrlString` to a `URL`, then to a `URI`, and then back to a `String`, which sanitizes the input and adds in any missing components.
+ // Convert the unformatted URL string to a URL, then to a URI, and then back to a string, which sanitizes the input and adds in any missing components.
try {
unformattedUrl = new URL(unformattedUrlString);
} catch (MalformedURLException e) {
currentWebView.loadUrl(temporaryMhtFile.toString());
} catch (Exception exception) {
// Display a snackbar.
- Snackbar.make(currentWebView, getString(R.string.error) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
+ Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show();
}
} else { // Let the WebView handle opening of the file.
// Open the file.
drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
- // Initialize the bookmarks database helper. The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
- bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
-
- // Initialize `currentBookmarksFolder`. `""` is the home folder in the database.
- currentBookmarksFolder = "";
-
- // Load the home folder, which is `""` in the database.
+ // Load the bookmarks folder.
loadBookmarksFolder();
+ // Handle clicks on bookmarks.
bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
// Convert the id from long to int to match the format of the bookmarks database.
int databaseId = (int) id;
addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), false);
// Display a snackbar.
- Snackbar.make(currentWebView, R.string.bookmark_opened_in_background, Snackbar.LENGTH_SHORT).show();
+ Snackbar.make(drawerLayout, R.string.bookmark_opened_in_background, Snackbar.LENGTH_SHORT).show();
}
// Consume the event.
}
});
- // Replace the header that `WebView` creates for `X-Requested-With` with a null value. The default value is the application ID (com.stoutner.privacybrowser.standard).
- customHeaders.put("X-Requested-With", "");
-
// Inflate a bare WebView to get the default user agent. It is not used to render content on the screen.
@SuppressLint("InflateParams") View webViewLayout = getLayoutInflater().inflate(R.layout.bare_webview, null, false);
// Store the values from the shared preferences in variables.
incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
- sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true);
- sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true);
- sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true);
+ sanitizeTrackingQueries = sharedPreferences.getBoolean(getString(R.string.tracking_queries_key), true);
+ sanitizeAmpRedirects = sharedPreferences.getBoolean(getString(R.string.amp_redirects_key), true);
proxyMode = sharedPreferences.getString("proxy", getString(R.string.proxy_default_value));
fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
downloadWithExternalApp = sharedPreferences.getBoolean(getString(R.string.download_with_external_app_key), false);
// Apply the proxy.
applyProxy(false);
- // Adjust the layout and scrolling parameters if the app bar is at the top of the screen.
- if (!bottomAppBar) {
+ // Adjust the layout and scrolling parameters according to the position of the app bar.
+ if (bottomAppBar) { // The app bar is on the bottom.
+ // Adjust the UI.
+ if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) { // The app bar scrolls or full screen browsing mode is engaged with the app bar hidden.
+ // Reset the WebView padding to fill the available space.
+ swipeRefreshLayout.setPadding(0, 0, 0, 0);
+ } else { // The app bar doesn't scroll or full screen browsing mode is not engaged with the app bar hidden.
+ // Move the WebView above the app bar layout.
+ swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight);
+
+ // Show the app bar if it is scrolled off the screen.
+ if (appBarLayout.getTranslationY() != 0) {
+ // Animate the bottom app bar onto the screen.
+ objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0);
+
+ // Make it so.
+ objectAnimator.start();
+ }
+ }
+ } else { // The app bar is on the top.
// Get the current layout parameters. Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
}
}
- // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
- DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
-
- // Get a full cursor from `domainsDatabaseHelper`.
+ // Get a full domain name cursor.
Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
// Initialize `domainSettingsSet`.
// Get the domain name column index.
int domainNameColumnIndex = domainNameCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME);
- // Populate `domainSettingsSet`.
+ // Populate the domain settings set.
for (int i = 0; i < domainNameCursor.getCount(); i++) {
// Move the domains cursor to the current row.
domainNameCursor.moveToPosition(i);
domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
}
- // Close `domainNameCursor.
+ // Close the domain name cursor.
domainNameCursor.close();
// Initialize the domain name in database variable.
}
// Check all the subdomains of the host name against wildcard domains in the domain cursor.
- while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name.
+ while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the hostname.
if (domainSettingsSet.contains("*." + newHostName)) { // Check the host name prepended by `*.`.
// Set the domain settings applied tracker to true.
nestedScrollWebView.setDomainSettingsApplied(true);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
// Store the general preference information.
+ boolean defaultXRequestedWithHeader = sharedPreferences.getBoolean(getString(R.string.x_requested_with_header_key), true);
String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
String[] userAgentDataArray = getResources().getStringArray(R.array.user_agent_data);
if (nestedScrollWebView.getDomainSettingsApplied()) { // The url has custom domain settings.
- // Get a cursor for the current host and move it to the first position.
+ // Remove the incorrect lint warning below that the domain name in database might be null.
+ assert domainNameInDatabase != null;
+
+ // Get a cursor for the current host.
Cursor currentDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
+
+ // Move to the first position.
currentDomainSettingsCursor.moveToFirst();
// Get the settings from the cursor.
- nestedScrollWebView.setDomainSettingsDatabaseId(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper._ID)));
+ nestedScrollWebView.setDomainSettingsDatabaseId(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ID)));
nestedScrollWebView.getSettings().setJavaScriptEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
nestedScrollWebView.setAcceptCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.COOKIES)) == 1);
nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
boolean saveFormData = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
nestedScrollWebView.setEasyListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
nestedScrollWebView.setEasyPrivacyEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
- nestedScrollWebView.setFanboysAnnoyanceListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
+ nestedScrollWebView.setFanboysAnnoyanceListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(
+ DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
nestedScrollWebView.setFanboysSocialBlockingListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(
DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
nestedScrollWebView.setUltraListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ULTRALIST)) == 1);
nestedScrollWebView.setUltraPrivacyEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
- nestedScrollWebView.setBlockAllThirdPartyRequests(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
+ nestedScrollWebView.setBlockAllThirdPartyRequests(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(
+ DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
String userAgentName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.USER_AGENT));
+ int xRequestedWithHeaderInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.X_REQUESTED_WITH_HEADER));
int fontSize = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.FONT_SIZE));
int swipeToRefreshInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
int webViewThemeInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WEBVIEW_THEME));
nestedScrollWebView.getSettings().setSaveFormData(saveFormData);
}
+ // Set the X-Requested-With header.
+ switch (xRequestedWithHeaderInt) {
+ case DomainsDatabaseHelper.SYSTEM_DEFAULT:
+ if (defaultXRequestedWithHeader)
+ nestedScrollWebView.setXRequestedWithHeader();
+ else
+ nestedScrollWebView.resetXRequestedWithHeader();
+ break;
+
+ case DomainsDatabaseHelper.ENABLED:
+ nestedScrollWebView.setXRequestedWithHeader();
+ break;
+
+ case DomainsDatabaseHelper.DISABLED:
+ nestedScrollWebView.resetXRequestedWithHeader();
+ break;
+ }
+
// Apply the font size.
try { // Try the specified font size to see if it is valid.
if (fontSize == 0) { // Apply the default font size.
// Disable swipe to refresh.
swipeRefreshLayout.setEnabled(false);
+ break;
}
// Check to see if WebView themes are supported.
break;
}
- // Get the current theme status.
- int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
// Set a background on the URL relative layout to indicate that custom domain settings are being used.
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
- urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_light_green, null));
- } else {
- urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_dark_blue, null));
- }
+ urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.domain_settings_url_background, null));
} else { // The new URL does not have custom domain settings. Load the defaults.
// Store the values from the shared preferences.
nestedScrollWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false));
nestedScrollWebView.getSettings().setSaveFormData(saveFormData);
}
+ // Store the X-Requested-With header status in the nested scroll WebView.
+ if (defaultXRequestedWithHeader)
+ nestedScrollWebView.setXRequestedWithHeader();
+ else
+ nestedScrollWebView.resetXRequestedWithHeader();
+
// Store the swipe to refresh status in the nested scroll WebView.
nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh);
// Load the URL if directed. This makes sure that the domain settings are properly loaded before the URL. By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent.
if (loadUrl) {
- nestedScrollWebView.loadUrl(url, customHeaders);
+ nestedScrollWebView.loadUrl(url, nestedScrollWebView.getXRequestedWithHeader());
}
}
optionsPrivacyMenuItem.setIcon(R.drawable.privacy_mode);
}
- // Get the current theme status.
- int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
// Update the cookies icon.
- if (currentWebView.getAcceptCookies()) { // Cookies are enabled.
+ if (currentWebView.getAcceptCookies()) {
optionsCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
- } else { // Cookies are disabled.
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
- optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled_day);
- } else {
- optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled_night);
- }
+ } else {
+ optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled);
}
// Update the refresh icon.
if (optionsRefreshMenuItem.getTitle() == getString(R.string.refresh)) { // The refresh icon is displayed.
- // Set the icon according to the theme.
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
- optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_day);
- } else {
- optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_night);
- }
+ // Set the icon. Once the minimum API is >= 26, the blue and black icons can be combined with a tint list.
+ optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled);
} else { // The stop icon is displayed.
- // Set the icon according to the theme.
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
- optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
- } else {
- optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
- }
+ // Set the icon. Once the minimum API is >= 26, the blue and black icons can be combined with a tint list.
+ optionsRefreshMenuItem.setIcon(R.drawable.close_blue);
}
// `invalidateOptionsMenu()` calls `onPrepareOptionsMenu()` and redraws the icons in the app bar.
}
private String sanitizeUrl(String url) {
- // Sanitize Google Analytics.
- if (sanitizeGoogleAnalytics) {
- // Remove `?utm_`.
- if (url.contains("?utm_")) {
- url = url.substring(0, url.indexOf("?utm_"));
- }
-
- // Remove `&utm_`.
- if (url.contains("&utm_")) {
- url = url.substring(0, url.indexOf("&utm_"));
- }
- }
-
- // Sanitize Facebook Click IDs.
- if (sanitizeFacebookClickIds) {
- // Remove `?fbclid=`.
- if (url.contains("?fbclid=")) {
- url = url.substring(0, url.indexOf("?fbclid="));
- }
-
- // Remove `&fbclid=`.
- if (url.contains("&fbclid=")) {
- url = url.substring(0, url.indexOf("&fbclid="));
- }
-
- // Remove `?fbadid=`.
- if (url.contains("?fbadid=")) {
- url = url.substring(0, url.indexOf("?fbadid="));
- }
+ // Sanitize tracking queries.
+ if (sanitizeTrackingQueries)
+ url = sanitizeUrlHelper.sanitizeTrackingQueries(url);
- // Remove `&fbadid=`.
- if (url.contains("&fbadid=")) {
- url = url.substring(0, url.indexOf("&fbadid="));
- }
- }
-
- // Sanitize Twitter AMP redirects.
- if (sanitizeTwitterAmpRedirects) {
- // Remove `?amp=1`.
- if (url.contains("?amp=1")) {
- url = url.substring(0, url.indexOf("?amp=1"));
- }
- }
+ // Sanitize AMP redirects.
+ if (sanitizeAmpRedirects)
+ url = sanitizeUrlHelper.sanitizeAmpRedirects(url);
// Return the sanitized URL.
return url;
}
}
- // Clear the custom headers.
- customHeaders.clear();
-
// Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
// See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
if (clearEverything) {
// Set the background to indicate the domain settings status.
if (currentWebView.getDomainSettingsApplied()) {
- // Get the current theme status.
- int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
- // Set a green background on the URL relative layout to indicate that custom domain settings are being used.
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
- urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_light_green, null));
- } else {
- urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_dark_blue, null));
- }
+ // Set a background on the URL relative layout to indicate that custom domain settings are being used.
+ urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.domain_settings_url_background, null));
} else {
+ // Remove any background on the URL relative layout.
urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null));
}
} else { // The fragment has not been populated. Try again in 100 milliseconds.
// Toggle the full screen browsing mode.
if (inFullScreenBrowsingMode) { // Switch to full screen mode.
// Hide the app bar if specified.
- if (hideAppBar) {
+ if (hideAppBar) { // The app bar is hidden.
// Close the find on page bar if it is visible.
closeFindOnPage(null);
// Hide the action bar.
actionBar.hide();
- // Set layout and scrolling parameters if the app bar is at the top of the screen.
- if (!bottomAppBar) {
+ // Set layout and scrolling parameters according to the position of the app bar.
+ if (bottomAppBar) { // The app bar is at the bottom.
+ // Reset the WebView padding to fill the available space.
+ swipeRefreshLayout.setPadding(0, 0, 0, 0);
+ } else { // The app bar is at the top.
// Check to see if the app bar is normally scrolled.
if (scrollAppBar) { // The app bar is scrolled when it is displayed.
// Get the swipe refresh layout parameters.
swipeRefreshLayout.setProgressViewOffset(false, -200, defaultProgressViewEndOffset);
}
}
+ } else { // The app bar is not hidden.
+ // Adjust the UI for the bottom app bar.
+ if (bottomAppBar) {
+ // Adjust the UI according to the scrolling of the app bar.
+ if (scrollAppBar) {
+ // Reset the WebView padding to fill the available space.
+ swipeRefreshLayout.setPadding(0, 0, 0, 0);
+ } else {
+ // Move the WebView above the app bar layout.
+ swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight);
+ }
+ }
}
/* Hide the system bars.
// Show the action bar.
actionBar.show();
+ }
- // Set layout and scrolling parameters if the app bar is at the top of the screen.
- if (!bottomAppBar) {
- // Check to see if the app bar is normally scrolled.
- if (scrollAppBar) { // The app bar is scrolled when it is displayed.
- // Get the swipe refresh layout parameters.
- CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
-
- // Add the off-screen scrolling layout.
- swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
- } else { // The app bar is not scrolled when it is displayed.
- // The swipe refresh layout must be manually moved below the app bar layout.
- swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
-
- // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
- swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
- }
+ // Set layout and scrolling parameters according to the position of the app bar.
+ if (bottomAppBar) { // The app bar is at the bottom.
+ // Adjust the UI.
+ if (scrollAppBar) {
+ // Reset the WebView padding to fill the available space.
+ swipeRefreshLayout.setPadding(0, 0, 0, 0);
+ } else {
+ // Move the WebView above the app bar layout.
+ swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight);
+ }
+ } else { // The app bar is at the top.
+ // Check to see if the app bar is normally scrolled.
+ if (scrollAppBar) { // The app bar is scrolled when it is displayed.
+ // Get the swipe refresh layout parameters.
+ CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+
+ // Add the off-screen scrolling layout.
+ swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
+ } else { // The app bar is not scrolled when it is displayed.
+ // The swipe refresh layout must be manually moved below the app bar layout.
+ swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
+
+ // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
+ swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
}
}
return false;
}
}
+
+ @Override
+ public boolean onFling(MotionEvent motionEvent1, MotionEvent motionEvent2, float velocityX, float velocityY) {
+ // Scroll the bottom app bar if enabled.
+ if (bottomAppBar && scrollAppBar && !objectAnimator.isRunning()) {
+ // Calculate the Y change.
+ float motionY = motionEvent2.getY() - motionEvent1.getY();
+
+ // Scroll the app bar if the change is greater than 50 pixels.
+ if (motionY > 50) {
+ // Animate the bottom app bar onto the screen.
+ objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0);
+ } else if (motionY < -50) {
+ // Animate the bottom app bar off the screen.
+ objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", appBarLayout.getHeight());
+ }
+
+ // Make it so.
+ objectAnimator.start();
+ }
+
+ // Do not consume the event.
+ return false;
+ }
});
// Pass all touch events on the WebView through the double-tap gesture detector.
}
});
- // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView. Also reinforce full screen browsing mode.
+ // Process scroll changes.
nestedScrollWebView.setOnScrollChangeListener((view, scrollX, scrollY, oldScrollX, oldScrollY) -> {
// Set the swipe to refresh status.
if (nestedScrollWebView.getSwipeToRefresh()) {
swipeRefreshLayout.setEnabled(false);
}
- // Scroll the bottom app bar if enabled.
- if (bottomAppBar && scrollAppBar && !objectAnimator.isRunning()) {
- if (scrollY < oldScrollY) { // The WebView was scrolled down.
- // Animate the bottom app bar onto the screen.
- objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0);
-
- // Make it so.
- objectAnimator.start();
- } else if (scrollY > oldScrollY) { // The WebView was scrolled up.
- // Animate the bottom app bar off the screen.
- objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", appBarLayout.getHeight());
-
- // Make it so.
- objectAnimator.start();
- }
- }
-
// Reinforce the system UI visibility flags if in full screen browsing mode.
// This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
if (inFullScreenBrowsingMode) {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
- // Set the padding and layout settings if the app bar is at the top of the screen.
- if (!bottomAppBar) {
+ // Get the app bar layout height. This can't be done in `applyAppSettings()` because the app bar is not yet populated there.
+ // This should only be populated if it is greater than 0 because otherwise it will be reset to 0 if the app bar is hidden in full screen browsing mode.
+ if (appBarLayout.getHeight() > 0) appBarHeight = appBarLayout.getHeight();
+
+ // Set the padding and layout settings according to the position of the app bar.
+ if (bottomAppBar) { // The app bar is on the bottom.
+ // Adjust the UI.
+ if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) { // The app bar scrolls or full screen browsing mode is engaged with the app bar hidden.
+ // Reset the WebView padding to fill the available space.
+ swipeRefreshLayout.setPadding(0, 0, 0, 0);
+ } else { // The app bar doesn't scroll or full screen browsing mode is not engaged with the app bar hidden.
+ // Move the WebView above the app bar layout.
+ swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight);
+ }
+ } else { // The app bar is on the top.
// Set the top padding of the swipe refresh layout according to the app bar scrolling preference. This can't be done in `appAppSettings()` because the app bar is not yet populated there.
if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {
// No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior.
// The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10, defaultProgressViewEndOffset);
} else {
- // Get the app bar layout height. This can't be done in `applyAppSettings()` because the app bar is not yet populated there.
- appBarHeight = appBarLayout.getHeight();
-
// The swipe refresh layout must be manually moved below the app bar layout.
swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
// Get the app bar and theme preferences.
boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false);
- // If the icon is displayed in the AppBar, set it according to the theme.
+ // Set the icon if it is displayed in the AppBar. Once the minimum API is >= 26, the blue and black icons can be combined with a tint list.
if (displayAdditionalAppBarIcons) {
- // Get the current theme status.
- int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
- // Set the stop icon according to the theme.
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
- optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
- } else {
- optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
- }
+ optionsRefreshMenuItem.setIcon(R.drawable.close_blue);
}
}
}
// If the icon is displayed in the app bar, reset it according to the theme.
if (displayAdditionalAppBarIcons) {
- // Get the current theme status.
- int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
- // Set the icon according to the theme.
- if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
- optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_day);
- } else {
- optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_night);
- }
+ // Set the icon.
+ optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled);
}
}
}
}
- // Handle SSL Certificate errors.
+ // Handle SSL Certificate errors. Suppress the lint warning that ignoring the error might be dangerous.
+ @SuppressLint("WebViewClientOnReceivedSslError")
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
// Get the current website SSL certificate.
// Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
if (nestedScrollWebView.hasPinnedSslCertificate()) {
// Get the pinned SSL certificate.
- ArrayList<Object> pinnedSslCertificateArrayList = nestedScrollWebView.getPinnedSslCertificate();
+ Pair<String[], Date[]> pinnedSslCertificatePair = nestedScrollWebView.getPinnedSslCertificate();
// Extract the arrays from the array list.
- String[] pinnedSslCertificateStringArray = (String[]) pinnedSslCertificateArrayList.get(0);
- Date[] pinnedSslCertificateDateArray = (Date[]) pinnedSslCertificateArrayList.get(1);
+ String[] pinnedSslCertificateStringArray = pinnedSslCertificatePair.getFirst();
+ Date[] pinnedSslCertificateDateArray = pinnedSslCertificatePair.getSecond();
// Check if the current SSL certificate matches the pinned certificate.
if (currentWebsiteIssuedToCName.equals(pinnedSslCertificateStringArray[0]) && currentWebsiteIssuedToOName.equals(pinnedSslCertificateStringArray[1]) &&