2 * Copyright © 2016-2022 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.app.Dialog;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.SharedPreferences;
27 import android.database.Cursor;
28 import android.graphics.Bitmap;
29 import android.graphics.BitmapFactory;
30 import android.graphics.Typeface;
31 import android.graphics.drawable.BitmapDrawable;
32 import android.graphics.drawable.Drawable;
33 import android.os.Bundle;
34 import android.preference.PreferenceManager;
35 import android.util.SparseBooleanArray;
36 import android.view.ActionMode;
37 import android.view.Menu;
38 import android.view.MenuItem;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.view.Window;
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 // Declare the public static variables, which are accessed from the bookmarks database view activity.
75 public static String currentFolder;
76 public static boolean restartFromBookmarksDatabaseViewActivity;
78 // Define the saved instance state constants.
79 private final String CHECKED_BOOKMARKS_ARRAY_LIST = "checked_bookmarks_array_list";
80 private final String CURRENT_FOLDER = "current_folder";
82 // Define the class menu items.
83 private MenuItem moveBookmarkUpMenuItem;
84 private MenuItem moveBookmarkDownMenuItem;
86 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`,
87 // `onMoveToFolder()`, `deleteBookmarkFolderContents()`, `loadFolder()`, and `onDestroy()`.
88 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
90 // `bookmarksListView` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`,
91 // `updateMoveIcons()`, `scrollBookmarks()`, and `loadFolder()`.
92 private ListView bookmarksListView;
94 // `bookmarksCursor` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, `deleteBookmarkFolderContents()`,
95 // `loadFolder()`, and `onDestroy()`.
96 private Cursor bookmarksCursor;
98 // `bookmarksCursorAdapter` is used in `onCreate(), `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, and `onLoadFolder()`.
99 private CursorAdapter bookmarksCursorAdapter;
101 // `contextualActionMode` is used in `onCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()` and `onMoveToFolder()`.
102 private ActionMode contextualActionMode;
104 // `appBar` is used in `onCreate()` and `loadFolder()`.
105 private ActionBar appBar;
107 // `oldFolderName` is used in `onCreate()` and `onSaveBookmarkFolder()`.
108 private String oldFolderNameString;
110 // `bookmarksDeletedSnackbar` is used in `onCreate()`, `onOptionsItemSelected()`, and `onBackPressed()`.
111 private Snackbar bookmarksDeletedSnackbar;
113 // `closeActivityAfterDismissingSnackbar` is used in `onCreate()`, `onOptionsItemSelected()`, and `onBackPressed()`.
114 private boolean closeActivityAfterDismissingSnackbar;
116 // The favorite icon byte array is populated in `onCreate()` and used in `onOptionsItemSelected()`.
117 private byte[] favoriteIconByteArray;
120 protected void onCreate(Bundle savedInstanceState) {
121 // Get a handle for the shared preferences.
122 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
124 // Get the preferences.
125 boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
126 boolean bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false);
128 // Disable screenshots if not allowed.
129 if (!allowScreenshots) {
130 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
133 // Run the default commands.
134 super.onCreate(savedInstanceState);
136 // Get the intent that launched the activity.
137 Intent launchingIntent = getIntent();
139 // Store the current URL and title.
140 String currentUrl = launchingIntent.getStringExtra("current_url");
141 String currentTitle = launchingIntent.getStringExtra("current_title");
143 // Set the current folder variable.
144 if (launchingIntent.getStringExtra("current_folder") != null) { // Set the current folder from the intent.
145 currentFolder = launchingIntent.getStringExtra("current_folder");
146 } else { // Set the current folder to be `""`, which is the home folder.
150 // Get the favorite icon byte array.
151 favoriteIconByteArray = launchingIntent.getByteArrayExtra("favorite_icon_byte_array");
153 // Remove the incorrect lint warning that the favorite icon byte array might be null.
154 assert favoriteIconByteArray != null;
156 // Convert the favorite icon byte array to a bitmap.
157 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
159 // Set the content according to the app bar position.
161 // Set the content view.
162 setContentView(R.layout.bookmarks_bottom_appbar);
164 // `Window.FEATURE_ACTION_MODE_OVERLAY` makes the contextual action mode cover the support action bar. It must be requested before the content is set.
165 supportRequestWindowFeature(Window.FEATURE_ACTION_MODE_OVERLAY);
167 // Set the content view.
168 setContentView(R.layout.bookmarks_top_appbar);
171 // Get a handle for the toolbar.
172 final Toolbar toolbar = findViewById(R.id.bookmarks_toolbar);
174 // Set the support action bar.
175 setSupportActionBar(toolbar);
177 // Get handles for the views.
178 appBar = getSupportActionBar();
179 bookmarksListView = findViewById(R.id.bookmarks_listview);
181 // Remove the incorrect lint warning that `appBar` might be null.
182 assert appBar != null;
184 // Display the home arrow on the app bar.
185 appBar.setDisplayHomeAsUpEnabled(true);
187 // Initialize the database helper.
188 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this);
190 // Set a listener so that tapping a list item loads the URL or folder.
191 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
192 // Convert the id from long to int to match the format of the bookmarks database.
193 int databaseId = (int) id;
195 // Get the bookmark cursor for this ID.
196 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
198 // Move the cursor to the first entry.
199 bookmarkCursor.moveToFirst();
201 // Act upon the bookmark according to the type.
202 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
203 // Update the current folder.
204 currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME));
206 // Load the new folder.
208 } else { // The selected bookmark is not a folder.
209 // Instantiate the edit bookmark dialog.
210 DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, favoriteIconBitmap);
213 editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark));
217 bookmarkCursor.close();
220 // Handle long presses on the list view.
221 bookmarksListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
222 // Instantiate the common variables.
223 MenuItem editBookmarkMenuItem;
224 MenuItem deleteBookmarksMenuItem;
225 MenuItem selectAllBookmarksMenuItem;
226 boolean deletingBookmarks;
229 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
230 // Inflate the menu for the contextual app bar.
231 getMenuInflater().inflate(R.menu.bookmarks_context_menu, menu);
234 if (currentFolder.isEmpty()) { // Use `R.string.bookmarks` if in the home folder.
235 mode.setTitle(R.string.bookmarks);
236 } else { // Use the current folder name as the title.
237 mode.setTitle(currentFolder);
240 // Get handles for menu items that need to be selectively disabled.
241 moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up);
242 moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down);
243 editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark);
244 deleteBookmarksMenuItem = menu.findItem(R.id.delete_bookmark);
245 selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks);
247 // Disable the delete bookmarks menu item if a delete is pending.
248 deleteBookmarksMenuItem.setEnabled(!deletingBookmarks);
250 // Store a handle for the contextual action mode so it can be closed programatically.
251 contextualActionMode = mode;
258 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
259 // Get a handle for the move to folder menu item.
260 MenuItem moveToFolderMenuItem = menu.findItem(R.id.move_to_folder);
262 // Get a Cursor with all of the folders.
263 Cursor folderCursor = bookmarksDatabaseHelper.getAllFolders();
265 // Enable the move to folder menu item if at least one folder exists.
266 moveToFolderMenuItem.setVisible(folderCursor.getCount() > 0);
273 public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
274 // Get the number of selected bookmarks.
275 int numberOfSelectedBookmarks = bookmarksListView.getCheckedItemCount();
277 // Only process commands if at least one bookmark is selected. Otherwise, a context menu with 0 selected bookmarks is briefly displayed.
278 if (numberOfSelectedBookmarks > 0) {
279 // Adjust the ActionMode and the menu according to the number of selected bookmarks.
280 if (numberOfSelectedBookmarks == 1) { // One bookmark is selected.
281 // List the number of selected bookmarks in the subtitle.
282 mode.setSubtitle(getString(R.string.selected) + " 1");
284 // Show the `Move Up`, `Move Down`, and `Edit` options.
285 moveBookmarkUpMenuItem.setVisible(true);
286 moveBookmarkDownMenuItem.setVisible(true);
287 editBookmarkMenuItem.setVisible(true);
289 // Update the enabled status of the move icons.
291 } else { // More than one bookmark is selected.
292 // List the number of selected bookmarks in the subtitle.
293 mode.setSubtitle(getString(R.string.selected) + " " + numberOfSelectedBookmarks);
295 // Hide non-applicable `MenuItems`.
296 moveBookmarkUpMenuItem.setVisible(false);
297 moveBookmarkDownMenuItem.setVisible(false);
298 editBookmarkMenuItem.setVisible(false);
301 // Show the select all menu item if all the bookmarks are not selected.
302 selectAllBookmarksMenuItem.setVisible(bookmarksListView.getCheckedItemCount() != bookmarksListView.getCount());
307 public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
308 // Declare the common variables.
309 int selectedBookmarkNewPosition;
310 final SparseBooleanArray selectedBookmarksPositionsSparseBooleanArray;
312 // Initialize the selected bookmark position.
313 int selectedBookmarkPosition = 0;
315 // Get the menu item ID.
316 int menuItemId = menuItem.getItemId();
318 // Run the commands according to the selected action item.
319 if (menuItemId == R.id.move_bookmark_up) { // Move the bookmark up.
320 // Get the array of checked bookmark positions.
321 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
323 // 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`.
324 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
325 // Check to see if the value for the bookmark is true, meaning it is currently selected.
326 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
327 // Only one bookmark should have a value of `true` when move bookmark up is enabled.
328 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
332 // Calculate the new position of the selected bookmark.
333 selectedBookmarkNewPosition = selectedBookmarkPosition - 1;
335 // Iterate through the bookmarks.
336 for (int i = 0; i < bookmarksListView.getCount(); i++) {
337 // Get the database ID for the current bookmark.
338 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
340 // Update the display order for the current bookmark.
341 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
342 // Move the current bookmark up one.
343 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1);
344 } else if ((i + 1) == selectedBookmarkPosition) { // The current bookmark is immediately above the selected bookmark.
345 // Move the current bookmark down one.
346 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1);
347 } else { // The current bookmark is not changing positions.
348 // Move `bookmarksCursor` to the current bookmark position.
349 bookmarksCursor.moveToPosition(i);
351 // Update the display order only if it is not correct in the database.
352 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
353 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
358 // Update the bookmarks cursor with the current contents of the bookmarks database.
359 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
361 // Update the list view.
362 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
364 // Scroll with the bookmark.
365 scrollBookmarks(selectedBookmarkNewPosition);
367 // Update the enabled status of the move icons.
369 } else if (menuItemId == R.id.move_bookmark_down) { // Move the bookmark down.
370 // Get the array of checked bookmark positions.
371 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
373 // 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`.
374 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
375 // Check to see if the value for the bookmark is true, meaning it is currently selected.
376 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
377 // Only one bookmark should have a value of `true` when move bookmark down is enabled.
378 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
382 // Calculate the new position of the selected bookmark.
383 selectedBookmarkNewPosition = selectedBookmarkPosition + 1;
385 // Iterate through the bookmarks.
386 for (int i = 0; i < bookmarksListView.getCount(); i++) {
387 // Get the database ID for the current bookmark.
388 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
390 // Update the display order for the current bookmark.
391 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
392 // Move the current bookmark down one.
393 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1);
394 } else if ((i - 1) == selectedBookmarkPosition) { // The current bookmark is immediately below the selected bookmark.
395 // Move the bookmark below the selected bookmark up one.
396 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1);
397 } else { // The current bookmark is not changing positions.
398 // Move `bookmarksCursor` to the current bookmark position.
399 bookmarksCursor.moveToPosition(i);
401 // Update the display order only if it is not correct in the database.
402 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
403 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
408 // Update the bookmarks cursor with the current contents of the bookmarks database.
409 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
411 // Update the list view.
412 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
414 // Scroll with the bookmark.
415 scrollBookmarks(selectedBookmarkNewPosition);
417 // Update the enabled status of the move icons.
419 } else if (menuItemId == R.id.move_to_folder) { // Move to folder.
420 // Instantiate the move to folder alert dialog.
421 DialogFragment moveToFolderDialog = MoveToFolderDialog.moveBookmarks(currentFolder, bookmarksListView.getCheckedItemIds());
423 // Show the move to folder alert dialog.
424 moveToFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.move_to_folder));
425 } else if (menuItemId == R.id.edit_bookmark) {
426 // Get the array of checked bookmark positions.
427 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
429 // 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`.
430 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
431 // Check to see if the value for the bookmark is true, meaning it is currently selected.
432 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
433 // Only one bookmark should have a value of `true` when move edit bookmark is enabled.
434 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
438 // Move the cursor to the selected position.
439 bookmarksCursor.moveToPosition(selectedBookmarkPosition);
441 // Find out if this bookmark is a folder.
442 boolean isFolder = (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1);
444 // Get the selected bookmark database ID.
445 int databaseId = bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID));
447 // Show the edit bookmark or edit bookmark folder dialog.
449 // Save the current folder name, which is used in `onSaveBookmarkFolder()`.
450 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME));
452 // Instantiate the edit bookmark folder dialog.
453 DialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, favoriteIconBitmap);
456 editFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_folder));
458 // Instantiate the edit bookmark dialog.
459 DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, favoriteIconBitmap);
462 editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark));
464 } else if (menuItemId == R.id.delete_bookmark) { // Delete bookmark.
465 // Set the deleting bookmarks flag, which prevents the delete menu item from being enabled until the current process finishes.
466 deletingBookmarks = true;
468 // Get an array of the selected row IDs.
469 final long[] selectedBookmarksIdsLongArray = bookmarksListView.getCheckedItemIds();
471 // Initialize a variable to count the number of bookmarks to delete.
472 int numberOfBookmarksToDelete = 0;
474 // Count the number of bookmarks.
475 for (long databaseIdLong : selectedBookmarksIdsLongArray) {
476 // Convert the database ID long to an int.
477 int databaseIdInt = (int) databaseIdLong;
479 // Count the contents of the folder if the selected bookmark is a folder.
480 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
481 // Add the bookmarks from the folder to the running total.
482 numberOfBookmarksToDelete = numberOfBookmarksToDelete + countBookmarkFolderContents(databaseIdInt);
485 // Increment the count of the number of bookmarks to delete.
486 numberOfBookmarksToDelete++;
489 // 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.
490 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions().clone();
492 // Update the bookmarks cursor with the current contents of the bookmarks database except for the specified database IDs.
493 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(selectedBookmarksIdsLongArray, currentFolder);
495 // Update the list view.
496 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
498 // Create a Snackbar with the number of deleted bookmarks.
499 bookmarksDeletedSnackbar = Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.bookmarks_deleted) + " " + numberOfBookmarksToDelete,
500 Snackbar.LENGTH_LONG)
501 .setAction(R.string.undo, view -> {
502 // Do nothing because everything will be handled by `onDismissed()` below.
504 .addCallback(new Snackbar.Callback() {
505 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
507 public void onDismissed(Snackbar snackbar, int event) {
508 if (event == Snackbar.Callback.DISMISS_EVENT_ACTION) { // The user pushed the undo button.
509 // Update the bookmarks cursor with the current contents of the bookmarks database, including the "deleted" bookmarks.
510 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
512 // Update the list view.
513 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
515 // Re-select the previously selected bookmarks.
516 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
517 bookmarksListView.setItemChecked(selectedBookmarksPositionsSparseBooleanArray.keyAt(i), true);
519 } else { // The snackbar was dismissed without the undo button being pushed.
520 // Delete each selected bookmark.
521 for (long databaseIdLong : selectedBookmarksIdsLongArray) {
522 // Convert `databaseIdLong` to an int.
523 int databaseIdInt = (int) databaseIdLong;
525 // Delete the contents of the folder if the selected bookmark is a folder.
526 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
527 deleteBookmarkFolderContents(databaseIdInt);
530 // Delete the selected bookmark.
531 bookmarksDatabaseHelper.deleteBookmark(databaseIdInt);
534 // Update the display order.
535 for (int i = 0; i < bookmarksListView.getCount(); i++) {
536 // Get the database ID for the current bookmark.
537 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
539 // Move `bookmarksCursor` to the current bookmark position.
540 bookmarksCursor.moveToPosition(i);
542 // Update the display order only if it is not correct in the database.
543 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
544 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
549 // Reset the deleting bookmarks flag.
550 deletingBookmarks = false;
552 // Enable the delete bookmarks menu item.
553 deleteBookmarksMenuItem.setEnabled(true);
555 // Close the activity if back has been pressed.
556 if (closeActivityAfterDismissingSnackbar) {
563 bookmarksDeletedSnackbar.show();
564 } else if (menuItemId == R.id.context_menu_select_all_bookmarks) { // Select all.
565 // Get the total number of bookmarks.
566 int numberOfBookmarks = bookmarksListView.getCount();
569 for (int i = 0; i < numberOfBookmarks; i++) {
570 bookmarksListView.setItemChecked(i, true);
574 // Consume the click.
579 public void onDestroyActionMode(ActionMode mode) {
584 // Get handles for the floating action buttons.
585 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
586 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
588 // Set the create new bookmark folder FAB to display the `AlertDialog`.
589 createBookmarkFolderFab.setOnClickListener(v -> {
590 // Create a create bookmark folder dialog.
591 DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(favoriteIconBitmap);
593 // Show the create bookmark folder dialog.
594 createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
597 // Set the create new bookmark FAB to display the alert dialog.
598 createBookmarkFab.setOnClickListener(view -> {
599 // Remove the incorrect lint warning below.
600 assert currentUrl != null;
601 assert currentTitle != null;
603 // Instantiate the create bookmark dialog.
604 DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentUrl, currentTitle, favoriteIconBitmap);
606 // Display the create bookmark dialog.
607 createBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_bookmark));
610 // Restore the state if the app has been restarted.
611 if (savedInstanceState != null) {
612 // Restore the current folder.
613 currentFolder = savedInstanceState.getString(CURRENT_FOLDER);
615 // Update the bookmarks list view after it has loaded.
616 bookmarksListView.post(() -> {
617 // Get the checked bookmarks array list.
618 ArrayList<Integer> checkedBookmarksArrayList = savedInstanceState.getIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST);
620 // Check each previously checked bookmark in the list view. When the minimum API >= 24 a `forEach()` command can be used instead.
621 if (checkedBookmarksArrayList != null) {
622 for (int i = 0; i < checkedBookmarksArrayList.size(); i++) {
623 bookmarksListView.setItemChecked(checkedBookmarksArrayList.get(i), true);
629 // Load the home folder.
634 public void onRestart() {
635 // Run the default commands.
638 // Update the list view if returning from the bookmarks database view activity.
639 if (restartFromBookmarksDatabaseViewActivity) {
640 // Load the current folder in the list view.
643 // Reset `restartFromBookmarksDatabaseViewActivity`.
644 restartFromBookmarksDatabaseViewActivity = false;
649 public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
650 // Run the default commands.
651 super.onSaveInstanceState(savedInstanceState);
653 // Get the array of the checked items.
654 SparseBooleanArray checkedBookmarksSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
656 // Create a checked items array list.
657 ArrayList<Integer> checkedBookmarksArrayList = new ArrayList<>();
659 // Add each checked bookmark position to the array list.
660 for (int i = 0; i < checkedBookmarksSparseBooleanArray.size(); i++) {
661 // 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.
662 if (checkedBookmarksSparseBooleanArray.valueAt(i)) {
663 // Add the bookmark position to the checked bookmarks array list.
664 checkedBookmarksArrayList.add(checkedBookmarksSparseBooleanArray.keyAt(i));
668 // Store the variables in the saved instance state.
669 savedInstanceState.putString(CURRENT_FOLDER, currentFolder);
670 savedInstanceState.putIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST, checkedBookmarksArrayList);
674 public boolean onCreateOptionsMenu(Menu menu) {
676 getMenuInflater().inflate(R.menu.bookmarks_options_menu, menu);
683 public boolean onOptionsItemSelected(MenuItem menuItem) {
684 // Get a handle for the menu item ID.
685 int menuItemId = menuItem.getItemId();
687 // Run the command according to the selected option.
688 if (menuItemId == android.R.id.home) { // Home. The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
689 if (currentFolder.isEmpty()) { // Currently in the home folder.
690 // Run the back commands.
692 } else { // Currently in a subfolder.
693 // Place the former parent folder in `currentFolder`.
694 currentFolder = bookmarksDatabaseHelper.getParentFolderName(currentFolder);
696 // Load the new folder.
699 } else if (menuItemId == R.id.options_menu_select_all_bookmarks) { // Select all.
700 // Get the total number of bookmarks.
701 int numberOfBookmarks = bookmarksListView.getCount();
704 for (int i = 0; i < numberOfBookmarks; i++) {
705 bookmarksListView.setItemChecked(i, true);
707 } else if (menuItemId == R.id.bookmarks_database_view) {
708 // Close the contextual action bar if it is displayed. This can happen if the bottom app bar is enabled.
709 if (contextualActionMode != null) {
710 contextualActionMode.finish();
713 // Create an intent to launch the bookmarks database view activity.
714 Intent bookmarksDatabaseViewIntent = new Intent(this, BookmarksDatabaseViewActivity.class);
716 // Include the favorite icon byte array to the intent.
717 bookmarksDatabaseViewIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
720 startActivity(bookmarksDatabaseViewIntent);
726 public void onBackPressed() {
727 // 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.
728 if ((bookmarksDeletedSnackbar != null) && bookmarksDeletedSnackbar.isShown()) { // Close the bookmarks deleted snackbar before going home.
729 // Set the close flag.
730 closeActivityAfterDismissingSnackbar = true;
732 // Dismiss the snackbar.
733 bookmarksDeletedSnackbar.dismiss();
734 } else { // Go home immediately.
735 // Update the bookmarks folder for the bookmarks drawer in the main WebView activity.
736 MainWebViewActivity.currentBookmarksFolder = currentFolder;
738 // Close the bookmarks drawer and reload the bookmarks ListView when returning to the main WebView activity.
739 MainWebViewActivity.restartFromBookmarksActivity = true;
741 // Exit the bookmarks activity.
742 super.onBackPressed();
747 public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
748 // Get the alert dialog from the fragment.
749 Dialog dialog = dialogFragment.getDialog();
751 // Remove the incorrect lint warning below that the dialog might be null.
752 assert dialog != null;
754 // Get the views from the dialog fragment.
755 EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext);
756 EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext);
758 // Extract the strings from the edit texts.
759 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
760 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
762 // Create a favorite icon byte array output stream.
763 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
765 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
766 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
768 // Convert the favorite icon byte array stream to a byte array.
769 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
771 // Display the new bookmark below the current items in the (0 indexed) list.
772 int newBookmarkDisplayOrder = bookmarksListView.getCount();
774 // Create the bookmark.
775 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
777 // Update the bookmarks cursor with the current contents of this folder.
778 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
780 // Update the `ListView`.
781 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
783 // Scroll to the new bookmark.
784 bookmarksListView.setSelection(newBookmarkDisplayOrder);
788 public void onCreateBookmarkFolder(DialogFragment dialogFragment, @NonNull Bitmap favoriteIconBitmap) {
789 // Get the dialog from the dialog fragment.
790 Dialog dialog = dialogFragment.getDialog();
792 // Remove the incorrect lint warning below that the dialog might be null.
793 assert dialog != null;
795 // Get handles for the views in the dialog fragment.
796 EditText folderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
797 RadioButton defaultIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
798 ImageView defaultIconImageView = dialog.findViewById(R.id.default_icon_imageview);
800 // Get new folder name string.
801 String folderNameString = folderNameEditText.getText().toString();
803 // Create a folder icon bitmap.
804 Bitmap folderIconBitmap;
806 // Set the folder icon bitmap according to the dialog.
807 if (defaultIconRadioButton.isChecked()) { // Use the default folder icon.
808 // Get the default folder icon drawable.
809 Drawable folderIconDrawable = defaultIconImageView.getDrawable();
811 // Convert the folder icon drawable to a bitmap drawable.
812 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
814 // Convert the folder icon bitmap drawable to a bitmap.
815 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
816 } else { // Use the WebView favorite icon.
817 // Copy the favorite icon bitmap to the folder icon bitmap.
818 folderIconBitmap = favoriteIconBitmap;
821 // Create a folder icon byte array output stream.
822 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
824 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
825 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
827 // Convert the folder icon byte array stream to a byte array.
828 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
830 // Move all the bookmarks down one in the display order.
831 for (int i = 0; i < bookmarksListView.getCount(); i++) {
832 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
833 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
836 // Create the folder, which will be placed at the top of the `ListView`.
837 bookmarksDatabaseHelper.createFolder(folderNameString, currentFolder, folderIconByteArray);
839 // Update the bookmarks cursor with the current contents of this folder.
840 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
842 // Update the list view.
843 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
845 // Scroll to the new folder.
846 bookmarksListView.setSelection(0);
850 public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
851 // Get the dialog from the dialog fragment.
852 Dialog dialog = dialogFragment.getDialog();
854 // Remove the incorrect lint warning below that the dialog might be null.
855 assert dialog != null;
857 // Get handles for the views from the dialog fragment.
858 EditText bookmarkNameEditText = dialog.findViewById(R.id.bookmark_name_edittext);
859 EditText bookmarkUrlEditText = dialog.findViewById(R.id.bookmark_url_edittext);
860 RadioButton currentIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton);
862 // Store the bookmark strings.
863 String bookmarkNameString = bookmarkNameEditText.getText().toString();
864 String bookmarkUrlString = bookmarkUrlEditText.getText().toString();
866 // Update the bookmark.
867 if (currentIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
868 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
869 } else { // Update the bookmark using the WebView favorite icon.
870 // Create a favorite icon byte array output stream.
871 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
873 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
874 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
876 // Convert the favorite icon byte array stream to a byte array.
877 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
879 // Update the bookmark and the favorite icon.
880 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
883 // Check to see if the contextual action mode has been created.
884 if (contextualActionMode != null) {
885 // Close the contextual action mode if it is open.
886 contextualActionMode.finish();
889 // Update the bookmarks cursor with the contents of the current folder.
890 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
892 // Update the `ListView`.
893 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
897 public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
898 // Get the dialog from the dialog fragment.
899 Dialog dialog = dialogFragment.getDialog();
901 // Remove the incorrect lint warning below that the dialog might be null.
902 assert dialog != null;
904 // Get handles for the views from the dialog fragment.
905 RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton);
906 RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
907 ImageView defaultFolderIconImageView = dialog.findViewById(R.id.default_icon_imageview);
908 EditText editFolderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
910 // Get the new folder name.
911 String newFolderNameString = editFolderNameEditText.getText().toString();
913 // Check if the favorite icon has changed.
914 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
915 // Update the name in the database.
916 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
917 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
918 // Create the new folder icon Bitmap.
919 Bitmap folderIconBitmap;
921 // Populate the new folder icon bitmap.
922 if (defaultFolderIconRadioButton.isChecked()) {
923 // Get the default folder icon drawable.
924 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
926 // Convert the folder icon drawable to a bitmap drawable.
927 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
929 // Convert the folder icon bitmap drawable to a bitmap.
930 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
931 } else { // Use the WebView favorite icon.
932 // Copy the favorite icon bitmap to the folder icon bitmap.
933 folderIconBitmap = favoriteIconBitmap;
936 // Create a folder icon byte array output stream.
937 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
939 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
940 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
942 // Convert the folder icon byte array stream to a byte array.
943 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
945 // Update the folder icon in the database.
946 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
947 } else { // The folder icon and the name have changed.
948 // Instantiate the new folder icon `Bitmap`.
949 Bitmap folderIconBitmap;
951 // Populate the new folder icon bitmap.
952 if (defaultFolderIconRadioButton.isChecked()) {
953 // Get the default folder icon drawable.
954 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
956 // Convert the folder icon drawable to a bitmap drawable.
957 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
959 // Convert the folder icon bitmap drawable to a bitmap.
960 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
961 } else { // Use the WebView favorite icon.
962 // Copy the favorite icon bitmap to the folder icon bitmap.
963 folderIconBitmap = favoriteIconBitmap;
966 // Create a folder icon byte array output stream.
967 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
969 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
970 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
972 // Convert the folder icon byte array stream to a byte array.
973 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
975 // Update the folder name and icon in the database.
976 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
979 // Update the bookmarks cursor with the current contents of this folder.
980 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
982 // Update the `ListView`.
983 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
985 // Close the contextual action mode.
986 contextualActionMode.finish();
990 public void onMoveToFolder(DialogFragment dialogFragment) {
991 // Get the dialog from the dialog fragment.
992 Dialog dialog = dialogFragment.getDialog();
994 // Remove the incorrect lint warning below that the dialog might be null.
995 assert dialog != null;
997 // Get a handle for the list view from the dialog.
998 ListView folderListView = dialog.findViewById(R.id.move_to_folder_listview);
1000 // Store a long array of the selected folders.
1001 long[] newFolderLongArray = folderListView.getCheckedItemIds();
1003 // Get the new folder database ID. Only one folder will be selected.
1004 int newFolderDatabaseId = (int) newFolderLongArray[0];
1006 // Instantiate `newFolderName`.
1007 String newFolderName;
1009 // Set the new folder name.
1010 if (newFolderDatabaseId == 0) {
1011 // The new folder is the home folder, represented as `""` in the database.
1014 // Get the new folder name from the database.
1015 newFolderName = bookmarksDatabaseHelper.getFolderName(newFolderDatabaseId);
1018 // Get a long array with the the database ID of the selected bookmarks.
1019 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
1021 // Move each of the selected bookmarks to the new folder.
1022 for (long databaseIdLong : selectedBookmarksLongArray) {
1023 // Get `databaseIdInt` for each selected bookmark.
1024 int databaseIdInt = (int) databaseIdLong;
1026 // Move the selected bookmark to the new folder.
1027 bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderName);
1030 // Update the bookmarks cursor with the current contents of this folder.
1031 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
1033 // Update the `ListView`.
1034 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
1036 // Close the contextual app bar.
1037 contextualActionMode.finish();
1040 private int countBookmarkFolderContents(int databaseId) {
1041 // Initialize the bookmark counter.
1042 int bookmarkCounter = 0;
1044 // Get the name of the folder.
1045 String folderName = bookmarksDatabaseHelper.getFolderName(databaseId);
1047 // Get the contents of the folder.
1048 Cursor folderCursor = bookmarksDatabaseHelper.getBookmarkIds(folderName);
1050 // Count each of the bookmarks in the folder.
1051 for (int i = 0; i < folderCursor.getCount(); i++) {
1052 // Move the folder cursor to the current row.
1053 folderCursor.moveToPosition(i);
1055 // Get the database ID of the item.
1056 int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID));
1058 // If this is a folder, recursively count the contents first.
1059 if (bookmarksDatabaseHelper.isFolder(itemDatabaseId)) {
1060 // Add the bookmarks from the folder to the running total.
1061 bookmarkCounter = bookmarkCounter + countBookmarkFolderContents(itemDatabaseId);
1064 // Add the bookmark to the running total.
1068 // Return the bookmark counter.
1069 return bookmarkCounter;
1072 private void deleteBookmarkFolderContents(int databaseId) {
1073 // Get the name of the folder.
1074 String folderName = bookmarksDatabaseHelper.getFolderName(databaseId);
1076 // Get the contents of the folder.
1077 Cursor folderCursor = bookmarksDatabaseHelper.getBookmarkIds(folderName);
1079 // Delete each of the bookmarks in the folder.
1080 for (int i = 0; i < folderCursor.getCount(); i++) {
1081 // Move the folder cursor to the current row.
1082 folderCursor.moveToPosition(i);
1084 // Get the database ID of the item.
1085 int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID));
1087 // If this is a folder, recursively delete the contents first.
1088 if (bookmarksDatabaseHelper.isFolder(itemDatabaseId)) {
1089 deleteBookmarkFolderContents(itemDatabaseId);
1092 // Delete the bookmark.
1093 bookmarksDatabaseHelper.deleteBookmark(itemDatabaseId);
1097 private void updateMoveIcons() {
1098 // Get a long array of the selected bookmarks.
1099 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
1101 // Get the database IDs for the first, last, and selected bookmarks.
1102 int selectedBookmarkDatabaseId = (int) selectedBookmarksLongArray[0];
1103 int firstBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(0);
1104 // bookmarksListView is 0 indexed.
1105 int lastBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(bookmarksListView.getCount() - 1);
1107 // Update the move bookmark up menu item.
1108 if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) { // The selected bookmark is in the first position.
1109 // Disable the move bookmark up menu item.
1110 moveBookmarkUpMenuItem.setEnabled(false);
1113 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_disabled);
1114 } else { // The selected bookmark is not in the first position.
1115 // Enable the move bookmark up menu item.
1116 moveBookmarkUpMenuItem.setEnabled(true);
1118 // Set the icon according to the theme.
1119 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled);
1122 // Update the move bookmark down menu item.
1123 if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) { // The selected bookmark is in the last position.
1124 // Disable the move bookmark down menu item.
1125 moveBookmarkDownMenuItem.setEnabled(false);
1128 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_disabled);
1129 } else { // The selected bookmark is not in the last position.
1130 // Enable the move bookmark down menu item.
1131 moveBookmarkDownMenuItem.setEnabled(true);
1134 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled);
1138 private void scrollBookmarks(int selectedBookmarkPosition) {
1139 // Get the first and last visible bookmark positions.
1140 int firstVisibleBookmarkPosition = bookmarksListView.getFirstVisiblePosition();
1141 int lastVisibleBookmarkPosition = bookmarksListView.getLastVisiblePosition();
1143 // Calculate the number of bookmarks per screen.
1144 int numberOfBookmarksPerScreen = lastVisibleBookmarkPosition - firstVisibleBookmarkPosition;
1146 // Scroll with the moved bookmark if necessary.
1147 if (selectedBookmarkPosition <= firstVisibleBookmarkPosition) { // The selected bookmark position is at or above the top of the screen.
1148 // Scroll to the selected bookmark position.
1149 bookmarksListView.setSelection(selectedBookmarkPosition);
1150 } else if (selectedBookmarkPosition >= (lastVisibleBookmarkPosition - 1)) { // The selected bookmark is at or below the bottom of the screen.
1151 // 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.
1152 // `+1` assures that the entire bookmark will be displayed in situations where only a partial bookmark fits at the bottom of the list view.
1153 bookmarksListView.setSelection(selectedBookmarkPosition - numberOfBookmarksPerScreen + 1);
1157 private void loadFolder() {
1158 // Update bookmarks cursor with the contents of the bookmarks database for the current folder.
1159 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
1161 // Setup a `CursorAdapter`. `this` specifies the `Context`. `false` disables `autoRequery`.
1162 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
1164 public View newView(Context context, Cursor cursor, ViewGroup parent) {
1165 // Inflate the individual item layout. `false` does not attach it to the root.
1166 return getLayoutInflater().inflate(R.layout.bookmarks_activity_item_linearlayout, parent, false);
1170 public void bindView(View view, Context context, Cursor cursor) {
1171 // Get handles for the views.
1172 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
1173 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
1175 // Get the favorite icon byte array from the `Cursor`.
1176 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON));
1178 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
1179 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
1181 // Display the bitmap in `bookmarkFavoriteIcon`.
1182 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
1184 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
1185 String bookmarkNameString = cursor.getString(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME));
1186 bookmarkNameTextView.setText(bookmarkNameString);
1188 // Make the font bold for folders.
1189 if (cursor.getInt(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
1190 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
1191 } else { // Reset the font to default for normal bookmarks.
1192 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
1197 // Populate the list view with the adapter.
1198 bookmarksListView.setAdapter(bookmarksCursorAdapter);
1200 // Set the `AppBar` title.
1201 if (currentFolder.isEmpty()) {
1202 appBar.setTitle(R.string.bookmarks);
1204 appBar.setTitle(currentFolder);
1209 public void onDestroy() {
1210 // Close the bookmarks cursor and database.
1211 bookmarksCursor.close();
1212 bookmarksDatabaseHelper.close();
1214 // Run the default commands.