import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.text.Spanned;
import android.text.TextWatcher;
import android.text.style.ForegroundColorSpan;
import android.text.Spanned;
import android.text.TextWatcher;
import android.text.style.ForegroundColorSpan;
import android.util.Patterns;
import android.view.ContextMenu;
import android.view.GestureDetector;
import android.util.Patterns;
import android.view.ContextMenu;
import android.view.GestureDetector;
+ // 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;
// `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
private SslErrorHandler sslErrorHandler;
// Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. The whole premise of Privacy Browser is built around an understanding of these dangers.
// Also, remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
@SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
// Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. The whole premise of Privacy Browser is built around an understanding of these dangers.
// Also, remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
@SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
@SuppressWarnings("deprecation")
protected void onCreate(Bundle savedInstanceState) {
// Get a handle for the shared preferences.
@SuppressWarnings("deprecation")
protected void onCreate(Bundle savedInstanceState) {
// Get a handle for the shared preferences.
// Get a handle for `inputMethodManager`.
inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
// Get a handle for `inputMethodManager`.
inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
// Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23.
appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
// Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23.
- redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
- initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
- finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
+ redColorSpan = new ForegroundColorSpan(resources.getColor(R.color.red_a700));
+ initialGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
+ finalGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
- launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_dark));
- createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_dark));
- createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_dark));
- bookmarksListView.setBackgroundColor(getResources().getColor(R.color.gray_850));
+ launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_dark));
+ createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_dark));
+ createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_dark));
+ bookmarksListView.setBackgroundColor(resources.getColor(R.color.gray_850));
- launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_light));
- createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_light));
- createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_light));
- bookmarksListView.setBackgroundColor(getResources().getColor(R.color.white));
+ launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_light));
+ createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_light));
+ createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_light));
+ bookmarksListView.setBackgroundColor(resources.getColor(R.color.white));
createBookmarkFolderFab.setOnClickListener(v -> {
// Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`.
AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
createBookmarkFolderFab.setOnClickListener(v -> {
// Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`.
AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
});
// Set the create new bookmark FAB to display an alert dialog.
createBookmarkFab.setOnClickListener(view -> {
// Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`.
AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog();
});
// Set the create new bookmark FAB to display an alert dialog.
createBookmarkFab.setOnClickListener(view -> {
// Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`.
AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog();
final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
final MenuItem navigationRequestsMenuItem = navigationMenu.getItem(4);
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.
bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
// Initialize `currentBookmarksFolder`. `""` is the home folder in the database.
// Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
// Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
} else {
// Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
} else {
// Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
- // The drawer listener is used to update the navigation menu.
+ // Get the status bar pixel size.
+ int statusBarResourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
+ int statusBarPixelSize = resources.getDimensionPixelSize(statusBarResourceId);
+
+ // Get the resource density.
+ float screenDensity = resources.getDisplayMetrics().density;
+
+ // Calculate the drawer header padding. This is used to move the text in the drawer headers below any cutouts.
+ drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
+ drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
+ drawerHeaderPaddingBottom = (int) (8 * screenDensity);
+
+ // The drawer listener is used to update the navigation menu.`
drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
@Override
public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
@Override
public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
@Override
public void onDrawerStateChanged(int newState) {
if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing.
@Override
public void onDrawerStateChanged(int newState) {
if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing.
+ // Get handles for the drawer headers.
+ TextView navigationHeaderTextView = findViewById(R.id.navigationText);
+ TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
+
+ // 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());
navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
// Update the back, forward, history, and requests menu items.
navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
// Initialize the user agent array adapter and string array.
userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
// Initialize the user agent array adapter and string array.
userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
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) {
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) {
// Sanitize the search input and convert it to a search.
try {
encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
// Sanitize the search input and convert it to a search.
try {
encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
// Get the information from the intent.
String intentAction = intent.getAction();
Uri intentUriData = intent.getData();
// Get the information from the intent.
String intentAction = intent.getAction();
Uri intentUriData = intent.getData();
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
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));
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));
String unformattedUrlString = urlTextBox.getText().toString().trim();
// Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search.
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://")) {
- // 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));
- 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 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);
+ }