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