2 * Copyright 2016-2023 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
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.
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.
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/>.
20 package com.stoutner.privacybrowser.activities
22 import android.content.Context
23 import android.content.Intent
24 import android.database.Cursor
25 import android.graphics.Bitmap
26 import android.graphics.BitmapFactory
27 import android.graphics.Typeface
28 import android.graphics.drawable.BitmapDrawable
29 import android.os.Bundle
30 import android.util.SparseBooleanArray
31 import android.view.ActionMode
32 import android.view.Menu
33 import android.view.MenuItem
34 import android.view.View
35 import android.view.ViewGroup
36 import android.view.Window
37 import android.view.WindowManager
38 import android.widget.AbsListView.MultiChoiceModeListener
39 import android.widget.AdapterView
40 import android.widget.EditText
41 import android.widget.ImageView
42 import android.widget.ListView
43 import android.widget.RadioButton
44 import android.widget.TextView
46 import androidx.activity.OnBackPressedCallback
47 import androidx.appcompat.app.ActionBar
48 import androidx.appcompat.app.AppCompatActivity
49 import androidx.appcompat.widget.Toolbar
50 import androidx.cursoradapter.widget.CursorAdapter
51 import androidx.fragment.app.DialogFragment
52 import androidx.preference.PreferenceManager
54 import com.google.android.material.floatingactionbutton.FloatingActionButton
55 import com.google.android.material.snackbar.Snackbar
57 import com.stoutner.privacybrowser.R
58 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog
59 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog
60 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog
61 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog
62 import com.stoutner.privacybrowser.dialogs.MoveToFolderDialog
63 import com.stoutner.privacybrowser.helpers.BOOKMARK_NAME
64 import com.stoutner.privacybrowser.helpers.DISPLAY_ORDER
65 import com.stoutner.privacybrowser.helpers.FAVORITE_ICON
66 import com.stoutner.privacybrowser.helpers.FOLDER_ID
67 import com.stoutner.privacybrowser.helpers.ID
68 import com.stoutner.privacybrowser.helpers.IS_FOLDER
69 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper
71 import java.io.ByteArrayOutputStream
72 import java.util.function.Consumer
74 // Define the public constants.
75 const val CURRENT_FAVORITE_ICON_BYTE_ARRAY = "A"
76 const val CURRENT_FOLDER_ID = "B"
77 const val CURRENT_TITLE = "C"
79 // Define the private constants.
80 private const val CHECKED_BOOKMARKS_ARRAY_LIST = "D"
82 class BookmarksActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, EditBookmarkDialog.EditBookmarkListener,
83 EditBookmarkFolderDialog.EditBookmarkFolderListener, MoveToFolderDialog.MoveToFolderListener {
86 // Define the public static variables, which are accessed from the bookmarks database view activity.
87 var currentFolderId: Long = 0
88 var restartFromBookmarksDatabaseViewActivity = false
91 // Define the class variables.
92 private var bookmarksDeletedSnackbar: Snackbar? = null
93 private var checkingManyBookmarks = false
94 private var closeActivityAfterDismissingSnackbar = false
95 private var contextualActionMode: ActionMode? = null
97 // Declare the class variables.
98 private lateinit var appBar: ActionBar
99 private lateinit var bookmarksCursor: Cursor
100 private lateinit var bookmarksCursorAdapter: CursorAdapter
101 private lateinit var bookmarksDatabaseHelper: BookmarksDatabaseHelper
102 private lateinit var bookmarksListView: ListView
103 private lateinit var currentFavoriteIconByteArray: ByteArray
104 private lateinit var moveBookmarkDownMenuItem: MenuItem
105 private lateinit var moveBookmarkUpMenuItem: MenuItem
106 private lateinit var moveToFolderMenuItem: MenuItem
108 override fun onCreate(savedInstanceState: Bundle?) {
109 // Get a handle for the shared preferences.
110 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
112 // Get the preferences.
113 val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
114 val bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)
116 // Disable screenshots if not allowed.
117 if (!allowScreenshots) {
118 window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
121 // Run the default commands.
122 super.onCreate(savedInstanceState)
124 // Get the intent that launched the activity.
125 val launchingIntent = intent
127 // Populate the variables from the launching intent.
128 currentFolderId = launchingIntent.getLongExtra(CURRENT_FOLDER_ID, HOME_FOLDER_ID)
129 val currentTitle = launchingIntent.getStringExtra(CURRENT_TITLE)!!
130 val currentUrl = launchingIntent.getStringExtra(CURRENT_URL)!!
131 currentFavoriteIconByteArray = launchingIntent.getByteArrayExtra(CURRENT_FAVORITE_ICON_BYTE_ARRAY)!!
133 // Convert the favorite icon byte array to a bitmap.
134 val currentFavoriteIconBitmap = BitmapFactory.decodeByteArray(currentFavoriteIconByteArray, 0, currentFavoriteIconByteArray.size)
136 // Set the content according to the app bar position.
138 // Set the content view.
139 setContentView(R.layout.bookmarks_bottom_appbar)
141 // `Window.FEATURE_ACTION_MODE_OVERLAY` makes the contextual action mode cover the support action bar. It must be requested before the content is set.
142 supportRequestWindowFeature(Window.FEATURE_ACTION_MODE_OVERLAY)
144 // Set the content view.
145 setContentView(R.layout.bookmarks_top_appbar)
148 // Get handles for the views.
149 val toolbar = findViewById<Toolbar>(R.id.bookmarks_toolbar)
150 bookmarksListView = findViewById(R.id.bookmarks_listview)
151 val createBookmarkFolderFab = findViewById<FloatingActionButton>(R.id.create_bookmark_folder_fab)
152 val createBookmarkFab = findViewById<FloatingActionButton>(R.id.create_bookmark_fab)
154 // Set the support action bar.
155 setSupportActionBar(toolbar)
157 // Get a handle for the app bar.
158 appBar = supportActionBar!!
160 // Display the home arrow on the app bar.
161 appBar.setDisplayHomeAsUpEnabled(true)
163 // Control what the system back command does.
164 val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
165 override fun handleOnBackPressed() {
166 // Prepare to finish the activity.
171 // Register the on back pressed callback.
172 onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
174 // Initialize the database helper.
175 bookmarksDatabaseHelper = BookmarksDatabaseHelper(this)
177 // Set a listener so that tapping a list item edits the bookmark or opens a folder.
178 bookmarksListView.onItemClickListener = AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, id: Long ->
179 // Convert the id from long to int to match the format of the bookmarks database.
180 val databaseId = id.toInt()
182 // Get the bookmark cursor for this ID.
183 val bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId)
185 // Move the cursor to the first entry.
186 bookmarkCursor.moveToFirst()
188 // Act upon the bookmark according to the type.
189 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(IS_FOLDER)) == 1) { // The selected bookmark is a folder.
190 // Update the current folder ID.
191 currentFolderId = bookmarkCursor.getLong(bookmarkCursor.getColumnIndexOrThrow(FOLDER_ID))
193 // Load the new folder.
195 } else { // The selected bookmark is not a folder.
196 // Instantiate the edit bookmark dialog.
197 val editBookmarkDialog = EditBookmarkDialog.editBookmark(databaseId, currentFavoriteIconBitmap)
200 editBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.edit_bookmark))
204 bookmarkCursor.close()
207 // Handle long-presses on the list view.
208 bookmarksListView.setMultiChoiceModeListener(object : MultiChoiceModeListener {
209 // Define the object variables.
210 private var deletingBookmarks = false
212 // Declare the object variables.
213 private lateinit var editBookmarkMenuItem: MenuItem
214 private lateinit var deleteBookmarksMenuItem: MenuItem
215 private lateinit var selectAllBookmarksMenuItem: MenuItem
217 override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
218 // Inflate the menu for the contextual app bar.
219 menuInflater.inflate(R.menu.bookmarks_context_menu, menu)
222 if (currentFolderId == HOME_FOLDER_ID) { // The current folder is the home folder.
223 mode.setTitle(R.string.bookmarks)
224 } else { // Use the current folder name as the title.
225 mode.title = bookmarksDatabaseHelper.getFolderName(currentFolderId)
228 // Get handles for menu items that need to be selectively disabled.
229 moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up)
230 moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down)
231 moveToFolderMenuItem = menu.findItem(R.id.move_to_folder)
232 editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark)
233 deleteBookmarksMenuItem = menu.findItem(R.id.delete_bookmark)
234 selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks)
236 // Disable the delete bookmarks menu item if a delete is pending.
237 deleteBookmarksMenuItem.isEnabled = !deletingBookmarks
239 // Store a handle for the contextual action bar so it can be closed programatically.
240 contextualActionMode = mode
246 override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
247 // Display the move to folder menu item if at least one other folder exists.
248 moveToFolderMenuItem.isVisible = bookmarksDatabaseHelper.hasFoldersExceptDatabaseId(bookmarksListView.checkedItemIds)
254 override fun onItemCheckedStateChanged(actionMode: ActionMode, position: Int, id: Long, checked: Boolean) {
255 // Only update the UI if not checking many bookmarks. In that case, the flag will be reset on the last bookmark so the UI is only updated once.
256 if (!checkingManyBookmarks) {
257 // Get the number of selected bookmarks.
258 val numberOfSelectedBookmarks = bookmarksListView.checkedItemCount
260 // Only process commands if at least one bookmark is selected. Otherwise, a context menu with 0 selected bookmarks is briefly displayed.
261 if (numberOfSelectedBookmarks > 0) {
262 // Adjust the action mode and the menu according to the number of selected bookmarks.
263 if (numberOfSelectedBookmarks == 1) { // One bookmark is selected.
264 // Show the applicable menu items.
265 moveBookmarkUpMenuItem.isVisible = true
266 moveBookmarkDownMenuItem.isVisible = true
267 editBookmarkMenuItem.isVisible = true
269 // Update the enabled status of the move icons.
271 } else { // More than one bookmark is selected.
272 // Hide non-applicable `MenuItems`.
273 moveBookmarkUpMenuItem.isVisible = false
274 moveBookmarkDownMenuItem.isVisible = false
275 editBookmarkMenuItem.isVisible = false
278 // Display the move to folder menu item if at least one other folder exists.
279 moveToFolderMenuItem.isVisible = bookmarksDatabaseHelper.hasFoldersExceptDatabaseId(bookmarksListView.checkedItemIds)
281 // List the number of selected bookmarks in the subtitle.
282 actionMode.subtitle = getString(R.string.selected, numberOfSelectedBookmarks)
284 // Show the select all menu item if all the bookmarks are not selected.
285 selectAllBookmarksMenuItem.isVisible = (numberOfSelectedBookmarks != bookmarksListView.count)
290 override fun onActionItemClicked(actionMode: ActionMode, menuItem: MenuItem): Boolean {
291 // Declare the variables.
292 val checkedBookmarkNewPosition: Int
293 val checkedBookmarksPositionsSparseBooleanArray: SparseBooleanArray
295 // Initialize the checked bookmark position.
296 var checkedBookmarkPosition = 0
298 // Get the menu item ID.
299 val menuItemId = menuItem.itemId
301 // Run the commands according to the selected action item.
302 if (menuItemId == R.id.move_bookmark_up) { // Move the bookmark up.
303 // Get the array of checked bookmark positions.
304 checkedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
306 // 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`.
307 for (i in 0 until checkedBookmarksPositionsSparseBooleanArray.size()) {
308 // Check to see if the value for the bookmark is true, meaning it is currently selected.
309 if (checkedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
310 // Only one bookmark should have a value of `true` when move bookmark up is enabled.
311 checkedBookmarkPosition = checkedBookmarksPositionsSparseBooleanArray.keyAt(i)
315 // Calculate the new position of the checked bookmark.
316 checkedBookmarkNewPosition = checkedBookmarkPosition - 1
318 // Get the bookmarks count.
319 val bookmarksCount = bookmarksListView.count
321 // Iterate through the bookmarks.
322 for (i in 0 until bookmarksCount) {
323 // Get the database ID for the current bookmark.
324 val currentBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
326 // Update the display order for the current bookmark.
327 if (i == checkedBookmarkPosition) { // The current bookmark is the selected bookmark.
328 // Move the current bookmark up one.
329 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1)
330 } else if ((i + 1) == checkedBookmarkPosition) { // The current bookmark is immediately above the selected bookmark.
331 // Move the current bookmark down one.
332 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1)
333 } else { // The current bookmark is not changing positions.
334 // Move the bookmarks cursor to the current bookmark position.
335 bookmarksCursor.moveToPosition(i)
337 // 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.
338 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(DISPLAY_ORDER)) != i)
339 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i)
343 // Update the bookmarks cursor with the current contents of the bookmarks database.
344 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
346 // Update the list view.
347 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
349 // Scroll to the new bookmark position.
350 scrollBookmarks(checkedBookmarkNewPosition)
352 // Update the enabled status of the move icons.
354 } else if (menuItemId == R.id.move_bookmark_down) { // Move the bookmark down.
355 // Get the array of checked bookmark positions.
356 checkedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
358 // Get the position of the bookmark that is selected. If other bookmarks have previously been checked they will be included in the sparse boolean array with a value of `false`.
359 for (i in 0 until checkedBookmarksPositionsSparseBooleanArray.size()) {
360 // Check to see if the value for the bookmark is true, meaning it is currently selected.
361 if (checkedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
362 // Only one bookmark should have a value of `true` when move bookmark down is enabled.
363 checkedBookmarkPosition = checkedBookmarksPositionsSparseBooleanArray.keyAt(i)
367 // Calculate the new position of the checked bookmark.
368 checkedBookmarkNewPosition = checkedBookmarkPosition + 1
370 // Iterate through the bookmarks.
371 for (i in 0 until bookmarksListView.count) {
372 // Get the database ID for the current bookmark.
373 val currentBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
375 // Update the display order for the current bookmark.
376 if (i == checkedBookmarkPosition) { // The current bookmark is the checked bookmark.
377 // Move the current bookmark down one.
378 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1)
379 } else if ((i - 1) == checkedBookmarkPosition) { // The current bookmark is immediately below the checked bookmark.
380 // Move the bookmark below the selected bookmark up one.
381 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1)
382 } else { // The current bookmark is not changing positions.
383 // Move the bookmarks cursor to the current bookmark position.
384 bookmarksCursor.moveToPosition(i)
386 // 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.
387 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(DISPLAY_ORDER)) != i) {
388 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i)
393 // Update the bookmarks cursor with the current contents of the bookmarks database.
394 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
396 // Update the list view.
397 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
399 // Scroll to the new bookmark position.
400 scrollBookmarks(checkedBookmarkNewPosition)
402 // Update the enabled status of the move icons.
404 } else if (menuItemId == R.id.move_to_folder) { // Move to folder.
405 // Instantiate the move to folder alert dialog.
406 val moveToFolderDialog = MoveToFolderDialog.moveBookmarks(currentFolderId, bookmarksListView.checkedItemIds)
408 // Show the move to folder alert dialog.
409 moveToFolderDialog.show(supportFragmentManager, resources.getString(R.string.move_to_folder))
410 } else if (menuItemId == R.id.edit_bookmark) {
411 // Get the array of checked bookmark positions.
412 checkedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
414 // 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`.
415 for (i in 0 until checkedBookmarksPositionsSparseBooleanArray.size()) {
416 // Check to see if the value for the bookmark is true, meaning it is currently selected.
417 if (checkedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
418 // Only one bookmark should have a value of `true` when move edit bookmark is enabled.
419 checkedBookmarkPosition = checkedBookmarksPositionsSparseBooleanArray.keyAt(i)
423 // Move the cursor to the selected position.
424 bookmarksCursor.moveToPosition(checkedBookmarkPosition)
426 // Get the selected bookmark database ID.
427 val databaseId = bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(ID))
429 // Show the edit bookmark or edit bookmark folder dialog.
430 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(IS_FOLDER)) == 1) { // A folder is selected.
431 // Instantiate the edit bookmark folder dialog.
432 val editFolderDialog = EditBookmarkFolderDialog.editFolder(databaseId, currentFavoriteIconBitmap)
435 editFolderDialog.show(supportFragmentManager, resources.getString(R.string.edit_folder))
436 } else { // A bookmark is selected.
437 // Instantiate the edit bookmark dialog.
438 val editBookmarkDialog = EditBookmarkDialog.editBookmark(databaseId, currentFavoriteIconBitmap)
441 editBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.edit_bookmark))
443 } else if (menuItemId == R.id.delete_bookmark) { // Delete bookmark.
444 // Set the deleting bookmarks flag, which prevents the delete menu item from being enabled until the current process finishes.
445 deletingBookmarks = true
447 // Get an array of the checked row IDs.
448 val checkedBookmarksIdsLongArray = bookmarksListView.checkedItemIds
450 // Initialize a variable to count the number of bookmarks to delete.
451 var numberOfBookmarksToDelete = 0
453 // Count the number of bookmarks to delete.
454 for (databaseIdLong in checkedBookmarksIdsLongArray) {
455 // Convert the database ID long to an int.
456 val databaseIdInt = databaseIdLong.toInt()
458 // Count the contents of the folder if the selected bookmark is a folder.
459 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
460 // Add the bookmarks from the folder to the running total.
461 numberOfBookmarksToDelete += countBookmarkFolderContents(databaseIdInt)
464 // Increment the count of the number of bookmarks to delete.
465 numberOfBookmarksToDelete++
468 // 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.
469 checkedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions.clone()
471 // Update the bookmarks cursor with the current contents of the bookmarks database except for the specified database IDs.
472 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(checkedBookmarksIdsLongArray, currentFolderId)
474 // Update the list view.
475 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
477 // Create a Snackbar with the number of deleted bookmarks.
478 bookmarksDeletedSnackbar = Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.bookmarks_deleted, numberOfBookmarksToDelete), Snackbar.LENGTH_LONG)
479 .setAction(R.string.undo) { } // Do nothing because everything will be handled by `onDismissed()` below.
480 .addCallback(object : Snackbar.Callback() {
481 override fun onDismissed(snackbar: Snackbar, event: Int) {
482 if (event == DISMISS_EVENT_ACTION) { // The user pushed the undo button.
483 // Update the bookmarks cursor with the current contents of the bookmarks database, including the "deleted" bookmarks.
484 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
486 // Update the list view.
487 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
489 // Get the number of checked bookmarks.
490 val numberOfCheckedBookmarks = checkedBookmarksPositionsSparseBooleanArray.size()
492 // Set the checking many bookmarks flag.
493 checkingManyBookmarks = true
495 // Re-check the previously checked bookmarks.
496 for (i in 0 until numberOfCheckedBookmarks) {
497 // Reset the checking many bookmarks flag on the last bookmark so the UI is updated.
498 if (i == (numberOfCheckedBookmarks - 1))
499 checkingManyBookmarks = false
501 // Check the bookmark.
502 bookmarksListView.setItemChecked(checkedBookmarksPositionsSparseBooleanArray.keyAt(i), true)
504 } else { // The snackbar was dismissed without the undo button being pushed.
505 // Delete each selected bookmark.
506 for (databaseIdLong in checkedBookmarksIdsLongArray) {
507 // Convert the database long ID to an int.
508 val databaseIdInt = databaseIdLong.toInt()
510 // Delete the contents of the folder if the selected bookmark is a folder.
511 if (bookmarksDatabaseHelper.isFolder(databaseIdInt))
512 deleteBookmarkFolderContents(databaseIdInt)
514 // Delete the selected bookmark.
515 bookmarksDatabaseHelper.deleteBookmark(databaseIdInt)
518 // Update the display order.
519 for (i in 0 until bookmarksListView.count) {
520 // Get the database ID for the current bookmark.
521 val currentBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
523 // Move bookmarks cursor to the current bookmark position.
524 bookmarksCursor.moveToPosition(i)
526 // Update the display order only if it is not correct in the database.
527 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(DISPLAY_ORDER)) != i)
528 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i)
532 // Reset the deleting bookmarks flag.
533 deletingBookmarks = false
535 // Enable the delete bookmarks menu item.
536 deleteBookmarksMenuItem.isEnabled = true
538 // Close the activity if back has been pressed.
539 if (closeActivityAfterDismissingSnackbar) {
540 // Close the bookmarks drawer and reload the bookmarks list view when returning to the main WebView activity.
541 MainWebViewActivity.restartFromBookmarksActivity = true
543 // Finish the activity.
549 // Show the Snackbar.
550 bookmarksDeletedSnackbar!!.show()
551 } else if (menuItemId == R.id.context_menu_select_all_bookmarks) { // Select all.
552 // Get the total number of bookmarks.
553 val numberOfBookmarks = bookmarksListView.count
555 // Set the checking many bookmarks flag.
556 checkingManyBookmarks = true
559 for (i in 0 until numberOfBookmarks) {
560 // Reset the checking many bookmarks flag on the last bookmark so the UI is updated.
561 if (i == (numberOfBookmarks - 1))
562 checkingManyBookmarks = false
564 // Check the bookmark.
565 bookmarksListView.setItemChecked(i, true)
569 // Consume the click.
573 override fun onDestroyActionMode(mode: ActionMode) {
578 // Set the create new bookmark folder FAB to display the alert dialog.
579 createBookmarkFolderFab.setOnClickListener {
580 // Create a create bookmark folder dialog.
581 val createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentFavoriteIconBitmap)
583 // Show the create bookmark folder dialog.
584 createBookmarkFolderDialog.show(supportFragmentManager, getString(R.string.create_folder))
587 // Set the create new bookmark FAB to display the alert dialog.
588 createBookmarkFab.setOnClickListener {
589 // Instantiate the create bookmark dialog.
590 val createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentUrl, currentTitle, currentFavoriteIconBitmap)
592 // Display the create bookmark dialog.
593 createBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.create_bookmark))
596 // Restore the state if the app has been restarted.
597 if (savedInstanceState != null) {
598 // Restore the current folder.
599 currentFolderId = savedInstanceState.getLong(CURRENT_FOLDER_ID, HOME_FOLDER_ID)
601 // Update the bookmarks list view after it has loaded.
602 bookmarksListView.post {
603 // Get the checked bookmarks array list.
604 val checkedBookmarksArrayList = savedInstanceState.getIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST)!!
606 // Check each previously checked bookmark in the list view.
607 checkedBookmarksArrayList.forEach(Consumer { position: Int -> bookmarksListView.setItemChecked(position, true) })
611 // Load the current folder.
615 public override fun onRestart() {
616 // Run the default commands.
619 // Update the list view if returning from the bookmarks database view activity.
620 if (restartFromBookmarksDatabaseViewActivity) {
621 // Load the current folder in the list view.
624 // Reset the restart from bookmarks database view activity flag.
625 restartFromBookmarksDatabaseViewActivity = false
629 public override fun onSaveInstanceState(savedInstanceState: Bundle) {
630 // Run the default commands.
631 super.onSaveInstanceState(savedInstanceState)
633 // Get the sparse boolean array of the checked items.
634 val checkedBookmarksSparseBooleanArray = bookmarksListView.checkedItemPositions
636 // Create a checked items array list.
637 val checkedBookmarksArrayList = ArrayList<Int>()
639 // Add each checked bookmark position to the array list.
640 for (i in 0 until checkedBookmarksSparseBooleanArray.size()) {
641 // 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.
642 if (checkedBookmarksSparseBooleanArray.valueAt(i)) {
643 // Add the bookmark position to the checked bookmarks array list.
644 checkedBookmarksArrayList.add(checkedBookmarksSparseBooleanArray.keyAt(i))
648 // Store the variables in the saved instance state.
649 savedInstanceState.putLong(CURRENT_FOLDER_ID, currentFolderId)
650 savedInstanceState.putIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST, checkedBookmarksArrayList)
653 override fun onCreateOptionsMenu(menu: Menu): Boolean {
655 menuInflater.inflate(R.menu.bookmarks_options_menu, menu)
661 override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
662 // Get a handle for the menu item ID.
663 val menuItemId = menuItem.itemId
665 // Run the command according to the selected option.
666 if (menuItemId == android.R.id.home) { // Home. The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
667 if (currentFolderId == HOME_FOLDER_ID) { // The current folder is the home folder.
668 // Prepare to finish the activity.
670 } else { // Currently in a subfolder.
671 // Set the former parent folder as the current folder.
672 currentFolderId = bookmarksDatabaseHelper.getParentFolderId(currentFolderId)
674 // Load the new current folder.
677 } else if (menuItemId == R.id.options_menu_select_all_bookmarks) { // Select all.
678 // Get the total number of bookmarks.
679 val numberOfBookmarks = bookmarksListView.count
681 // Set the checking many bookmarks flag.
682 checkingManyBookmarks = true
685 for (i in 0 until numberOfBookmarks) {
686 // Reset the checking many bookmarks flag on the last bookmark so the UI is updated.
687 if (i == (numberOfBookmarks - 1))
688 checkingManyBookmarks = false
690 // Check the bookmark.
691 bookmarksListView.setItemChecked(i, true)
693 } else if (menuItemId == R.id.bookmarks_database_view) {
694 // Close the contextual action bar if it is displayed. This can happen if the bottom app bar is enabled.
695 contextualActionMode?.finish()
697 // Create an intent to launch the bookmarks database view activity.
698 val bookmarksDatabaseViewIntent = Intent(this, BookmarksDatabaseViewActivity::class.java)
700 // Include the favorite icon byte array to the intent.
701 bookmarksDatabaseViewIntent.putExtra(CURRENT_FAVORITE_ICON_BYTE_ARRAY, currentFavoriteIconByteArray)
704 startActivity(bookmarksDatabaseViewIntent)
709 override fun createBookmark(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
710 // Get the alert dialog from the fragment.
711 val dialog = dialogFragment.dialog!!
713 // Get the views from the dialog fragment.
714 val createBookmarkNameEditText = dialog.findViewById<EditText>(R.id.create_bookmark_name_edittext)
715 val createBookmarkUrlEditText = dialog.findViewById<EditText>(R.id.create_bookmark_url_edittext)
717 // Extract the strings from the edit texts.
718 val bookmarkNameString = createBookmarkNameEditText.text.toString()
719 val bookmarkUrlString = createBookmarkUrlEditText.text.toString()
721 // Create a favorite icon byte array output stream.
722 val favoriteIconByteArrayOutputStream = ByteArrayOutputStream()
724 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
725 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream)
727 // Convert the favorite icon byte array stream to a byte array.
728 val favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray()
730 // Display the new bookmark below the current items in the (0 indexed) list.
731 val newBookmarkDisplayOrder = bookmarksListView.count
733 // Create the bookmark.
734 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentFolderId, newBookmarkDisplayOrder, favoriteIconByteArray)
736 // Update the bookmarks cursor with the current contents of this folder.
737 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
739 // Update the list view.
740 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
742 // Scroll to the new bookmark.
743 bookmarksListView.setSelection(newBookmarkDisplayOrder)
746 override fun createBookmarkFolder(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
747 // Get the dialog from the dialog fragment.
748 val dialog = dialogFragment.dialog!!
750 // Get handles for the views in the dialog fragment.
751 val folderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
752 val defaultIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
753 val defaultIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
755 // Get new folder name string.
756 val folderNameString = folderNameEditText.text.toString()
758 // Set the folder icon bitmap according to the dialog.
759 val folderIconBitmap = if (defaultIconRadioButton.isChecked) { // Use the default folder icon.
760 // Get the default folder icon drawable.
761 val folderIconDrawable = defaultIconImageView.drawable
763 // Convert the folder icon drawable to a bitmap drawable.
764 val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
766 // Convert the folder icon bitmap drawable to a bitmap.
767 folderIconBitmapDrawable.bitmap
768 } else { // Use the WebView favorite icon.
769 // Copy the favorite icon bitmap to the folder icon bitmap.
773 // Create a folder icon byte array output stream.
774 val folderIconByteArrayOutputStream = ByteArrayOutputStream()
776 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
777 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream)
779 // Convert the folder icon byte array stream to a byte array.
780 val folderIconByteArray = folderIconByteArrayOutputStream.toByteArray()
782 // Move all the bookmarks down one in the display order.
783 for (i in 0 until bookmarksListView.count) {
784 val databaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
785 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, displayOrder = i + 1)
788 // Create the folder, which will be placed at the top of the list view.
789 bookmarksDatabaseHelper.createFolder(folderNameString, currentFolderId, displayOrder = 0, folderIconByteArray)
791 // Update the bookmarks cursor with the contents of the current folder.
792 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
794 // Update the list view.
795 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
797 // Scroll to the new folder.
798 bookmarksListView.setSelection(0)
801 override fun onSaveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int, favoriteIconBitmap: Bitmap) {
802 // Get the dialog from the dialog fragment.
803 val dialog = dialogFragment.dialog!!
805 // Get handles for the views from the dialog fragment.
806 val bookmarkNameEditText = dialog.findViewById<EditText>(R.id.bookmark_name_edittext)
807 val bookmarkUrlEditText = dialog.findViewById<EditText>(R.id.bookmark_url_edittext)
808 val currentIconRadioButton = dialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)
810 // Get the bookmark strings.
811 val bookmarkNameString = bookmarkNameEditText.text.toString()
812 val bookmarkUrlString = bookmarkUrlEditText.text.toString()
814 // Update the bookmark.
815 if (currentIconRadioButton.isChecked) { // Update the bookmark without changing the favorite icon.
816 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString)
817 } else { // Update the bookmark using the WebView favorite icon.
818 // Create a favorite icon byte array output stream.
819 val newFavoriteIconByteArrayOutputStream = ByteArrayOutputStream()
821 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
822 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream)
824 // Convert the favorite icon byte array stream to a byte array.
825 val newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray()
827 // Update the bookmark and the favorite icon.
828 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray)
831 // Close the contextual action bar if it is displayed.
832 contextualActionMode?.finish()
834 // Update the bookmarks cursor with the contents of the current folder.
835 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
837 // Update the list view.
838 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
841 override fun onSaveBookmarkFolder(dialogFragment: DialogFragment, selectedFolderDatabaseId: Int, favoriteIconBitmap: Bitmap) {
842 // Get the dialog from the dialog fragment.
843 val dialog = dialogFragment.dialog!!
845 // Get handles for the views from the dialog fragment.
846 val currentFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)
847 val defaultFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
848 val defaultFolderIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
849 val editFolderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
851 // Get the new folder name.
852 val newFolderName = editFolderNameEditText.text.toString()
854 // Check if the favorite icon has changed.
855 if (currentFolderIconRadioButton.isChecked) { // Only the name has changed.
856 // Update the name in the database.
857 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderName)
858 } else { // The icon has changed.
859 // Populate the new folder icon bitmap.
860 val folderIconBitmap: Bitmap = if (defaultFolderIconRadioButton.isChecked) {
861 // Get the default folder icon drawable.
862 val folderIconDrawable = defaultFolderIconImageView.drawable
864 // Convert the folder icon drawable to a bitmap drawable.
865 val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
867 // Convert the folder icon bitmap drawable to a bitmap.
868 folderIconBitmapDrawable.bitmap
869 } else { // Use the WebView favorite icon.
870 // Copy the favorite icon bitmap to the folder icon bitmap.
874 // Create a folder icon byte array output stream.
875 val newFolderIconByteArrayOutputStream = ByteArrayOutputStream()
877 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
878 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream)
880 // Convert the folder icon byte array stream to a byte array.
881 val newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray()
883 // Update the database.
884 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderName, newFolderIconByteArray)
887 // Update the bookmarks cursor with the current contents of this folder.
888 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
890 // Update the list view.
891 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
893 // Close the contextual action mode.
894 contextualActionMode!!.finish()
897 override fun onMoveToFolder(dialogFragment: DialogFragment) {
898 // Get the dialog from the dialog fragment.
899 val dialog = dialogFragment.dialog!!
901 // Get a handle for the folder list view from the dialog.
902 val folderListView = dialog.findViewById<ListView>(R.id.move_to_folder_listview)
904 // Store a long array of the selected folders.
905 val newFolderLongArray = folderListView.checkedItemIds
907 // Get the new folder database ID. Only one folder will be selected so it will be the first one.
908 val newFolderDatabaseId = newFolderLongArray[0].toInt()
910 // Set the new folder name.
911 val newFolderId = if (newFolderDatabaseId == HOME_FOLDER_DATABASE_ID)
912 // The new folder is the home folder.
915 // Get the new folder name from the database.
916 bookmarksDatabaseHelper.getFolderId(newFolderDatabaseId)
918 // Get a long array with the the database ID of the selected bookmarks.
919 val selectedBookmarksLongArray = bookmarksListView.checkedItemIds
921 // Move each of the selected bookmarks to the new folder.
922 for (databaseIdLong in selectedBookmarksLongArray) {
923 // Convert the database long ID to an int for each selected bookmark.
924 val databaseIdInt = databaseIdLong.toInt()
926 // Move the selected bookmark to the new folder.
927 bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderId)
930 // Update the bookmarks cursor with the current contents of this folder.
931 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
933 // Update the list view.
934 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
936 // Close the contextual app bar.
937 contextualActionMode!!.finish()
940 private fun countBookmarkFolderContents(folderDatabaseId: Int): Int {
941 // Get the folder ID.
942 val folderId = bookmarksDatabaseHelper.getFolderId(folderDatabaseId)
944 // Get the contents of the folder.
945 val folderCursor = bookmarksDatabaseHelper.getBookmarkAndFolderIds(folderId)
947 // Initialize the bookmark counter.
948 var bookmarkCounter = 0
950 // Count each of the bookmarks in the folder.
951 for (i in 0 until folderCursor.count) {
952 // Move the folder cursor to the current row.
953 folderCursor.moveToPosition(i)
955 // Get the database ID of the item.
956 val itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(ID))
958 // If this is a folder, recursively count the contents first.
959 if (bookmarksDatabaseHelper.isFolder(itemDatabaseId))
960 bookmarkCounter += countBookmarkFolderContents(itemDatabaseId)
962 // Add the bookmark to the running total.
966 // Return the bookmark counter.
967 return bookmarkCounter
970 private fun deleteBookmarkFolderContents(folderDatabaseId: Int) {
971 // Get the folder ID.
972 val folderId = bookmarksDatabaseHelper.getFolderId(folderDatabaseId)
974 // Get the contents of the folder.
975 val folderCursor = bookmarksDatabaseHelper.getBookmarkAndFolderIds(folderId)
977 // Delete each of the bookmarks in the folder.
978 for (i in 0 until folderCursor.count) {
979 // Move the folder cursor to the current row.
980 folderCursor.moveToPosition(i)
982 // Get the database ID of the item.
983 val itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(ID))
985 // If this is a folder, recursively delete the contents first.
986 if (bookmarksDatabaseHelper.isFolder(itemDatabaseId))
987 deleteBookmarkFolderContents(itemDatabaseId)
989 // Delete the bookmark.
990 bookmarksDatabaseHelper.deleteBookmark(itemDatabaseId)
994 private fun prepareFinish() {
995 // 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.
996 if (bookmarksDeletedSnackbar != null && bookmarksDeletedSnackbar!!.isShown) { // Close the bookmarks deleted snackbar before going home.
997 // Set the close flag.
998 closeActivityAfterDismissingSnackbar = true
1000 // Dismiss the snackbar.
1001 bookmarksDeletedSnackbar!!.dismiss()
1002 } else { // Go home immediately.
1003 // Update the bookmarks folder for the bookmarks drawer in the main WebView activity.
1004 MainWebViewActivity.currentBookmarksFolderId = currentFolderId
1006 // Close the bookmarks drawer and reload the bookmarks list view when returning to the main WebView activity.
1007 MainWebViewActivity.restartFromBookmarksActivity = true
1009 // Exit the bookmarks activity.
1014 private fun updateMoveIcons() {
1015 // Get a long array of the selected bookmarks.
1016 val selectedBookmarksLongArray = bookmarksListView.checkedItemIds
1018 // Get the database IDs for the first, last, and selected bookmarks.
1019 val firstBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(0).toInt()
1020 val lastBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(bookmarksListView.count - 1).toInt() // The bookmarks list view is 0 indexed.
1021 val selectedBookmarkDatabaseId = selectedBookmarksLongArray[0].toInt()
1023 // Update the move bookmark up menu item.
1024 if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) { // The selected bookmark is in the first position.
1025 // Disable the move bookmark up menu item.
1026 moveBookmarkUpMenuItem.isEnabled = false
1029 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_disabled)
1030 } else { // The selected bookmark is not in the first position.
1031 // Enable the move bookmark up menu item.
1032 moveBookmarkUpMenuItem.isEnabled = true
1034 // Set the icon according to the theme.
1035 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled)
1038 // Update the move bookmark down menu item.
1039 if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) { // The selected bookmark is in the last position.
1040 // Disable the move bookmark down menu item.
1041 moveBookmarkDownMenuItem.isEnabled = false
1044 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_disabled)
1045 } else { // The selected bookmark is not in the last position.
1046 // Enable the move bookmark down menu item.
1047 moveBookmarkDownMenuItem.isEnabled = true
1050 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled)
1054 private fun scrollBookmarks(selectedBookmarkPosition: Int) {
1055 // Get the first and last visible bookmark positions.
1056 val firstVisibleBookmarkPosition = bookmarksListView.firstVisiblePosition
1057 val lastVisibleBookmarkPosition = bookmarksListView.lastVisiblePosition
1059 // Calculate the number of bookmarks per screen.
1060 val numberOfBookmarksPerScreen = lastVisibleBookmarkPosition - firstVisibleBookmarkPosition
1062 // Scroll with the moved bookmark if necessary.
1063 if (selectedBookmarkPosition <= firstVisibleBookmarkPosition) { // The selected bookmark position is at or above the top of the screen.
1064 // Scroll to the selected bookmark position.
1065 bookmarksListView.setSelection(selectedBookmarkPosition)
1066 } else if (selectedBookmarkPosition >= lastVisibleBookmarkPosition - 1) { // The selected bookmark is at or below the bottom of the screen.
1067 // 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.
1068 // `+1` assures that the entire bookmark will be displayed in situations where only a partial bookmark fits at the bottom of the list view.
1069 bookmarksListView.setSelection(selectedBookmarkPosition - numberOfBookmarksPerScreen + 1)
1073 private fun loadFolder() {
1074 // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
1075 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolderId)
1077 // Setup a cursor adapter.
1078 bookmarksCursorAdapter = object : CursorAdapter(this, bookmarksCursor, false) {
1079 override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
1080 // Inflate the individual item layout.
1081 return layoutInflater.inflate(R.layout.bookmarks_activity_item_linearlayout, parent, false)
1084 override fun bindView(view: View, context: Context, cursor: Cursor) {
1085 // Get handles for the views.
1086 val bookmarkFavoriteIconImageView = view.findViewById<ImageView>(R.id.bookmark_favorite_icon)
1087 val bookmarkNameTextView = view.findViewById<TextView>(R.id.bookmark_name)
1089 // Get the favorite icon byte array from the cursor.
1090 val favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndexOrThrow(FAVORITE_ICON))
1092 // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
1093 val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size)
1095 // Display the bitmap in the bookmark favorite icon image view.
1096 bookmarkFavoriteIconImageView.setImageBitmap(favoriteIconBitmap)
1098 // Get the bookmark name from the cursor.
1099 val bookmarkNameString = cursor.getString(cursor.getColumnIndexOrThrow(BOOKMARK_NAME))
1101 // Display the bookmark name.
1102 bookmarkNameTextView.text = bookmarkNameString
1104 // Make the font bold for folders.
1105 if (cursor.getInt(cursor.getColumnIndexOrThrow(IS_FOLDER)) == 1)
1106 bookmarkNameTextView.typeface = Typeface.DEFAULT_BOLD
1107 else // Reset the font to default for normal bookmarks.
1108 bookmarkNameTextView.typeface = Typeface.DEFAULT
1112 // Populate the list view with the adapter.
1113 bookmarksListView.adapter = bookmarksCursorAdapter
1115 // Set the app bar title.
1116 if (currentFolderId == HOME_FOLDER_ID) // The home folder is the current folder.
1117 appBar.setTitle(R.string.bookmarks)
1119 appBar.title = bookmarksDatabaseHelper.getFolderName(currentFolderId)
1122 public override fun onDestroy() {
1123 // Close the bookmarks cursor and database.
1124 bookmarksCursor.close()
1125 bookmarksDatabaseHelper.close()
1127 // Run the default commands.