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 /* TODO. Test if not needed.
131 // Set the current folder variable.
132 if (launchingIntent.getStringExtra(CURRENT_FOLDER) != null) { // Set the current folder from the intent.
133 currentFolder = launchingIntent.getStringExtra(CURRENT_FOLDER)
134 } else { // Set the current folder to be `""`, which is the home folder.
140 // Convert the favorite icon byte array to a bitmap.
141 val currentFavoriteIconBitmap = BitmapFactory.decodeByteArray(currentFavoriteIconByteArray, 0, currentFavoriteIconByteArray.size)
143 // Set the content according to the app bar position.
145 // Set the content view.
146 setContentView(R.layout.bookmarks_bottom_appbar)
148 // `Window.FEATURE_ACTION_MODE_OVERLAY` makes the contextual action mode cover the support action bar. It must be requested before the content is set.
149 supportRequestWindowFeature(Window.FEATURE_ACTION_MODE_OVERLAY)
151 // Set the content view.
152 setContentView(R.layout.bookmarks_top_appbar)
155 // Get handles for the views.
156 val toolbar = findViewById<Toolbar>(R.id.bookmarks_toolbar)
157 bookmarksListView = findViewById(R.id.bookmarks_listview)
158 val createBookmarkFolderFab = findViewById<FloatingActionButton>(R.id.create_bookmark_folder_fab)
159 val createBookmarkFab = findViewById<FloatingActionButton>(R.id.create_bookmark_fab)
161 // Set the support action bar.
162 setSupportActionBar(toolbar)
164 // Get a handle for the app bar.
165 appBar = supportActionBar!!
167 // Display the home arrow on the app bar.
168 appBar.setDisplayHomeAsUpEnabled(true)
170 // Control what the system back command does.
171 val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
172 override fun handleOnBackPressed() {
173 // Prepare to finish the activity.
178 // Register the on back pressed callback.
179 onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
181 // Initialize the database helper.
182 bookmarksDatabaseHelper = BookmarksDatabaseHelper(this)
184 // Set a listener so that tapping a list item loads the URL or folder.
185 bookmarksListView.onItemClickListener = AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, id: Long ->
186 // Convert the id from long to int to match the format of the bookmarks database.
187 val databaseId = id.toInt()
189 // Get the bookmark cursor for this ID.
190 val bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId)
192 // Move the cursor to the first entry.
193 bookmarkCursor.moveToFirst()
195 // Act upon the bookmark according to the type.
196 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
197 // Update the current folder.
198 currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME))
200 // Load the new folder.
202 } else { // The selected bookmark is not a folder.
203 // Instantiate the edit bookmark dialog.
204 val editBookmarkDialog: DialogFragment = bookmarkDatabaseId(databaseId, currentFavoriteIconBitmap)
207 editBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.edit_bookmark))
211 bookmarkCursor.close()
214 // Handle long-presses on the list view.
215 bookmarksListView.setMultiChoiceModeListener(object : MultiChoiceModeListener {
216 // Define the object variables.
217 private var deletingBookmarks = false
219 // Declare the object variables.
220 private lateinit var editBookmarkMenuItem: MenuItem
221 private lateinit var deleteBookmarksMenuItem: MenuItem
222 private lateinit var selectAllBookmarksMenuItem: MenuItem
224 override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
225 // Inflate the menu for the contextual app bar.
226 menuInflater.inflate(R.menu.bookmarks_context_menu, menu)
229 if (currentFolder.isEmpty()) { // Use `R.string.bookmarks` if in the home folder.
230 mode.setTitle(R.string.bookmarks)
231 } else { // Use the current folder name as the title.
232 mode.title = currentFolder
235 // Get handles for menu items that need to be selectively disabled.
236 moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up)
237 moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down)
238 editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark)
239 deleteBookmarksMenuItem = menu.findItem(R.id.delete_bookmark)
240 selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks)
242 // Disable the delete bookmarks menu item if a delete is pending.
243 deleteBookmarksMenuItem.isEnabled = !deletingBookmarks
245 // Store a handle for the contextual action bar so it can be closed programatically.
246 contextualActionMode = mode
252 override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
253 // Get a handle for the move to folder menu item.
254 val moveToFolderMenuItem = menu.findItem(R.id.move_to_folder)
256 // Get a Cursor with all of the folders.
257 val folderCursor = bookmarksDatabaseHelper.allFolders
259 // Display the move to folder menu item if at least one folder exists.
260 moveToFolderMenuItem.isVisible = folderCursor.count > 0
266 override fun onItemCheckedStateChanged(mode: ActionMode, position: Int, id: Long, checked: Boolean) {
267 // Get the number of selected bookmarks.
268 val numberOfSelectedBookmarks = bookmarksListView.checkedItemCount
270 // Only process commands if at least one bookmark is selected. Otherwise, a context menu with 0 selected bookmarks is briefly displayed.
271 if (numberOfSelectedBookmarks > 0) {
272 // Adjust the action mode and the menu according to the number of selected bookmarks.
273 if (numberOfSelectedBookmarks == 1) { // One bookmark is selected.
274 // Show the applicable menu items.
275 moveBookmarkUpMenuItem.isVisible = true
276 moveBookmarkDownMenuItem.isVisible = true
277 editBookmarkMenuItem.isVisible = true
279 // Update the enabled status of the move icons.
281 } else { // More than one bookmark is selected.
282 // Hide non-applicable `MenuItems`.
283 moveBookmarkUpMenuItem.isVisible = false
284 moveBookmarkDownMenuItem.isVisible = false
285 editBookmarkMenuItem.isVisible = false
288 // List the number of selected bookmarks in the subtitle.
289 mode.subtitle = getString(R.string.selected, numberOfSelectedBookmarks)
291 // Show the select all menu item if all the bookmarks are not selected.
292 selectAllBookmarksMenuItem.isVisible = (numberOfSelectedBookmarks != bookmarksListView.count)
296 override fun onActionItemClicked(actionMode: ActionMode, menuItem: MenuItem): Boolean {
297 // Declare the variables.
298 val selectedBookmarkNewPosition: Int
299 val selectedBookmarksPositionsSparseBooleanArray: SparseBooleanArray
301 // Initialize the selected bookmark position.
302 var selectedBookmarkPosition = 0
304 // Get the menu item ID.
305 val menuItemId = menuItem.itemId
307 // Run the commands according to the selected action item.
308 if (menuItemId == R.id.move_bookmark_up) { // Move the bookmark up.
309 // Get the array of checked bookmark positions.
310 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
312 // Get the position of the bookmark that is selected. If other bookmarks have previously been selected they will be included in the sparse boolean array with a value of `false`.
313 for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size()) {
314 // Check to see if the value for the bookmark is true, meaning it is currently selected.
315 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
316 // Only one bookmark should have a value of `true` when move bookmark up is enabled.
317 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i)
321 // Calculate the new position of the selected bookmark.
322 selectedBookmarkNewPosition = selectedBookmarkPosition - 1
324 // Iterate through the bookmarks.
325 for (i in 0 until bookmarksListView.count) {
326 // Get the database ID for the current bookmark.
327 val currentBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
329 // Update the display order for the current bookmark.
330 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
331 // Move the current bookmark up one.
332 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1)
333 } else if ((i + 1) == selectedBookmarkPosition) { // The current bookmark is immediately above the selected bookmark.
334 // Move the current bookmark down one.
335 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1)
336 } else { // The current bookmark is not changing positions.
337 // Move the bookmarks cursor to the current bookmark position.
338 bookmarksCursor.moveToPosition(i)
340 // Update the display order only if it is not correct in the database. This fixes problems where the display order somehow got out of sync.
341 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i)
342 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i)
346 // Update the bookmarks cursor with the current contents of the bookmarks database.
347 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
349 // Update the list view.
350 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
352 // Scroll to the new bookmark position.
353 scrollBookmarks(selectedBookmarkNewPosition)
355 // Update the enabled status of the move icons.
357 } else if (menuItemId == R.id.move_bookmark_down) { // Move the bookmark down.
358 // Get the array of checked bookmark positions.
359 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
361 // Get the position of the bookmark that is selected. If other bookmarks have previously been selected they will be included in the sparse boolean array with a value of `false`.
362 for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size()) {
363 // Check to see if the value for the bookmark is true, meaning it is currently selected.
364 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
365 // Only one bookmark should have a value of `true` when move bookmark down is enabled.
366 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i)
370 // Calculate the new position of the selected bookmark.
371 selectedBookmarkNewPosition = selectedBookmarkPosition + 1
373 // Iterate through the bookmarks.
374 for (i in 0 until bookmarksListView.count) {
375 // Get the database ID for the current bookmark.
376 val currentBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
378 // Update the display order for the current bookmark.
379 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
380 // Move the current bookmark down one.
381 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1)
382 } else if (i - 1 == selectedBookmarkPosition) { // The current bookmark is immediately below the selected bookmark.
383 // Move the bookmark below the selected bookmark up one.
384 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1)
385 } else { // The current bookmark is not changing positions.
386 // Move the bookmarks cursor to the current bookmark position.
387 bookmarksCursor.moveToPosition(i)
389 // Update the display order only if it is not correct in the database. This fixes problems where the display order somehow got out of sync.
390 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
391 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i)
396 // Update the bookmarks cursor with the current contents of the bookmarks database.
397 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
399 // Update the list view.
400 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
402 // Scroll to the new bookmark position.
403 scrollBookmarks(selectedBookmarkNewPosition)
405 // Update the enabled status of the move icons.
407 } else if (menuItemId == R.id.move_to_folder) { // Move to folder.
408 // Instantiate the move to folder alert dialog.
409 val moveToFolderDialog: DialogFragment = moveBookmarks(currentFolder, bookmarksListView.checkedItemIds)
411 // Show the move to folder alert dialog.
412 moveToFolderDialog.show(supportFragmentManager, resources.getString(R.string.move_to_folder))
413 } else if (menuItemId == R.id.edit_bookmark) {
414 // Get the array of checked bookmark positions.
415 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions
417 // Get the position of the bookmark that is selected. If other bookmarks have previously been selected they will be included in the sparse boolean array with a value of `false`.
418 for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size()) {
419 // Check to see if the value for the bookmark is true, meaning it is currently selected.
420 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
421 // Only one bookmark should have a value of `true` when move edit bookmark is enabled.
422 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i)
426 // Move the cursor to the selected position.
427 bookmarksCursor.moveToPosition(selectedBookmarkPosition)
429 // Get the selected bookmark database ID.
430 val databaseId = bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID))
432 // Show the edit bookmark or edit bookmark folder dialog.
433 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // A folder is selected.
434 // Save the current folder name, which is used in `onSaveBookmarkFolder()`.
435 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME))
437 // Instantiate the edit bookmark folder dialog.
438 val editFolderDialog: DialogFragment = folderDatabaseId(databaseId, currentFavoriteIconBitmap)
441 editFolderDialog.show(supportFragmentManager, resources.getString(R.string.edit_folder))
442 } else { // A bookmark is selected.
443 // Instantiate the edit bookmark dialog.
444 val editBookmarkDialog: DialogFragment = bookmarkDatabaseId(databaseId, currentFavoriteIconBitmap)
447 editBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.edit_bookmark))
449 } else if (menuItemId == R.id.delete_bookmark) { // Delete bookmark.
450 // Set the deleting bookmarks flag, which prevents the delete menu item from being enabled until the current process finishes.
451 deletingBookmarks = true
453 // Get an array of the selected row IDs.
454 val selectedBookmarksIdsLongArray = bookmarksListView.checkedItemIds
456 // Initialize a variable to count the number of bookmarks to delete.
457 var numberOfBookmarksToDelete = 0
459 // Count the number of bookmarks to delete.
460 for (databaseIdLong in selectedBookmarksIdsLongArray) {
461 // Convert the database ID long to an int.
462 val databaseIdInt = databaseIdLong.toInt()
464 // Count the contents of the folder if the selected bookmark is a folder.
465 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
466 // Add the bookmarks from the folder to the running total.
467 numberOfBookmarksToDelete += countBookmarkFolderContents(databaseIdInt)
470 // Increment the count of the number of bookmarks to delete.
471 numberOfBookmarksToDelete++
474 // Get an array of checked bookmarks. `.clone()` makes a copy that won't change if the list view is reloaded, which is needed for re-selecting the bookmarks on undelete.
475 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.checkedItemPositions.clone()
477 // Update the bookmarks cursor with the current contents of the bookmarks database except for the specified database IDs.
478 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(selectedBookmarksIdsLongArray, currentFolder)
480 // Update the list view.
481 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
483 // Create a Snackbar with the number of deleted bookmarks.
484 bookmarksDeletedSnackbar = Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.bookmarks_deleted, numberOfBookmarksToDelete), Snackbar.LENGTH_LONG)
485 .setAction(R.string.undo) { } // Do nothing because everything will be handled by `onDismissed()` below.
486 .addCallback(object : Snackbar.Callback() {
487 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
488 override fun onDismissed(snackbar: Snackbar, event: Int) {
489 if (event == DISMISS_EVENT_ACTION) { // The user pushed the undo button.
490 // Update the bookmarks cursor with the current contents of the bookmarks database, including the "deleted" bookmarks.
491 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
493 // Update the list view.
494 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
496 // Re-select the previously selected bookmarks.
497 for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size())
498 bookmarksListView.setItemChecked(selectedBookmarksPositionsSparseBooleanArray.keyAt(i), true)
499 } else { // The snackbar was dismissed without the undo button being pushed.
500 // Delete each selected bookmark.
501 for (databaseIdLong in selectedBookmarksIdsLongArray) {
502 // Convert the database long ID to an int.
503 val databaseIdInt = databaseIdLong.toInt()
505 // Delete the contents of the folder if the selected bookmark is a folder.
506 if (bookmarksDatabaseHelper.isFolder(databaseIdInt))
507 deleteBookmarkFolderContents(databaseIdInt)
509 // Delete the selected bookmark.
510 bookmarksDatabaseHelper.deleteBookmark(databaseIdInt)
513 // Update the display order.
514 for (i in 0 until bookmarksListView.count) {
515 // Get the database ID for the current bookmark.
516 val currentBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
518 // Move bookmarks cursor to the current bookmark position.
519 bookmarksCursor.moveToPosition(i)
521 // Update the display order only if it is not correct in the database.
522 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i)
523 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i)
527 // Reset the deleting bookmarks flag.
528 deletingBookmarks = false
530 // Enable the delete bookmarks menu item.
531 deleteBookmarksMenuItem.isEnabled = true
533 // Close the activity if back has been pressed.
534 if (closeActivityAfterDismissingSnackbar)
539 // Show the Snackbar.
540 bookmarksDeletedSnackbar!!.show()
541 } else if (menuItemId == R.id.context_menu_select_all_bookmarks) { // Select all.
542 // Get the total number of bookmarks.
543 val numberOfBookmarks = bookmarksListView.count
546 for (i in 0 until numberOfBookmarks) {
547 bookmarksListView.setItemChecked(i, true)
551 // Consume the click.
555 override fun onDestroyActionMode(mode: ActionMode) {
560 // Set the create new bookmark folder FAB to display the alert dialog.
561 createBookmarkFolderFab.setOnClickListener {
562 // Create a create bookmark folder dialog.
563 val createBookmarkFolderDialog: DialogFragment = createBookmarkFolder(currentFavoriteIconBitmap)
565 // Show the create bookmark folder dialog.
566 createBookmarkFolderDialog.show(supportFragmentManager, getString(R.string.create_folder))
569 // Set the create new bookmark FAB to display the alert dialog.
570 createBookmarkFab.setOnClickListener {
571 // Instantiate the create bookmark dialog.
572 val createBookmarkDialog: DialogFragment = createBookmark(currentUrl, currentTitle, currentFavoriteIconBitmap)
574 // Display the create bookmark dialog.
575 createBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.create_bookmark))
578 // Restore the state if the app has been restarted.
579 if (savedInstanceState != null) {
580 // Restore the current folder.
581 currentFolder = savedInstanceState.getString(CURRENT_FOLDER)!!
583 // Update the bookmarks list view after it has loaded.
584 bookmarksListView.post {
585 // Get the checked bookmarks array list.
586 val checkedBookmarksArrayList = savedInstanceState.getIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST)!!
588 // Check each previously checked bookmark in the list view.
589 checkedBookmarksArrayList.forEach(Consumer { position: Int -> bookmarksListView.setItemChecked(position, true) })
593 // Load the current folder.
597 public override fun onRestart() {
598 // Run the default commands.
601 // Update the list view if returning from the bookmarks database view activity.
602 if (restartFromBookmarksDatabaseViewActivity) {
603 // Load the current folder in the list view.
606 // Reset the restart from bookmarks database view activity flag.
607 restartFromBookmarksDatabaseViewActivity = false
611 public override fun onSaveInstanceState(savedInstanceState: Bundle) {
612 // Run the default commands.
613 super.onSaveInstanceState(savedInstanceState)
615 // Get the sparse boolean array of the checked items.
616 val checkedBookmarksSparseBooleanArray = bookmarksListView.checkedItemPositions
618 // Create a checked items array list.
619 val checkedBookmarksArrayList = ArrayList<Int>()
621 // Add each checked bookmark position to the array list.
622 for (i in 0 until checkedBookmarksSparseBooleanArray.size()) {
623 // Check to see if the bookmark is currently checked. Bookmarks that have previously been checked but currently aren't will be populated in the sparse boolean array, but will return false.
624 if (checkedBookmarksSparseBooleanArray.valueAt(i)) {
625 // Add the bookmark position to the checked bookmarks array list.
626 checkedBookmarksArrayList.add(checkedBookmarksSparseBooleanArray.keyAt(i))
630 // Store the variables in the saved instance state.
631 savedInstanceState.putString(CURRENT_FOLDER, currentFolder)
632 savedInstanceState.putIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST, checkedBookmarksArrayList)
635 override fun onCreateOptionsMenu(menu: Menu): Boolean {
637 menuInflater.inflate(R.menu.bookmarks_options_menu, menu)
643 override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
644 // Get a handle for the menu item ID.
645 val menuItemId = menuItem.itemId
647 // Run the command according to the selected option.
648 if (menuItemId == android.R.id.home) { // Home. The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
649 if (currentFolder.isEmpty()) { // Currently in the home folder.
650 // Prepare to finish the activity.
652 } else { // Currently in a subfolder.
653 // Set the former parent folder as the current folder.
654 currentFolder = bookmarksDatabaseHelper.getParentFolderName(currentFolder)
656 // Load the new current folder.
659 } else if (menuItemId == R.id.options_menu_select_all_bookmarks) { // Select all.
660 // Get the total number of bookmarks.
661 val numberOfBookmarks = bookmarksListView.count
664 for (i in 0 until numberOfBookmarks) {
665 bookmarksListView.setItemChecked(i, true)
667 } else if (menuItemId == R.id.bookmarks_database_view) {
668 // Close the contextual action bar if it is displayed. This can happen if the bottom app bar is enabled.
669 contextualActionMode?.finish()
671 // Create an intent to launch the bookmarks database view activity.
672 val bookmarksDatabaseViewIntent = Intent(this, BookmarksDatabaseViewActivity::class.java)
674 // Include the favorite icon byte array to the intent.
675 bookmarksDatabaseViewIntent.putExtra(CURRENT_FAVORITE_ICON_BYTE_ARRAY, currentFavoriteIconByteArray)
678 startActivity(bookmarksDatabaseViewIntent)
683 override fun onCreateBookmark(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
684 // Get the alert dialog from the fragment.
685 val dialog = dialogFragment.dialog!!
687 // Get the views from the dialog fragment.
688 val createBookmarkNameEditText = dialog.findViewById<EditText>(R.id.create_bookmark_name_edittext)
689 val createBookmarkUrlEditText = dialog.findViewById<EditText>(R.id.create_bookmark_url_edittext)
691 // Extract the strings from the edit texts.
692 val bookmarkNameString = createBookmarkNameEditText.text.toString()
693 val bookmarkUrlString = createBookmarkUrlEditText.text.toString()
695 // Create a favorite icon byte array output stream.
696 val favoriteIconByteArrayOutputStream = ByteArrayOutputStream()
698 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
699 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream)
701 // Convert the favorite icon byte array stream to a byte array.
702 val favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray()
704 // Display the new bookmark below the current items in the (0 indexed) list.
705 val newBookmarkDisplayOrder = bookmarksListView.count
707 // Create the bookmark.
708 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentFolder, newBookmarkDisplayOrder, favoriteIconByteArray)
710 // Update the bookmarks cursor with the current contents of this folder.
711 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
713 // Update the list view.
714 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
716 // Scroll to the new bookmark.
717 bookmarksListView.setSelection(newBookmarkDisplayOrder)
720 override fun onCreateBookmarkFolder(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
721 // Get the dialog from the dialog fragment.
722 val dialog = dialogFragment.dialog!!
724 // Get handles for the views in the dialog fragment.
725 val folderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
726 val defaultIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
727 val defaultIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
729 // Get new folder name string.
730 val folderNameString = folderNameEditText.text.toString()
732 // Set the folder icon bitmap according to the dialog.
733 val folderIconBitmap = if (defaultIconRadioButton.isChecked) { // Use the default folder icon.
734 // Get the default folder icon drawable.
735 val folderIconDrawable = defaultIconImageView.drawable
737 // Convert the folder icon drawable to a bitmap drawable.
738 val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
740 // Convert the folder icon bitmap drawable to a bitmap.
741 folderIconBitmapDrawable.bitmap
742 } else { // Use the WebView favorite icon.
743 // Copy the favorite icon bitmap to the folder icon bitmap.
747 // Create a folder icon byte array output stream.
748 val folderIconByteArrayOutputStream = ByteArrayOutputStream()
750 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
751 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream)
753 // Convert the folder icon byte array stream to a byte array.
754 val folderIconByteArray = folderIconByteArrayOutputStream.toByteArray()
756 // Move all the bookmarks down one in the display order.
757 for (i in 0 until bookmarksListView.count) {
758 val databaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
759 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1)
762 // Create the folder, which will be placed at the top of the list view.
763 bookmarksDatabaseHelper.createFolder(folderNameString, currentFolder, folderIconByteArray)
765 // Update the bookmarks cursor with the contents of the current folder.
766 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
768 // Update the list view.
769 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
771 // Scroll to the new folder.
772 bookmarksListView.setSelection(0)
775 override fun onSaveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int, favoriteIconBitmap: Bitmap) {
776 // Get the dialog from the dialog fragment.
777 val dialog = dialogFragment.dialog!!
779 // Get handles for the views from the dialog fragment.
780 val bookmarkNameEditText = dialog.findViewById<EditText>(R.id.bookmark_name_edittext)
781 val bookmarkUrlEditText = dialog.findViewById<EditText>(R.id.bookmark_url_edittext)
782 val currentIconRadioButton = dialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)
784 // Get the bookmark strings.
785 val bookmarkNameString = bookmarkNameEditText.text.toString()
786 val bookmarkUrlString = bookmarkUrlEditText.text.toString()
788 // Update the bookmark.
789 if (currentIconRadioButton.isChecked) { // Update the bookmark without changing the favorite icon.
790 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString)
791 } else { // Update the bookmark using the WebView favorite icon.
792 // Create a favorite icon byte array output stream.
793 val newFavoriteIconByteArrayOutputStream = ByteArrayOutputStream()
795 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
796 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream)
798 // Convert the favorite icon byte array stream to a byte array.
799 val newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray()
801 // Update the bookmark and the favorite icon.
802 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray)
805 // Close the contextual action bar if it is displayed.
806 contextualActionMode?.finish()
808 // Update the bookmarks cursor with the contents of the current folder.
809 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
811 // Update the list view.
812 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
815 override fun onSaveBookmarkFolder(dialogFragment: DialogFragment, selectedFolderDatabaseId: Int, favoriteIconBitmap: Bitmap) {
816 // Get the dialog from the dialog fragment.
817 val dialog = dialogFragment.dialog!!
819 // Get handles for the views from the dialog fragment.
820 val currentFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.current_icon_radiobutton)
821 val defaultFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
822 val defaultFolderIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
823 val editFolderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
825 // Get the new folder name.
826 val newFolderNameString = editFolderNameEditText.text.toString()
828 // Check if the favorite icon has changed.
829 if (currentFolderIconRadioButton.isChecked) { // Only the name has changed.
830 // Update the name in the database.
831 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString)
832 } else { // The icon has changed. TODO: Test.
833 // Populate the new folder icon bitmap.
834 val folderIconBitmap: Bitmap = if (defaultFolderIconRadioButton.isChecked) {
835 // Get the default folder icon drawable.
836 val folderIconDrawable = defaultFolderIconImageView.drawable
838 // Convert the folder icon drawable to a bitmap drawable.
839 val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
841 // Convert the folder icon bitmap drawable to a bitmap.
842 folderIconBitmapDrawable.bitmap
843 } else { // Use the WebView favorite icon.
844 // Copy the favorite icon bitmap to the folder icon bitmap.
848 // Create a folder icon byte array output stream.
849 val newFolderIconByteArrayOutputStream = ByteArrayOutputStream()
851 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
852 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream)
854 // Convert the folder icon byte array stream to a byte array.
855 val newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray()
857 // Update the database.
858 if (!currentFolderIconRadioButton.isChecked && newFolderNameString == oldFolderNameString) // Only the icon has changed.
859 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray)
860 else // The folder icon and the name have changed.
861 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray)
864 // Update the bookmarks cursor with the current contents of this folder.
865 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
867 // Update the list view.
868 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
870 // Close the contextual action mode.
871 contextualActionMode!!.finish()
874 override fun onMoveToFolder(dialogFragment: DialogFragment) {
875 // Get the dialog from the dialog fragment.
876 val dialog = dialogFragment.dialog!!
878 // Get a handle for the folder list view from the dialog.
879 val folderListView = dialog.findViewById<ListView>(R.id.move_to_folder_listview)
881 // Store a long array of the selected folders.
882 val newFolderLongArray = folderListView.checkedItemIds
884 // Get the new folder database ID. Only one folder will be selected so it will be the first one.
885 val newFolderDatabaseId = newFolderLongArray[0].toInt()
887 // Set the new folder name.
888 val newFolderName = if (newFolderDatabaseId == 0) {
889 // The new folder is the home folder, represented as `""` in the database.
892 // Get the new folder name from the database.
893 bookmarksDatabaseHelper.getFolderName(newFolderDatabaseId)
896 // Get a long array with the the database ID of the selected bookmarks.
897 val selectedBookmarksLongArray = bookmarksListView.checkedItemIds
899 // Move each of the selected bookmarks to the new folder.
900 for (databaseIdLong in selectedBookmarksLongArray) {
901 // Convert the database long ID to an int for each selected bookmark.
902 val databaseIdInt = databaseIdLong.toInt()
904 // Move the selected bookmark to the new folder.
905 bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderName)
908 // Update the bookmarks cursor with the current contents of this folder.
909 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
911 // Update the list view.
912 bookmarksCursorAdapter.changeCursor(bookmarksCursor)
914 // Close the contextual app bar.
915 contextualActionMode!!.finish()
918 private fun countBookmarkFolderContents(folderDatabaseId: Int): Int {
919 // Get the name of the folder.
920 val folderName = bookmarksDatabaseHelper.getFolderName(folderDatabaseId)
922 // Get the contents of the folder.
923 val folderCursor = bookmarksDatabaseHelper.getBookmarkIds(folderName)
925 // Initialize the bookmark counter.
926 var bookmarkCounter = 0
928 // Count each of the bookmarks in the folder.
929 for (i in 0 until folderCursor.count) {
930 // Move the folder cursor to the current row.
931 folderCursor.moveToPosition(i)
933 // Get the database ID of the item.
934 val itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID))
936 // If this is a folder, recursively count the contents first.
937 if (bookmarksDatabaseHelper.isFolder(itemDatabaseId))
938 bookmarkCounter += countBookmarkFolderContents(itemDatabaseId)
940 // Add the bookmark to the running total.
944 // Return the bookmark counter.
945 return bookmarkCounter
948 private fun deleteBookmarkFolderContents(folderDatabaseId: Int) {
949 // Get the name of the folder.
950 val folderName = bookmarksDatabaseHelper.getFolderName(folderDatabaseId)
952 // Get the contents of the folder.
953 val folderCursor = bookmarksDatabaseHelper.getBookmarkIds(folderName)
955 // Delete each of the bookmarks in the folder.
956 for (i in 0 until folderCursor.count) {
957 // Move the folder cursor to the current row.
958 folderCursor.moveToPosition(i)
960 // Get the database ID of the item.
961 val itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID))
963 // If this is a folder, recursively delete the contents first.
964 if (bookmarksDatabaseHelper.isFolder(itemDatabaseId))
965 deleteBookmarkFolderContents(itemDatabaseId)
967 // Delete the bookmark.
968 bookmarksDatabaseHelper.deleteBookmark(itemDatabaseId)
972 private fun prepareFinish() {
973 // Check to see if a snackbar is currently displayed. If so, it must be closed before exiting so that a pending delete is completed before reloading the list view in the bookmarks drawer.
974 if (bookmarksDeletedSnackbar != null && bookmarksDeletedSnackbar!!.isShown) { // Close the bookmarks deleted snackbar before going home.
975 // Set the close flag.
976 closeActivityAfterDismissingSnackbar = true
978 // Dismiss the snackbar.
979 bookmarksDeletedSnackbar!!.dismiss()
980 } else { // Go home immediately.
981 // Update the bookmarks folder for the bookmarks drawer in the main WebView activity.
982 MainWebViewActivity.currentBookmarksFolder = currentFolder
984 // Close the bookmarks drawer and reload the bookmarks list view when returning to the main WebView activity.
985 MainWebViewActivity.restartFromBookmarksActivity = true
987 // Exit the bookmarks activity.
992 private fun updateMoveIcons() {
993 // Get a long array of the selected bookmarks.
994 val selectedBookmarksLongArray = bookmarksListView.checkedItemIds
996 // Get the database IDs for the first, last, and selected bookmarks.
997 val firstBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(0).toInt()
998 val lastBookmarkDatabaseId = bookmarksListView.getItemIdAtPosition(bookmarksListView.count - 1).toInt() // The bookmarks list view is 0 indexed.
999 val selectedBookmarkDatabaseId = selectedBookmarksLongArray[0].toInt()
1001 // Update the move bookmark up menu item.
1002 if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) { // The selected bookmark is in the first position.
1003 // Disable the move bookmark up menu item.
1004 moveBookmarkUpMenuItem.isEnabled = false
1007 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_disabled)
1008 } else { // The selected bookmark is not in the first position.
1009 // Enable the move bookmark up menu item.
1010 moveBookmarkUpMenuItem.isEnabled = true
1012 // Set the icon according to the theme.
1013 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled)
1016 // Update the move bookmark down menu item.
1017 if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) { // The selected bookmark is in the last position.
1018 // Disable the move bookmark down menu item.
1019 moveBookmarkDownMenuItem.isEnabled = false
1022 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_disabled)
1023 } else { // The selected bookmark is not in the last position.
1024 // Enable the move bookmark down menu item.
1025 moveBookmarkDownMenuItem.isEnabled = true
1028 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled)
1032 private fun scrollBookmarks(selectedBookmarkPosition: Int) {
1033 // Get the first and last visible bookmark positions.
1034 val firstVisibleBookmarkPosition = bookmarksListView.firstVisiblePosition
1035 val lastVisibleBookmarkPosition = bookmarksListView.lastVisiblePosition
1037 // Calculate the number of bookmarks per screen.
1038 val numberOfBookmarksPerScreen = lastVisibleBookmarkPosition - firstVisibleBookmarkPosition
1040 // Scroll with the moved bookmark if necessary.
1041 if (selectedBookmarkPosition <= firstVisibleBookmarkPosition) { // The selected bookmark position is at or above the top of the screen.
1042 // Scroll to the selected bookmark position.
1043 bookmarksListView.setSelection(selectedBookmarkPosition)
1044 } else if (selectedBookmarkPosition >= lastVisibleBookmarkPosition - 1) { // The selected bookmark is at or below the bottom of the screen.
1045 // The `-1` handles partial bookmarks displayed at the bottom of the list view. This command scrolls to display the selected bookmark at the bottom of the screen.
1046 // `+1` assures that the entire bookmark will be displayed in situations where only a partial bookmark fits at the bottom of the list view.
1047 bookmarksListView.setSelection(selectedBookmarkPosition - numberOfBookmarksPerScreen + 1)
1051 private fun loadFolder() {
1052 // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
1053 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder)
1055 // Setup a cursor adapter.
1056 bookmarksCursorAdapter = object : CursorAdapter(this, bookmarksCursor, false) {
1057 override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
1058 // Inflate the individual item layout.
1059 return layoutInflater.inflate(R.layout.bookmarks_activity_item_linearlayout, parent, false)
1062 override fun bindView(view: View, context: Context, cursor: Cursor) {
1063 // Get handles for the views.
1064 val bookmarkFavoriteIconImageView = view.findViewById<ImageView>(R.id.bookmark_favorite_icon)
1065 val bookmarkNameTextView = view.findViewById<TextView>(R.id.bookmark_name)
1067 // Get the favorite icon byte array from the cursor.
1068 val favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON))
1070 // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
1071 val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size)
1073 // Display the bitmap in the bookmark favorite icon image view.
1074 bookmarkFavoriteIconImageView.setImageBitmap(favoriteIconBitmap)
1076 // Get the bookmark name from the cursor.
1077 val bookmarkNameString = cursor.getString(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME))
1079 // Display the bookmark name.
1080 bookmarkNameTextView.text = bookmarkNameString
1082 // Make the font bold for folders.
1083 if (cursor.getInt(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1)
1084 bookmarkNameTextView.typeface = Typeface.DEFAULT_BOLD
1085 else // Reset the font to default for normal bookmarks.
1086 bookmarkNameTextView.typeface = Typeface.DEFAULT
1090 // Populate the list view with the adapter.
1091 bookmarksListView.adapter = bookmarksCursorAdapter
1093 // Set the app bar title.
1094 if (currentFolder.isEmpty())
1095 appBar.setTitle(R.string.bookmarks)
1097 appBar.title = currentFolder
1100 public override fun onDestroy() {
1101 // Close the bookmarks cursor and database.
1102 bookmarksCursor.close()
1103 bookmarksDatabaseHelper.close()
1105 // Run the default commands.