]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.java
Fix Russian plurals. https://redmine.stoutner.com/issues/291
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / BookmarksActivity.java
1 /*
2  * Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
6  * Privacy Browser is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.activities;
21
22 import android.app.Activity;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.database.Cursor;
26 import android.graphics.Bitmap;
27 import android.graphics.BitmapFactory;
28 import android.graphics.Typeface;
29 import android.graphics.drawable.BitmapDrawable;
30 import android.graphics.drawable.Drawable;
31 import android.os.Bundle;
32 import android.support.design.widget.FloatingActionButton;
33 import android.support.design.widget.Snackbar;
34 import android.support.v4.app.NavUtils;
35 import android.support.v7.app.ActionBar;
36 import android.support.v7.app.AppCompatActivity;
37 // `AppCompatDialogFragment` is required instead of `DialogFragment` or an error is produced on API <=22.
38 import android.support.v7.app.AppCompatDialogFragment;
39 import android.support.v7.widget.Toolbar;
40 import android.util.SparseBooleanArray;
41 import android.view.ActionMode;
42 import android.view.Menu;
43 import android.view.MenuItem;
44 import android.view.View;
45 import android.view.ViewGroup;
46 import android.view.WindowManager;
47 import android.widget.AbsListView;
48 import android.widget.CursorAdapter;
49 import android.widget.EditText;
50 import android.widget.ImageView;
51 import android.widget.ListView;
52 import android.widget.RadioButton;
53 import android.widget.TextView;
54
55 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
56 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
57 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
58 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
59 import com.stoutner.privacybrowser.dialogs.MoveToFolderDialog;
60 import com.stoutner.privacybrowser.R;
61 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
62
63 import java.io.ByteArrayOutputStream;
64
65 public class BookmarksActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, EditBookmarkDialog.EditBookmarkListener,
66         EditBookmarkFolderDialog.EditBookmarkFolderListener, MoveToFolderDialog.MoveToFolderListener {
67
68     // `currentFolder` is public static so it can be accessed from `MoveToFolderDialog`.
69     // It is used in `onCreate`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, and `loadFolder()`.
70     public static String currentFolder;
71
72     // `checkedItemIds` is public static so it can be accessed from `MoveToFolderDialog`.  It is also used in `onCreate()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, and `updateMoveIcons()`.
73     public static long[] checkedItemIds;
74
75
76     // `bookmarksDatabaseHelper` is used in `onCreate()`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, `deleteBookmarkFolderContents()`,
77     // and `loadFolder().
78     private BookmarksDatabaseHelper bookmarksDatabaseHelper;
79
80     // `bookmarksListView` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, `updateMoveIcons()`, `scrollBookmarks()`,
81     // and `loadFolder()`.
82     private ListView bookmarksListView;
83
84     // `bookmarksCursor` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, `deleteBookmarkFolderContents()`, and `loadFolder()`.
85     private Cursor bookmarksCursor;
86
87     // `bookmarksCursorAdapter` is used in `onCreate(), `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, and `onLoadFolder()`.
88     private CursorAdapter bookmarksCursorAdapter;
89
90     // `contextualActionMode` is used in `onCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()` and `onMoveToFolder()`.
91     private ActionMode contextualActionMode;
92
93     // `appBar` is used in `onCreate()` and `loadFolder()`.
94     private ActionBar appBar;
95
96     // `oldFolderName` is used in `onCreate()` and `onSaveBookmarkFolder()`.
97     private String oldFolderNameString;
98
99     // `moveBookmarkUpMenuItem` is used in `onCreate()` and `updateMoveIcons`.
100     private MenuItem moveBookmarkUpMenuItem;
101
102     // `moveBookmarkDownMenuItem` is used in `onCreate()` and `updateMoveIcons`.
103     private MenuItem moveBookmarkDownMenuItem;
104
105     @Override
106     protected void onCreate(Bundle savedInstanceState) {
107         // Disable screenshots if not allowed.
108         if (!MainWebViewActivity.allowScreenshots) {
109             getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
110         }
111
112         // Set the activity theme.
113         if (MainWebViewActivity.darkTheme) {
114             setTheme(R.style.PrivacyBrowserDark_SecondaryActivity);
115         } else {
116             setTheme(R.style.PrivacyBrowserLight_SecondaryActivity);
117         }
118
119         // Run the default commands.
120         super.onCreate(savedInstanceState);
121
122         // Get the intent that launched the activity.
123         Intent launchingIntent = getIntent();
124
125         // Set the current folder variable.
126         if (launchingIntent.getStringExtra("Current Folder") != null) {  // Set the current folder from the intent.
127             currentFolder = launchingIntent.getStringExtra("Current Folder");
128         } else {  // Set the current folder to be `""`, which is the home folder.
129             currentFolder = "";
130         }
131
132         // Set the content view.
133         setContentView(R.layout.bookmarks_coordinatorlayout);
134
135         // Use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21.
136         final Toolbar bookmarksAppBar = findViewById(R.id.bookmarks_toolbar);
137         setSupportActionBar(bookmarksAppBar);
138
139         // Get a handle for the activity, the app bar, and the ListView.
140         final Activity bookmarksActivity = this;
141         appBar = getSupportActionBar();
142         bookmarksListView = findViewById(R.id.bookmarks_listview);
143
144         // Remove the incorrect lint warning that `appBar` might be null.
145         assert appBar != null;
146
147         // Display the home arrow on the app bar.
148         appBar.setDisplayHomeAsUpEnabled(true);
149
150         // Initialize the database helper.  `this` specifies the context.  The two `nulls` do not specify the database name or a `CursorFactory`.
151         // The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
152         bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
153
154         // Load the home folder.
155         loadFolder();
156
157         // Set a listener so that tapping a list item loads the URL or folder.
158         bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
159             // Convert the id from long to int to match the format of the bookmarks database.
160             int databaseID = (int) id;
161
162             // Get the bookmark cursor for this ID and move it to the first row.
163             Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID);
164             bookmarkCursor.moveToFirst();
165
166             // Act upon the bookmark according to the type.
167             if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {  // The selected bookmark is a folder.
168                 // Update the current folder.
169                 currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
170
171                 // Load the new folder.
172                 loadFolder();
173             } else {  // The selected bookmark is not a folder.
174                 // Get the bookmark URL and assign it to `formattedUrlString`.
175                 MainWebViewActivity.formattedUrlString = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL));
176
177                 // Set `MainWebViewActivity` to load the new URL on restart.
178                 MainWebViewActivity.loadUrlOnRestart = true;
179
180                 // Update the bookmarks folder for the bookmarks drawer in `MainWebViewActivity`.
181                 MainWebViewActivity.currentBookmarksFolder = currentFolder;
182
183                 // Close the bookmarks drawer and reload the bookmarks `ListView` when returning to `MainWebViewActivity`.
184                 MainWebViewActivity.restartFromBookmarksActivity = true;
185
186                 // Return to `MainWebViewActivity`.
187                 NavUtils.navigateUpFromSameTask(bookmarksActivity);
188             }
189
190             // Close the `Cursor`.
191             bookmarkCursor.close();
192         });
193
194         // `MultiChoiceModeListener` handles long clicks.
195         bookmarksListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
196             // Instantiate the common variables.
197             MenuItem editBookmarkMenuItem;
198             MenuItem deleteBookmarksMenuItem;
199             MenuItem selectAllBookmarksMenuItem;
200             boolean deletingBookmarks;
201
202             @Override
203             public boolean onCreateActionMode(ActionMode mode, Menu menu) {
204                 // Inflate the menu for the contextual app bar and set the title.
205                 getMenuInflater().inflate(R.menu.bookmarks_context_menu, menu);
206
207                 // Set the title.
208                 if (currentFolder.isEmpty()) {
209                     // Use `R.string.bookmarks` if we are in the home folder.
210                     mode.setTitle(R.string.bookmarks);
211                 } else {  // Use the current folder name as the title.
212                     mode.setTitle(currentFolder);
213                 }
214
215                 // Get a handle for `MenuItems` that need to be selectively disabled.
216                 moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up);
217                 moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down);
218                 editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark);
219                 deleteBookmarksMenuItem = menu.findItem(R.id.delete_bookmark);
220                 selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks);
221
222                 // Disable the delete bookmarks menu item if a delete is pending.
223                 if (deletingBookmarks) {
224                     deleteBookmarksMenuItem.setEnabled(false);
225                 }
226
227                 // Store `contextualActionMode` so it can be closed programatically.
228                 contextualActionMode = mode;
229
230                 // Make it so.
231                 return true;
232             }
233
234             @Override
235             public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
236                 // Get a handle for the move to folder menu item.
237                 MenuItem moveToFolderMenuItem = menu.findItem(R.id.move_to_folder);
238
239                 // Get a `Cursor` with all of the folders.
240                 Cursor folderCursor = bookmarksDatabaseHelper.getAllFoldersCursor();
241
242                 // Enable the move to folder menu item if at least one folder exists.
243                 moveToFolderMenuItem.setVisible(folderCursor.getCount() > 0);
244
245                 // Make it so.
246                 return true;
247             }
248
249             @Override
250             public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
251                 // Get a long array of the selected bookmarks.
252                 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
253
254                 // Calculate the number of selected bookmarks.
255                 int numberOfSelectedBookmarks = selectedBookmarksLongArray.length;
256
257                 // Adjust the `ActionMode` and the `Menu` according to the number of selected bookmarks.
258                 if (numberOfSelectedBookmarks == 0) {  // No bookmarks are selected.
259                     // Close the `ActionMode`.
260                     mode.finish();
261                 } else if (numberOfSelectedBookmarks == 1) {  // One bookmark is selected.
262                     // List the number of selected bookmarks in the subtitle.
263                     mode.setSubtitle(getString(R.string.one_selected));
264
265                     // Show the `Move Up`, `Move Down`, and  `Edit` options.
266                     moveBookmarkUpMenuItem.setVisible(true);
267                     moveBookmarkDownMenuItem.setVisible(true);
268                     editBookmarkMenuItem.setVisible(true);
269
270                     // Update the enabled status of the move icons.
271                     updateMoveIcons();
272                 } else {  // More than one bookmark is selected.
273                     // List the number of selected bookmarks according to the language.
274                     if (getString(R.string.android_asset_path).equals("ru")) {  // The Russian translation is used.
275                         // Convert the number of selected bookmarks to a string.
276                         String numberOfSelectedBookmarksString = String.valueOf(numberOfSelectedBookmarks);
277
278                         // Russian follows rule #7 at <https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals>.
279                         if (numberOfSelectedBookmarksString.endsWith("1") && !numberOfSelectedBookmarksString.equals("11")) {  //  Ends in 1.
280                             mode.setSubtitle(numberOfSelectedBookmarks + " " + getString(R.string.selected_russian_ends_in_1));
281                         } else if ((numberOfSelectedBookmarksString.endsWith("2") || numberOfSelectedBookmarksString.endsWith("3") || numberOfSelectedBookmarksString.endsWith("4")) &&
282                                 !numberOfSelectedBookmarksString.equals("12") && !numberOfSelectedBookmarksString.equals("13") && !numberOfSelectedBookmarksString.equals("14")) {  // Ends in 2-4.
283                             mode.setSubtitle(numberOfSelectedBookmarks + " " + getString(R.string.selected_russian_ends_in_2));
284                         } else {  // Everything else.
285                             mode.setSubtitle(numberOfSelectedBookmarks + " " + getString(R.string.selected_russian_everything_else));
286                         }
287                     } else {  // Another language is used.
288                         // List the number of selected bookmarks in the subtitle.
289                         mode.setSubtitle(numberOfSelectedBookmarks + " " + getString(R.string.selected));
290                     }
291
292                     // Hide non-applicable `MenuItems`.
293                     moveBookmarkUpMenuItem.setVisible(false);
294                     moveBookmarkDownMenuItem.setVisible(false);
295                     editBookmarkMenuItem.setVisible(false);
296                 }
297
298                 // Do not show `Select All` if all the bookmarks are already checked.
299                 if (bookmarksListView.getCheckedItemIds().length == bookmarksListView.getCount()) {
300                     selectAllBookmarksMenuItem.setVisible(false);
301                 } else {
302                     selectAllBookmarksMenuItem.setVisible(true);
303                 }
304             }
305
306             @Override
307             public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
308                 // Get the menu item ID.
309                 int menuItemId = item.getItemId();
310
311                 // Instantiate the common variables.
312                 int selectedBookmarkPosition;
313                 int selectedBookmarkNewPosition;
314                 final SparseBooleanArray selectedBookmarksPositionsSparseBooleanArray;
315
316                 switch (menuItemId) {
317                     case R.id.move_bookmark_up:
318                         // Get the array of checked bookmark positions.
319                         selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
320
321                         // Store the position of the selected bookmark.  Only one bookmark is selected when `move_bookmark_up` is enabled.
322                         selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(0);
323
324                         // Calculate the new position of the selected bookmark.
325                         selectedBookmarkNewPosition = selectedBookmarkPosition - 1;
326
327                         // Iterate through the bookmarks.
328                         for (int i = 0; i < bookmarksListView.getCount(); i++) {
329                             // Get the database ID for the current bookmark.
330                             int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
331
332                             // Update the display order for the current bookmark.
333                             if (i == selectedBookmarkPosition) {  // The current bookmark is the selected bookmark.
334                                 // Move the current bookmark up one.
335                                 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1);
336                             } else if ((i + 1) == selectedBookmarkPosition){  // The current bookmark is immediately above the selected bookmark.
337                                 // Move the current bookmark down one.
338                                 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1);
339                             } else {  // The current bookmark is not changing positions.
340                                 // Move `bookmarksCursor` to the current bookmark position.
341                                 bookmarksCursor.moveToPosition(i);
342
343                                 // Update the display order only if it is not correct in the database.
344                                 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
345                                     bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
346                                 }
347                             }
348                         }
349
350                         // Update `bookmarksCursor` with the current contents of the bookmarks database.
351                         bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder);
352
353                         // Update the `ListView`.
354                         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
355
356                         // Scroll with the bookmark.
357                         scrollBookmarks(selectedBookmarkNewPosition);
358
359                         // Update the enabled status of the move icons.
360                         updateMoveIcons();
361                         break;
362
363                     case R.id.move_bookmark_down:
364                         // Get the array of checked bookmark positions.
365                         selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
366
367                         // Store the position of the selected bookmark.  Only one bookmark is selected when `move_bookmark_down` is enabled.
368                         selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(0);
369
370                         // Calculate the new position of the selected bookmark.
371                         selectedBookmarkNewPosition = selectedBookmarkPosition + 1;
372
373                         // Iterate through the bookmarks.
374                         for (int i = 0; i <bookmarksListView.getCount(); i++) {
375                             // Get the database ID for the current bookmark.
376                             int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
377
378                             // Update the display order for the current bookmark.
379                             if (i == selectedBookmarkPosition) {  // The current bookmark is the selected bookmark.
380                                 // Move the current bookmark down one.
381                                 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1);
382                             } else if ((i - 1) == selectedBookmarkPosition) {  // The current bookmark is immediately below the selected bookmark.
383                                 // Move the bookmark below the selected bookmark up one.
384                                 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1);
385                             } else {  // The current bookmark is not changing positions.
386                                 // Move `bookmarksCursor` to the current bookmark position.
387                                 bookmarksCursor.moveToPosition(i);
388
389                                 // Update the display order only if it is not correct in the database.
390                                 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
391                                     bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
392                                 }
393                             }
394                         }
395
396                         // Update `bookmarksCursor` with the current contents of the bookmarks database.
397                         bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder);
398
399                         // Update the `ListView`.
400                         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
401
402                         // Scroll with the bookmark.
403                         scrollBookmarks(selectedBookmarkNewPosition);
404
405                         // Update the enabled status of the move icons.
406                         updateMoveIcons();
407                         break;
408
409                     case R.id.move_to_folder:
410                         // Store `checkedItemIds` for use by the `AlertDialog`.
411                         checkedItemIds = bookmarksListView.getCheckedItemIds();
412
413                         // Show the `MoveToFolderDialog` `AlertDialog` and name the instance `@string/move_to_folder
414                         AppCompatDialogFragment moveToFolderDialog = new MoveToFolderDialog();
415                         moveToFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.move_to_folder));
416                         break;
417
418                     case R.id.edit_bookmark:
419                         // Get the array of checked bookmark positions.
420                         selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
421
422                         // Get the position of the selected bookmark.  Only one bookmark is selected when `edit_bookmark_down` is enabled.
423                         selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(0);
424
425                         // Move the `Cursor` to the selected position and find out if it is a folder.
426                         bookmarksCursor.moveToPosition(selectedBookmarkPosition);
427                         boolean isFolder = (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1);
428
429                         // Get the selected bookmark database ID.
430                         int databaseId = bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper._ID));
431
432                         // Show the edit bookmark or edit bookmark folder dialog.
433                         if (isFolder) {
434                             // Save the current folder name, which is used in `onSaveBookmarkFolder()`.
435                             oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
436
437                             // Show the edit bookmark folder dialog.
438                             AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
439                             editFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_folder));
440                         } else {
441                             // Show the edit bookmark dialog.
442                             AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
443                             editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark));
444                         }
445                         break;
446
447                     case R.id.delete_bookmark:
448                         // Set the deleting bookmarks flag, which prevents the delete menu item from being enabled until the current process finishes.
449                         deletingBookmarks = true;
450
451                         // Get an array of the selected row IDs.
452                         final long[] selectedBookmarksIdsLongArray = bookmarksListView.getCheckedItemIds();
453
454                         // Get the array of checked bookmarks.  `.clone()` makes a copy that won't change if `bookmarksListView` is reloaded, which is needed for re-selecting the bookmarks on undelete.
455                         selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions().clone();
456
457                         // Update `bookmarksCursor` with the current contents of the bookmarks database except for the specified database IDs.
458                         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksCursorExcept(selectedBookmarksIdsLongArray, currentFolder);
459
460                         // Update the `ListView`.
461                         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
462
463                         // Instantiate `snackbarMessage`.
464                         String snackbarMessage;
465
466                         // Determine how many items are in the array and prepare an appropriate snackbar message.
467                         if (selectedBookmarksIdsLongArray.length == 1) {
468                             snackbarMessage = getString(R.string.one_bookmark_deleted);
469                         } else {
470                             // Prepare a snackbar according to the language.
471                             if (getString(R.string.android_asset_path).equals("ru")) {  // The Russian translation is used.
472                                 // Convert the number of selected bookmarks to a string.
473                                 String numberOfBookmarksString = String.valueOf(selectedBookmarksIdsLongArray.length);
474
475                                 // Russian follows rule #7 at <https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals>.
476                                 if (numberOfBookmarksString.endsWith("1") && !numberOfBookmarksString.equals("11")) {  //  Ends in 1.
477                                     snackbarMessage = numberOfBookmarksString + " " + getString(R.string.bookmarks_deleted_russian_ends_in_1);
478                                 } else if ((numberOfBookmarksString.endsWith("2") || numberOfBookmarksString.endsWith("3") || numberOfBookmarksString.endsWith("4")) &&
479                                         !numberOfBookmarksString.equals("12") && !numberOfBookmarksString.equals("13") && !numberOfBookmarksString.equals("14")) {  // Ends in 2-4.
480                                     snackbarMessage = numberOfBookmarksString + " " + getString(R.string.bookmarks_deleted_russian_ends_in_2);
481                                 } else {  // Everything else.
482                                     snackbarMessage = numberOfBookmarksString + " " + getString(R.string.bookmarks_deleted_russian_everything_else);
483                                 }
484                             } else {  // Another language is used.
485                                 snackbarMessage = selectedBookmarksIdsLongArray.length + " " + getString(R.string.bookmarks_deleted);
486                             }
487                         }
488
489                         // Show a SnackBar.
490                         Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), snackbarMessage, Snackbar.LENGTH_LONG)
491                                 .setAction(R.string.undo, view -> {
492                                     // Do nothing because everything will be handled by `onDismissed()` below.
493                                 })
494                                 .addCallback(new Snackbar.Callback() {
495                                     @Override
496                                     public void onDismissed(Snackbar snackbar, int event) {
497                                         switch (event) {
498                                             // The user pushed the `Undo` button.
499                                             case Snackbar.Callback.DISMISS_EVENT_ACTION:
500                                                 // Update `bookmarksCursor` with the current contents of the bookmarks database, including the "deleted" bookmarks.
501                                                 bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder);
502
503                                                 // Update the `ListView`.
504                                                 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
505
506                                                 // Re-select the previously selected bookmarks.
507                                                 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
508                                                     bookmarksListView.setItemChecked(selectedBookmarksPositionsSparseBooleanArray.keyAt(i), true);
509                                                 }
510
511                                                 break;
512
513                                             // The `Snackbar` was dismissed without the `Undo` button being pushed.
514                                             default:
515                                                 // Delete each selected bookmark.
516                                                 for (long databaseIdLong : selectedBookmarksIdsLongArray) {
517                                                     // Convert `databaseIdLong` to an int.
518                                                     int databaseIdInt = (int) databaseIdLong;
519
520                                                     // Delete the contents of the folder if the selected bookmark is a folder.
521                                                     if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
522                                                         deleteBookmarkFolderContents(databaseIdInt);
523                                                     }
524
525                                                     // Delete the selected bookmark.
526                                                     bookmarksDatabaseHelper.deleteBookmark(databaseIdInt);
527                                                 }
528
529                                                 // Update the display order.
530                                                 for (int i = 0; i < bookmarksListView.getCount(); i++) {
531                                                     // Get the database ID for the current bookmark.
532                                                     int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
533
534                                                     // Move `bookmarksCursor` to the current bookmark position.
535                                                     bookmarksCursor.moveToPosition(i);
536
537                                                     // Update the display order only if it is not correct in the database.
538                                                     if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
539                                                         bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
540                                                     }
541                                                 }
542                                         }
543
544                                         // Enable the delete bookmarks menu item.
545                                         deleteBookmarksMenuItem.setEnabled(true);
546
547                                         // Reset the deleting bookmarks flag.
548                                         deletingBookmarks = false;
549                                     }
550                                 })
551                                 //Show the `SnackBar`.
552                                 .show();
553
554                         // Close the `ActionBar`.
555                         mode.finish();
556                         break;
557
558                     case R.id.context_menu_select_all_bookmarks:
559                         // Get the total number of bookmarks.
560                         int numberOfBookmarks = bookmarksListView.getCount();
561
562                         // Select them all.
563                         for (int i = 0; i < numberOfBookmarks; i++) {
564                             bookmarksListView.setItemChecked(i, true);
565                         }
566                         break;
567                 }
568
569                 // Consume the click.
570                 return true;
571             }
572
573             @Override
574             public void onDestroyActionMode(ActionMode mode) {
575                 // Do nothing.
576             }
577         });
578
579         // Get handles for the `FloatingActionButtons`.
580         FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
581         FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
582
583         // Set the create new bookmark folder FAB to display the `AlertDialog`.
584         createBookmarkFolderFab.setOnClickListener(v -> {
585             // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`.
586             AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
587             createBookmarkFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_folder));
588         });
589
590         // Set the create new bookmark FAB to display the `AlertDialog`.
591         createBookmarkFab.setOnClickListener(view -> {
592             // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`.
593             AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog();
594             createBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_bookmark));
595         });
596     }
597
598     @Override
599     public boolean onCreateOptionsMenu(Menu menu) {
600         // Inflate the menu.
601         getMenuInflater().inflate(R.menu.bookmarks_options_menu, menu);
602
603         // Success.
604         return true;
605     }
606
607     @Override
608     public boolean onOptionsItemSelected(MenuItem menuItem) {
609         // Get the ID of the `MenuItem` that was selected.
610         int menuItemId = menuItem.getItemId();
611
612         switch (menuItemId) {
613             case android.R.id.home:  // The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
614                 if (currentFolder.isEmpty()) {  // Currently in the home folder.
615                     // Update the bookmarks folder for the bookmarks drawer in `MainWebViewActivity`.
616                     MainWebViewActivity.currentBookmarksFolder = "";
617
618                     // Close the bookmarks drawer and reload the bookmarks `ListView` when returning to `MainWebViewActivity`.
619                     MainWebViewActivity.restartFromBookmarksActivity = true;
620
621                     // Return to `MainWebViewActivity`.
622                     NavUtils.navigateUpFromSameTask(this);
623                 } else {  // Currently in a subfolder.
624                     // Place the former parent folder in `currentFolder`.
625                     currentFolder = bookmarksDatabaseHelper.getParentFolder(currentFolder);
626
627                     // Load the new folder.
628                     loadFolder();
629                 }
630                 break;
631
632             case R.id.options_menu_select_all_bookmarks:
633                 // Get the total number of bookmarks.
634                 int numberOfBookmarks = bookmarksListView.getCount();
635
636                 // Select them all.
637                 for (int i = 0; i < numberOfBookmarks; i++) {
638                     bookmarksListView.setItemChecked(i, true);
639                 }
640                 break;
641
642             case R.id.bookmarks_database_view:
643                 // Launch `BookmarksDatabaseViewActivity`.
644                 Intent bookmarksDatabaseViewIntent = new Intent(this, BookmarksDatabaseViewActivity.class);
645                 startActivity(bookmarksDatabaseViewIntent);
646                 break;
647         }
648         return true;
649     }
650
651     @Override
652     public void onBackPressed() {
653         if (currentFolder.isEmpty()) {  // Currently in the home folder.
654             // Update the bookmarks folder for the bookmarks drawer in `MainWebViewActivity`.
655             MainWebViewActivity.currentBookmarksFolder = "";
656
657             // Close the bookmarks drawer and reload the bookmarks `ListView` when returning to `MainWebViewActivity`.
658             MainWebViewActivity.restartFromBookmarksActivity = true;
659
660             // Exit `BookmarksActivity`.
661             super.onBackPressed();
662         } else {  // Currently in a subfolder.
663             // Place the former parent folder in `currentFolder`.
664             currentFolder = bookmarksDatabaseHelper.getParentFolder(currentFolder);
665
666             // Load the new folder.
667             loadFolder();
668         }
669     }
670
671     @Override
672     public void onCreateBookmark(AppCompatDialogFragment dialogFragment) {
673         // Get the `EditTexts` from the `dialogFragment`.
674         EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
675         EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
676
677         // Extract the strings from the `EditTexts`.
678         String bookmarkNameString = createBookmarkNameEditText.getText().toString();
679         String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
680
681         // Convert the favoriteIcon Bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
682         ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
683         MainWebViewActivity.favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
684         byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
685
686         // Display the new bookmark below the current items in the (0 indexed) list.
687         int newBookmarkDisplayOrder = bookmarksListView.getCount();
688
689         // Create the bookmark.
690         bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
691
692         // Update `bookmarksCursor` with the current contents of this folder.
693         bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder);
694
695         // Update the `ListView`.
696         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
697
698         // Scroll to the new bookmark.
699         bookmarksListView.setSelection(newBookmarkDisplayOrder);
700     }
701
702     @Override
703     public void onCreateBookmarkFolder(AppCompatDialogFragment dialogFragment) {
704         // Get handles for the views in `dialogFragment`.
705         EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
706         RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
707         ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
708
709         // Get new folder name string.
710         String folderNameString = createFolderNameEditText.getText().toString();
711
712         // Get the new folder icon `Bitmap`.
713         Bitmap folderIconBitmap;
714         if (defaultFolderIconRadioButton.isChecked()) {  // Use the default folder icon.
715             // Get the default folder icon and convert it to a `Bitmap`.
716             Drawable folderIconDrawable = folderIconImageView.getDrawable();
717             BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
718             folderIconBitmap = folderIconBitmapDrawable.getBitmap();
719         } else {  // Use the `WebView` favorite icon.
720             folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
721         }
722
723         // Convert `folderIconBitmap` to a byte array.  `0` is for lossless compression (the only option for a PNG).
724         ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
725         folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
726         byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
727
728         // Move all the bookmarks down one in the display order.
729         for (int i = 0; i < bookmarksListView.getCount(); i++) {
730             int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
731             bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
732         }
733
734         // Create the folder, which will be placed at the top of the `ListView`.
735         bookmarksDatabaseHelper.createFolder(folderNameString, currentFolder, folderIconByteArray);
736
737         // Update `bookmarksCursor` with the current contents of this folder.
738         bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder);
739
740         // Update the `ListView`.
741         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
742
743         // Scroll to the new folder.
744         bookmarksListView.setSelection(0);
745     }
746
747     @Override
748     public void onSaveBookmark(AppCompatDialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
749         // Get handles for the views from `dialogFragment`.
750         EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
751         EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
752         RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
753
754         // Store the bookmark strings.
755         String bookmarkNameString = editBookmarkNameEditText.getText().toString();
756         String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
757
758         // Update the bookmark.
759         if (currentBookmarkIconRadioButton.isChecked()) {  // Update the bookmark without changing the favorite icon.
760             bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
761         } else {  // Update the bookmark using the `WebView` favorite icon.
762             // Convert the favorite icon to a byte array.  `0` is for lossless compression (the only option for a PNG).
763             ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
764             MainWebViewActivity.favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
765             byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
766
767             //  Update the bookmark and the favorite icon.
768             bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
769         }
770
771         // Close the contextual action mode.
772         contextualActionMode.finish();
773
774         // Update `bookmarksCursor` with the contents of the current folder.
775         bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder);
776
777         // Update the `ListView`.
778         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
779     }
780
781     @Override
782     public void onSaveBookmarkFolder(AppCompatDialogFragment dialogFragment, int selectedFolderDatabaseId) {
783         // Get handles for the views from `dialogFragment`.
784         RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
785         RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
786         ImageView defaultFolderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
787         EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
788
789         // Get the new folder name.
790         String newFolderNameString = editFolderNameEditText.getText().toString();
791
792         // Check if the favorite icon has changed.
793         if (currentFolderIconRadioButton.isChecked()) {  // Only the name has changed.
794             // Update the name in the database.
795             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
796         } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) {  // Only the icon has changed.
797             // Get the new folder icon `Bitmap`.
798             Bitmap folderIconBitmap;
799             if (defaultFolderIconRadioButton.isChecked()) {
800                 // Get the default folder icon and convert it to a `Bitmap`.
801                 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
802                 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
803                 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
804             } else {  // Use the `WebView` favorite icon.
805                 folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
806             }
807
808             // Convert the folder `Bitmap` to a byte array.  `0` is for lossless compression (the only option for a PNG).
809             ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
810             folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
811             byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
812
813             // Update the folder icon in the database.
814             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, folderIconByteArray);
815         } else {  // The folder icon and the name have changed.
816             // Instantiate the new folder icon `Bitmap`.
817             Bitmap folderIconBitmap;
818
819             // Populate the new folder icon bitmap.
820             if (defaultFolderIconRadioButton.isChecked()) {
821                 // Get the default folder icon and convert it to a `Bitmap`.
822                 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
823                 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
824                 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
825             } else {  // Use the `WebView` favorite icon.
826                 folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
827             }
828
829             // Convert the folder `Bitmap` to a byte array.  `0` is for lossless compression (the only option for a PNG).
830             ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
831             folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
832             byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
833
834             // Update the folder name and icon in the database.
835             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray);
836         }
837
838         // Update `bookmarksCursor` with the current contents of this folder.
839         bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder);
840
841         // Update the `ListView`.
842         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
843
844         // Close the contextual action mode.
845         contextualActionMode.finish();
846     }
847
848     @Override
849     public void onMoveToFolder(AppCompatDialogFragment dialogFragment) {
850         // Get a handle for the `ListView` from `dialogFragment`.
851         ListView folderListView = dialogFragment.getDialog().findViewById(R.id.move_to_folder_listview);
852
853         // Store a long array of the selected folders.
854         long[] newFolderLongArray = folderListView.getCheckedItemIds();
855
856         // Get the new folder database ID.  Only one folder will be selected.
857         int newFolderDatabaseId = (int) newFolderLongArray[0];
858
859         // Instantiate `newFolderName`.
860         String newFolderName;
861
862         // Set the new folder name.
863         if (newFolderDatabaseId == 0) {
864             // The new folder is the home folder, represented as `""` in the database.
865             newFolderName = "";
866         } else {
867             // Get the new folder name from the database.
868             newFolderName = bookmarksDatabaseHelper.getFolderName(newFolderDatabaseId);
869         }
870
871         // Get a long array with the the database ID of the selected bookmarks.
872         long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
873
874         // Move each of the selected bookmarks to the new folder.
875         for (long databaseIdLong : selectedBookmarksLongArray) {
876             // Get `databaseIdInt` for each selected bookmark.
877             int databaseIdInt = (int) databaseIdLong;
878
879             // Move the selected bookmark to the new folder.
880             bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderName);
881         }
882
883         // Update `bookmarksCursor` with the current contents of this folder.
884         bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder);
885
886         // Update the `ListView`.
887         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
888
889         // Close the contextual app bar.
890         contextualActionMode.finish();
891     }
892
893     private void deleteBookmarkFolderContents(int databaseId) {
894         // Get the name of the folder.
895         String folderName = bookmarksDatabaseHelper.getFolderName(databaseId);
896
897         // Get the contents of the folder.
898         Cursor folderCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(folderName);
899
900         // Delete each of the bookmarks in the folder.
901         for (int i = 0; i < folderCursor.getCount(); i++) {
902             // Move `folderCursor` to the current row.
903             folderCursor.moveToPosition(i);
904
905             // Get the database ID of the item.
906             int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndex(BookmarksDatabaseHelper._ID));
907
908             // If this is a folder, recursively delete the contents first.
909             if (bookmarksDatabaseHelper.isFolder(itemDatabaseId)) {
910                 deleteBookmarkFolderContents(itemDatabaseId);
911             }
912
913             // Delete the bookmark.
914             bookmarksDatabaseHelper.deleteBookmark(itemDatabaseId);
915         }
916     }
917
918     private void updateMoveIcons() {
919         // Get a long array of the selected bookmarks.
920         long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
921
922         // Get the database IDs for the first, last, and selected bookmarks.
923         int selectedBookmarkDatabaseId = (int) selectedBookmarksLongArray[0];
924         int firstBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(0);
925         // bookmarksListView is 0 indexed.
926         int lastBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(bookmarksListView.getCount() - 1);
927
928         // Update the move bookmark up `MenuItem`.
929         if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) {  // The selected bookmark is in the first position.
930             // Disable the move bookmark up `MenuItem`.
931             moveBookmarkUpMenuItem.setEnabled(false);
932
933             //  Set the move bookmark up icon to be ghosted.
934             moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_disabled);
935         } else {  // The selected bookmark is not in the first position.
936             // Enable the move bookmark up `MenuItem`.
937             moveBookmarkUpMenuItem.setEnabled(true);
938
939             // Set the icon according to the theme.
940             if (MainWebViewActivity.darkTheme) {
941                 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled_dark);
942             } else {
943                 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled_light);
944             }
945         }
946
947         // Update the move bookmark down `MenuItem`.
948         if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) {  // The selected bookmark is in the last position.
949             // Disable the move bookmark down `MenuItem`.
950             moveBookmarkDownMenuItem.setEnabled(false);
951
952             // Set the move bookmark down icon to be ghosted.
953             moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_disabled);
954         } else {  // The selected bookmark is not in the last position.
955             // Enable the move bookmark down `MenuItem`.
956             moveBookmarkDownMenuItem.setEnabled(true);
957
958             // Set the icon according to the theme.
959             if (MainWebViewActivity.darkTheme) {
960                 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled_dark);
961             } else {
962                 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled_light);
963             }
964         }
965     }
966
967     private void scrollBookmarks(int selectedBookmarkPosition) {
968         // Get the first and last visible bookmark positions.
969         int firstVisibleBookmarkPosition = bookmarksListView.getFirstVisiblePosition();
970         int lastVisibleBookmarkPosition = bookmarksListView.getLastVisiblePosition();
971
972         // Calculate the number of bookmarks per screen.
973         int numberOfBookmarksPerScreen = lastVisibleBookmarkPosition - firstVisibleBookmarkPosition;
974
975         // Scroll with the moved bookmark if necessary.
976         if (selectedBookmarkPosition <= firstVisibleBookmarkPosition) {  // The selected bookmark position is at or above the top of the screen.
977             // Scroll to the selected bookmark position.
978             bookmarksListView.setSelection(selectedBookmarkPosition);
979         } else if (selectedBookmarkPosition >= (lastVisibleBookmarkPosition - 1)) {  // The selected bookmark is at or below the bottom of the screen.  The `-1` handles partial bookmarks displayed at the bottom of the `ListView`.
980             // Scroll to display the selected bookmark at the bottom of the screen.  `+1` assured that the entire bookmark will be displayed in situations where only a partial bookmark fits at the bottom of the `ListView`.
981             bookmarksListView.setSelection(selectedBookmarkPosition - numberOfBookmarksPerScreen + 1);
982         }
983     }
984
985     private void loadFolder() {
986         // Update `bookmarksCursor` with the contents of the bookmarks database for the current folder.
987         bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentFolder);
988
989         // Setup a `CursorAdapter`.  `this` specifies the `Context`.  `false` disables `autoRequery`.
990         bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
991             @Override
992             public View newView(Context context, Cursor cursor, ViewGroup parent) {
993                 // Inflate the individual item layout.  `false` does not attach it to the root.
994                 return getLayoutInflater().inflate(R.layout.bookmarks_activity_item_linearlayout, parent, false);
995             }
996
997             @Override
998             public void bindView(View view, Context context, Cursor cursor) {
999                 // Get handles for the views.
1000                 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
1001                 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
1002
1003                 // Get the favorite icon byte array from the `Cursor`.
1004                 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
1005
1006                 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
1007                 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
1008
1009                 // Display the bitmap in `bookmarkFavoriteIcon`.
1010                 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
1011
1012                 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
1013                 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
1014                 bookmarkNameTextView.setText(bookmarkNameString);
1015
1016                 // Make the font bold for folders.
1017                 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
1018                     bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
1019                 } else {  // Reset the font to default for normal bookmarks.
1020                     bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
1021                 }
1022             }
1023         };
1024
1025         // Populate the `ListView` with the adapter.
1026         bookmarksListView.setAdapter(bookmarksCursorAdapter);
1027
1028         // Set the `AppBar` title.
1029         if (currentFolder.isEmpty()) {
1030             appBar.setTitle(R.string.bookmarks);
1031         } else {
1032             appBar.setTitle(currentFolder);
1033         }
1034     }
1035 }