2 * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
6 * Privacy Browser is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser.activities;
22 import android.annotation.SuppressLint;
23 import android.app.Dialog;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.SharedPreferences;
27 import android.content.res.Configuration;
28 import android.database.Cursor;
29 import android.graphics.Bitmap;
30 import android.graphics.BitmapFactory;
31 import android.graphics.Typeface;
32 import android.graphics.drawable.BitmapDrawable;
33 import android.graphics.drawable.Drawable;
34 import android.os.Bundle;
35 import android.preference.PreferenceManager;
36 import android.util.SparseBooleanArray;
37 import android.view.ActionMode;
38 import android.view.Menu;
39 import android.view.MenuItem;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.view.WindowManager;
43 import android.widget.AbsListView;
44 import android.widget.CursorAdapter;
45 import android.widget.EditText;
46 import android.widget.ImageView;
47 import android.widget.ListView;
48 import android.widget.RadioButton;
49 import android.widget.TextView;
51 import androidx.annotation.NonNull;
52 import androidx.appcompat.app.ActionBar;
53 import androidx.appcompat.app.AppCompatActivity;
54 import androidx.appcompat.widget.Toolbar;
55 import androidx.fragment.app.DialogFragment;
57 import com.google.android.material.floatingactionbutton.FloatingActionButton;
58 import com.google.android.material.snackbar.Snackbar;
60 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
61 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
62 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
63 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
64 import com.stoutner.privacybrowser.dialogs.MoveToFolderDialog;
65 import com.stoutner.privacybrowser.R;
66 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
68 import java.io.ByteArrayOutputStream;
69 import java.util.ArrayList;
71 public class BookmarksActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, EditBookmarkDialog.EditBookmarkListener,
72 EditBookmarkFolderDialog.EditBookmarkFolderListener, MoveToFolderDialog.MoveToFolderListener {
74 // `currentFolder` is public static so it can be accessed from `BookmarksDatabaseViewActivity`.
75 public static String currentFolder;
77 // `restartFromBookmarksDatabaseViewActivity` is public static so it can be accessed from `BookmarksDatabaseViewActivity`. It is also used in `onRestart()`.
78 public static boolean restartFromBookmarksDatabaseViewActivity;
81 // Define the saved instance state constants.
82 private final String CHECKED_BOOKMARKS_ARRAY_LIST = "checked_bookmarks_array_list";
84 // Define the class menu items.
85 private MenuItem moveBookmarkUpMenuItem;
86 private MenuItem moveBookmarkDownMenuItem;
88 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`,
89 // `onMoveToFolder()`, `deleteBookmarkFolderContents()`, `loadFolder()`, and `onDestroy()`.
90 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
92 // `bookmarksListView` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`,
93 // `updateMoveIcons()`, `scrollBookmarks()`, and `loadFolder()`.
94 private ListView bookmarksListView;
96 // `bookmarksCursor` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, `deleteBookmarkFolderContents()`,
97 // `loadFolder()`, and `onDestroy()`.
98 private Cursor bookmarksCursor;
100 // `bookmarksCursorAdapter` is used in `onCreate(), `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, and `onLoadFolder()`.
101 private CursorAdapter bookmarksCursorAdapter;
103 // `contextualActionMode` is used in `onCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()` and `onMoveToFolder()`.
104 private ActionMode contextualActionMode;
106 // `appBar` is used in `onCreate()` and `loadFolder()`.
107 private ActionBar appBar;
109 // `oldFolderName` is used in `onCreate()` and `onSaveBookmarkFolder()`.
110 private String oldFolderNameString;
112 // `bookmarksDeletedSnackbar` is used in `onCreate()`, `onOptionsItemSelected()`, and `onBackPressed()`.
113 private Snackbar bookmarksDeletedSnackbar;
115 // `closeActivityAfterDismissingSnackbar` is used in `onCreate()`, `onOptionsItemSelected()`, and `onBackPressed()`.
116 private boolean closeActivityAfterDismissingSnackbar;
118 // The favorite icon byte array is populated in `onCreate()` and used in `onOptionsItemSelected()`.
119 private byte[] favoriteIconByteArray;
122 protected void onCreate(Bundle savedInstanceState) {
123 // Get a handle for the shared preferences.
124 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
126 // Get the screenshot preference.
127 boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
129 // Disable screenshots if not allowed.
130 if (!allowScreenshots) {
131 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
135 setTheme(R.style.PrivacyBrowser);
137 // Run the default commands.
138 super.onCreate(savedInstanceState);
140 // Get the intent that launched the activity.
141 Intent launchingIntent = getIntent();
143 // Store the current URL and title.
144 String currentUrl = launchingIntent.getStringExtra("current_url");
145 String currentTitle = launchingIntent.getStringExtra("current_title");
147 // Set the current folder variable.
148 if (launchingIntent.getStringExtra("current_folder") != null) { // Set the current folder from the intent.
149 currentFolder = launchingIntent.getStringExtra("current_folder");
150 } else { // Set the current folder to be `""`, which is the home folder.
154 // Get the favorite icon byte array.
155 favoriteIconByteArray = launchingIntent.getByteArrayExtra("favorite_icon_byte_array");
157 // Remove the incorrect lint warning that the favorite icon byte array might be null.
158 assert favoriteIconByteArray != null;
160 // Convert the favorite icon byte array to a bitmap.
161 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
163 // Set the content view.
164 setContentView(R.layout.bookmarks_coordinatorlayout);
166 // The AndroidX toolbar must be used until the minimum API is >= 21.
167 final Toolbar toolbar = findViewById(R.id.bookmarks_toolbar);
168 setSupportActionBar(toolbar);
170 // Get handles for the views.
171 appBar = getSupportActionBar();
172 bookmarksListView = findViewById(R.id.bookmarks_listview);
174 // Remove the incorrect lint warning that `appBar` might be null.
175 assert appBar != null;
177 // Display the home arrow on the app bar.
178 appBar.setDisplayHomeAsUpEnabled(true);
180 // Initialize the database helper. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`.
181 // The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
182 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
184 // Load the home folder.
187 // Set a listener so that tapping a list item loads the URL or folder.
188 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
189 // Convert the id from long to int to match the format of the bookmarks database.
190 int databaseId = (int) id;
192 // Get the bookmark cursor for this ID.
193 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
195 // Move the cursor to the first entry.
196 bookmarkCursor.moveToFirst();
198 // Act upon the bookmark according to the type.
199 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
200 // Update the current folder.
201 currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
203 // Load the new folder.
205 } else { // The selected bookmark is not a folder.
206 // Instantiate the edit bookmark dialog.
207 DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, favoriteIconBitmap);
210 editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark));
214 bookmarkCursor.close();
217 // Handle long presses on the list view.
218 bookmarksListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
219 // Instantiate the common variables.
220 MenuItem editBookmarkMenuItem;
221 MenuItem deleteBookmarksMenuItem;
222 MenuItem selectAllBookmarksMenuItem;
223 boolean deletingBookmarks;
226 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
227 // Inflate the menu for the contextual app bar.
228 getMenuInflater().inflate(R.menu.bookmarks_context_menu, menu);
231 if (currentFolder.isEmpty()) { // Use `R.string.bookmarks` if in the home folder.
232 mode.setTitle(R.string.bookmarks);
233 } else { // Use the current folder name as the title.
234 mode.setTitle(currentFolder);
237 // Get handles for menu items that need to be selectively disabled.
238 moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up);
239 moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down);
240 editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark);
241 deleteBookmarksMenuItem = menu.findItem(R.id.delete_bookmark);
242 selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks);
244 // Disable the delete bookmarks menu item if a delete is pending.
245 deleteBookmarksMenuItem.setEnabled(!deletingBookmarks);
247 // Store a handle for the contextual action mode so it can be closed programatically.
248 contextualActionMode = mode;
255 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
256 // Get a handle for the move to folder menu item.
257 MenuItem moveToFolderMenuItem = menu.findItem(R.id.move_to_folder);
259 // Get a Cursor with all of the folders.
260 Cursor folderCursor = bookmarksDatabaseHelper.getAllFolders();
262 // Enable the move to folder menu item if at least one folder exists.
263 moveToFolderMenuItem.setVisible(folderCursor.getCount() > 0);
270 public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
271 // Get the number of selected bookmarks.
272 int numberOfSelectedBookmarks = bookmarksListView.getCheckedItemCount();
274 // Only process commands if at least one bookmark is selected. Otherwise, a context menu with 0 selected bookmarks is briefly displayed.
275 if (numberOfSelectedBookmarks > 0) {
276 // Adjust the ActionMode and the menu according to the number of selected bookmarks.
277 if (numberOfSelectedBookmarks == 1) { // One bookmark is selected.
278 // List the number of selected bookmarks in the subtitle.
279 mode.setSubtitle(getString(R.string.selected) + " 1");
281 // Show the `Move Up`, `Move Down`, and `Edit` options.
282 moveBookmarkUpMenuItem.setVisible(true);
283 moveBookmarkDownMenuItem.setVisible(true);
284 editBookmarkMenuItem.setVisible(true);
286 // Update the enabled status of the move icons.
288 } else { // More than one bookmark is selected.
289 // List the number of selected bookmarks in the subtitle.
290 mode.setSubtitle(getString(R.string.selected) + " " + numberOfSelectedBookmarks);
292 // Hide non-applicable `MenuItems`.
293 moveBookmarkUpMenuItem.setVisible(false);
294 moveBookmarkDownMenuItem.setVisible(false);
295 editBookmarkMenuItem.setVisible(false);
298 // Show the select all menu item if all the bookmarks are not selected.
299 selectAllBookmarksMenuItem.setVisible(bookmarksListView.getCheckedItemCount() != bookmarksListView.getCount());
304 public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
305 // Declare the common variables.
306 int selectedBookmarkNewPosition;
307 final SparseBooleanArray selectedBookmarksPositionsSparseBooleanArray;
309 // Initialize the selected bookmark position.
310 int selectedBookmarkPosition = 0;
312 // Get the menu item ID.
313 int menuItemId = menuItem.getItemId();
315 // Run the commands according to the selected action item.
316 if (menuItemId == R.id.move_bookmark_up) { // Move the bookmark up.
317 // Get the array of checked bookmark positions.
318 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
320 // 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`.
321 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
322 // Check to see if the value for the bookmark is true, meaning it is currently selected.
323 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
324 // Only one bookmark should have a value of `true` when move bookmark up is enabled.
325 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
329 // Calculate the new position of the selected bookmark.
330 selectedBookmarkNewPosition = selectedBookmarkPosition - 1;
332 // Iterate through the bookmarks.
333 for (int i = 0; i < bookmarksListView.getCount(); i++) {
334 // Get the database ID for the current bookmark.
335 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
337 // Update the display order for the current bookmark.
338 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
339 // Move the current bookmark up one.
340 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1);
341 } else if ((i + 1) == selectedBookmarkPosition) { // The current bookmark is immediately above the selected bookmark.
342 // Move the current bookmark down one.
343 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1);
344 } else { // The current bookmark is not changing positions.
345 // Move `bookmarksCursor` to the current bookmark position.
346 bookmarksCursor.moveToPosition(i);
348 // Update the display order only if it is not correct in the database.
349 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
350 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
355 // Update the bookmarks cursor with the current contents of the bookmarks database.
356 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
358 // Update the list view.
359 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
361 // Scroll with the bookmark.
362 scrollBookmarks(selectedBookmarkNewPosition);
364 // Update the enabled status of the move icons.
366 } else if (menuItemId == R.id.move_bookmark_down) { // Move the bookmark down.
367 // Get the array of checked bookmark positions.
368 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
370 // 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`.
371 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
372 // Check to see if the value for the bookmark is true, meaning it is currently selected.
373 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
374 // Only one bookmark should have a value of `true` when move bookmark down is enabled.
375 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
379 // Calculate the new position of the selected bookmark.
380 selectedBookmarkNewPosition = selectedBookmarkPosition + 1;
382 // Iterate through the bookmarks.
383 for (int i = 0; i < bookmarksListView.getCount(); i++) {
384 // Get the database ID for the current bookmark.
385 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
387 // Update the display order for the current bookmark.
388 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
389 // Move the current bookmark down one.
390 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1);
391 } else if ((i - 1) == selectedBookmarkPosition) { // The current bookmark is immediately below the selected bookmark.
392 // Move the bookmark below the selected bookmark up one.
393 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1);
394 } else { // The current bookmark is not changing positions.
395 // Move `bookmarksCursor` to the current bookmark position.
396 bookmarksCursor.moveToPosition(i);
398 // Update the display order only if it is not correct in the database.
399 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
400 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
405 // Update the bookmarks cursor with the current contents of the bookmarks database.
406 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
408 // Update the list view.
409 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
411 // Scroll with the bookmark.
412 scrollBookmarks(selectedBookmarkNewPosition);
414 // Update the enabled status of the move icons.
416 } else if (menuItemId == R.id.move_to_folder) { // Move to folder.
417 // Instantiate the move to folder alert dialog.
418 DialogFragment moveToFolderDialog = MoveToFolderDialog.moveBookmarks(currentFolder, bookmarksListView.getCheckedItemIds());
420 // Show the move to folder alert dialog.
421 moveToFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.move_to_folder));
422 } else if (menuItemId == R.id.edit_bookmark) {
423 // Get the array of checked bookmark positions.
424 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
426 // 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`.
427 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
428 // Check to see if the value for the bookmark is true, meaning it is currently selected.
429 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
430 // Only one bookmark should have a value of `true` when move edit bookmark is enabled.
431 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
435 // Move the cursor to the selected position.
436 bookmarksCursor.moveToPosition(selectedBookmarkPosition);
438 // Find out if this bookmark is a folder.
439 boolean isFolder = (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1);
441 // Get the selected bookmark database ID.
442 int databaseId = bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper._ID));
444 // Show the edit bookmark or edit bookmark folder dialog.
446 // Save the current folder name, which is used in `onSaveBookmarkFolder()`.
447 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
449 // Instantiate the edit bookmark folder dialog.
450 DialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, favoriteIconBitmap);
453 editFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_folder));
455 // Instantiate the edit bookmark dialog.
456 DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, favoriteIconBitmap);
459 editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark));
461 } else if (menuItemId == R.id.delete_bookmark) { // Delete bookmark.
462 // Set the deleting bookmarks flag, which prevents the delete menu item from being enabled until the current process finishes.
463 deletingBookmarks = true;
465 // Get an array of the selected row IDs.
466 final long[] selectedBookmarksIdsLongArray = bookmarksListView.getCheckedItemIds();
468 // Get an array of checked bookmarks. `.clone()` makes a copy that won't change if the list view is reloaded, which is needed for re-selecting the bookmarks on undelete.
469 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions().clone();
471 // Update the bookmarks cursor with the current contents of the bookmarks database except for the specified database IDs.
472 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(selectedBookmarksIdsLongArray, currentFolder);
474 // Update the list view.
475 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
477 // Create a Snackbar with the number of deleted bookmarks.
478 bookmarksDeletedSnackbar = Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.bookmarks_deleted) + " " + selectedBookmarksIdsLongArray.length,
479 Snackbar.LENGTH_LONG)
480 .setAction(R.string.undo, view -> {
481 // Do nothing because everything will be handled by `onDismissed()` below.
483 .addCallback(new Snackbar.Callback() {
484 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
486 public void onDismissed(Snackbar snackbar, int event) {
487 if (event == Snackbar.Callback.DISMISS_EVENT_ACTION) { // The user pushed the undo button.
488 // Update the bookmarks cursor with the current contents of the bookmarks database, including the "deleted" bookmarks.
489 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
491 // Update the list view.
492 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
494 // Re-select the previously selected bookmarks.
495 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
496 bookmarksListView.setItemChecked(selectedBookmarksPositionsSparseBooleanArray.keyAt(i), true);
498 } else { // The snackbar was dismissed without the undo button being pushed.
499 // Delete each selected bookmark.
500 for (long databaseIdLong : selectedBookmarksIdsLongArray) {
501 // Convert `databaseIdLong` to an int.
502 int databaseIdInt = (int) databaseIdLong;
504 // Delete the contents of the folder if the selected bookmark is a folder.
505 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
506 deleteBookmarkFolderContents(databaseIdInt);
509 // Delete the selected bookmark.
510 bookmarksDatabaseHelper.deleteBookmark(databaseIdInt);
513 // Update the display order.
514 for (int i = 0; i < bookmarksListView.getCount(); i++) {
515 // Get the database ID for the current bookmark.
516 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
518 // Move `bookmarksCursor` 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.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
523 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
528 // Reset the deleting bookmarks flag.
529 deletingBookmarks = false;
531 // Enable the delete bookmarks menu item.
532 deleteBookmarksMenuItem.setEnabled(true);
534 // Close the activity if back has been pressed.
535 if (closeActivityAfterDismissingSnackbar) {
542 bookmarksDeletedSnackbar.show();
543 } else if (menuItemId == R.id.context_menu_select_all_bookmarks) { // Select all.
544 // Get the total number of bookmarks.
545 int numberOfBookmarks = bookmarksListView.getCount();
548 for (int i = 0; i < numberOfBookmarks; i++) {
549 bookmarksListView.setItemChecked(i, true);
553 // Consume the click.
558 public void onDestroyActionMode(ActionMode mode) {
563 // Get handles for the floating action buttons.
564 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
565 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
567 // Set the create new bookmark folder FAB to display the `AlertDialog`.
568 createBookmarkFolderFab.setOnClickListener(v -> {
569 // Create a create bookmark folder dialog.
570 DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(favoriteIconBitmap);
572 // Show the create bookmark folder dialog.
573 createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
576 // Set the create new bookmark FAB to display the alert dialog.
577 createBookmarkFab.setOnClickListener(view -> {
578 // Remove the incorrect lint warning below.
579 assert currentUrl != null;
580 assert currentTitle != null;
582 // Instantiate the create bookmark dialog.
583 DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentUrl, currentTitle, favoriteIconBitmap);
585 // Display the create bookmark dialog.
586 createBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_bookmark));
589 // Restore the state if the app has been restarted.
590 if (savedInstanceState != null) {
591 // Update the bookmarks list view after it has loaded.
592 bookmarksListView.post(() -> {
593 // Get the checked bookmarks array list.
594 ArrayList<Integer> checkedBookmarksArrayList = savedInstanceState.getIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST);
596 // Check each previously checked bookmark in the list view. When the minimum API >= 24 a `forEach()` command can be used instead.
597 if (checkedBookmarksArrayList != null) {
598 for (int i = 0; i < checkedBookmarksArrayList.size(); i++) {
599 bookmarksListView.setItemChecked(checkedBookmarksArrayList.get(i), true);
607 public void onRestart() {
608 // Run the default commands.
611 // Update the list view if returning from the bookmarks database view activity.
612 if (restartFromBookmarksDatabaseViewActivity) {
613 // Load the current folder in the list view.
616 // Reset `restartFromBookmarksDatabaseViewActivity`.
617 restartFromBookmarksDatabaseViewActivity = false;
622 public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
623 // Run the default commands.
624 super.onSaveInstanceState(savedInstanceState);
626 // Get the array of the checked items.
627 SparseBooleanArray checkedBookmarksSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
629 // Create a checked items array list.
630 ArrayList<Integer> checkedBookmarksArrayList = new ArrayList<>();
632 // Add each checked bookmark position to the array list.
633 for (int i = 0; i < checkedBookmarksSparseBooleanArray.size(); i++) {
634 // 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.
635 if (checkedBookmarksSparseBooleanArray.valueAt(i)) {
636 // Add the bookmark position to the checked bookmarks array list.
637 checkedBookmarksArrayList.add(checkedBookmarksSparseBooleanArray.keyAt(i));
641 // Store the checked items array list in the saved instance state.
642 savedInstanceState.putIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST, checkedBookmarksArrayList);
646 public boolean onCreateOptionsMenu(Menu menu) {
648 getMenuInflater().inflate(R.menu.bookmarks_options_menu, menu);
655 public boolean onOptionsItemSelected(MenuItem menuItem) {
656 // Get a handle for the menu item ID.
657 int menuItemId = menuItem.getItemId();
659 // Run the command according to the selected option.
660 if (menuItemId == android.R.id.home) { // Home. The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
661 if (currentFolder.isEmpty()) { // Currently in the home folder.
662 // Run the back commands.
664 } else { // Currently in a subfolder.
665 // Place the former parent folder in `currentFolder`.
666 currentFolder = bookmarksDatabaseHelper.getParentFolderName(currentFolder);
668 // Load the new folder.
671 } else if (menuItemId == R.id.options_menu_select_all_bookmarks) { // Select all.
672 // Get the total number of bookmarks.
673 int numberOfBookmarks = bookmarksListView.getCount();
676 for (int i = 0; i < numberOfBookmarks; i++) {
677 bookmarksListView.setItemChecked(i, true);
679 } else if (menuItemId == R.id.bookmarks_database_view) {
680 // Create an intent to launch the bookmarks database view activity.
681 Intent bookmarksDatabaseViewIntent = new Intent(this, BookmarksDatabaseViewActivity.class);
683 // Include the favorite icon byte array to the intent.
684 bookmarksDatabaseViewIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
687 startActivity(bookmarksDatabaseViewIntent);
693 public void onBackPressed() {
694 // 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.
695 if ((bookmarksDeletedSnackbar != null) && bookmarksDeletedSnackbar.isShown()) { // Close the bookmarks deleted snackbar before going home.
696 // Set the close flag.
697 closeActivityAfterDismissingSnackbar = true;
699 // Dismiss the snackbar.
700 bookmarksDeletedSnackbar.dismiss();
701 } else { // Go home immediately.
702 // Update the bookmarks folder for the bookmarks drawer in the main WebView activity.
703 MainWebViewActivity.currentBookmarksFolder = currentFolder;
705 // Close the bookmarks drawer and reload the bookmarks ListView when returning to the main WebView activity.
706 MainWebViewActivity.restartFromBookmarksActivity = true;
708 // Exit the bookmarks activity.
709 super.onBackPressed();
714 public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
715 // Get the alert dialog from the fragment.
716 Dialog dialog = dialogFragment.getDialog();
718 // Remove the incorrect lint warning below that the dialog might be null.
719 assert dialog != null;
721 // Get the views from the dialog fragment.
722 EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext);
723 EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext);
725 // Extract the strings from the edit texts.
726 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
727 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
729 // Create a favorite icon byte array output stream.
730 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
732 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
733 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
735 // Convert the favorite icon byte array stream to a byte array.
736 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
738 // Display the new bookmark below the current items in the (0 indexed) list.
739 int newBookmarkDisplayOrder = bookmarksListView.getCount();
741 // Create the bookmark.
742 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
744 // Update the bookmarks cursor with the current contents of this folder.
745 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
747 // Update the `ListView`.
748 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
750 // Scroll to the new bookmark.
751 bookmarksListView.setSelection(newBookmarkDisplayOrder);
755 public void onCreateBookmarkFolder(DialogFragment dialogFragment, @NonNull Bitmap favoriteIconBitmap) {
756 // Get the dialog from the dialog fragment.
757 Dialog dialog = dialogFragment.getDialog();
759 // Remove the incorrect lint warning below that the dialog might be null.
760 assert dialog != null;
762 // Get handles for the views in the dialog fragment.
763 EditText folderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
764 RadioButton defaultIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
765 ImageView defaultIconImageView = dialog.findViewById(R.id.default_icon_imageview);
767 // Get new folder name string.
768 String folderNameString = folderNameEditText.getText().toString();
770 // Create a folder icon bitmap.
771 Bitmap folderIconBitmap;
773 // Set the folder icon bitmap according to the dialog.
774 if (defaultIconRadioButton.isChecked()) { // Use the default folder icon.
775 // Get the default folder icon drawable.
776 Drawable folderIconDrawable = defaultIconImageView.getDrawable();
778 // Convert the folder icon drawable to a bitmap drawable.
779 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
781 // Convert the folder icon bitmap drawable to a bitmap.
782 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
783 } else { // Use the WebView favorite icon.
784 // Copy the favorite icon bitmap to the folder icon bitmap.
785 folderIconBitmap = favoriteIconBitmap;
788 // Create a folder icon byte array output stream.
789 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
791 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
792 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
794 // Convert the folder icon byte array stream to a byte array.
795 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
797 // Move all the bookmarks down one in the display order.
798 for (int i = 0; i < bookmarksListView.getCount(); i++) {
799 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
800 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
803 // Create the folder, which will be placed at the top of the `ListView`.
804 bookmarksDatabaseHelper.createFolder(folderNameString, currentFolder, folderIconByteArray);
806 // Update the bookmarks cursor with the current contents of this folder.
807 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
809 // Update the list view.
810 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
812 // Scroll to the new folder.
813 bookmarksListView.setSelection(0);
817 public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
818 // Get the dialog from the dialog fragment.
819 Dialog dialog = dialogFragment.getDialog();
821 // Remove the incorrect lint warning below that the dialog might be null.
822 assert dialog != null;
824 // Get handles for the views from the dialog fragment.
825 EditText bookmarkNameEditText = dialog.findViewById(R.id.bookmark_name_edittext);
826 EditText bookmarkUrlEditText = dialog.findViewById(R.id.bookmark_url_edittext);
827 RadioButton currentIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton);
829 // Store the bookmark strings.
830 String bookmarkNameString = bookmarkNameEditText.getText().toString();
831 String bookmarkUrlString = bookmarkUrlEditText.getText().toString();
833 // Update the bookmark.
834 if (currentIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
835 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
836 } else { // Update the bookmark using the WebView favorite icon.
837 // Create a favorite icon byte array output stream.
838 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
840 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
841 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
843 // Convert the favorite icon byte array stream to a byte array.
844 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
846 // Update the bookmark and the favorite icon.
847 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
850 // Check to see if the contextual action mode has been created.
851 if (contextualActionMode != null) {
852 // Close the contextual action mode if it is open.
853 contextualActionMode.finish();
856 // Update the bookmarks cursor with the contents of the current folder.
857 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
859 // Update the `ListView`.
860 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
864 public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
865 // Get the dialog from the dialog fragment.
866 Dialog dialog = dialogFragment.getDialog();
868 // Remove the incorrect lint warning below that the dialog might be null.
869 assert dialog != null;
871 // Get handles for the views from the dialog fragment.
872 RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton);
873 RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
874 ImageView defaultFolderIconImageView = dialog.findViewById(R.id.default_icon_imageview);
875 EditText editFolderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
877 // Get the new folder name.
878 String newFolderNameString = editFolderNameEditText.getText().toString();
880 // Check if the favorite icon has changed.
881 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
882 // Update the name in the database.
883 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
884 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
885 // Create the new folder icon Bitmap.
886 Bitmap folderIconBitmap;
888 // Populate the new folder icon bitmap.
889 if (defaultFolderIconRadioButton.isChecked()) {
890 // Get the default folder icon drawable.
891 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
893 // Convert the folder icon drawable to a bitmap drawable.
894 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
896 // Convert the folder icon bitmap drawable to a bitmap.
897 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
898 } else { // Use the WebView favorite icon.
899 // Copy the favorite icon bitmap to the folder icon bitmap.
900 folderIconBitmap = favoriteIconBitmap;
903 // Create a folder icon byte array output stream.
904 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
906 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
907 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
909 // Convert the folder icon byte array stream to a byte array.
910 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
912 // Update the folder icon in the database.
913 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
914 } else { // The folder icon and the name have changed.
915 // Instantiate the new folder icon `Bitmap`.
916 Bitmap folderIconBitmap;
918 // Populate the new folder icon bitmap.
919 if (defaultFolderIconRadioButton.isChecked()) {
920 // Get the default folder icon drawable.
921 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
923 // Convert the folder icon drawable to a bitmap drawable.
924 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
926 // Convert the folder icon bitmap drawable to a bitmap.
927 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
928 } else { // Use the WebView favorite icon.
929 // Copy the favorite icon bitmap to the folder icon bitmap.
930 folderIconBitmap = favoriteIconBitmap;
933 // Create a folder icon byte array output stream.
934 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
936 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
937 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
939 // Convert the folder icon byte array stream to a byte array.
940 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
942 // Update the folder name and icon in the database.
943 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
946 // Update the bookmarks cursor with the current contents of this folder.
947 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
949 // Update the `ListView`.
950 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
952 // Close the contextual action mode.
953 contextualActionMode.finish();
957 public void onMoveToFolder(DialogFragment dialogFragment) {
958 // Get the dialog from the dialog fragment.
959 Dialog dialog = dialogFragment.getDialog();
961 // Remove the incorrect lint warning below that the dialog might be null.
962 assert dialog != null;
964 // Get a handle for the list view from the dialog.
965 ListView folderListView = dialog.findViewById(R.id.move_to_folder_listview);
967 // Store a long array of the selected folders.
968 long[] newFolderLongArray = folderListView.getCheckedItemIds();
970 // Get the new folder database ID. Only one folder will be selected.
971 int newFolderDatabaseId = (int) newFolderLongArray[0];
973 // Instantiate `newFolderName`.
974 String newFolderName;
976 // Set the new folder name.
977 if (newFolderDatabaseId == 0) {
978 // The new folder is the home folder, represented as `""` in the database.
981 // Get the new folder name from the database.
982 newFolderName = bookmarksDatabaseHelper.getFolderName(newFolderDatabaseId);
985 // Get a long array with the the database ID of the selected bookmarks.
986 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
988 // Move each of the selected bookmarks to the new folder.
989 for (long databaseIdLong : selectedBookmarksLongArray) {
990 // Get `databaseIdInt` for each selected bookmark.
991 int databaseIdInt = (int) databaseIdLong;
993 // Move the selected bookmark to the new folder.
994 bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderName);
997 // Update the bookmarks cursor with the current contents of this folder.
998 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
1000 // Update the `ListView`.
1001 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
1003 // Close the contextual app bar.
1004 contextualActionMode.finish();
1007 private void deleteBookmarkFolderContents(int databaseId) {
1008 // Get the name of the folder.
1009 String folderName = bookmarksDatabaseHelper.getFolderName(databaseId);
1011 // Get the contents of the folder.
1012 Cursor folderCursor = bookmarksDatabaseHelper.getBookmarkIDs(folderName);
1014 // Delete each of the bookmarks in the folder.
1015 for (int i = 0; i < folderCursor.getCount(); i++) {
1016 // Move `folderCursor` to the current row.
1017 folderCursor.moveToPosition(i);
1019 // Get the database ID of the item.
1020 int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndex(BookmarksDatabaseHelper._ID));
1022 // If this is a folder, recursively delete the contents first.
1023 if (bookmarksDatabaseHelper.isFolder(itemDatabaseId)) {
1024 deleteBookmarkFolderContents(itemDatabaseId);
1027 // Delete the bookmark.
1028 bookmarksDatabaseHelper.deleteBookmark(itemDatabaseId);
1032 private void updateMoveIcons() {
1033 // Get a long array of the selected bookmarks.
1034 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
1036 // Get the database IDs for the first, last, and selected bookmarks.
1037 int selectedBookmarkDatabaseId = (int) selectedBookmarksLongArray[0];
1038 int firstBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(0);
1039 // bookmarksListView is 0 indexed.
1040 int lastBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(bookmarksListView.getCount() - 1);
1042 // Get the current theme status.
1043 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
1045 // Update the move bookmark up `MenuItem`.
1046 if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) { // The selected bookmark is in the first position.
1047 // Disable the move bookmark up `MenuItem`.
1048 moveBookmarkUpMenuItem.setEnabled(false);
1050 // Set the move bookmark up icon to be ghosted.
1051 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_disabled);
1052 } else { // The selected bookmark is not in the first position.
1053 // Enable the move bookmark up menu item.
1054 moveBookmarkUpMenuItem.setEnabled(true);
1056 // Set the icon according to the theme.
1057 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
1058 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled_night);
1060 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled_day);
1064 // Update the move bookmark down `MenuItem`.
1065 if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) { // The selected bookmark is in the last position.
1066 // Disable the move bookmark down `MenuItem`.
1067 moveBookmarkDownMenuItem.setEnabled(false);
1069 // Set the move bookmark down icon to be ghosted.
1070 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_disabled);
1071 } else { // The selected bookmark is not in the last position.
1072 // Enable the move bookmark down `MenuItem`.
1073 moveBookmarkDownMenuItem.setEnabled(true);
1075 // Set the icon according to the theme.
1076 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
1077 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled_night);
1079 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled_day);
1084 private void scrollBookmarks(int selectedBookmarkPosition) {
1085 // Get the first and last visible bookmark positions.
1086 int firstVisibleBookmarkPosition = bookmarksListView.getFirstVisiblePosition();
1087 int lastVisibleBookmarkPosition = bookmarksListView.getLastVisiblePosition();
1089 // Calculate the number of bookmarks per screen.
1090 int numberOfBookmarksPerScreen = lastVisibleBookmarkPosition - firstVisibleBookmarkPosition;
1092 // Scroll with the moved bookmark if necessary.
1093 if (selectedBookmarkPosition <= firstVisibleBookmarkPosition) { // The selected bookmark position is at or above the top of the screen.
1094 // Scroll to the selected bookmark position.
1095 bookmarksListView.setSelection(selectedBookmarkPosition);
1096 } else if (selectedBookmarkPosition >= (lastVisibleBookmarkPosition - 1)) { // The selected bookmark is at or below the bottom of the screen.
1097 // 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.
1098 // `+1` assures that the entire bookmark will be displayed in situations where only a partial bookmark fits at the bottom of the list view.
1099 bookmarksListView.setSelection(selectedBookmarkPosition - numberOfBookmarksPerScreen + 1);
1103 private void loadFolder() {
1104 // Update bookmarks cursor with the contents of the bookmarks database for the current folder.
1105 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
1107 // Setup a `CursorAdapter`. `this` specifies the `Context`. `false` disables `autoRequery`.
1108 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
1110 public View newView(Context context, Cursor cursor, ViewGroup parent) {
1111 // Inflate the individual item layout. `false` does not attach it to the root.
1112 return getLayoutInflater().inflate(R.layout.bookmarks_activity_item_linearlayout, parent, false);
1116 public void bindView(View view, Context context, Cursor cursor) {
1117 // Get handles for the views.
1118 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
1119 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
1121 // Get the favorite icon byte array from the `Cursor`.
1122 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
1124 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
1125 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
1127 // Display the bitmap in `bookmarkFavoriteIcon`.
1128 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
1130 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
1131 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
1132 bookmarkNameTextView.setText(bookmarkNameString);
1134 // Make the font bold for folders.
1135 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
1136 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
1137 } else { // Reset the font to default for normal bookmarks.
1138 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
1143 // Populate the list view with the adapter.
1144 bookmarksListView.setAdapter(bookmarksCursorAdapter);
1146 // Set the `AppBar` title.
1147 if (currentFolder.isEmpty()) {
1148 appBar.setTitle(R.string.bookmarks);
1150 appBar.setTitle(currentFolder);
1155 public void onDestroy() {
1156 // Close the bookmarks cursor and database.
1157 bookmarksCursor.close();
1158 bookmarksDatabaseHelper.close();
1160 // Run the default commands.