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.activity.OnBackPressedCallback;
52 import androidx.annotation.NonNull;
53 import androidx.appcompat.app.ActionBar;
54 import androidx.appcompat.app.AppCompatActivity;
55 import androidx.appcompat.widget.Toolbar;
56 import androidx.fragment.app.DialogFragment;
58 import com.google.android.material.floatingactionbutton.FloatingActionButton;
59 import com.google.android.material.snackbar.Snackbar;
61 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
62 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
63 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
64 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
65 import com.stoutner.privacybrowser.dialogs.MoveToFolderDialog;
66 import com.stoutner.privacybrowser.R;
67 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
69 import java.io.ByteArrayOutputStream;
70 import java.util.ArrayList;
72 public class BookmarksActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, EditBookmarkDialog.EditBookmarkListener,
73 EditBookmarkFolderDialog.EditBookmarkFolderListener, MoveToFolderDialog.MoveToFolderListener {
75 // Declare the public static variables, which are accessed from the bookmarks database view activity.
76 public static String currentFolder;
77 public static boolean restartFromBookmarksDatabaseViewActivity;
79 // Define the saved instance state constants.
80 private final String CHECKED_BOOKMARKS_ARRAY_LIST = "checked_bookmarks_array_list";
81 private final String CURRENT_FOLDER = "current_folder";
83 // Define the class menu items.
84 private MenuItem moveBookmarkUpMenuItem;
85 private MenuItem moveBookmarkDownMenuItem;
87 // Declare the class variables.
88 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
89 private Snackbar bookmarksDeletedSnackbar;
90 private boolean closeActivityAfterDismissingSnackbar;
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 // The favorite icon byte array is populated in `onCreate()` and used in `onOptionsItemSelected()`.
113 private byte[] favoriteIconByteArray;
116 protected void onCreate(Bundle savedInstanceState) {
117 // Get a handle for the shared preferences.
118 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
120 // Get the preferences.
121 boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
122 boolean bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false);
124 // Disable screenshots if not allowed.
125 if (!allowScreenshots) {
126 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
129 // Run the default commands.
130 super.onCreate(savedInstanceState);
132 // Get the intent that launched the activity.
133 Intent launchingIntent = getIntent();
135 // Store the current URL and title.
136 String currentUrl = launchingIntent.getStringExtra("current_url");
137 String currentTitle = launchingIntent.getStringExtra("current_title");
139 // Set the current folder variable.
140 if (launchingIntent.getStringExtra("current_folder") != null) { // Set the current folder from the intent.
141 currentFolder = launchingIntent.getStringExtra("current_folder");
142 } else { // Set the current folder to be `""`, which is the home folder.
146 // Get the favorite icon byte array.
147 favoriteIconByteArray = launchingIntent.getByteArrayExtra("favorite_icon_byte_array");
149 // Remove the incorrect lint warning that the favorite icon byte array might be null.
150 assert favoriteIconByteArray != null;
152 // Convert the favorite icon byte array to a bitmap.
153 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
155 // Set the content according to the app bar position.
157 // Set the content view.
158 setContentView(R.layout.bookmarks_bottom_appbar);
160 // `Window.FEATURE_ACTION_MODE_OVERLAY` makes the contextual action mode cover the support action bar. It must be requested before the content is set.
161 supportRequestWindowFeature(Window.FEATURE_ACTION_MODE_OVERLAY);
163 // Set the content view.
164 setContentView(R.layout.bookmarks_top_appbar);
167 // Get a handle for the toolbar.
168 final Toolbar toolbar = findViewById(R.id.bookmarks_toolbar);
170 // Set the support action bar.
171 setSupportActionBar(toolbar);
173 // Get handles for the views.
174 appBar = getSupportActionBar();
175 bookmarksListView = findViewById(R.id.bookmarks_listview);
177 // Remove the incorrect lint warning that `appBar` might be null.
178 assert appBar != null;
180 // Display the home arrow on the app bar.
181 appBar.setDisplayHomeAsUpEnabled(true);
183 // Control what the system back command does.
184 OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) {
186 public void handleOnBackPressed() {
187 // Prepare to finish the activity.
192 // Register the on back pressed callback.
193 getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
195 // Initialize the database helper.
196 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this);
198 // Set a listener so that tapping a list item loads the URL or folder.
199 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
200 // Convert the id from long to int to match the format of the bookmarks database.
201 int databaseId = (int) id;
203 // Get the bookmark cursor for this ID.
204 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
206 // Move the cursor to the first entry.
207 bookmarkCursor.moveToFirst();
209 // Act upon the bookmark according to the type.
210 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
211 // Update the current folder.
212 currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME));
214 // Load the new folder.
216 } else { // The selected bookmark is not a folder.
217 // Instantiate the edit bookmark dialog.
218 DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, favoriteIconBitmap);
221 editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark));
225 bookmarkCursor.close();
228 // Handle long presses on the list view.
229 bookmarksListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
230 // Instantiate the common variables.
231 MenuItem editBookmarkMenuItem;
232 MenuItem deleteBookmarksMenuItem;
233 MenuItem selectAllBookmarksMenuItem;
234 boolean deletingBookmarks;
237 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
238 // Inflate the menu for the contextual app bar.
239 getMenuInflater().inflate(R.menu.bookmarks_context_menu, menu);
242 if (currentFolder.isEmpty()) { // Use `R.string.bookmarks` if in the home folder.
243 mode.setTitle(R.string.bookmarks);
244 } else { // Use the current folder name as the title.
245 mode.setTitle(currentFolder);
248 // Get handles for menu items that need to be selectively disabled.
249 moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up);
250 moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down);
251 editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark);
252 deleteBookmarksMenuItem = menu.findItem(R.id.delete_bookmark);
253 selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks);
255 // Disable the delete bookmarks menu item if a delete is pending.
256 deleteBookmarksMenuItem.setEnabled(!deletingBookmarks);
258 // Store a handle for the contextual action mode so it can be closed programatically.
259 contextualActionMode = mode;
266 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
267 // Get a handle for the move to folder menu item.
268 MenuItem moveToFolderMenuItem = menu.findItem(R.id.move_to_folder);
270 // Get a Cursor with all of the folders.
271 Cursor folderCursor = bookmarksDatabaseHelper.getAllFolders();
273 // Enable the move to folder menu item if at least one folder exists.
274 moveToFolderMenuItem.setVisible(folderCursor.getCount() > 0);
281 public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
282 // Get the number of selected bookmarks.
283 int numberOfSelectedBookmarks = bookmarksListView.getCheckedItemCount();
285 // Only process commands if at least one bookmark is selected. Otherwise, a context menu with 0 selected bookmarks is briefly displayed.
286 if (numberOfSelectedBookmarks > 0) {
287 // Adjust the ActionMode and the menu according to the number of selected bookmarks.
288 if (numberOfSelectedBookmarks == 1) { // One bookmark is selected.
289 // List the number of selected bookmarks in the subtitle.
290 mode.setSubtitle(getString(R.string.selected) + " 1");
292 // Show the `Move Up`, `Move Down`, and `Edit` options.
293 moveBookmarkUpMenuItem.setVisible(true);
294 moveBookmarkDownMenuItem.setVisible(true);
295 editBookmarkMenuItem.setVisible(true);
297 // Update the enabled status of the move icons.
299 } else { // More than one bookmark is selected.
300 // List the number of selected bookmarks in the subtitle.
301 mode.setSubtitle(getString(R.string.selected) + " " + numberOfSelectedBookmarks);
303 // Hide non-applicable `MenuItems`.
304 moveBookmarkUpMenuItem.setVisible(false);
305 moveBookmarkDownMenuItem.setVisible(false);
306 editBookmarkMenuItem.setVisible(false);
309 // Show the select all menu item if all the bookmarks are not selected.
310 selectAllBookmarksMenuItem.setVisible(bookmarksListView.getCheckedItemCount() != bookmarksListView.getCount());
315 public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
316 // Declare the common variables.
317 int selectedBookmarkNewPosition;
318 final SparseBooleanArray selectedBookmarksPositionsSparseBooleanArray;
320 // Initialize the selected bookmark position.
321 int selectedBookmarkPosition = 0;
323 // Get the menu item ID.
324 int menuItemId = menuItem.getItemId();
326 // Run the commands according to the selected action item.
327 if (menuItemId == R.id.move_bookmark_up) { // Move the bookmark up.
328 // Get the array of checked bookmark positions.
329 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
331 // 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`.
332 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
333 // Check to see if the value for the bookmark is true, meaning it is currently selected.
334 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
335 // Only one bookmark should have a value of `true` when move bookmark up is enabled.
336 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
340 // Calculate the new position of the selected bookmark.
341 selectedBookmarkNewPosition = selectedBookmarkPosition - 1;
343 // Iterate through the bookmarks.
344 for (int i = 0; i < bookmarksListView.getCount(); i++) {
345 // Get the database ID for the current bookmark.
346 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
348 // Update the display order for the current bookmark.
349 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
350 // Move the current bookmark up one.
351 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1);
352 } else if ((i + 1) == selectedBookmarkPosition) { // The current bookmark is immediately above the selected bookmark.
353 // Move the current bookmark down one.
354 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1);
355 } else { // The current bookmark is not changing positions.
356 // Move `bookmarksCursor` to the current bookmark position.
357 bookmarksCursor.moveToPosition(i);
359 // Update the display order only if it is not correct in the database.
360 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
361 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
366 // Update the bookmarks cursor with the current contents of the bookmarks database.
367 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
369 // Update the list view.
370 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
372 // Scroll with the bookmark.
373 scrollBookmarks(selectedBookmarkNewPosition);
375 // Update the enabled status of the move icons.
377 } else if (menuItemId == R.id.move_bookmark_down) { // Move the bookmark down.
378 // Get the array of checked bookmark positions.
379 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
381 // 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`.
382 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
383 // Check to see if the value for the bookmark is true, meaning it is currently selected.
384 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
385 // Only one bookmark should have a value of `true` when move bookmark down is enabled.
386 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
390 // Calculate the new position of the selected bookmark.
391 selectedBookmarkNewPosition = selectedBookmarkPosition + 1;
393 // Iterate through the bookmarks.
394 for (int i = 0; i < bookmarksListView.getCount(); i++) {
395 // Get the database ID for the current bookmark.
396 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
398 // Update the display order for the current bookmark.
399 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
400 // Move the current bookmark down one.
401 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1);
402 } else if ((i - 1) == selectedBookmarkPosition) { // The current bookmark is immediately below the selected bookmark.
403 // Move the bookmark below the selected bookmark up one.
404 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1);
405 } else { // The current bookmark is not changing positions.
406 // Move `bookmarksCursor` to the current bookmark position.
407 bookmarksCursor.moveToPosition(i);
409 // Update the display order only if it is not correct in the database.
410 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
411 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
416 // Update the bookmarks cursor with the current contents of the bookmarks database.
417 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
419 // Update the list view.
420 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
422 // Scroll with the bookmark.
423 scrollBookmarks(selectedBookmarkNewPosition);
425 // Update the enabled status of the move icons.
427 } else if (menuItemId == R.id.move_to_folder) { // Move to folder.
428 // Instantiate the move to folder alert dialog.
429 DialogFragment moveToFolderDialog = MoveToFolderDialog.moveBookmarks(currentFolder, bookmarksListView.getCheckedItemIds());
431 // Show the move to folder alert dialog.
432 moveToFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.move_to_folder));
433 } else if (menuItemId == R.id.edit_bookmark) {
434 // Get the array of checked bookmark positions.
435 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
437 // 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`.
438 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
439 // Check to see if the value for the bookmark is true, meaning it is currently selected.
440 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
441 // Only one bookmark should have a value of `true` when move edit bookmark is enabled.
442 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
446 // Move the cursor to the selected position.
447 bookmarksCursor.moveToPosition(selectedBookmarkPosition);
449 // Find out if this bookmark is a folder.
450 boolean isFolder = (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1);
452 // Get the selected bookmark database ID.
453 int databaseId = bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID));
455 // Show the edit bookmark or edit bookmark folder dialog.
457 // Save the current folder name, which is used in `onSaveBookmarkFolder()`.
458 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME));
460 // Instantiate the edit bookmark folder dialog.
461 DialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, favoriteIconBitmap);
464 editFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_folder));
466 // Instantiate the edit bookmark dialog.
467 DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, favoriteIconBitmap);
470 editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark));
472 } else if (menuItemId == R.id.delete_bookmark) { // Delete bookmark.
473 // Set the deleting bookmarks flag, which prevents the delete menu item from being enabled until the current process finishes.
474 deletingBookmarks = true;
476 // Get an array of the selected row IDs.
477 final long[] selectedBookmarksIdsLongArray = bookmarksListView.getCheckedItemIds();
479 // Initialize a variable to count the number of bookmarks to delete.
480 int numberOfBookmarksToDelete = 0;
482 // Count the number of bookmarks.
483 for (long databaseIdLong : selectedBookmarksIdsLongArray) {
484 // Convert the database ID long to an int.
485 int databaseIdInt = (int) databaseIdLong;
487 // Count the contents of the folder if the selected bookmark is a folder.
488 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
489 // Add the bookmarks from the folder to the running total.
490 numberOfBookmarksToDelete = numberOfBookmarksToDelete + countBookmarkFolderContents(databaseIdInt);
493 // Increment the count of the number of bookmarks to delete.
494 numberOfBookmarksToDelete++;
497 // 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.
498 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions().clone();
500 // Update the bookmarks cursor with the current contents of the bookmarks database except for the specified database IDs.
501 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(selectedBookmarksIdsLongArray, currentFolder);
503 // Update the list view.
504 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
506 // Create a Snackbar with the number of deleted bookmarks.
507 bookmarksDeletedSnackbar = Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.bookmarks_deleted) + " " + numberOfBookmarksToDelete,
508 Snackbar.LENGTH_LONG)
509 .setAction(R.string.undo, view -> {
510 // Do nothing because everything will be handled by `onDismissed()` below.
512 .addCallback(new Snackbar.Callback() {
513 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
515 public void onDismissed(Snackbar snackbar, int event) {
516 if (event == Snackbar.Callback.DISMISS_EVENT_ACTION) { // The user pushed the undo button.
517 // Update the bookmarks cursor with the current contents of the bookmarks database, including the "deleted" bookmarks.
518 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
520 // Update the list view.
521 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
523 // Re-select the previously selected bookmarks.
524 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
525 bookmarksListView.setItemChecked(selectedBookmarksPositionsSparseBooleanArray.keyAt(i), true);
527 } else { // The snackbar was dismissed without the undo button being pushed.
528 // Delete each selected bookmark.
529 for (long databaseIdLong : selectedBookmarksIdsLongArray) {
530 // Convert `databaseIdLong` to an int.
531 int databaseIdInt = (int) databaseIdLong;
533 // Delete the contents of the folder if the selected bookmark is a folder.
534 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
535 deleteBookmarkFolderContents(databaseIdInt);
538 // Delete the selected bookmark.
539 bookmarksDatabaseHelper.deleteBookmark(databaseIdInt);
542 // Update the display order.
543 for (int i = 0; i < bookmarksListView.getCount(); i++) {
544 // Get the database ID for the current bookmark.
545 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
547 // Move `bookmarksCursor` to the current bookmark position.
548 bookmarksCursor.moveToPosition(i);
550 // Update the display order only if it is not correct in the database.
551 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
552 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
557 // Reset the deleting bookmarks flag.
558 deletingBookmarks = false;
560 // Enable the delete bookmarks menu item.
561 deleteBookmarksMenuItem.setEnabled(true);
563 // Close the activity if back has been pressed.
564 if (closeActivityAfterDismissingSnackbar) {
571 bookmarksDeletedSnackbar.show();
572 } else if (menuItemId == R.id.context_menu_select_all_bookmarks) { // Select all.
573 // Get the total number of bookmarks.
574 int numberOfBookmarks = bookmarksListView.getCount();
577 for (int i = 0; i < numberOfBookmarks; i++) {
578 bookmarksListView.setItemChecked(i, true);
582 // Consume the click.
587 public void onDestroyActionMode(ActionMode mode) {
592 // Get handles for the floating action buttons.
593 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
594 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
596 // Set the create new bookmark folder FAB to display the `AlertDialog`.
597 createBookmarkFolderFab.setOnClickListener(v -> {
598 // Create a create bookmark folder dialog.
599 DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(favoriteIconBitmap);
601 // Show the create bookmark folder dialog.
602 createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
605 // Set the create new bookmark FAB to display the alert dialog.
606 createBookmarkFab.setOnClickListener(view -> {
607 // Remove the incorrect lint warning below.
608 assert currentUrl != null;
609 assert currentTitle != null;
611 // Instantiate the create bookmark dialog.
612 DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentUrl, currentTitle, favoriteIconBitmap);
614 // Display the create bookmark dialog.
615 createBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_bookmark));
618 // Restore the state if the app has been restarted.
619 if (savedInstanceState != null) {
620 // Restore the current folder.
621 currentFolder = savedInstanceState.getString(CURRENT_FOLDER);
623 // Update the bookmarks list view after it has loaded.
624 bookmarksListView.post(() -> {
625 // Get the checked bookmarks array list.
626 ArrayList<Integer> checkedBookmarksArrayList = savedInstanceState.getIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST);
628 // Check each previously checked bookmark in the list view. When the minimum API >= 24 a `forEach()` command can be used instead.
629 if (checkedBookmarksArrayList != null) {
630 for (int i = 0; i < checkedBookmarksArrayList.size(); i++) {
631 bookmarksListView.setItemChecked(checkedBookmarksArrayList.get(i), true);
637 // Load the home folder.
642 public void onRestart() {
643 // Run the default commands.
646 // Update the list view if returning from the bookmarks database view activity.
647 if (restartFromBookmarksDatabaseViewActivity) {
648 // Load the current folder in the list view.
651 // Reset `restartFromBookmarksDatabaseViewActivity`.
652 restartFromBookmarksDatabaseViewActivity = false;
657 public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
658 // Run the default commands.
659 super.onSaveInstanceState(savedInstanceState);
661 // Get the array of the checked items.
662 SparseBooleanArray checkedBookmarksSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
664 // Create a checked items array list.
665 ArrayList<Integer> checkedBookmarksArrayList = new ArrayList<>();
667 // Add each checked bookmark position to the array list.
668 for (int i = 0; i < checkedBookmarksSparseBooleanArray.size(); i++) {
669 // 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.
670 if (checkedBookmarksSparseBooleanArray.valueAt(i)) {
671 // Add the bookmark position to the checked bookmarks array list.
672 checkedBookmarksArrayList.add(checkedBookmarksSparseBooleanArray.keyAt(i));
676 // Store the variables in the saved instance state.
677 savedInstanceState.putString(CURRENT_FOLDER, currentFolder);
678 savedInstanceState.putIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST, checkedBookmarksArrayList);
682 public boolean onCreateOptionsMenu(Menu menu) {
684 getMenuInflater().inflate(R.menu.bookmarks_options_menu, menu);
691 public boolean onOptionsItemSelected(MenuItem menuItem) {
692 // Get a handle for the menu item ID.
693 int menuItemId = menuItem.getItemId();
695 // Run the command according to the selected option.
696 if (menuItemId == android.R.id.home) { // Home. The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
697 if (currentFolder.isEmpty()) { // Currently in the home folder.
698 // Prepare to finish the activity.
700 } else { // Currently in a subfolder.
701 // Place the former parent folder in `currentFolder`.
702 currentFolder = bookmarksDatabaseHelper.getParentFolderName(currentFolder);
704 // Load the new folder.
707 } else if (menuItemId == R.id.options_menu_select_all_bookmarks) { // Select all.
708 // Get the total number of bookmarks.
709 int numberOfBookmarks = bookmarksListView.getCount();
712 for (int i = 0; i < numberOfBookmarks; i++) {
713 bookmarksListView.setItemChecked(i, true);
715 } else if (menuItemId == R.id.bookmarks_database_view) {
716 // Close the contextual action bar if it is displayed. This can happen if the bottom app bar is enabled.
717 if (contextualActionMode != null) {
718 contextualActionMode.finish();
721 // Create an intent to launch the bookmarks database view activity.
722 Intent bookmarksDatabaseViewIntent = new Intent(this, BookmarksDatabaseViewActivity.class);
724 // Include the favorite icon byte array to the intent.
725 bookmarksDatabaseViewIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
728 startActivity(bookmarksDatabaseViewIntent);
734 public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
735 // Get the alert dialog from the fragment.
736 Dialog dialog = dialogFragment.getDialog();
738 // Remove the incorrect lint warning below that the dialog might be null.
739 assert dialog != null;
741 // Get the views from the dialog fragment.
742 EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext);
743 EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext);
745 // Extract the strings from the edit texts.
746 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
747 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
749 // Create a favorite icon byte array output stream.
750 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
752 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
753 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
755 // Convert the favorite icon byte array stream to a byte array.
756 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
758 // Display the new bookmark below the current items in the (0 indexed) list.
759 int newBookmarkDisplayOrder = bookmarksListView.getCount();
761 // Create the bookmark.
762 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
764 // Update the bookmarks cursor with the current contents of this folder.
765 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
767 // Update the `ListView`.
768 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
770 // Scroll to the new bookmark.
771 bookmarksListView.setSelection(newBookmarkDisplayOrder);
775 public void onCreateBookmarkFolder(DialogFragment dialogFragment, @NonNull Bitmap favoriteIconBitmap) {
776 // Get the dialog from the dialog fragment.
777 Dialog dialog = dialogFragment.getDialog();
779 // Remove the incorrect lint warning below that the dialog might be null.
780 assert dialog != null;
782 // Get handles for the views in the dialog fragment.
783 EditText folderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
784 RadioButton defaultIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
785 ImageView defaultIconImageView = dialog.findViewById(R.id.default_icon_imageview);
787 // Get new folder name string.
788 String folderNameString = folderNameEditText.getText().toString();
790 // Create a folder icon bitmap.
791 Bitmap folderIconBitmap;
793 // Set the folder icon bitmap according to the dialog.
794 if (defaultIconRadioButton.isChecked()) { // Use the default folder icon.
795 // Get the default folder icon drawable.
796 Drawable folderIconDrawable = defaultIconImageView.getDrawable();
798 // Convert the folder icon drawable to a bitmap drawable.
799 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
801 // Convert the folder icon bitmap drawable to a bitmap.
802 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
803 } else { // Use the WebView favorite icon.
804 // Copy the favorite icon bitmap to the folder icon bitmap.
805 folderIconBitmap = favoriteIconBitmap;
808 // Create a folder icon byte array output stream.
809 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
811 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
812 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
814 // Convert the folder icon byte array stream to a byte array.
815 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
817 // Move all the bookmarks down one in the display order.
818 for (int i = 0; i < bookmarksListView.getCount(); i++) {
819 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
820 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
823 // Create the folder, which will be placed at the top of the `ListView`.
824 bookmarksDatabaseHelper.createFolder(folderNameString, currentFolder, folderIconByteArray);
826 // Update the bookmarks cursor with the current contents of this folder.
827 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
829 // Update the list view.
830 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
832 // Scroll to the new folder.
833 bookmarksListView.setSelection(0);
837 public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
838 // Get the dialog from the dialog fragment.
839 Dialog dialog = dialogFragment.getDialog();
841 // Remove the incorrect lint warning below that the dialog might be null.
842 assert dialog != null;
844 // Get handles for the views from the dialog fragment.
845 EditText bookmarkNameEditText = dialog.findViewById(R.id.bookmark_name_edittext);
846 EditText bookmarkUrlEditText = dialog.findViewById(R.id.bookmark_url_edittext);
847 RadioButton currentIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton);
849 // Store the bookmark strings.
850 String bookmarkNameString = bookmarkNameEditText.getText().toString();
851 String bookmarkUrlString = bookmarkUrlEditText.getText().toString();
853 // Update the bookmark.
854 if (currentIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
855 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
856 } else { // Update the bookmark using the WebView favorite icon.
857 // Create a favorite icon byte array output stream.
858 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
860 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
861 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
863 // Convert the favorite icon byte array stream to a byte array.
864 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
866 // Update the bookmark and the favorite icon.
867 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
870 // Check to see if the contextual action mode has been created.
871 if (contextualActionMode != null) {
872 // Close the contextual action mode if it is open.
873 contextualActionMode.finish();
876 // Update the bookmarks cursor with the contents of the current folder.
877 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
879 // Update the `ListView`.
880 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
884 public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
885 // Get the dialog from the dialog fragment.
886 Dialog dialog = dialogFragment.getDialog();
888 // Remove the incorrect lint warning below that the dialog might be null.
889 assert dialog != null;
891 // Get handles for the views from the dialog fragment.
892 RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton);
893 RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
894 ImageView defaultFolderIconImageView = dialog.findViewById(R.id.default_icon_imageview);
895 EditText editFolderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
897 // Get the new folder name.
898 String newFolderNameString = editFolderNameEditText.getText().toString();
900 // Check if the favorite icon has changed.
901 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
902 // Update the name in the database.
903 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
904 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
905 // Create the new folder icon Bitmap.
906 Bitmap folderIconBitmap;
908 // Populate the new folder icon bitmap.
909 if (defaultFolderIconRadioButton.isChecked()) {
910 // Get the default folder icon drawable.
911 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
913 // Convert the folder icon drawable to a bitmap drawable.
914 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
916 // Convert the folder icon bitmap drawable to a bitmap.
917 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
918 } else { // Use the WebView favorite icon.
919 // Copy the favorite icon bitmap to the folder icon bitmap.
920 folderIconBitmap = favoriteIconBitmap;
923 // Create a folder icon byte array output stream.
924 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
926 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
927 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
929 // Convert the folder icon byte array stream to a byte array.
930 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
932 // Update the folder icon in the database.
933 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
934 } else { // The folder icon and the name have changed.
935 // Instantiate the new folder icon `Bitmap`.
936 Bitmap folderIconBitmap;
938 // Populate the new folder icon bitmap.
939 if (defaultFolderIconRadioButton.isChecked()) {
940 // Get the default folder icon drawable.
941 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
943 // Convert the folder icon drawable to a bitmap drawable.
944 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
946 // Convert the folder icon bitmap drawable to a bitmap.
947 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
948 } else { // Use the WebView favorite icon.
949 // Copy the favorite icon bitmap to the folder icon bitmap.
950 folderIconBitmap = favoriteIconBitmap;
953 // Create a folder icon byte array output stream.
954 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
956 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
957 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
959 // Convert the folder icon byte array stream to a byte array.
960 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
962 // Update the folder name and icon in the database.
963 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
966 // Update the bookmarks cursor with the current contents of this folder.
967 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
969 // Update the `ListView`.
970 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
972 // Close the contextual action mode.
973 contextualActionMode.finish();
977 public void onMoveToFolder(DialogFragment dialogFragment) {
978 // Get the dialog from the dialog fragment.
979 Dialog dialog = dialogFragment.getDialog();
981 // Remove the incorrect lint warning below that the dialog might be null.
982 assert dialog != null;
984 // Get a handle for the list view from the dialog.
985 ListView folderListView = dialog.findViewById(R.id.move_to_folder_listview);
987 // Store a long array of the selected folders.
988 long[] newFolderLongArray = folderListView.getCheckedItemIds();
990 // Get the new folder database ID. Only one folder will be selected.
991 int newFolderDatabaseId = (int) newFolderLongArray[0];
993 // Instantiate `newFolderName`.
994 String newFolderName;
996 // Set the new folder name.
997 if (newFolderDatabaseId == 0) {
998 // The new folder is the home folder, represented as `""` in the database.
1001 // Get the new folder name from the database.
1002 newFolderName = bookmarksDatabaseHelper.getFolderName(newFolderDatabaseId);
1005 // Get a long array with the the database ID of the selected bookmarks.
1006 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
1008 // Move each of the selected bookmarks to the new folder.
1009 for (long databaseIdLong : selectedBookmarksLongArray) {
1010 // Get `databaseIdInt` for each selected bookmark.
1011 int databaseIdInt = (int) databaseIdLong;
1013 // Move the selected bookmark to the new folder.
1014 bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderName);
1017 // Update the bookmarks cursor with the current contents of this folder.
1018 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
1020 // Update the `ListView`.
1021 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
1023 // Close the contextual app bar.
1024 contextualActionMode.finish();
1027 private int countBookmarkFolderContents(int databaseId) {
1028 // Initialize the bookmark counter.
1029 int bookmarkCounter = 0;
1031 // Get the name of the folder.
1032 String folderName = bookmarksDatabaseHelper.getFolderName(databaseId);
1034 // Get the contents of the folder.
1035 Cursor folderCursor = bookmarksDatabaseHelper.getBookmarkIds(folderName);
1037 // Count each of the bookmarks in the folder.
1038 for (int i = 0; i < folderCursor.getCount(); i++) {
1039 // Move the folder cursor to the current row.
1040 folderCursor.moveToPosition(i);
1042 // Get the database ID of the item.
1043 int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID));
1045 // If this is a folder, recursively count the contents first.
1046 if (bookmarksDatabaseHelper.isFolder(itemDatabaseId)) {
1047 // Add the bookmarks from the folder to the running total.
1048 bookmarkCounter = bookmarkCounter + countBookmarkFolderContents(itemDatabaseId);
1051 // Add the bookmark to the running total.
1055 // Return the bookmark counter.
1056 return bookmarkCounter;
1059 private void deleteBookmarkFolderContents(int databaseId) {
1060 // Get the name of the folder.
1061 String folderName = bookmarksDatabaseHelper.getFolderName(databaseId);
1063 // Get the contents of the folder.
1064 Cursor folderCursor = bookmarksDatabaseHelper.getBookmarkIds(folderName);
1066 // Delete each of the bookmarks in the folder.
1067 for (int i = 0; i < folderCursor.getCount(); i++) {
1068 // Move the folder cursor to the current row.
1069 folderCursor.moveToPosition(i);
1071 // Get the database ID of the item.
1072 int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.ID));
1074 // If this is a folder, recursively delete the contents first.
1075 if (bookmarksDatabaseHelper.isFolder(itemDatabaseId)) {
1076 deleteBookmarkFolderContents(itemDatabaseId);
1079 // Delete the bookmark.
1080 bookmarksDatabaseHelper.deleteBookmark(itemDatabaseId);
1084 private void prepareFinish() {
1085 // 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.
1086 if ((bookmarksDeletedSnackbar != null) && bookmarksDeletedSnackbar.isShown()) { // Close the bookmarks deleted snackbar before going home.
1087 // Set the close flag.
1088 closeActivityAfterDismissingSnackbar = true;
1090 // Dismiss the snackbar.
1091 bookmarksDeletedSnackbar.dismiss();
1092 } else { // Go home immediately.
1093 // Update the bookmarks folder for the bookmarks drawer in the main WebView activity.
1094 MainWebViewActivity.currentBookmarksFolder = currentFolder;
1096 // Close the bookmarks drawer and reload the bookmarks ListView when returning to the main WebView activity.
1097 MainWebViewActivity.restartFromBookmarksActivity = true;
1099 // Exit the bookmarks activity.
1104 private void updateMoveIcons() {
1105 // Get a long array of the selected bookmarks.
1106 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
1108 // Get the database IDs for the first, last, and selected bookmarks.
1109 int selectedBookmarkDatabaseId = (int) selectedBookmarksLongArray[0];
1110 int firstBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(0);
1111 // bookmarksListView is 0 indexed.
1112 int lastBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(bookmarksListView.getCount() - 1);
1114 // Update the move bookmark up menu item.
1115 if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) { // The selected bookmark is in the first position.
1116 // Disable the move bookmark up menu item.
1117 moveBookmarkUpMenuItem.setEnabled(false);
1120 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_disabled);
1121 } else { // The selected bookmark is not in the first position.
1122 // Enable the move bookmark up menu item.
1123 moveBookmarkUpMenuItem.setEnabled(true);
1125 // Set the icon according to the theme.
1126 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled);
1129 // Update the move bookmark down menu item.
1130 if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) { // The selected bookmark is in the last position.
1131 // Disable the move bookmark down menu item.
1132 moveBookmarkDownMenuItem.setEnabled(false);
1135 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_disabled);
1136 } else { // The selected bookmark is not in the last position.
1137 // Enable the move bookmark down menu item.
1138 moveBookmarkDownMenuItem.setEnabled(true);
1141 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled);
1145 private void scrollBookmarks(int selectedBookmarkPosition) {
1146 // Get the first and last visible bookmark positions.
1147 int firstVisibleBookmarkPosition = bookmarksListView.getFirstVisiblePosition();
1148 int lastVisibleBookmarkPosition = bookmarksListView.getLastVisiblePosition();
1150 // Calculate the number of bookmarks per screen.
1151 int numberOfBookmarksPerScreen = lastVisibleBookmarkPosition - firstVisibleBookmarkPosition;
1153 // Scroll with the moved bookmark if necessary.
1154 if (selectedBookmarkPosition <= firstVisibleBookmarkPosition) { // The selected bookmark position is at or above the top of the screen.
1155 // Scroll to the selected bookmark position.
1156 bookmarksListView.setSelection(selectedBookmarkPosition);
1157 } else if (selectedBookmarkPosition >= (lastVisibleBookmarkPosition - 1)) { // The selected bookmark is at or below the bottom of the screen.
1158 // 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.
1159 // `+1` assures that the entire bookmark will be displayed in situations where only a partial bookmark fits at the bottom of the list view.
1160 bookmarksListView.setSelection(selectedBookmarkPosition - numberOfBookmarksPerScreen + 1);
1164 private void loadFolder() {
1165 // Update bookmarks cursor with the contents of the bookmarks database for the current folder.
1166 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
1168 // Setup a `CursorAdapter`. `this` specifies the `Context`. `false` disables `autoRequery`.
1169 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
1171 public View newView(Context context, Cursor cursor, ViewGroup parent) {
1172 // Inflate the individual item layout. `false` does not attach it to the root.
1173 return getLayoutInflater().inflate(R.layout.bookmarks_activity_item_linearlayout, parent, false);
1177 public void bindView(View view, Context context, Cursor cursor) {
1178 // Get handles for the views.
1179 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
1180 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
1182 // Get the favorite icon byte array from the `Cursor`.
1183 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON));
1185 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
1186 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
1188 // Display the bitmap in `bookmarkFavoriteIcon`.
1189 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
1191 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
1192 String bookmarkNameString = cursor.getString(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME));
1193 bookmarkNameTextView.setText(bookmarkNameString);
1195 // Make the font bold for folders.
1196 if (cursor.getInt(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
1197 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
1198 } else { // Reset the font to default for normal bookmarks.
1199 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
1204 // Populate the list view with the adapter.
1205 bookmarksListView.setAdapter(bookmarksCursorAdapter);
1207 // Set the `AppBar` title.
1208 if (currentFolder.isEmpty()) {
1209 appBar.setTitle(R.string.bookmarks);
1211 appBar.setTitle(currentFolder);
1216 public void onDestroy() {
1217 // Close the bookmarks cursor and database.
1218 bookmarksCursor.close();
1219 bookmarksDatabaseHelper.close();
1221 // Run the default commands.