]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.kt
Migrate the remaining classes to Kotlin. https://redmine.stoutner.com/issues/989
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / BookmarksActivity.kt
1 /*
2  * Copyright 2016-2023 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
5  *
6  * Privacy Browser Android 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 Android 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 Android.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.activities
21
22 import android.annotation.SuppressLint
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.os.Bundle
31 import android.util.SparseBooleanArray
32 import android.view.ActionMode
33 import android.view.Menu
34 import android.view.MenuItem
35 import android.view.View
36 import android.view.ViewGroup
37 import android.view.Window
38 import android.view.WindowManager
39 import android.widget.AbsListView.MultiChoiceModeListener
40 import android.widget.AdapterView
41 import android.widget.EditText
42 import android.widget.ImageView
43 import android.widget.ListView
44 import android.widget.RadioButton
45 import android.widget.TextView
46
47 import androidx.activity.OnBackPressedCallback
48 import androidx.appcompat.app.ActionBar
49 import androidx.appcompat.app.AppCompatActivity
50 import androidx.appcompat.widget.Toolbar
51 import androidx.cursoradapter.widget.CursorAdapter
52 import androidx.fragment.app.DialogFragment
53 import androidx.preference.PreferenceManager
54
55 import com.google.android.material.floatingactionbutton.FloatingActionButton
56 import com.google.android.material.snackbar.Snackbar
57
58 import com.stoutner.privacybrowser.R
59 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog.Companion.createBookmark
60 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog.CreateBookmarkListener
61 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog.Companion.createBookmarkFolder
62 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog.CreateBookmarkFolderListener
63 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog.Companion.bookmarkDatabaseId
64 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog.EditBookmarkListener
65 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog.Companion.folderDatabaseId
66 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog.EditBookmarkFolderListener
67 import com.stoutner.privacybrowser.dialogs.MoveToFolderDialog.Companion.moveBookmarks
68 import com.stoutner.privacybrowser.dialogs.MoveToFolderDialog.MoveToFolderListener
69 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper
70
71 import java.io.ByteArrayOutputStream
72 import java.util.function.Consumer
73
74 // Define the public constants.
75 const val CURRENT_FOLDER = "current_folder"
76 const val CURRENT_TITLE = "current_title"
77 const val CURRENT_FAVORITE_ICON_BYTE_ARRAY = "current_favorite_icon_byte_array"
78
79 // Define the private constants.
80 private const val CHECKED_BOOKMARKS_ARRAY_LIST = "checked_bookmarks_array_list"
81
82 class BookmarksActivity : AppCompatActivity(), CreateBookmarkListener, CreateBookmarkFolderListener, EditBookmarkListener, EditBookmarkFolderListener, MoveToFolderListener {
83     companion object {
84         // Define the public static variables, which are accessed from the bookmarks database view activity.
85         var currentFolder: String = ""
86         var restartFromBookmarksDatabaseViewActivity = false
87     }
88
89     // Define the class variables.
90     private var bookmarksDeletedSnackbar: Snackbar? = null
91     private var closeActivityAfterDismissingSnackbar = false
92     private var contextualActionMode: ActionMode? = null
93
94     // Declare the class variables.
95     private lateinit var appBar: ActionBar
96     private lateinit var bookmarksCursor: Cursor
97     private lateinit var bookmarksCursorAdapter: CursorAdapter
98     private lateinit var bookmarksDatabaseHelper: BookmarksDatabaseHelper
99     private lateinit var bookmarksListView: ListView
100     private lateinit var currentFavoriteIconByteArray: ByteArray
101     private lateinit var moveBookmarkDownMenuItem: MenuItem
102     private lateinit var moveBookmarkUpMenuItem: MenuItem
103     private lateinit var oldFolderNameString: String
104
105     override fun onCreate(savedInstanceState: Bundle?) {
106         // Get a handle for the shared preferences.
107         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
108
109         // Get the preferences.
110         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
111         val bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)
112
113         // Disable screenshots if not allowed.
114         if (!allowScreenshots) {
115             window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
116         }
117
118         // Run the default commands.
119         super.onCreate(savedInstanceState)
120
121         // Get the intent that launched the activity.
122         val launchingIntent = intent
123
124         // Populate the variables from the launching intent.
125         currentFolder = launchingIntent.getStringExtra(CURRENT_FOLDER)!!
126         val currentTitle = launchingIntent.getStringExtra(CURRENT_TITLE)!!
127         val currentUrl = launchingIntent.getStringExtra(CURRENT_URL)!!
128         currentFavoriteIconByteArray = launchingIntent.getByteArrayExtra(CURRENT_FAVORITE_ICON_BYTE_ARRAY)!!
129
130         /*  TODO.  Test if not needed.
131         // Set the current folder variable.
132         if (launchingIntent.getStringExtra(CURRENT_FOLDER) != null) {  // Set the current folder from the intent.
133             currentFolder = launchingIntent.getStringExtra(CURRENT_FOLDER)
134         } else {  // Set the current folder to be `""`, which is the home folder.
135             currentFolder = ""
136         }
137
138          */
139
140         // Convert the favorite icon byte array to a bitmap.
141         val currentFavoriteIconBitmap = BitmapFactory.decodeByteArray(currentFavoriteIconByteArray, 0, currentFavoriteIconByteArray.size)
142
143         // Set the content according to the app bar position.
144         if (bottomAppBar) {
145             // Set the content view.
146             setContentView(R.layout.bookmarks_bottom_appbar)
147         } else {
148             // `Window.FEATURE_ACTION_MODE_OVERLAY` makes the contextual action mode cover the support action bar.  It must be requested before the content is set.
149             supportRequestWindowFeature(Window.FEATURE_ACTION_MODE_OVERLAY)
150
151             // Set the content view.
152             setContentView(R.layout.bookmarks_top_appbar)
153         }
154
155         // Get handles for the views.
156         val toolbar = findViewById<Toolbar>(R.id.bookmarks_toolbar)
157         bookmarksListView = findViewById(R.id.bookmarks_listview)
158         val createBookmarkFolderFab = findViewById<FloatingActionButton>(R.id.create_bookmark_folder_fab)
159         val createBookmarkFab = findViewById<FloatingActionButton>(R.id.create_bookmark_fab)
160
161         // Set the support action bar.
162         setSupportActionBar(toolbar)
163
164         // Get a handle for the app bar.
165         appBar = supportActionBar!!
166
167         // Display the home arrow on the app bar.
168         appBar.setDisplayHomeAsUpEnabled(true)
169
170         // Control what the system back command does.
171         val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
172             override fun handleOnBackPressed() {
173                 // Prepare to finish the activity.
174                 prepareFinish()
175             }
176         }
177
178         // Register the on back pressed callback.
179         onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
180
181         // Initialize the database helper.
182         bookmarksDatabaseHelper = BookmarksDatabaseHelper(this)
183
184         // Set a listener so that tapping a list item loads the URL or folder.
185         bookmarksListView.onItemClickListener = AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, id: Long ->
186             // Convert the id from long to int to match the format of the bookmarks database.
187             val databaseId = id.toInt()
188
189             // Get the bookmark cursor for this ID.
190             val bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId)
191
192             // Move the cursor to the first entry.
193             bookmarkCursor.moveToFirst()
194
195             // Act upon the bookmark according to the type.
196             if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {  // The selected bookmark is a folder.
197                 // Update the current folder.
198                 currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME))
199
200                 // Load the new folder.
201                 loadFolder()
202             } else {  // The selected bookmark is not a folder.
203                 // Instantiate the edit bookmark dialog.
204                 val editBookmarkDialog: DialogFragment = bookmarkDatabaseId(databaseId, currentFavoriteIconBitmap)
205
206                 // Make it so.
207                 editBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.edit_bookmark))
208             }
209
210             // Close the cursor.
211             bookmarkCursor.close()
212         }
213
214         // Handle long-presses on the list view.
215         bookmarksListView.setMultiChoiceModeListener(object : MultiChoiceModeListener {
216             // Define the object variables.
217             private var deletingBookmarks = false
218
219             // Declare the object variables.
220             private lateinit var editBookmarkMenuItem: MenuItem
221             private lateinit var deleteBookmarksMenuItem: MenuItem
222             private lateinit var selectAllBookmarksMenuItem: MenuItem
223
224             override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
225                 // Inflate the menu for the contextual app bar.
226                 menuInflater.inflate(R.menu.bookmarks_context_menu, menu)
227
228                 // Set the title.
229                 if (currentFolder.isEmpty()) {  // Use `R.string.bookmarks` if in the home folder.
230                     mode.setTitle(R.string.bookmarks)
231                 } else {  // Use the current folder name as the title.
232                     mode.title = currentFolder
233                 }
234
235                 // Get handles for menu items that need to be selectively disabled.
236                 moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up)
237                 moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down)
238                 editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark)
239                 deleteBookmarksMenuItem = menu.findItem(R.id.delete_bookmark)
240                 selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks)
241
242                 // Disable the delete bookmarks menu item if a delete is pending.
243                 deleteBookmarksMenuItem.isEnabled = !deletingBookmarks
244
245                 // Store a handle for the contextual action bar so it can be closed programatically.
246                 contextualActionMode = mode
247
248                 // Make it so.
249                 return true
250             }
251
252             override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
253                 // Get a handle for the move to folder menu item.
254                 val moveToFolderMenuItem = menu.findItem(R.id.move_to_folder)
255
256                 // Get a Cursor with all of the folders.
257                 val folderCursor = bookmarksDatabaseHelper.allFolders
258
259                 // Display the move to folder menu item if at least one folder exists.
260                 moveToFolderMenuItem.isVisible = folderCursor.count > 0
261
262                 // Make it so.
263                 return true
264             }
265
266             override fun onItemCheckedStateChanged(mode: ActionMode, position: Int, id: Long, checked: Boolean) {
267                 // Get the number of selected bookmarks.
268                 val numberOfSelectedBookmarks = bookmarksListView.checkedItemCount
269
270                 // Only process commands if at least one bookmark is selected.  Otherwise, a context menu with 0 selected bookmarks is briefly displayed.
271                 if (numberOfSelectedBookmarks > 0) {
272                     // Adjust the action mode and the menu according to the number of selected bookmarks.
273                     if (numberOfSelectedBookmarks == 1) {  // One bookmark is selected.
274                         // Show the applicable menu items.
275                         moveBookmarkUpMenuItem.isVisible = true
276                         moveBookmarkDownMenuItem.isVisible = true
277                         editBookmarkMenuItem.isVisible = true
278
279                         // Update the enabled status of the move icons.
280                         updateMoveIcons()
281                     } else {  // More than one bookmark is selected.
282                         // Hide non-applicable `MenuItems`.
283                         moveBookmarkUpMenuItem.isVisible = false
284                         moveBookmarkDownMenuItem.isVisible = false
285                         editBookmarkMenuItem.isVisible = false
286                     }
287
288                     // List the number of selected bookmarks in the subtitle.
289                     mode.subtitle = getString(R.string.selected, numberOfSelectedBookmarks)
290
291                     // Show the select all menu item if all the bookmarks are not selected.
292                     selectAllBookmarksMenuItem.isVisible = (numberOfSelectedBookmarks != bookmarksListView.count)
293                 }
294             }
295
296             override fun onActionItemClicked(actionMode: ActionMode, menuItem: MenuItem): Boolean {
297                 // Declare the variables.
298                 val selectedBookmarkNewPosition: Int
299                 val selectedBookmarksPositionsSparseBooleanArray: SparseBooleanArray
300
301                 // Initialize the selected bookmark position.
302                 var selectedBookmarkPosition = 0
303
304                 // Get the menu item ID.
305                 val menuItemId = menuItem.itemId
306
307                 // Run the commands according to the selected action item.
308                 if (menuItemId == R.id.move_bookmark_up) {  // Move the bookmark up.
309                     // Get the array of checked bookmark positions.
310                     selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
311
312                     // Get the position of the bookmark that is selected.  If other bookmarks have previously been selected they will be included in the sparse boolean array with a value of `false`.
313                     for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size()) {
314                         // Check to see if the value for the bookmark is true, meaning it is currently selected.
315                         if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
316                             // Only one bookmark should have a value of `true` when move bookmark up is enabled.
317                             selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i)
318                         }
319                     }
320
321                     // Calculate the new position of the selected bookmark.
322                     selectedBookmarkNewPosition = selectedBookmarkPosition - 1
323
324                     // Iterate through the bookmarks.
325                     for (i in 0 until bookmarksListView.count) {
326                         // Get the database ID for the current bookmark.
327                         val currentBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
328
329                         // Update the display order for the current bookmark.
330                         if (i == selectedBookmarkPosition) {  // The current bookmark is the selected bookmark.
331                             // Move the current bookmark up one.
332                             bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1)
333                         } else if ((i + 1) == selectedBookmarkPosition) {  // The current bookmark is immediately above the selected bookmark.
334                             // Move the current bookmark down one.
335                             bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1)
336                         } else {  // The current bookmark is not changing positions.
337                             // Move the bookmarks cursor to the current bookmark position.
338                             bookmarksCursor.moveToPosition(i)
339
340                             // Update the display order only if it is not correct in the database.  This fixes problems where the display order somehow got out of sync.
341                             if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i)
342                                 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i)
343                         }
344                     }
345
346                     // Update the bookmarks cursor with the current contents of the bookmarks database.
347                     bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
348
349                     // Update the list view.
350                     bookmarksCursorAdapter.changeCursor(bookmarksCursor)
351
352                     // Scroll to the new bookmark position.
353                     scrollBookmarks(selectedBookmarkNewPosition)
354
355                     // Update the enabled status of the move icons.
356                     updateMoveIcons()
357                 } else if (menuItemId == R.id.move_bookmark_down) {  // Move the bookmark down.
358                     // Get the array of checked bookmark positions.
359                     selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
360
361                     // Get the position of the bookmark that is selected.  If other bookmarks have previously been selected they will be included in the sparse boolean array with a value of `false`.
362                     for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size()) {
363                         // Check to see if the value for the bookmark is true, meaning it is currently selected.
364                         if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
365                             // Only one bookmark should have a value of `true` when move bookmark down is enabled.
366                             selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i)
367                         }
368                     }
369
370                     // Calculate the new position of the selected bookmark.
371                     selectedBookmarkNewPosition = selectedBookmarkPosition + 1
372
373                     // Iterate through the bookmarks.
374                     for (i in 0 until bookmarksListView.count) {
375                         // Get the database ID for the current bookmark.
376                         val currentBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
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 the bookmarks cursor to the current bookmark position.
387                             bookmarksCursor.moveToPosition(i)
388
389                             // Update the display order only if it is not correct in the database.  This fixes problems where the display order somehow got out of sync.
390                             if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
391                                 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i)
392                             }
393                         }
394                     }
395
396                     // Update the bookmarks cursor with the current contents of the bookmarks database.
397                     bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
398
399                     // Update the list view.
400                     bookmarksCursorAdapter.changeCursor(bookmarksCursor)
401
402                     // Scroll to the new bookmark position.
403                     scrollBookmarks(selectedBookmarkNewPosition)
404
405                     // Update the enabled status of the move icons.
406                     updateMoveIcons()
407                 } else if (menuItemId == R.id.move_to_folder) {  // Move to folder.
408                     // Instantiate the move to folder alert dialog.
409                     val moveToFolderDialog: DialogFragment = moveBookmarks(currentFolder, bookmarksListView.checkedItemIds)
410
411                     // Show the move to folder alert dialog.
412                     moveToFolderDialog.show(supportFragmentManager, resources.getString(R.string.move_to_folder))
413                 } else if (menuItemId == R.id.edit_bookmark) {
414                     // Get the array of checked bookmark positions.
415                     selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
416
417                     // Get the position of the bookmark that is selected.  If other bookmarks have previously been selected they will be included in the sparse boolean array with a value of `false`.
418                     for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size()) {
419                         // Check to see if the value for the bookmark is true, meaning it is currently selected.
420                         if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
421                             // Only one bookmark should have a value of `true` when move edit bookmark is enabled.
422                             selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i)
423                         }
424                     }
425
426                     // Move the cursor to the selected position.
427                     bookmarksCursor.moveToPosition(selectedBookmarkPosition)
428
429                     // Get the selected bookmark database ID.
430                     val databaseId = bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID))
431
432                     // Show the edit bookmark or edit bookmark folder dialog.
433                     if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {  // A folder is selected.
434                         // Save the current folder name, which is used in `onSaveBookmarkFolder()`.
435                         oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME))
436
437                         // Instantiate the edit bookmark folder dialog.
438                         val editFolderDialog: DialogFragment = folderDatabaseId(databaseId, currentFavoriteIconBitmap)
439
440                         // Make it so.
441                         editFolderDialog.show(supportFragmentManager, resources.getString(R.string.edit_folder))
442                     } else {  // A bookmark is selected.
443                         // Instantiate the edit bookmark dialog.
444                         val editBookmarkDialog: DialogFragment = bookmarkDatabaseId(databaseId, currentFavoriteIconBitmap)
445
446                         // Make it so.
447                         editBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.edit_bookmark))
448                     }
449                 } else if (menuItemId == R.id.delete_bookmark) {  // Delete bookmark.
450                     // Set the deleting bookmarks flag, which prevents the delete menu item from being enabled until the current process finishes.
451                     deletingBookmarks = true
452
453                     // Get an array of the selected row IDs.
454                     val selectedBookmarksIdsLongArray = bookmarksListView.checkedItemIds
455
456                     // Initialize a variable to count the number of bookmarks to delete.
457                     var numberOfBookmarksToDelete = 0
458
459                     // Count the number of bookmarks to delete.
460                     for (databaseIdLong in selectedBookmarksIdsLongArray) {
461                         // Convert the database ID long to an int.
462                         val databaseIdInt = databaseIdLong.toInt()
463
464                         // Count the contents of the folder if the selected bookmark is a folder.
465                         if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
466                             // Add the bookmarks from the folder to the running total.
467                             numberOfBookmarksToDelete += countBookmarkFolderContents(databaseIdInt)
468                         }
469
470                         // Increment the count of the number of bookmarks to delete.
471                         numberOfBookmarksToDelete++
472                     }
473
474                     // Get an array of checked bookmarks.  `.clone()` makes a copy that won't change if the list view is reloaded, which is needed for re-selecting the bookmarks on undelete.
475                     selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions.clone()
476
477                     // Update the bookmarks cursor with the current contents of the bookmarks database except for the specified database IDs.
478                     bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(selectedBookmarksIdsLongArray, currentFolder)
479
480                     // Update the list view.
481                     bookmarksCursorAdapter.changeCursor(bookmarksCursor)
482
483                     // Create a Snackbar with the number of deleted bookmarks.
484                     bookmarksDeletedSnackbar = Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.bookmarks_deleted, numberOfBookmarksToDelete), Snackbar.LENGTH_LONG)
485                         .setAction(R.string.undo) { }  // Do nothing because everything will be handled by `onDismissed()` below.
486                         .addCallback(object : Snackbar.Callback() {
487                             @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
488                             override fun onDismissed(snackbar: Snackbar, event: Int) {
489                                 if (event == DISMISS_EVENT_ACTION) {  // The user pushed the undo button.
490                                     // Update the bookmarks cursor with the current contents of the bookmarks database, including the "deleted" bookmarks.
491                                     bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
492
493                                     // Update the list view.
494                                     bookmarksCursorAdapter.changeCursor(bookmarksCursor)
495
496                                     // Re-select the previously selected bookmarks.
497                                     for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size())
498                                         bookmarksListView.setItemChecked(selectedBookmarksPositionsSparseBooleanArray.keyAt(i), true)
499                                 } else {  // The snackbar was dismissed without the undo button being pushed.
500                                     // Delete each selected bookmark.
501                                     for (databaseIdLong in selectedBookmarksIdsLongArray) {
502                                         // Convert the database long ID to an int.
503                                         val databaseIdInt = databaseIdLong.toInt()
504
505                                         // Delete the contents of the folder if the selected bookmark is a folder.
506                                         if (bookmarksDatabaseHelper.isFolder(databaseIdInt))
507                                             deleteBookmarkFolderContents(databaseIdInt)
508
509                                         // Delete the selected bookmark.
510                                         bookmarksDatabaseHelper.deleteBookmark(databaseIdInt)
511                                     }
512
513                                     // Update the display order.
514                                     for (i in 0 until bookmarksListView.count) {
515                                         // Get the database ID for the current bookmark.
516                                         val currentBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
517
518                                         // Move bookmarks cursor to the current bookmark position.
519                                         bookmarksCursor.moveToPosition(i)
520
521                                         // Update the display order only if it is not correct in the database.
522                                         if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i)
523                                             bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i)
524                                     }
525                                 }
526
527                                 // Reset the deleting bookmarks flag.
528                                 deletingBookmarks = false
529
530                                 // Enable the delete bookmarks menu item.
531                                 deleteBookmarksMenuItem.isEnabled = true
532
533                                 // Close the activity if back has been pressed.
534                                 if (closeActivityAfterDismissingSnackbar)
535                                     finish()
536                             }
537                         })
538
539                     // Show the Snackbar.
540                     bookmarksDeletedSnackbar!!.show()
541                 } else if (menuItemId == R.id.context_menu_select_all_bookmarks) {  // Select all.
542                     // Get the total number of bookmarks.
543                     val numberOfBookmarks = bookmarksListView.count
544
545                     // Select them all.
546                     for (i in 0 until numberOfBookmarks) {
547                         bookmarksListView.setItemChecked(i, true)
548                     }
549                 }
550
551                 // Consume the click.
552                 return true
553             }
554
555             override fun onDestroyActionMode(mode: ActionMode) {
556                 // Do nothing.
557             }
558         })
559
560         // Set the create new bookmark folder FAB to display the alert dialog.
561         createBookmarkFolderFab.setOnClickListener {
562             // Create a create bookmark folder dialog.
563             val createBookmarkFolderDialog: DialogFragment = createBookmarkFolder(currentFavoriteIconBitmap)
564
565             // Show the create bookmark folder dialog.
566             createBookmarkFolderDialog.show(supportFragmentManager, getString(R.string.create_folder))
567         }
568
569         // Set the create new bookmark FAB to display the alert dialog.
570         createBookmarkFab.setOnClickListener {
571             // Instantiate the create bookmark dialog.
572             val createBookmarkDialog: DialogFragment = createBookmark(currentUrl, currentTitle, currentFavoriteIconBitmap)
573
574             // Display the create bookmark dialog.
575             createBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.create_bookmark))
576         }
577
578         // Restore the state if the app has been restarted.
579         if (savedInstanceState != null) {
580             // Restore the current folder.
581             currentFolder = savedInstanceState.getString(CURRENT_FOLDER)!!
582
583             // Update the bookmarks list view after it has loaded.
584             bookmarksListView.post {
585                 // Get the checked bookmarks array list.
586                 val checkedBookmarksArrayList = savedInstanceState.getIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST)!!
587
588                 // Check each previously checked bookmark in the list view.
589                 checkedBookmarksArrayList.forEach(Consumer { position: Int -> bookmarksListView.setItemChecked(position, true) })
590             }
591         }
592
593         // Load the current folder.
594         loadFolder()
595     }
596
597     public override fun onRestart() {
598         // Run the default commands.
599         super.onRestart()
600
601         // Update the list view if returning from the bookmarks database view activity.
602         if (restartFromBookmarksDatabaseViewActivity) {
603             // Load the current folder in the list view.
604             loadFolder()
605
606             // Reset the restart from bookmarks database view activity flag.
607             restartFromBookmarksDatabaseViewActivity = false
608         }
609     }
610
611     public override fun onSaveInstanceState(savedInstanceState: Bundle) {
612         // Run the default commands.
613         super.onSaveInstanceState(savedInstanceState)
614
615         // Get the sparse boolean array of the checked items.
616         val checkedBookmarksSparseBooleanArray = bookmarksListView.checkedItemPositions
617
618         // Create a checked items array list.
619         val checkedBookmarksArrayList = ArrayList<Int>()
620
621         // Add each checked bookmark position to the array list.
622         for (i in 0 until checkedBookmarksSparseBooleanArray.size()) {
623             // Check to see if the bookmark is currently checked.  Bookmarks that have previously been checked but currently aren't will be populated in the sparse boolean array, but will return false.
624             if (checkedBookmarksSparseBooleanArray.valueAt(i)) {
625                 // Add the bookmark position to the checked bookmarks array list.
626                 checkedBookmarksArrayList.add(checkedBookmarksSparseBooleanArray.keyAt(i))
627             }
628         }
629
630         // Store the variables in the saved instance state.
631         savedInstanceState.putString(CURRENT_FOLDER, currentFolder)
632         savedInstanceState.putIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST, checkedBookmarksArrayList)
633     }
634
635     override fun onCreateOptionsMenu(menu: Menu): Boolean {
636         // Inflate the menu.
637         menuInflater.inflate(R.menu.bookmarks_options_menu, menu)
638
639         // Success.
640         return true
641     }
642
643     override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
644         // Get a handle for the menu item ID.
645         val menuItemId = menuItem.itemId
646
647         // Run the command according to the selected option.
648         if (menuItemId == android.R.id.home) {  // Home.  The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
649             if (currentFolder.isEmpty()) {  // Currently in the home folder.
650                 // Prepare to finish the activity.
651                 prepareFinish()
652             } else {  // Currently in a subfolder.
653                 // Set the former parent folder as the current folder.
654                 currentFolder = bookmarksDatabaseHelper.getParentFolderName(currentFolder)
655
656                 // Load the new current folder.
657                 loadFolder()
658             }
659         } else if (menuItemId == R.id.options_menu_select_all_bookmarks) {  // Select all.
660             // Get the total number of bookmarks.
661             val numberOfBookmarks = bookmarksListView.count
662
663             // Select them all.
664             for (i in 0 until numberOfBookmarks) {
665                 bookmarksListView.setItemChecked(i, true)
666             }
667         } else if (menuItemId == R.id.bookmarks_database_view) {
668             // Close the contextual action bar if it is displayed.  This can happen if the bottom app bar is enabled.
669             contextualActionMode?.finish()
670
671             // Create an intent to launch the bookmarks database view activity.
672             val bookmarksDatabaseViewIntent = Intent(this, BookmarksDatabaseViewActivity::class.java)
673
674             // Include the favorite icon byte array to the intent.
675             bookmarksDatabaseViewIntent.putExtra(CURRENT_FAVORITE_ICON_BYTE_ARRAY, currentFavoriteIconByteArray)
676
677             // Make it so.
678             startActivity(bookmarksDatabaseViewIntent)
679         }
680         return true
681     }
682
683     override fun onCreateBookmark(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
684         // Get the alert dialog from the fragment.
685         val dialog = dialogFragment.dialog!!
686
687         // Get the views from the dialog fragment.
688         val createBookmarkNameEditText = dialog.findViewById<EditText>(R.id.create_bookmark_name_edittext)
689         val createBookmarkUrlEditText = dialog.findViewById<EditText>(R.id.create_bookmark_url_edittext)
690
691         // Extract the strings from the edit texts.
692         val bookmarkNameString = createBookmarkNameEditText.text.toString()
693         val bookmarkUrlString = createBookmarkUrlEditText.text.toString()
694
695         // Create a favorite icon byte array output stream.
696         val favoriteIconByteArrayOutputStream = ByteArrayOutputStream()
697
698         // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
699         favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream)
700
701         // Convert the favorite icon byte array stream to a byte array.
702         val favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray()
703
704         // Display the new bookmark below the current items in the (0 indexed) list.
705         val newBookmarkDisplayOrder = bookmarksListView.count
706
707         // Create the bookmark.
708         bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentFolder, newBookmarkDisplayOrder, favoriteIconByteArray)
709
710         // Update the bookmarks cursor with the current contents of this folder.
711         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
712
713         // Update the list view.
714         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
715
716         // Scroll to the new bookmark.
717         bookmarksListView.setSelection(newBookmarkDisplayOrder)
718     }
719
720     override fun onCreateBookmarkFolder(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
721         // Get the dialog from the dialog fragment.
722         val dialog = dialogFragment.dialog!!
723
724         // Get handles for the views in the dialog fragment.
725         val folderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
726         val defaultIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
727         val defaultIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
728
729         // Get new folder name string.
730         val folderNameString = folderNameEditText.text.toString()
731
732         // Set the folder icon bitmap according to the dialog.
733         val folderIconBitmap = if (defaultIconRadioButton.isChecked) {  // Use the default folder icon.
734             // Get the default folder icon drawable.
735             val folderIconDrawable = defaultIconImageView.drawable
736
737             // Convert the folder icon drawable to a bitmap drawable.
738             val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
739
740             // Convert the folder icon bitmap drawable to a bitmap.
741             folderIconBitmapDrawable.bitmap
742         } else {  // Use the WebView favorite icon.
743             // Copy the favorite icon bitmap to the folder icon bitmap.
744             favoriteIconBitmap
745         }
746
747         // Create a folder icon byte array output stream.
748         val folderIconByteArrayOutputStream = ByteArrayOutputStream()
749
750         // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
751         folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream)
752
753         // Convert the folder icon byte array stream to a byte array.
754         val folderIconByteArray = folderIconByteArrayOutputStream.toByteArray()
755
756         // Move all the bookmarks down one in the display order.
757         for (i in 0 until bookmarksListView.count) {
758             val databaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
759             bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1)
760         }
761
762         // Create the folder, which will be placed at the top of the list view.
763         bookmarksDatabaseHelper.createFolder(folderNameString, currentFolder, folderIconByteArray)
764
765         // Update the bookmarks cursor with the contents of the current folder.
766         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
767
768         // Update the list view.
769         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
770
771         // Scroll to the new folder.
772         bookmarksListView.setSelection(0)
773     }
774
775     override fun onSaveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int, favoriteIconBitmap: Bitmap) {
776         // Get the dialog from the dialog fragment.
777         val dialog = dialogFragment.dialog!!
778
779         // Get handles for the views from the dialog fragment.
780         val bookmarkNameEditText = dialog.findViewById<EditText>(R.id.bookmark_name_edittext)
781         val bookmarkUrlEditText = dialog.findViewById<EditText>(R.id.bookmark_url_edittext)
782         val currentIconRadioButton = dialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)
783
784         // Get the bookmark strings.
785         val bookmarkNameString = bookmarkNameEditText.text.toString()
786         val bookmarkUrlString = bookmarkUrlEditText.text.toString()
787
788         // Update the bookmark.
789         if (currentIconRadioButton.isChecked) {  // Update the bookmark without changing the favorite icon.
790             bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString)
791         } else {  // Update the bookmark using the WebView favorite icon.
792             // Create a favorite icon byte array output stream.
793             val newFavoriteIconByteArrayOutputStream = ByteArrayOutputStream()
794
795             // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
796             favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream)
797
798             // Convert the favorite icon byte array stream to a byte array.
799             val newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray()
800
801             //  Update the bookmark and the favorite icon.
802             bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray)
803         }
804
805         // Close the contextual action bar if it is displayed.
806         contextualActionMode?.finish()
807
808         // Update the bookmarks cursor with the contents of the current folder.
809         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
810
811         // Update the list view.
812         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
813     }
814
815     override fun onSaveBookmarkFolder(dialogFragment: DialogFragment, selectedFolderDatabaseId: Int, favoriteIconBitmap: Bitmap) {
816         // Get the dialog from the dialog fragment.
817         val dialog = dialogFragment.dialog!!
818
819         // Get handles for the views from the dialog fragment.
820         val currentFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)
821         val defaultFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
822         val defaultFolderIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
823         val editFolderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
824
825         // Get the new folder name.
826         val newFolderNameString = editFolderNameEditText.text.toString()
827
828         // Check if the favorite icon has changed.
829         if (currentFolderIconRadioButton.isChecked) {  // Only the name has changed.
830             // Update the name in the database.
831             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString)
832         } else {  // The icon has changed.  TODO:  Test.
833             // Populate the new folder icon bitmap.
834             val folderIconBitmap: Bitmap = if (defaultFolderIconRadioButton.isChecked) {
835                 // Get the default folder icon drawable.
836                 val folderIconDrawable = defaultFolderIconImageView.drawable
837
838                 // Convert the folder icon drawable to a bitmap drawable.
839                 val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
840
841                 // Convert the folder icon bitmap drawable to a bitmap.
842                 folderIconBitmapDrawable.bitmap
843             } else {  // Use the WebView favorite icon.
844                 // Copy the favorite icon bitmap to the folder icon bitmap.
845                 favoriteIconBitmap
846             }
847
848             // Create a folder icon byte array output stream.
849             val newFolderIconByteArrayOutputStream = ByteArrayOutputStream()
850
851             // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
852             folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream)
853
854             // Convert the folder icon byte array stream to a byte array.
855             val newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray()
856
857             // Update the database.
858             if (!currentFolderIconRadioButton.isChecked && newFolderNameString == oldFolderNameString)  // Only the icon has changed.
859                 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray)
860             else  // The folder icon and the name have changed.
861                 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray)
862         }
863
864         // Update the bookmarks cursor with the current contents of this folder.
865         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
866
867         // Update the list view.
868         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
869
870         // Close the contextual action mode.
871         contextualActionMode!!.finish()
872     }
873
874     override fun onMoveToFolder(dialogFragment: DialogFragment) {
875         // Get the dialog from the dialog fragment.
876         val dialog = dialogFragment.dialog!!
877
878         // Get a handle for the folder list view from the dialog.
879         val folderListView = dialog.findViewById<ListView>(R.id.move_to_folder_listview)
880
881         // Store a long array of the selected folders.
882         val newFolderLongArray = folderListView.checkedItemIds
883
884         // Get the new folder database ID.  Only one folder will be selected so it will be the first one.
885         val newFolderDatabaseId = newFolderLongArray[0].toInt()
886
887         // Set the new folder name.
888         val newFolderName = if (newFolderDatabaseId == 0) {
889             // The new folder is the home folder, represented as `""` in the database.
890             ""
891         } else {
892             // Get the new folder name from the database.
893             bookmarksDatabaseHelper.getFolderName(newFolderDatabaseId)
894         }
895
896         // Get a long array with the the database ID of the selected bookmarks.
897         val selectedBookmarksLongArray = bookmarksListView.checkedItemIds
898
899         // Move each of the selected bookmarks to the new folder.
900         for (databaseIdLong in selectedBookmarksLongArray) {
901             // Convert the database long ID to an int for each selected bookmark.
902             val databaseIdInt = databaseIdLong.toInt()
903
904             // Move the selected bookmark to the new folder.
905             bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderName)
906         }
907
908         // Update the bookmarks cursor with the current contents of this folder.
909         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
910
911         // Update the list view.
912         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
913
914         // Close the contextual app bar.
915         contextualActionMode!!.finish()
916     }
917
918     private fun countBookmarkFolderContents(folderDatabaseId: Int): Int {
919         // Get the name of the folder.
920         val folderName = bookmarksDatabaseHelper.getFolderName(folderDatabaseId)
921
922         // Get the contents of the folder.
923         val folderCursor = bookmarksDatabaseHelper.getBookmarkIds(folderName)
924
925         // Initialize the bookmark counter.
926         var bookmarkCounter = 0
927
928         // Count each of the bookmarks in the folder.
929         for (i in 0 until folderCursor.count) {
930             // Move the folder cursor to the current row.
931             folderCursor.moveToPosition(i)
932
933             // Get the database ID of the item.
934             val itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID))
935
936             // If this is a folder, recursively count the contents first.
937             if (bookmarksDatabaseHelper.isFolder(itemDatabaseId))
938                 bookmarkCounter += countBookmarkFolderContents(itemDatabaseId)
939
940             // Add the bookmark to the running total.
941             bookmarkCounter++
942         }
943
944         // Return the bookmark counter.
945         return bookmarkCounter
946     }
947
948     private fun deleteBookmarkFolderContents(folderDatabaseId: Int) {
949         // Get the name of the folder.
950         val folderName = bookmarksDatabaseHelper.getFolderName(folderDatabaseId)
951
952         // Get the contents of the folder.
953         val folderCursor = bookmarksDatabaseHelper.getBookmarkIds(folderName)
954
955         // Delete each of the bookmarks in the folder.
956         for (i in 0 until folderCursor.count) {
957             // Move the folder cursor to the current row.
958             folderCursor.moveToPosition(i)
959
960             // Get the database ID of the item.
961             val itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID))
962
963             // If this is a folder, recursively delete the contents first.
964             if (bookmarksDatabaseHelper.isFolder(itemDatabaseId))
965                 deleteBookmarkFolderContents(itemDatabaseId)
966
967             // Delete the bookmark.
968             bookmarksDatabaseHelper.deleteBookmark(itemDatabaseId)
969         }
970     }
971
972     private fun prepareFinish() {
973         // Check to see if a snackbar is currently displayed.  If so, it must be closed before exiting so that a pending delete is completed before reloading the list view in the bookmarks drawer.
974         if (bookmarksDeletedSnackbar != null && bookmarksDeletedSnackbar!!.isShown) {  // Close the bookmarks deleted snackbar before going home.
975             // Set the close flag.
976             closeActivityAfterDismissingSnackbar = true
977
978             // Dismiss the snackbar.
979             bookmarksDeletedSnackbar!!.dismiss()
980         } else {  // Go home immediately.
981             // Update the bookmarks folder for the bookmarks drawer in the main WebView activity.
982             MainWebViewActivity.currentBookmarksFolder = currentFolder
983
984             // Close the bookmarks drawer and reload the bookmarks list view when returning to the main WebView activity.
985             MainWebViewActivity.restartFromBookmarksActivity = true
986
987             // Exit the bookmarks activity.
988             finish()
989         }
990     }
991
992     private fun updateMoveIcons() {
993         // Get a long array of the selected bookmarks.
994         val selectedBookmarksLongArray = bookmarksListView.checkedItemIds
995
996         // Get the database IDs for the first, last, and selected bookmarks.
997         val firstBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(0).toInt()
998         val lastBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(bookmarksListView.count - 1).toInt()  // The bookmarks list view is 0 indexed.
999         val selectedBookmarkDatabaseId = selectedBookmarksLongArray[0].toInt()
1000
1001         // Update the move bookmark up menu item.
1002         if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) {  // The selected bookmark is in the first position.
1003             // Disable the move bookmark up menu item.
1004             moveBookmarkUpMenuItem.isEnabled = false
1005
1006             //  Set the icon.
1007             moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_disabled)
1008         } else {  // The selected bookmark is not in the first position.
1009             // Enable the move bookmark up menu item.
1010             moveBookmarkUpMenuItem.isEnabled = true
1011
1012             // Set the icon according to the theme.
1013             moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled)
1014         }
1015
1016         // Update the move bookmark down menu item.
1017         if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) {  // The selected bookmark is in the last position.
1018             // Disable the move bookmark down menu item.
1019             moveBookmarkDownMenuItem.isEnabled = false
1020
1021             // Set the icon.
1022             moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_disabled)
1023         } else {  // The selected bookmark is not in the last position.
1024             // Enable the move bookmark down menu item.
1025             moveBookmarkDownMenuItem.isEnabled = true
1026
1027             // Set the icon.
1028             moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled)
1029         }
1030     }
1031
1032     private fun scrollBookmarks(selectedBookmarkPosition: Int) {
1033         // Get the first and last visible bookmark positions.
1034         val firstVisibleBookmarkPosition = bookmarksListView.firstVisiblePosition
1035         val lastVisibleBookmarkPosition = bookmarksListView.lastVisiblePosition
1036
1037         // Calculate the number of bookmarks per screen.
1038         val numberOfBookmarksPerScreen = lastVisibleBookmarkPosition - firstVisibleBookmarkPosition
1039
1040         // Scroll with the moved bookmark if necessary.
1041         if (selectedBookmarkPosition <= firstVisibleBookmarkPosition) {  // The selected bookmark position is at or above the top of the screen.
1042             // Scroll to the selected bookmark position.
1043             bookmarksListView.setSelection(selectedBookmarkPosition)
1044         } else if (selectedBookmarkPosition >= lastVisibleBookmarkPosition - 1) {  // The selected bookmark is at or below the bottom of the screen.
1045             // The `-1` handles partial bookmarks displayed at the bottom of the list view.  This command scrolls to display the selected bookmark at the bottom of the screen.
1046             // `+1` assures that the entire bookmark will be displayed in situations where only a partial bookmark fits at the bottom of the list view.
1047             bookmarksListView.setSelection(selectedBookmarkPosition - numberOfBookmarksPerScreen + 1)
1048         }
1049     }
1050
1051     private fun loadFolder() {
1052         // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
1053         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
1054
1055         // Setup a cursor adapter.
1056         bookmarksCursorAdapter = object : CursorAdapter(this, bookmarksCursor, false) {
1057             override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
1058                 // Inflate the individual item layout.
1059                 return layoutInflater.inflate(R.layout.bookmarks_activity_item_linearlayout, parent, false)
1060             }
1061
1062             override fun bindView(view: View, context: Context, cursor: Cursor) {
1063                 // Get handles for the views.
1064                 val bookmarkFavoriteIconImageView = view.findViewById<ImageView>(R.id.bookmark_favorite_icon)
1065                 val bookmarkNameTextView = view.findViewById<TextView>(R.id.bookmark_name)
1066
1067                 // Get the favorite icon byte array from the cursor.
1068                 val favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON))
1069
1070                 // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
1071                 val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size)
1072
1073                 // Display the bitmap in the bookmark favorite icon image view.
1074                 bookmarkFavoriteIconImageView.setImageBitmap(favoriteIconBitmap)
1075
1076                 // Get the bookmark name from the cursor.
1077                 val bookmarkNameString = cursor.getString(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME))
1078
1079                 // Display the bookmark name.
1080                 bookmarkNameTextView.text = bookmarkNameString
1081
1082                 // Make the font bold for folders.
1083                 if (cursor.getInt(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1)
1084                     bookmarkNameTextView.typeface = Typeface.DEFAULT_BOLD
1085                 else  // Reset the font to default for normal bookmarks.
1086                     bookmarkNameTextView.typeface = Typeface.DEFAULT
1087             }
1088         }
1089
1090         // Populate the list view with the adapter.
1091         bookmarksListView.adapter = bookmarksCursorAdapter
1092
1093         // Set the app bar title.
1094         if (currentFolder.isEmpty())
1095             appBar.setTitle(R.string.bookmarks)
1096         else
1097             appBar.title = currentFolder
1098     }
1099
1100     public override fun onDestroy() {
1101         // Close the bookmarks cursor and database.
1102         bookmarksCursor.close()
1103         bookmarksDatabaseHelper.close()
1104
1105         // Run the default commands.
1106         super.onDestroy()
1107     }
1108 }