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.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
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
55 import com.google.android.material.floatingactionbutton.FloatingActionButton
56 import com.google.android.material.snackbar.Snackbar
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
71 import java.io.ByteArrayOutputStream
72 import java.util.function.Consumer
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"
79 // Define the private constants.
80 private const val CHECKED_BOOKMARKS_ARRAY_LIST = "checked_bookmarks_array_list"
82 class BookmarksActivity : AppCompatActivity(), CreateBookmarkListener, CreateBookmarkFolderListener, EditBookmarkListener, EditBookmarkFolderListener, MoveToFolderListener {
84 // Define the public static variables, which are accessed from the bookmarks database view activity.
85 var currentFolder: String = ""
86 var restartFromBookmarksDatabaseViewActivity = false
89 // Define the class variables.
90 private var bookmarksDeletedSnackbar: Snackbar? = null
91 private var closeActivityAfterDismissingSnackbar = false
92 private var contextualActionMode: ActionMode? = null
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
105 override fun onCreate(savedInstanceState: Bundle?) {
106 // Get a handle for the shared preferences.
107 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
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)
113 // Disable screenshots if not allowed.
114 if (!allowScreenshots) {
115 window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
118 // Run the default commands.
119 super.onCreate(savedInstanceState)
121 // Get the intent that launched the activity.
122 val launchingIntent = intent
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)!!
130 // Convert the favorite icon byte array to a bitmap.
131 val currentFavoriteIconBitmap = BitmapFactory.decodeByteArray(currentFavoriteIconByteArray, 0, currentFavoriteIconByteArray.size)
133 // Set the content according to the app bar position.
135 // Set the content view.
136 setContentView(R.layout.bookmarks_bottom_appbar)
138 // `Window.FEATURE_ACTION_MODE_OVERLAY` makes the contextual action mode cover the support action bar. It must be requested before the content is set.
139 supportRequestWindowFeature(Window.FEATURE_ACTION_MODE_OVERLAY)
141 // Set the content view.
142 setContentView(R.layout.bookmarks_top_appbar)
145 // Get handles for the views.
146 val toolbar = findViewById<Toolbar>(R.id.bookmarks_toolbar)
147 bookmarksListView = findViewById(R.id.bookmarks_listview)
148 val createBookmarkFolderFab = findViewById<FloatingActionButton>(R.id.create_bookmark_folder_fab)
149 val createBookmarkFab = findViewById<FloatingActionButton>(R.id.create_bookmark_fab)
151 // Set the support action bar.
152 setSupportActionBar(toolbar)
154 // Get a handle for the app bar.
155 appBar = supportActionBar!!
157 // Display the home arrow on the app bar.
158 appBar.setDisplayHomeAsUpEnabled(true)
160 // Control what the system back command does.
161 val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
162 override fun handleOnBackPressed() {
163 // Prepare to finish the activity.
168 // Register the on back pressed callback.
169 onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
171 // Initialize the database helper.
172 bookmarksDatabaseHelper = BookmarksDatabaseHelper(this)
174 // Set a listener so that tapping a list item loads the URL or folder.
175 bookmarksListView.onItemClickListener = AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, id: Long ->
176 // Convert the id from long to int to match the format of the bookmarks database.
177 val databaseId = id.toInt()
179 // Get the bookmark cursor for this ID.
180 val bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId)
182 // Move the cursor to the first entry.
183 bookmarkCursor.moveToFirst()
185 // Act upon the bookmark according to the type.
186 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
187 // Update the current folder.
188 currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME))
190 // Load the new folder.
192 } else { // The selected bookmark is not a folder.
193 // Instantiate the edit bookmark dialog.
194 val editBookmarkDialog: DialogFragment = bookmarkDatabaseId(databaseId, currentFavoriteIconBitmap)
197 editBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.edit_bookmark))
201 bookmarkCursor.close()
204 // Handle long-presses on the list view.
205 bookmarksListView.setMultiChoiceModeListener(object : MultiChoiceModeListener {
206 // Define the object variables.
207 private var deletingBookmarks = false
209 // Declare the object variables.
210 private lateinit var editBookmarkMenuItem: MenuItem
211 private lateinit var deleteBookmarksMenuItem: MenuItem
212 private lateinit var selectAllBookmarksMenuItem: MenuItem
214 override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
215 // Inflate the menu for the contextual app bar.
216 menuInflater.inflate(R.menu.bookmarks_context_menu, menu)
219 if (currentFolder.isEmpty()) { // Use `R.string.bookmarks` if in the home folder.
220 mode.setTitle(R.string.bookmarks)
221 } else { // Use the current folder name as the title.
222 mode.title = currentFolder
225 // Get handles for menu items that need to be selectively disabled.
226 moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up)
227 moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down)
228 editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark)
229 deleteBookmarksMenuItem = menu.findItem(R.id.delete_bookmark)
230 selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks)
232 // Disable the delete bookmarks menu item if a delete is pending.
233 deleteBookmarksMenuItem.isEnabled = !deletingBookmarks
235 // Store a handle for the contextual action bar so it can be closed programatically.
236 contextualActionMode = mode
242 override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
243 // Get a handle for the move to folder menu item.
244 val moveToFolderMenuItem = menu.findItem(R.id.move_to_folder)
246 // Get a Cursor with all of the folders.
247 val folderCursor = bookmarksDatabaseHelper.allFolders
249 // Display the move to folder menu item if at least one folder exists.
250 moveToFolderMenuItem.isVisible = folderCursor.count > 0
256 override fun onItemCheckedStateChanged(mode: ActionMode, position: Int, id: Long, checked: Boolean) {
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 // List the number of selected bookmarks in the subtitle.
279 mode.subtitle = getString(R.string.selected, numberOfSelectedBookmarks)
281 // Show the select all menu item if all the bookmarks are not selected.
282 selectAllBookmarksMenuItem.isVisible = (numberOfSelectedBookmarks != bookmarksListView.count)
286 override fun onActionItemClicked(actionMode: ActionMode, menuItem: MenuItem): Boolean {
287 // Declare the variables.
288 val selectedBookmarkNewPosition: Int
289 val selectedBookmarksPositionsSparseBooleanArray: SparseBooleanArray
291 // Initialize the selected bookmark position.
292 var selectedBookmarkPosition = 0
294 // Get the menu item ID.
295 val menuItemId = menuItem.itemId
297 // Run the commands according to the selected action item.
298 if (menuItemId == R.id.move_bookmark_up) { // Move the bookmark up.
299 // Get the array of checked bookmark positions.
300 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
302 // 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`.
303 for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size()) {
304 // Check to see if the value for the bookmark is true, meaning it is currently selected.
305 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
306 // Only one bookmark should have a value of `true` when move bookmark up is enabled.
307 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i)
311 // Calculate the new position of the selected bookmark.
312 selectedBookmarkNewPosition = selectedBookmarkPosition - 1
314 // Iterate through the bookmarks.
315 for (i in 0 until bookmarksListView.count) {
316 // Get the database ID for the current bookmark.
317 val currentBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
319 // Update the display order for the current bookmark.
320 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
321 // Move the current bookmark up one.
322 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1)
323 } else if ((i + 1) == selectedBookmarkPosition) { // The current bookmark is immediately above the selected bookmark.
324 // Move the current bookmark down one.
325 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1)
326 } else { // The current bookmark is not changing positions.
327 // Move the bookmarks cursor to the current bookmark position.
328 bookmarksCursor.moveToPosition(i)
330 // 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.
331 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i)
332 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i)
336 // Update the bookmarks cursor with the current contents of the bookmarks database.
337 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
339 // Update the list view.
340 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
342 // Scroll to the new bookmark position.
343 scrollBookmarks(selectedBookmarkNewPosition)
345 // Update the enabled status of the move icons.
347 } else if (menuItemId == R.id.move_bookmark_down) { // Move the bookmark down.
348 // Get the array of checked bookmark positions.
349 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
351 // 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`.
352 for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size()) {
353 // Check to see if the value for the bookmark is true, meaning it is currently selected.
354 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
355 // Only one bookmark should have a value of `true` when move bookmark down is enabled.
356 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i)
360 // Calculate the new position of the selected bookmark.
361 selectedBookmarkNewPosition = selectedBookmarkPosition + 1
363 // Iterate through the bookmarks.
364 for (i in 0 until bookmarksListView.count) {
365 // Get the database ID for the current bookmark.
366 val currentBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
368 // Update the display order for the current bookmark.
369 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
370 // Move the current bookmark down one.
371 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1)
372 } else if (i - 1 == selectedBookmarkPosition) { // The current bookmark is immediately below the selected bookmark.
373 // Move the bookmark below the selected bookmark up one.
374 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1)
375 } else { // The current bookmark is not changing positions.
376 // Move the bookmarks cursor to the current bookmark position.
377 bookmarksCursor.moveToPosition(i)
379 // 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.
380 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
381 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i)
386 // Update the bookmarks cursor with the current contents of the bookmarks database.
387 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
389 // Update the list view.
390 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
392 // Scroll to the new bookmark position.
393 scrollBookmarks(selectedBookmarkNewPosition)
395 // Update the enabled status of the move icons.
397 } else if (menuItemId == R.id.move_to_folder) { // Move to folder.
398 // Instantiate the move to folder alert dialog.
399 val moveToFolderDialog: DialogFragment = moveBookmarks(currentFolder, bookmarksListView.checkedItemIds)
401 // Show the move to folder alert dialog.
402 moveToFolderDialog.show(supportFragmentManager, resources.getString(R.string.move_to_folder))
403 } else if (menuItemId == R.id.edit_bookmark) {
404 // Get the array of checked bookmark positions.
405 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
407 // 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`.
408 for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size()) {
409 // Check to see if the value for the bookmark is true, meaning it is currently selected.
410 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
411 // Only one bookmark should have a value of `true` when move edit bookmark is enabled.
412 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i)
416 // Move the cursor to the selected position.
417 bookmarksCursor.moveToPosition(selectedBookmarkPosition)
419 // Get the selected bookmark database ID.
420 val databaseId = bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID))
422 // Show the edit bookmark or edit bookmark folder dialog.
423 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // A folder is selected.
424 // Save the current folder name, which is used in `onSaveBookmarkFolder()`.
425 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME))
427 // Instantiate the edit bookmark folder dialog.
428 val editFolderDialog: DialogFragment = folderDatabaseId(databaseId, currentFavoriteIconBitmap)
431 editFolderDialog.show(supportFragmentManager, resources.getString(R.string.edit_folder))
432 } else { // A bookmark is selected.
433 // Instantiate the edit bookmark dialog.
434 val editBookmarkDialog: DialogFragment = bookmarkDatabaseId(databaseId, currentFavoriteIconBitmap)
437 editBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.edit_bookmark))
439 } else if (menuItemId == R.id.delete_bookmark) { // Delete bookmark.
440 // Set the deleting bookmarks flag, which prevents the delete menu item from being enabled until the current process finishes.
441 deletingBookmarks = true
443 // Get an array of the selected row IDs.
444 val selectedBookmarksIdsLongArray = bookmarksListView.checkedItemIds
446 // Initialize a variable to count the number of bookmarks to delete.
447 var numberOfBookmarksToDelete = 0
449 // Count the number of bookmarks to delete.
450 for (databaseIdLong in selectedBookmarksIdsLongArray) {
451 // Convert the database ID long to an int.
452 val databaseIdInt = databaseIdLong.toInt()
454 // Count the contents of the folder if the selected bookmark is a folder.
455 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
456 // Add the bookmarks from the folder to the running total.
457 numberOfBookmarksToDelete += countBookmarkFolderContents(databaseIdInt)
460 // Increment the count of the number of bookmarks to delete.
461 numberOfBookmarksToDelete++
464 // 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.
465 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions.clone()
467 // Update the bookmarks cursor with the current contents of the bookmarks database except for the specified database IDs.
468 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(selectedBookmarksIdsLongArray, currentFolder)
470 // Update the list view.
471 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
473 // Create a Snackbar with the number of deleted bookmarks.
474 bookmarksDeletedSnackbar = Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.bookmarks_deleted, numberOfBookmarksToDelete), Snackbar.LENGTH_LONG)
475 .setAction(R.string.undo) { } // Do nothing because everything will be handled by `onDismissed()` below.
476 .addCallback(object : Snackbar.Callback() {
477 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
478 override fun onDismissed(snackbar: Snackbar, event: Int) {
479 if (event == DISMISS_EVENT_ACTION) { // The user pushed the undo button.
480 // Update the bookmarks cursor with the current contents of the bookmarks database, including the "deleted" bookmarks.
481 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
483 // Update the list view.
484 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
486 // Re-select the previously selected bookmarks.
487 for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size())
488 bookmarksListView.setItemChecked(selectedBookmarksPositionsSparseBooleanArray.keyAt(i), true)
489 } else { // The snackbar was dismissed without the undo button being pushed.
490 // Delete each selected bookmark.
491 for (databaseIdLong in selectedBookmarksIdsLongArray) {
492 // Convert the database long ID to an int.
493 val databaseIdInt = databaseIdLong.toInt()
495 // Delete the contents of the folder if the selected bookmark is a folder.
496 if (bookmarksDatabaseHelper.isFolder(databaseIdInt))
497 deleteBookmarkFolderContents(databaseIdInt)
499 // Delete the selected bookmark.
500 bookmarksDatabaseHelper.deleteBookmark(databaseIdInt)
503 // Update the display order.
504 for (i in 0 until bookmarksListView.count) {
505 // Get the database ID for the current bookmark.
506 val currentBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
508 // Move bookmarks cursor to the current bookmark position.
509 bookmarksCursor.moveToPosition(i)
511 // Update the display order only if it is not correct in the database.
512 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i)
513 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i)
517 // Reset the deleting bookmarks flag.
518 deletingBookmarks = false
520 // Enable the delete bookmarks menu item.
521 deleteBookmarksMenuItem.isEnabled = true
523 // Close the activity if back has been pressed.
524 if (closeActivityAfterDismissingSnackbar)
529 // Show the Snackbar.
530 bookmarksDeletedSnackbar!!.show()
531 } else if (menuItemId == R.id.context_menu_select_all_bookmarks) { // Select all.
532 // Get the total number of bookmarks.
533 val numberOfBookmarks = bookmarksListView.count
536 for (i in 0 until numberOfBookmarks) {
537 bookmarksListView.setItemChecked(i, true)
541 // Consume the click.
545 override fun onDestroyActionMode(mode: ActionMode) {
550 // Set the create new bookmark folder FAB to display the alert dialog.
551 createBookmarkFolderFab.setOnClickListener {
552 // Create a create bookmark folder dialog.
553 val createBookmarkFolderDialog: DialogFragment = createBookmarkFolder(currentFavoriteIconBitmap)
555 // Show the create bookmark folder dialog.
556 createBookmarkFolderDialog.show(supportFragmentManager, getString(R.string.create_folder))
559 // Set the create new bookmark FAB to display the alert dialog.
560 createBookmarkFab.setOnClickListener {
561 // Instantiate the create bookmark dialog.
562 val createBookmarkDialog: DialogFragment = createBookmark(currentUrl, currentTitle, currentFavoriteIconBitmap)
564 // Display the create bookmark dialog.
565 createBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.create_bookmark))
568 // Restore the state if the app has been restarted.
569 if (savedInstanceState != null) {
570 // Restore the current folder.
571 currentFolder = savedInstanceState.getString(CURRENT_FOLDER)!!
573 // Update the bookmarks list view after it has loaded.
574 bookmarksListView.post {
575 // Get the checked bookmarks array list.
576 val checkedBookmarksArrayList = savedInstanceState.getIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST)!!
578 // Check each previously checked bookmark in the list view.
579 checkedBookmarksArrayList.forEach(Consumer { position: Int -> bookmarksListView.setItemChecked(position, true) })
583 // Load the current folder.
587 public override fun onRestart() {
588 // Run the default commands.
591 // Update the list view if returning from the bookmarks database view activity.
592 if (restartFromBookmarksDatabaseViewActivity) {
593 // Load the current folder in the list view.
596 // Reset the restart from bookmarks database view activity flag.
597 restartFromBookmarksDatabaseViewActivity = false
601 public override fun onSaveInstanceState(savedInstanceState: Bundle) {
602 // Run the default commands.
603 super.onSaveInstanceState(savedInstanceState)
605 // Get the sparse boolean array of the checked items.
606 val checkedBookmarksSparseBooleanArray = bookmarksListView.checkedItemPositions
608 // Create a checked items array list.
609 val checkedBookmarksArrayList = ArrayList<Int>()
611 // Add each checked bookmark position to the array list.
612 for (i in 0 until checkedBookmarksSparseBooleanArray.size()) {
613 // 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.
614 if (checkedBookmarksSparseBooleanArray.valueAt(i)) {
615 // Add the bookmark position to the checked bookmarks array list.
616 checkedBookmarksArrayList.add(checkedBookmarksSparseBooleanArray.keyAt(i))
620 // Store the variables in the saved instance state.
621 savedInstanceState.putString(CURRENT_FOLDER, currentFolder)
622 savedInstanceState.putIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST, checkedBookmarksArrayList)
625 override fun onCreateOptionsMenu(menu: Menu): Boolean {
627 menuInflater.inflate(R.menu.bookmarks_options_menu, menu)
633 override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
634 // Get a handle for the menu item ID.
635 val menuItemId = menuItem.itemId
637 // Run the command according to the selected option.
638 if (menuItemId == android.R.id.home) { // Home. The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
639 if (currentFolder.isEmpty()) { // Currently in the home folder.
640 // Prepare to finish the activity.
642 } else { // Currently in a subfolder.
643 // Set the former parent folder as the current folder.
644 currentFolder = bookmarksDatabaseHelper.getParentFolderName(currentFolder)
646 // Load the new current folder.
649 } else if (menuItemId == R.id.options_menu_select_all_bookmarks) { // Select all.
650 // Get the total number of bookmarks.
651 val numberOfBookmarks = bookmarksListView.count
654 for (i in 0 until numberOfBookmarks) {
655 bookmarksListView.setItemChecked(i, true)
657 } else if (menuItemId == R.id.bookmarks_database_view) {
658 // Close the contextual action bar if it is displayed. This can happen if the bottom app bar is enabled.
659 contextualActionMode?.finish()
661 // Create an intent to launch the bookmarks database view activity.
662 val bookmarksDatabaseViewIntent = Intent(this, BookmarksDatabaseViewActivity::class.java)
664 // Include the favorite icon byte array to the intent.
665 bookmarksDatabaseViewIntent.putExtra(CURRENT_FAVORITE_ICON_BYTE_ARRAY, currentFavoriteIconByteArray)
668 startActivity(bookmarksDatabaseViewIntent)
673 override fun createBookmark(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
674 // Get the alert dialog from the fragment.
675 val dialog = dialogFragment.dialog!!
677 // Get the views from the dialog fragment.
678 val createBookmarkNameEditText = dialog.findViewById<EditText>(R.id.create_bookmark_name_edittext)
679 val createBookmarkUrlEditText = dialog.findViewById<EditText>(R.id.create_bookmark_url_edittext)
681 // Extract the strings from the edit texts.
682 val bookmarkNameString = createBookmarkNameEditText.text.toString()
683 val bookmarkUrlString = createBookmarkUrlEditText.text.toString()
685 // Create a favorite icon byte array output stream.
686 val favoriteIconByteArrayOutputStream = ByteArrayOutputStream()
688 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
689 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream)
691 // Convert the favorite icon byte array stream to a byte array.
692 val favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray()
694 // Display the new bookmark below the current items in the (0 indexed) list.
695 val newBookmarkDisplayOrder = bookmarksListView.count
697 // Create the bookmark.
698 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentFolder, newBookmarkDisplayOrder, favoriteIconByteArray)
700 // Update the bookmarks cursor with the current contents of this folder.
701 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
703 // Update the list view.
704 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
706 // Scroll to the new bookmark.
707 bookmarksListView.setSelection(newBookmarkDisplayOrder)
710 override fun createBookmarkFolder(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
711 // Get the dialog from the dialog fragment.
712 val dialog = dialogFragment.dialog!!
714 // Get handles for the views in the dialog fragment.
715 val folderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
716 val defaultIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
717 val defaultIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
719 // Get new folder name string.
720 val folderNameString = folderNameEditText.text.toString()
722 // Set the folder icon bitmap according to the dialog.
723 val folderIconBitmap = if (defaultIconRadioButton.isChecked) { // Use the default folder icon.
724 // Get the default folder icon drawable.
725 val folderIconDrawable = defaultIconImageView.drawable
727 // Convert the folder icon drawable to a bitmap drawable.
728 val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
730 // Convert the folder icon bitmap drawable to a bitmap.
731 folderIconBitmapDrawable.bitmap
732 } else { // Use the WebView favorite icon.
733 // Copy the favorite icon bitmap to the folder icon bitmap.
737 // Create a folder icon byte array output stream.
738 val folderIconByteArrayOutputStream = ByteArrayOutputStream()
740 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
741 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream)
743 // Convert the folder icon byte array stream to a byte array.
744 val folderIconByteArray = folderIconByteArrayOutputStream.toByteArray()
746 // Move all the bookmarks down one in the display order.
747 for (i in 0 until bookmarksListView.count) {
748 val databaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
749 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1)
752 // Create the folder, which will be placed at the top of the list view.
753 bookmarksDatabaseHelper.createFolder(folderNameString, currentFolder, folderIconByteArray)
755 // Update the bookmarks cursor with the contents of the current folder.
756 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
758 // Update the list view.
759 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
761 // Scroll to the new folder.
762 bookmarksListView.setSelection(0)
765 override fun onSaveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int, favoriteIconBitmap: Bitmap) {
766 // Get the dialog from the dialog fragment.
767 val dialog = dialogFragment.dialog!!
769 // Get handles for the views from the dialog fragment.
770 val bookmarkNameEditText = dialog.findViewById<EditText>(R.id.bookmark_name_edittext)
771 val bookmarkUrlEditText = dialog.findViewById<EditText>(R.id.bookmark_url_edittext)
772 val currentIconRadioButton = dialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)
774 // Get the bookmark strings.
775 val bookmarkNameString = bookmarkNameEditText.text.toString()
776 val bookmarkUrlString = bookmarkUrlEditText.text.toString()
778 // Update the bookmark.
779 if (currentIconRadioButton.isChecked) { // Update the bookmark without changing the favorite icon.
780 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString)
781 } else { // Update the bookmark using the WebView favorite icon.
782 // Create a favorite icon byte array output stream.
783 val newFavoriteIconByteArrayOutputStream = ByteArrayOutputStream()
785 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
786 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream)
788 // Convert the favorite icon byte array stream to a byte array.
789 val newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray()
791 // Update the bookmark and the favorite icon.
792 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray)
795 // Close the contextual action bar if it is displayed.
796 contextualActionMode?.finish()
798 // Update the bookmarks cursor with the contents of the current folder.
799 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
801 // Update the list view.
802 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
805 override fun onSaveBookmarkFolder(dialogFragment: DialogFragment, selectedFolderDatabaseId: Int, favoriteIconBitmap: Bitmap) {
806 // Get the dialog from the dialog fragment.
807 val dialog = dialogFragment.dialog!!
809 // Get handles for the views from the dialog fragment.
810 val currentFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)
811 val defaultFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
812 val defaultFolderIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
813 val editFolderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
815 // Get the new folder name.
816 val newFolderNameString = editFolderNameEditText.text.toString()
818 // Check if the favorite icon has changed.
819 if (currentFolderIconRadioButton.isChecked) { // Only the name has changed.
820 // Update the name in the database.
821 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString)
822 } else { // The icon has changed.
823 // Populate the new folder icon bitmap.
824 val folderIconBitmap: Bitmap = if (defaultFolderIconRadioButton.isChecked) {
825 // Get the default folder icon drawable.
826 val folderIconDrawable = defaultFolderIconImageView.drawable
828 // Convert the folder icon drawable to a bitmap drawable.
829 val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
831 // Convert the folder icon bitmap drawable to a bitmap.
832 folderIconBitmapDrawable.bitmap
833 } else { // Use the WebView favorite icon.
834 // Copy the favorite icon bitmap to the folder icon bitmap.
838 // Create a folder icon byte array output stream.
839 val newFolderIconByteArrayOutputStream = ByteArrayOutputStream()
841 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
842 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream)
844 // Convert the folder icon byte array stream to a byte array.
845 val newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray()
847 // Update the database.
848 if (!currentFolderIconRadioButton.isChecked && newFolderNameString == oldFolderNameString) // Only the icon has changed.
849 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray)
850 else // The folder icon and the name have changed.
851 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray)
854 // Update the bookmarks cursor with the current contents of this folder.
855 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
857 // Update the list view.
858 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
860 // Close the contextual action mode.
861 contextualActionMode!!.finish()
864 override fun onMoveToFolder(dialogFragment: DialogFragment) {
865 // Get the dialog from the dialog fragment.
866 val dialog = dialogFragment.dialog!!
868 // Get a handle for the folder list view from the dialog.
869 val folderListView = dialog.findViewById<ListView>(R.id.move_to_folder_listview)
871 // Store a long array of the selected folders.
872 val newFolderLongArray = folderListView.checkedItemIds
874 // Get the new folder database ID. Only one folder will be selected so it will be the first one.
875 val newFolderDatabaseId = newFolderLongArray[0].toInt()
877 // Set the new folder name.
878 val newFolderName = if (newFolderDatabaseId == 0) {
879 // The new folder is the home folder, represented as `""` in the database.
882 // Get the new folder name from the database.
883 bookmarksDatabaseHelper.getFolderName(newFolderDatabaseId)
886 // Get a long array with the the database ID of the selected bookmarks.
887 val selectedBookmarksLongArray = bookmarksListView.checkedItemIds
889 // Move each of the selected bookmarks to the new folder.
890 for (databaseIdLong in selectedBookmarksLongArray) {
891 // Convert the database long ID to an int for each selected bookmark.
892 val databaseIdInt = databaseIdLong.toInt()
894 // Move the selected bookmark to the new folder.
895 bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderName)
898 // Update the bookmarks cursor with the current contents of this folder.
899 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
901 // Update the list view.
902 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
904 // Close the contextual app bar.
905 contextualActionMode!!.finish()
908 private fun countBookmarkFolderContents(folderDatabaseId: Int): Int {
909 // Get the name of the folder.
910 val folderName = bookmarksDatabaseHelper.getFolderName(folderDatabaseId)
912 // Get the contents of the folder.
913 val folderCursor = bookmarksDatabaseHelper.getBookmarkIds(folderName)
915 // Initialize the bookmark counter.
916 var bookmarkCounter = 0
918 // Count each of the bookmarks in the folder.
919 for (i in 0 until folderCursor.count) {
920 // Move the folder cursor to the current row.
921 folderCursor.moveToPosition(i)
923 // Get the database ID of the item.
924 val itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID))
926 // If this is a folder, recursively count the contents first.
927 if (bookmarksDatabaseHelper.isFolder(itemDatabaseId))
928 bookmarkCounter += countBookmarkFolderContents(itemDatabaseId)
930 // Add the bookmark to the running total.
934 // Return the bookmark counter.
935 return bookmarkCounter
938 private fun deleteBookmarkFolderContents(folderDatabaseId: Int) {
939 // Get the name of the folder.
940 val folderName = bookmarksDatabaseHelper.getFolderName(folderDatabaseId)
942 // Get the contents of the folder.
943 val folderCursor = bookmarksDatabaseHelper.getBookmarkIds(folderName)
945 // Delete each of the bookmarks in the folder.
946 for (i in 0 until folderCursor.count) {
947 // Move the folder cursor to the current row.
948 folderCursor.moveToPosition(i)
950 // Get the database ID of the item.
951 val itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID))
953 // If this is a folder, recursively delete the contents first.
954 if (bookmarksDatabaseHelper.isFolder(itemDatabaseId))
955 deleteBookmarkFolderContents(itemDatabaseId)
957 // Delete the bookmark.
958 bookmarksDatabaseHelper.deleteBookmark(itemDatabaseId)
962 private fun prepareFinish() {
963 // 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.
964 if (bookmarksDeletedSnackbar != null && bookmarksDeletedSnackbar!!.isShown) { // Close the bookmarks deleted snackbar before going home.
965 // Set the close flag.
966 closeActivityAfterDismissingSnackbar = true
968 // Dismiss the snackbar.
969 bookmarksDeletedSnackbar!!.dismiss()
970 } else { // Go home immediately.
971 // Update the bookmarks folder for the bookmarks drawer in the main WebView activity.
972 MainWebViewActivity.currentBookmarksFolder = currentFolder
974 // Close the bookmarks drawer and reload the bookmarks list view when returning to the main WebView activity.
975 MainWebViewActivity.restartFromBookmarksActivity = true
977 // Exit the bookmarks activity.
982 private fun updateMoveIcons() {
983 // Get a long array of the selected bookmarks.
984 val selectedBookmarksLongArray = bookmarksListView.checkedItemIds
986 // Get the database IDs for the first, last, and selected bookmarks.
987 val firstBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(0).toInt()
988 val lastBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(bookmarksListView.count - 1).toInt() // The bookmarks list view is 0 indexed.
989 val selectedBookmarkDatabaseId = selectedBookmarksLongArray[0].toInt()
991 // Update the move bookmark up menu item.
992 if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) { // The selected bookmark is in the first position.
993 // Disable the move bookmark up menu item.
994 moveBookmarkUpMenuItem.isEnabled = false
997 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_disabled)
998 } else { // The selected bookmark is not in the first position.
999 // Enable the move bookmark up menu item.
1000 moveBookmarkUpMenuItem.isEnabled = true
1002 // Set the icon according to the theme.
1003 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled)
1006 // Update the move bookmark down menu item.
1007 if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) { // The selected bookmark is in the last position.
1008 // Disable the move bookmark down menu item.
1009 moveBookmarkDownMenuItem.isEnabled = false
1012 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_disabled)
1013 } else { // The selected bookmark is not in the last position.
1014 // Enable the move bookmark down menu item.
1015 moveBookmarkDownMenuItem.isEnabled = true
1018 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled)
1022 private fun scrollBookmarks(selectedBookmarkPosition: Int) {
1023 // Get the first and last visible bookmark positions.
1024 val firstVisibleBookmarkPosition = bookmarksListView.firstVisiblePosition
1025 val lastVisibleBookmarkPosition = bookmarksListView.lastVisiblePosition
1027 // Calculate the number of bookmarks per screen.
1028 val numberOfBookmarksPerScreen = lastVisibleBookmarkPosition - firstVisibleBookmarkPosition
1030 // Scroll with the moved bookmark if necessary.
1031 if (selectedBookmarkPosition <= firstVisibleBookmarkPosition) { // The selected bookmark position is at or above the top of the screen.
1032 // Scroll to the selected bookmark position.
1033 bookmarksListView.setSelection(selectedBookmarkPosition)
1034 } else if (selectedBookmarkPosition >= lastVisibleBookmarkPosition - 1) { // The selected bookmark is at or below the bottom of the screen.
1035 // 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.
1036 // `+1` assures that the entire bookmark will be displayed in situations where only a partial bookmark fits at the bottom of the list view.
1037 bookmarksListView.setSelection(selectedBookmarkPosition - numberOfBookmarksPerScreen + 1)
1041 private fun loadFolder() {
1042 // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
1043 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
1045 // Setup a cursor adapter.
1046 bookmarksCursorAdapter = object : CursorAdapter(this, bookmarksCursor, false) {
1047 override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
1048 // Inflate the individual item layout.
1049 return layoutInflater.inflate(R.layout.bookmarks_activity_item_linearlayout, parent, false)
1052 override fun bindView(view: View, context: Context, cursor: Cursor) {
1053 // Get handles for the views.
1054 val bookmarkFavoriteIconImageView = view.findViewById<ImageView>(R.id.bookmark_favorite_icon)
1055 val bookmarkNameTextView = view.findViewById<TextView>(R.id.bookmark_name)
1057 // Get the favorite icon byte array from the cursor.
1058 val favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON))
1060 // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
1061 val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size)
1063 // Display the bitmap in the bookmark favorite icon image view.
1064 bookmarkFavoriteIconImageView.setImageBitmap(favoriteIconBitmap)
1066 // Get the bookmark name from the cursor.
1067 val bookmarkNameString = cursor.getString(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME))
1069 // Display the bookmark name.
1070 bookmarkNameTextView.text = bookmarkNameString
1072 // Make the font bold for folders.
1073 if (cursor.getInt(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1)
1074 bookmarkNameTextView.typeface = Typeface.DEFAULT_BOLD
1075 else // Reset the font to default for normal bookmarks.
1076 bookmarkNameTextView.typeface = Typeface.DEFAULT
1080 // Populate the list view with the adapter.
1081 bookmarksListView.adapter = bookmarksCursorAdapter
1083 // Set the app bar title.
1084 if (currentFolder.isEmpty())
1085 appBar.setTitle(R.string.bookmarks)
1087 appBar.title = currentFolder
1090 public override fun onDestroy() {
1091 // Close the bookmarks cursor and database.
1092 bookmarksCursor.close()
1093 bookmarksDatabaseHelper.close()
1095 // Run the default commands.