/*
- * Copyright © 2015-2018 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2015-2019 Soren Stoutner <soren@stoutner.com>.
*
* Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
*
private ForegroundColorSpan initialGrayColorSpan;
private ForegroundColorSpan finalGrayColorSpan;
+ // The drawer header padding variables are used in `onCreate()` and `onConfigurationChanged()`.
+ private int drawerHeaderPaddingLeftAndRight;
+ private int drawerHeaderPaddingTop;
+ private int drawerHeaderPaddingBottom;
+
// `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
private SslErrorHandler sslErrorHandler;
// Update `findOnPageCountTextView`.
mainWebView.setFindListener(new WebView.FindListener() {
// Get a handle for `findOnPageCountTextView`.
- final TextView findOnPageCountTextView = (TextView) findViewById(R.id.find_on_page_count_textview);
+ final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
@Override
public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
final MenuItem navigationRequestsMenuItem = navigationMenu.getItem(4);
- // Initialize the bookmarks database helper. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`.
- // The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
+ // 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.
int databaseID = (int) id;
// Get the bookmark cursor for this ID and move it to the first row.
- Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID);
+ Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
bookmarkCursor.moveToFirst();
// Act upon the bookmark according to the type.
float screenDensity = resources.getDisplayMetrics().density;
// Calculate the drawer header padding. This is used to move the text in the drawer headers below any cutouts.
- int drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
- int drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
- int drawerHeaderPaddingBottom = (int) (8 * screenDensity);
+ drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
+ drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
+ drawerHeaderPaddingBottom = (int) (8 * screenDensity);
- // The drawer listener is used to update the navigation menu.
+ // The drawer listener is used to update the navigation menu.`
drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
@Override
public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
TextView navigationHeaderTextView = findViewById(R.id.navigationText);
TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
- // Apply the calculated drawer paddings. This moves the text in the header below any cutouts.
- navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
- bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
+ // Apply the navigation header paddings if the view is not null (sometimes it is null if another activity has already started). This moves the text in the header below any cutouts.
+ if (navigationHeaderTextView != null) {
+ navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
+ }
+
+ // Apply the bookmarks header paddings if the view is not null (sometimes it is null if another activity has already started). This moves the text in the header below any cutouts.
+ if (bookmarksHeaderTextView != null) {
+ bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
+ }
// Update the back, forward, history, and requests menu items.
navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
// Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
Runnable displayWebViewRunnable = () -> {
- // Only display `mainWebView` if the progress bar is one. This prevents the display of the `WebView` while it is still loading.
+ // Only display `mainWebView` if the progress bar is gone. This prevents the display of the `WebView` while it is still loading.
if (progressBar.getVisibility() == View.GONE) {
mainWebView.setVisibility(View.VISIBLE);
}
// Check to see if Privacy Browser is waiting on Orbot.
if (!waitingForOrbot) { // We are not waiting on Orbot, so we need to process the URL.
- // We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
+ // 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.
public void onPageFinished(WebView view, String url) {
// Reset the wide view port if it has been turned off by the waiting for Orbot message.
if (!waitingForOrbot) {
- mainWebView.getSettings().setUseWideViewPort(true);
+ // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`.
+ mainWebView.getSettings().setUseWideViewPort(url.startsWith("http"));
}
// Flush any cookies to persistent storage. `CookieManager` has become very lazy about flushing cookies in recent versions.
// Apply the domain settings. This clears any settings from the previous domain.
applyDomainSettings(formattedUrlString, true, false);
} else { // `WebView` has loaded a webpage.
- // Set `formattedUrlString`.
- formattedUrlString = url;
+ // 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 = mainWebView.getUrl();
- // Only update `urlTextBox` if the user is not typing in it.
+ // Only update the URL text box if the user is not typing in it.
if (!urlTextBox.hasFocus()) {
// Display the formatted URL text.
urlTextBox.setText(formattedUrlString);
printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
return true;
+ case R.id.find_on_page:
+ // Hide the URL app bar.
+ supportAppBar.setVisibility(View.GONE);
+
+ // Show the Find on Page `RelativeLayout`.
+ findOnPageLinearLayout.setVisibility(View.VISIBLE);
+
+ // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android.
+ // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
+ findOnPageEditText.postDelayed(() -> {
+ // Set the focus on `findOnPageEditText`.
+ findOnPageEditText.requestFocus();
+
+ // Display the keyboard. `0` sets no input flags.
+ inputMethodManager.showSoftInput(findOnPageEditText, 0);
+ }, 200);
+ return true;
+
+ case R.id.add_to_homescreen:
+ // Show the alert dialog.
+ AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
+ createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
+
+ //Everything else will be handled by the alert dialog and the associated listener below.
+ return true;
+
case R.id.view_source:
// Launch the View Source activity.
Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
startActivity(viewSourceIntent);
return true;
- case R.id.proxy_through_orbot:
- // Toggle the proxy through Orbot variable.
- proxyThroughOrbot = !proxyThroughOrbot;
-
- // Apply the proxy through Orbot settings.
- applyProxyThroughOrbot(true);
- return true;
-
- case R.id.share:
+ case R.id.share_url:
// Setup the share string.
String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
shareIntent.setType("text/plain");
// Make it so.
- startActivity(Intent.createChooser(shareIntent, "Share URL"));
+ startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
return true;
- case R.id.find_on_page:
- // Hide the URL app bar.
- supportAppBar.setVisibility(View.GONE);
+ case R.id.open_with_app:
+ // Create the open with intent with `ACTION_VIEW`.
+ Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
- // Show the Find on Page `RelativeLayout`.
- findOnPageLinearLayout.setVisibility(View.VISIBLE);
+ // Set the URI but not the MIME type. This should open all available apps.
+ openWithAppIntent.setData(Uri.parse(formattedUrlString));
- // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android.
- // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
- findOnPageEditText.postDelayed(() -> {
- // Set the focus on `findOnPageEditText`.
- findOnPageEditText.requestFocus();
+ // Flag the intent to open in a new task.
+ openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // Display the keyboard. `0` sets no input flags.
- inputMethodManager.showSoftInput(findOnPageEditText, 0);
- }, 200);
+ // Show the chooser.
+ startActivity(Intent.createChooser(openWithAppIntent, getString(R.string.open_with)));
return true;
- case R.id.add_to_homescreen:
- // Show the `CreateHomeScreenShortcutDialog` `AlertDialog` and name this instance `R.string.create_shortcut`.
- AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
- createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
+ case R.id.open_with_browser:
+ // Create the open with intent with `ACTION_VIEW`.
+ Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW);
- //Everything else will be handled by `CreateHomeScreenShortcutDialog` and the associated listener below.
+ // Set the URI and the MIME type. `"text/html"` should load browser options.
+ openWithBrowserIntent.setDataAndType(Uri.parse(formattedUrlString), "text/html");
+
+ // Flag the intent to open in a new task.
+ openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Show the chooser.
+ startActivity(Intent.createChooser(openWithBrowserIntent, getString(R.string.open_with)));
+ return true;
+
+ case R.id.proxy_through_orbot:
+ // Toggle the proxy through Orbot variable.
+ proxyThroughOrbot = !proxyThroughOrbot;
+
+ // Apply the proxy through Orbot settings.
+ applyProxyThroughOrbot(true);
return true;
case R.id.refresh:
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- // Reload the ad for the free flavor if we not in full screen mode.
+ // Get the status bar pixel size.
+ int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
+ int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
+
+ // Get the resource density.
+ float screenDensity = getResources().getDisplayMetrics().density;
+
+ // Recalculate the drawer header padding.
+ drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
+ drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
+ drawerHeaderPaddingBottom = (int) (8 * screenDensity);
+
+ // Reload the ad for the free flavor if not in full screen mode.
if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
// Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
// Create the bookmark.
bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
- // Update `bookmarksCursor` with the current contents of this folder.
- bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+ // Update the bookmarks cursor with the current contents of this folder.
+ bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
// Update the `ListView`.
bookmarksCursorAdapter.changeCursor(bookmarksCursor);
// Create the folder, which will be placed at the top of the `ListView`.
bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
- // Update `bookmarksCursor` with the current contents of this folder.
- bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+ // Update the bookmarks cursor with the current contents of this folder.
+ bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
// Update the `ListView`.
bookmarksCursorAdapter.changeCursor(bookmarksCursor);
bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
}
- // Update `bookmarksCursor` with the current contents of this folder.
- bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+ // Update the bookmarks cursor with the current contents of this folder.
+ bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
// Update the `ListView`.
bookmarksCursorAdapter.changeCursor(bookmarksCursor);
bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray);
}
- // Update `bookmarksCursor` with the current contents of this folder.
- bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+ // Update the bookmarks cursor with the current contents of this folder.
+ bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
// Update the `ListView`.
bookmarksCursorAdapter.changeCursor(bookmarksCursor);
drawerLayout.closeDrawer(GravityCompat.END);
} else { // A subfolder is displayed.
// Place the former parent folder in `currentFolder`.
- currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolder(currentBookmarksFolder);
+ currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
// Load the new folder.
loadBookmarksFolder();
String unformattedUrlString = urlTextBox.getText().toString().trim();
// Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search.
- if ((Patterns.WEB_URL.matcher(unformattedUrlString).matches()) || (unformattedUrlString.startsWith("http://")) || (unformattedUrlString.startsWith("https://"))) {
- // Add `https://` at the beginning if it is missing. Otherwise the app will segfault.
- if (!unformattedUrlString.startsWith("http")) {
+ if (unformattedUrlString.startsWith("content://")) {
+ // Load the entire content URL.
+ formattedUrlString = unformattedUrlString;
+ } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://")
+ || unformattedUrlString.startsWith("file://")) {
+ // 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;
}
// Create a download intent. Not specifying the action type will display the maximum number of options.
Intent downloadIntent = new Intent();
- // Set the URI and the mime type. `"*/*"` will display the maximum number of options.
+ // Set the URI and the MIME type. Specifying `text/html` displays a good number of options.
downloadIntent.setDataAndType(Uri.parse(url), "text/html");
// Flag the intent to open in a new task.
// Get the URL string.
String urlString = urlTextBox.getText().toString();
- // Get the index of the `/` immediately after the domain name.
- int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
+ // Highlight the URL according to the protocol.
+ if (urlString.startsWith("file://")) { // This is a file URL.
+ // De-emphasize only the protocol.
+ urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ } else if (urlString.startsWith("content://")) {
+ // De-emphasize only the protocol.
+ urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ } else { // This is a web URL.
+ // Get the index of the `/` immediately after the domain name.
+ int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
- // Create a base URL string.
- String baseUrl;
+ // Create a base URL string.
+ String baseUrl;
- // Get the base URL.
- if (endOfDomainName > 0) { // There is at least one character after the base URL.
// Get the base URL.
- baseUrl = urlString.substring(0, endOfDomainName);
- } else { // There are no characters after the base URL.
- // Set the base URL to be the entire URL string.
- baseUrl = urlString;
- }
+ if (endOfDomainName > 0) { // There is at least one character after the base URL.
+ // Get the base URL.
+ baseUrl = urlString.substring(0, endOfDomainName);
+ } else { // There are no characters after the base URL.
+ // Set the base URL to be the entire URL string.
+ baseUrl = urlString;
+ }
- // Get the index of the last `.` in the domain.
- int lastDotIndex = baseUrl.lastIndexOf(".");
+ // Get the index of the last `.` in the domain.
+ int lastDotIndex = baseUrl.lastIndexOf(".");
- // Get the index of the penultimate `.` in the domain.
- int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
+ // Get the index of the penultimate `.` in the domain.
+ int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
- // Markup the beginning of the URL.
- if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
- urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ // Markup the beginning of the URL.
+ if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
+ urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- // De-emphasize subdomains.
- if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
- urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- }
- } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
- if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
- // De-emphasize the protocol and the additional subdomains.
- urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- } else { // There is only one subdomain in the domain name.
- // De-emphasize only the protocol.
- urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ // De-emphasize subdomains.
+ if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
+ urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
+ if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
+ // De-emphasize the protocol and the additional subdomains.
+ urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ } else { // There is only one subdomain in the domain name.
+ // De-emphasize only the protocol.
+ urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ }
}
- }
- // De-emphasize the text after the domain name.
- if (endOfDomainName > 0) {
- urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ // De-emphasize the text after the domain name.
+ if (endOfDomainName > 0) {
+ urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ }
}
}
private void loadBookmarksFolder() {
// Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
- bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+ bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
// Populate the bookmarks cursor adapter. `this` specifies the `Context`. `false` disables `autoRequery`.
bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {