X-Git-Url: https://gitweb.stoutner.com/?a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FMainWebViewActivity.java;h=bb7ae1dedb6eac55911b4d0a0f9db371e439dc3e;hb=65ff2d87f3a8fd1c26a26678498a7451e76ebb16;hp=b13a785235c96e6d72208d92d0fc794e17140714;hpb=e75f230075b4059be6a9b6d27d8b6b202c74a6ff;p=PrivacyBrowserAndroid.git diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index b13a7852..bb7ae1de 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -34,6 +34,8 @@ import android.content.SharedPreferences; import android.content.res.Configuration; import android.database.Cursor; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -41,11 +43,13 @@ import android.net.http.SslCertificate; import android.net.http.SslError; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.preference.PreferenceManager; import android.print.PrintDocumentAdapter; import android.print.PrintManager; import android.support.annotation.NonNull; import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.FloatingActionButton; import android.support.design.widget.NavigationView; import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; @@ -70,12 +74,14 @@ import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.webkit.CookieManager; import android.webkit.DownloadListener; import android.webkit.HttpAuthHandler; import android.webkit.SslErrorHandler; +import android.webkit.ValueCallback; import android.webkit.WebBackForwardList; import android.webkit.WebChromeClient; import android.webkit.WebResourceResponse; @@ -83,23 +89,32 @@ import android.webkit.WebStorage; import android.webkit.WebView; import android.webkit.WebViewClient; import android.webkit.WebViewDatabase; +import android.widget.AdapterView; +import android.widget.CursorAdapter; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.ListView; import android.widget.ProgressBar; +import android.widget.RadioButton; import android.widget.RelativeLayout; import android.widget.TextView; import com.stoutner.privacybrowser.BannerAd; import com.stoutner.privacybrowser.BuildConfig; import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog; +import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog; import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog; import com.stoutner.privacybrowser.dialogs.DownloadImageDialog; +import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog; +import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog; import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog; import com.stoutner.privacybrowser.dialogs.PinnedSslCertificateMismatchDialog; import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog; import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog; +import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper; import com.stoutner.privacybrowser.helpers.OrbotProxyHelper; import com.stoutner.privacybrowser.dialogs.DownloadFileDialog; @@ -107,6 +122,7 @@ import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog; import java.io.BufferedReader; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; @@ -122,9 +138,9 @@ import java.util.Map; import java.util.Set; // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21. -public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener, - HttpAuthenticationDialog.HttpAuthenticationListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, DownloadFileDialog.DownloadFileListener, - DownloadImageDialog.DownloadImageListener, UrlHistoryDialog.UrlHistoryListener { +public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener, + DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener, HttpAuthenticationDialog.HttpAuthenticationListener, + NavigationView.OnNavigationItemSelectedListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener { // `darkTheme` is public static so it can be accessed from `AboutActivity`, `GuideActivity`, `AddDomainDialog`, `SettingsActivity`, `DomainsActivity`, `DomainsListFragment`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`, // `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `DownloadFileDialog`, `DownloadImageDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `HttpAuthenticationDialog`, `MoveToFolderDialog`, `SslCertificateErrorDialog`, `UrlHistoryDialog`, @@ -132,7 +148,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation public static boolean darkTheme; // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, - // and `ViewSslCertificateDialog`. It is also used in `onCreate()`, `onCreateHomeScreenShortcutCreate()`, and `applyDomainSettings`. + // and `ViewSslCertificateDialog`. It is also used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcutCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`. public static Bitmap favoriteIconBitmap; // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`, `CreateBookmarkDialog`, and `AddDomainDialog`. @@ -151,8 +167,18 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `displayWebpageImagesBoolean` is public static so it can be accessed from `DomainSettingsFragment`. It is also used in `applyAppSettings()` and `applyDomainSettings()`. public static boolean displayWebpageImagesBoolean; - // `reloadOnRestartBoolean` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()` - public static boolean reloadOnRestartBoolean; + // `reloadOnRestart` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()` + public static boolean reloadOnRestart; + + // `reloadUrlOnRestart` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()`. + public static boolean loadUrlOnRestart; + + // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`. + public static boolean restartFromBookmarksActivity; + + // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and + // `loadBookmarksFolder()`. + public static String currentBookmarksFolder; // The pinned domain SSL Certificate variables are public static so they can be accessed from `PinnedSslCertificateMismatchDialog`. They are also used in `onCreate()` and `applyDomainSettings()`. public static int domainSettingsDatabaseId; @@ -175,7 +201,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `favoriteIconDefaultBitmap` is used in `onCreate()` and `applyDomainSettings`. private Bitmap favoriteIconDefaultBitmap; - // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, and `onBackPressed()`. + // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, `onBackPressed()`, and `onRestart()`. private DrawerLayout drawerLayout; // `rootCoordinatorLayout` is used in `onCreate()` and `applyAppSettings()`. @@ -203,22 +229,24 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`. private final Map customHeaders = new HashMap<>(); - // `javaScriptEnabled` is also used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `applyAppSettings()`. - // It is `Boolean` instead of `boolean` because `applyAppSettings()` needs to know if it is `null`. - private Boolean javaScriptEnabled; + // `javaScriptEnabled` is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyDomainSettings()`, and `updatePrivacyIcons()`. + private boolean javaScriptEnabled; - // `firstPartyCookiesEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyAppSettings()`. + // `firstPartyCookiesEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyDomainSettings()`. private boolean firstPartyCookiesEnabled; - // `thirdPartyCookiesEnabled` used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`. + // `thirdPartyCookiesEnabled` used in `onCreate()`, `onPrepareOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. private boolean thirdPartyCookiesEnabled; - // `domStorageEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`. + // `domStorageEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. private boolean domStorageEnabled; - // `saveFormDataEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`. + // `saveFormDataEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. private boolean saveFormDataEnabled; + // `nightMode` is used in `onCreate()` and `applyDomainSettings()`. + private boolean nightMode; + // `swipeToRefreshEnabled` is used in `onPrepareOptionsMenu()` and `applyAppSettings()`. private boolean swipeToRefreshEnabled; @@ -321,6 +349,23 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `pinnedDomainSslCertificate` is used in `onCreate()` and `applyDomainSettings()`. private boolean pinnedDomainSslCertificate; + // `bookmarksDatabaseHelper` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`. + private BookmarksDatabaseHelper bookmarksDatabaseHelper; + + // `bookmarksListView` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, and `loadBookmarksFolder()`. + private ListView bookmarksListView; + + // `bookmarksTitleTextView` is used in `onCreate()` and `loadBookmarksFolder()`. + private TextView bookmarksTitleTextView; + + // `bookmarksCursor` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`. + private Cursor bookmarksCursor; + + // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`. + private CursorAdapter bookmarksCursorAdapter; + + // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`. + private String oldFolderNameString; @Override // 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. @@ -350,7 +395,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Get a handle for `inputMethodManager`. inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - // We need to use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21. + // `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21. supportAppBar = (Toolbar) findViewById(R.id.app_bar); setSupportActionBar(supportAppBar); appBar = getSupportActionBar(); @@ -439,6 +484,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Get handles for views that need to be accessed. drawerLayout = (DrawerLayout) findViewById(R.id.drawerlayout); rootCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.root_coordinatorlayout); + bookmarksListView = (ListView) findViewById(R.id.bookmarks_drawer_listview); + bookmarksTitleTextView = (TextView) findViewById(R.id.bookmarks_title_textview); + FloatingActionButton launchBookmarksActivityFab = (FloatingActionButton) findViewById(R.id.launch_bookmarks_activity_fab); + FloatingActionButton createBookmarkFolderFab = (FloatingActionButton) findViewById(R.id.create_bookmark_folder_fab); + FloatingActionButton createBookmarkFab = (FloatingActionButton) findViewById(R.id.create_bookmark_fab); mainWebViewRelativeLayout = (RelativeLayout) findViewById(R.id.main_webview_relativelayout); mainWebView = (WebView) findViewById(R.id.main_webview); findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout); @@ -447,6 +497,54 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation urlAppBarRelativeLayout = (RelativeLayout) findViewById(R.id.url_app_bar_relativelayout); favoriteIconImageView = (ImageView) findViewById(R.id.favorite_icon); + // Set the bookmarks drawer resources according to the theme. This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget. + if (darkTheme) { + 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)); + } else { + 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)); + } + + // Set the launch bookmarks activity FAB to launch the bookmarks activity. + launchBookmarksActivityFab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // Create an intent to launch the bookmarks activity. + Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class); + + // Include the current folder with the `Intent`. + bookmarksIntent.putExtra("Current Folder", currentBookmarksFolder); + + // Make it so. + startActivity(bookmarksIntent); + } + }); + + // Set the create new bookmark folder FAB to display the `AlertDialog`. + createBookmarkFolderFab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`. + AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog(); + createBookmarkFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_folder)); + } + }); + + // Set the create new bookmark FAB to display the `AlertDialog`. + createBookmarkFab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`. + AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog(); + createBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_bookmark)); + } + }); + // Create a double-tap listener to toggle full-screen mode. final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { // Override `onDoubleTap()`. All other events are handled using the default settings. @@ -599,8 +697,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation } }); - // `DrawerTitle` identifies the `DrawerLayout` in accessibility mode. + // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode. drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer)); + drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks)); // Listen for touches on the navigation menu. final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationview); @@ -612,6 +711,73 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2); final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3); + // 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`. + 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. + loadBookmarksFolder(); + + bookmarksListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + // Convert the id from long to int to match the format of the bookmarks database. + int databaseID = (int) id; + + // Get the bookmark `Cursor` for this ID and move it to the first row. + Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID); + bookmarkCursor.moveToFirst(); + + // Act upon the bookmark according to the type. + if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder. + // Store the new folder name in `currentBookmarksFolder`. + currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); + + // Load the new folder. + loadBookmarksFolder(); + } else { // The selected bookmark is not a folder. + // Load the bookmark URL. + loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL))); + + // Close the bookmarks drawer. + drawerLayout.closeDrawer(GravityCompat.END); + } + + // Close the `Cursor`. + bookmarkCursor.close(); + } + }); + + bookmarksListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + // Convert the database ID from `long` to `int`. + int databaseId = (int) id; + + // Find out if the selected bookmark is a folder. + boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId); + + if (isFolder) { + // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`. + oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); + + // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`. + AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId); + editFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_folder)); + } else { + // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`. + AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId); + editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark)); + } + + // Consume the event. + return true; + } + }); + // The `DrawerListener` allows us to update the Navigation Menu. drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() { @Override @@ -749,6 +915,14 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Update the URL in urlTextBox when the page starts to load. @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { + // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied. + if (nightMode) { + mainWebView.setVisibility(View.INVISIBLE); + } + + // Hide the keyboard. `0` indicates no additional flags. + inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); + // Check to see if we are 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. @@ -770,7 +944,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation } } - // Update formattedUrlString and urlTextBox. It is necessary to do this after the page finishes loading because the final URL can change during load. + // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load. @Override public void onPageFinished(WebView view, String url) { // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes. @@ -931,12 +1105,50 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Update the progress bar when a page is loading. @Override public void onProgressChanged(WebView view, int progress) { + // Inject the night mode CSS if night mode is enabled. + if (nightMode) { + // `background-color: #212121` sets the background to be dark gray. `color: #BDBDBD` sets the text color to be light gray. `box-shadow: none` removes a lower underline on links used by WordPress. + // `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color. `border: none` removes all borders, which can also be used to underline text. + // `a {color: #1565C0}` sets links to be a dark blue. `!important` takes precedent over any existing sub-settings. + mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = '" + + "* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important; text-shadow: none !important; border: none !important;}" + + "a {color: #1565C0 !important;}" + + "'; parent.appendChild(style)})()", new ValueCallback() { + @Override + public void onReceiveValue(String value) { + // Initialize a `Handler` to display `mainWebView`. + Handler displayWebViewHandler = new Handler(); + + // Setup a `Runnable` to display `mainWebView` after a delay to allow the CSS to be applied. + Runnable displayWebViewRunnable = new Runnable() { + public void run() { + // Only display `mainWebView` if the progress bar is one. This prevents the display of the `WebView` while it is still loading. + if (progressBar.getVisibility() == View.GONE) { + mainWebView.setVisibility(View.VISIBLE); + } + } + }; + + // Use `displayWebViewHandler` to delay the displaying of `mainWebView` for 500 milliseconds. + displayWebViewHandler.postDelayed(displayWebViewRunnable, 500); + } + }); + } + progressBar.setProgress(progress); if (progress < 100) { + // Show the progress bar. progressBar.setVisibility(View.VISIBLE); } else { + // Hide the progress bar. progressBar.setVisibility(View.GONE); + // Display `mainWebView` if night mode is disabled. + // Because of a race condition between `applyDomainSettings` and `onPageStarted`, when night mode is set by domain settings the `WebView` may be hidden even if night mode is not currently enabled. + if (!nightMode) { + mainWebView.setVisibility(View.VISIBLE); + } + //Stop the `SwipeToRefresh` indicator if it is running swipeRefreshLayout.setRefreshing(false); } @@ -1079,6 +1291,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation thirdPartyCookiesEnabled = false; domStorageEnabled = false; saveFormDataEnabled = false; + nightMode = false; // Initialize `webViewTitle`. webViewTitle = getString(R.string.no_title); @@ -1139,12 +1352,33 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation setDisplayWebpageImages(); // Reload the webpage if displaying of images has been disabled in `SettingsFragment`. - if (reloadOnRestartBoolean) { + if (reloadOnRestart) { // Reload `mainWebView`. mainWebView.reload(); // Reset `reloadOnRestartBoolean`. - reloadOnRestartBoolean = false; + reloadOnRestart = false; + } + + // Load the URL on restart to apply changes to night mode. + if (loadUrlOnRestart) { + // Load the current `formattedUrlString`. + loadUrl(formattedUrlString); + + // Reset `loadUrlOnRestart. + loadUrlOnRestart = false; + } + + // + if (restartFromBookmarksActivity) { + // Close the bookmarks drawer. + drawerLayout.closeDrawer(GravityCompat.END); + + // Reload the bookmarks drawer. + loadBookmarksFolder(); + + // Reset `restartFromBookmarksActivity`. + restartFromBookmarksActivity = false; } } @@ -1709,12 +1943,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history)); break; - case R.id.bookmarks: - // Launch BookmarksActivity. - Intent bookmarksIntent = new Intent(this, BookmarksActivity.class); - startActivity(bookmarksIntent); - break; - case R.id.downloads: // Launch the system Download Manager. Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS); @@ -1725,22 +1953,22 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation startActivity(downloadManagerIntent); break; - case R.id.settings: + case R.id.domains: // Reset `currentDomainName` so that domain settings are reapplied after returning to `MainWebViewActivity`. currentDomainName = ""; - // Launch `SettingsActivity`. - Intent settingsIntent = new Intent(this, SettingsActivity.class); - startActivity(settingsIntent); + // Launch `DomainsActivity`. + Intent domainsIntent = new Intent(this, DomainsActivity.class); + startActivity(domainsIntent); break; - case R.id.domains: + case R.id.settings: // Reset `currentDomainName` so that domain settings are reapplied after returning to `MainWebViewActivity`. currentDomainName = ""; - // Launch `DomainsActivity`. - Intent domainsIntent = new Intent(this, DomainsActivity.class); - startActivity(domainsIntent); + // Launch `SettingsActivity`. + Intent settingsIntent = new Intent(this, SettingsActivity.class); + startActivity(settingsIntent); break; case R.id.guide: @@ -2086,6 +2314,82 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation } } + @Override + public void onCreateBookmark(AppCompatDialogFragment dialogFragment) { + // Get the `EditTexts` from the `dialogFragment`. + EditText createBookmarkNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext); + EditText createBookmarkUrlEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext); + + // Extract the strings from the `EditTexts`. + String bookmarkNameString = createBookmarkNameEditText.getText().toString(); + String bookmarkUrlString = createBookmarkUrlEditText.getText().toString(); + + // Convert the favoriteIcon Bitmap to a byte array. `0` is for lossless compression (the only option for a PNG). + ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream(); + favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream); + byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray(); + + // Display the new bookmark below the current items in the (0 indexed) list. + int newBookmarkDisplayOrder = bookmarksListView.getCount(); + + // Create the bookmark. + bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, newBookmarkDisplayOrder, currentBookmarksFolder, favoriteIconByteArray); + + // Update `bookmarksCursor` with the current contents of this folder. + bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder); + + // Update the `ListView`. + bookmarksCursorAdapter.changeCursor(bookmarksCursor); + + // Scroll to the new bookmark. + bookmarksListView.setSelection(newBookmarkDisplayOrder); + } + + @Override + public void onCreateBookmarkFolder(AppCompatDialogFragment dialogFragment) { + // Get handles for the views in `dialogFragment`. + EditText createFolderNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext); + RadioButton defaultFolderIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton); + ImageView folderIconImageView = (ImageView) dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon); + + // Get new folder name string. + String folderNameString = createFolderNameEditText.getText().toString(); + + // Get the new folder icon `Bitmap`. + Bitmap folderIconBitmap; + if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon. + // Get the default folder icon and convert it to a `Bitmap`. + Drawable folderIconDrawable = folderIconImageView.getDrawable(); + BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable; + folderIconBitmap = folderIconBitmapDrawable.getBitmap(); + } else { // Use the `WebView` favorite icon. + folderIconBitmap = favoriteIconBitmap; + } + + // Convert `folderIconBitmap` to a byte array. `0` is for lossless compression (the only option for a PNG). + ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream(); + folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream); + byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray(); + + // Move all the bookmarks down one in the display order. + for (int i = 0; i < bookmarksListView.getCount(); i++) { + int databaseId = (int) bookmarksListView.getItemIdAtPosition(i); + bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1); + } + + // Create the folder, placing it at the top of the ListView + bookmarksDatabaseHelper.createFolder(folderNameString, 0, currentBookmarksFolder, folderIconByteArray); + + // Update `bookmarksCursor` with the current contents of this folder. + bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder); + + // Update the `ListView`. + bookmarksCursorAdapter.changeCursor(bookmarksCursor); + + // Scroll to the new folder. + bookmarksListView.setSelection(0); + } + @Override public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) { // Get shortcutNameEditText from the alert dialog. @@ -2200,6 +2504,99 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation } } + @Override + public void onSaveEditBookmark(AppCompatDialogFragment dialogFragment, int selectedBookmarkDatabaseId) { + // Get handles for the views from `dialogFragment`. + EditText editBookmarkNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext); + EditText editBookmarkUrlEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext); + RadioButton currentBookmarkIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton); + + // Store the bookmark strings. + String bookmarkNameString = editBookmarkNameEditText.getText().toString(); + String bookmarkUrlString = editBookmarkUrlEditText.getText().toString(); + + // Update the bookmark. + if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon. + bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString); + } else { // Update the bookmark using the `WebView` favorite icon. + // Convert the favorite icon to a byte array. `0` is for lossless compression (the only option for a PNG). + ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream(); + favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream); + byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray(); + + // Update the bookmark and the favorite icon. + bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray); + } + + // Update `bookmarksCursor` with the current contents of this folder. + bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder); + + // Update the `ListView`. + bookmarksCursorAdapter.changeCursor(bookmarksCursor); + } + + @Override + public void onSaveEditBookmarkFolder(AppCompatDialogFragment dialogFragment, int selectedFolderDatabaseId) { + // Get handles for the views from `dialogFragment`. + EditText editFolderNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext); + RadioButton currentFolderIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton); + RadioButton defaultFolderIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton); + ImageView folderIconImageView = (ImageView) dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon); + + // Get the new folder name. + String newFolderNameString = editFolderNameEditText.getText().toString(); + + // Check if the favorite icon has changed. + if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed. + // Update the name in the database. + bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString); + } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed. + // Get the new folder icon `Bitmap`. + Bitmap folderIconBitmap; + if (defaultFolderIconRadioButton.isChecked()) { + // Get the default folder icon and convert it to a `Bitmap`. + Drawable folderIconDrawable = folderIconImageView.getDrawable(); + BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable; + folderIconBitmap = folderIconBitmapDrawable.getBitmap(); + } else { // Use the `WebView` favorite icon. + folderIconBitmap = favoriteIconBitmap; + } + + // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG). + ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream(); + folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream); + byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray(); + + // Update the folder icon in the database. + bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, folderIconByteArray); + } else { // The folder icon and the name have changed. + // Get the new folder icon `Bitmap`. + Bitmap folderIconBitmap; + if (defaultFolderIconRadioButton.isChecked()) { + // Get the default folder icon and convert it to a `Bitmap`. + Drawable folderIconDrawable = folderIconImageView.getDrawable(); + BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable; + folderIconBitmap = folderIconBitmapDrawable.getBitmap(); + } else { // Use the `WebView` favorite icon. + folderIconBitmap = MainWebViewActivity.favoriteIconBitmap; + } + + // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG). + ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream(); + folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream); + byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray(); + + // Update the folder name and icon in the database. + bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray); + } + + // Update `bookmarksCursor` with the current contents of this folder. + bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder); + + // Update the `ListView`. + bookmarksCursorAdapter.changeCursor(bookmarksCursor); + } + @Override public void onHttpAuthenticationCancel() { // Cancel the `HttpAuthHandler`. @@ -2270,21 +2667,30 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Override `onBackPressed` to handle the navigation drawer and `mainWebView`. @Override public void onBackPressed() { - // Close the navigation drawer if it is available. GravityCompat.START is the drawer on the left on Left-to-Right layout text. - if (drawerLayout.isDrawerVisible(GravityCompat.START)) { + if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open. + // Close the navigation drawer. drawerLayout.closeDrawer(GravityCompat.START); - } else { - // Load the previous URL if available. - if (mainWebView.canGoBack()) { - // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded. - navigatingHistory = true; - - // Go back. - mainWebView.goBack(); - } else { - // Pass `onBackPressed()` to the system. - super.onBackPressed(); + } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open. + if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed. + // close the bookmarks drawer. + drawerLayout.closeDrawer(GravityCompat.END); + } else { // A subfolder is displayed. + // Place the former parent folder in `currentFolder`. + currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolder(currentBookmarksFolder); + + // Load the new folder. + loadBookmarksFolder(); } + + } else if (mainWebView.canGoBack()) { // There is at least one item in the `WebView` history. + // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded. + navigatingHistory = true; + + // Go back. + mainWebView.goBack(); + } else { // There isn't anything to do in Privacy Browser. + // Pass `onBackPressed()` to the system. + super.onBackPressed(); } } @@ -2331,9 +2737,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation } loadUrl(formattedUrlString); - - // Hide the keyboard so we can see the webpage. `0` indicates no additional flags. - inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); } @@ -2619,10 +3022,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Get a handle for the shared preference. `this` references the current context. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - // Store the default font size and user agent information. + // Store the general preference information. String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100"); String defaultUserAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0"); String defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"); + nightMode = sharedPreferences.getBoolean("night_mode", false); if (domainSettingsApplied) { // The url we are loading has custom domain settings. // Get a cursor for the current host and move it to the first position. @@ -2639,6 +3043,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation String userAgentString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT)); int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE)); displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES)); + int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE)); pinnedDomainSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1); pinnedDomainSslIssuedToCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)); pinnedDomainSslIssuedToONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)); @@ -2647,6 +3052,22 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation pinnedDomainSslIssuedByONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)); pinnedDomainSslIssuedByUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)); + // Set `nightMode` according to `nightModeInt`. If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used. + switch (nightModeInt) { + case DomainsDatabaseHelper.NIGHT_MODE_ENABLED: + nightMode = true; + break; + + case DomainsDatabaseHelper.NIGHT_MODE_DISABLED: + nightMode = false; + break; + } + + // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`. + if (nightMode) { + javaScriptEnabled = true; + } + // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0. if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) { pinnedDomainSslStartDate = null; @@ -2729,6 +3150,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false); saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false); + // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`. + if (nightMode) { + javaScriptEnabled = true; + } + // Apply the default settings. mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); cookieManager.setAcceptCookie(firstPartyCookiesEnabled); @@ -2890,4 +3316,55 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); } } + + private void loadBookmarksFolder() { + // Update `bookmarksCursor` with the contents of the bookmarks database for the current folder. + bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder); + + // Populate the bookmarks cursor adapter. `this` specifies the `Context`. `false` disables `autoRequery`. + bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) { + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + // Inflate the individual item layout. `false` does not attach it to the root. + return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + // Get handles for the views. + ImageView bookmarkFavoriteIcon = (ImageView) view.findViewById(R.id.bookmark_favorite_icon); + TextView bookmarkNameTextView = (TextView) view.findViewById(R.id.bookmark_name); + + // Get the favorite icon byte array from the `Cursor`. + byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); + + // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last. + Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length); + + // Display the bitmap in `bookmarkFavoriteIcon`. + bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap); + + // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`. + String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); + bookmarkNameTextView.setText(bookmarkNameString); + + // Make the font bold for folders. + if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { + bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD); + } else { // Reset the font to default for normal bookmarks. + bookmarkNameTextView.setTypeface(Typeface.DEFAULT); + } + } + }; + + // Populate the `ListView` with the adapter. + bookmarksListView.setAdapter(bookmarksCursorAdapter); + + // Set the bookmarks drawer title. + if (currentBookmarksFolder.isEmpty()) { + bookmarksTitleTextView.setText(R.string.bookmarks); + } else { + bookmarksTitleTextView.setText(currentBookmarksFolder); + } + } }