2 * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
6 * Privacy Browser is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser.activities;
22 import android.annotation.SuppressLint;
23 import android.app.Dialog;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.SharedPreferences;
27 import android.content.res.Configuration;
28 import android.database.Cursor;
29 import android.graphics.Bitmap;
30 import android.graphics.BitmapFactory;
31 import android.graphics.Typeface;
32 import android.graphics.drawable.BitmapDrawable;
33 import android.graphics.drawable.Drawable;
34 import android.os.Bundle;
35 import android.preference.PreferenceManager;
36 import android.util.SparseBooleanArray;
37 import android.view.ActionMode;
38 import android.view.Menu;
39 import android.view.MenuItem;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.view.WindowManager;
43 import android.widget.AbsListView;
44 import android.widget.CursorAdapter;
45 import android.widget.EditText;
46 import android.widget.ImageView;
47 import android.widget.ListView;
48 import android.widget.RadioButton;
49 import android.widget.TextView;
51 import androidx.annotation.NonNull;
52 import androidx.appcompat.app.ActionBar;
53 import androidx.appcompat.app.AppCompatActivity;
54 import androidx.appcompat.widget.Toolbar;
55 import androidx.fragment.app.DialogFragment;
57 import com.google.android.material.floatingactionbutton.FloatingActionButton;
58 import com.google.android.material.snackbar.Snackbar;
60 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
61 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
62 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
63 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
64 import com.stoutner.privacybrowser.dialogs.MoveToFolderDialog;
65 import com.stoutner.privacybrowser.R;
66 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
68 import java.io.ByteArrayOutputStream;
69 import java.util.ArrayList;
71 public class BookmarksActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, EditBookmarkDialog.EditBookmarkListener,
72 EditBookmarkFolderDialog.EditBookmarkFolderListener, MoveToFolderDialog.MoveToFolderListener {
74 // `currentFolder` is public static so it can be accessed from `MoveToFolderDialog` and `BookmarksDatabaseViewActivity`.
75 // It is used in `onCreate`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`,
76 // and `loadFolder()`.
77 public static String currentFolder;
79 // `checkedItemIds` is public static so it can be accessed from `MoveToFolderDialog`. It is also used in `onCreate()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`,
80 // and `updateMoveIcons()`.
81 public static long[] checkedItemIds;
83 // `restartFromBookmarksDatabaseViewActivity` is public static so it can be accessed from `BookmarksDatabaseViewActivity`. It is also used in `onRestart()`.
84 public static boolean restartFromBookmarksDatabaseViewActivity;
87 // Define the saved instance state constants.
88 private final String CHECKED_BOOKMARKS_ARRAY_LIST = "checked_bookmarks_array_list";
90 // Define the class menu items.
91 private MenuItem moveBookmarkUpMenuItem;
92 private MenuItem moveBookmarkDownMenuItem;
94 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`,
95 // `onMoveToFolder()`, `deleteBookmarkFolderContents()`, `loadFolder()`, and `onDestroy()`.
96 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
98 // `bookmarksListView` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`,
99 // `updateMoveIcons()`, `scrollBookmarks()`, and `loadFolder()`.
100 private ListView bookmarksListView;
102 // `bookmarksCursor` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, `deleteBookmarkFolderContents()`,
103 // `loadFolder()`, and `onDestroy()`.
104 private Cursor bookmarksCursor;
106 // `bookmarksCursorAdapter` is used in `onCreate(), `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, and `onLoadFolder()`.
107 private CursorAdapter bookmarksCursorAdapter;
109 // `contextualActionMode` is used in `onCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()` and `onMoveToFolder()`.
110 private ActionMode contextualActionMode;
112 // `appBar` is used in `onCreate()` and `loadFolder()`.
113 private ActionBar appBar;
115 // `oldFolderName` is used in `onCreate()` and `onSaveBookmarkFolder()`.
116 private String oldFolderNameString;
118 // `bookmarksDeletedSnackbar` is used in `onCreate()`, `onOptionsItemSelected()`, and `onBackPressed()`.
119 private Snackbar bookmarksDeletedSnackbar;
121 // `closeActivityAfterDismissingSnackbar` is used in `onCreate()`, `onOptionsItemSelected()`, and `onBackPressed()`.
122 private boolean closeActivityAfterDismissingSnackbar;
124 // The favorite icon byte array is populated in `onCreate()` and used in `onOptionsItemSelected()`.
125 private byte[] favoriteIconByteArray;
128 protected void onCreate(Bundle savedInstanceState) {
129 // Get a handle for the shared preferences.
130 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
132 // Get the screenshot preference.
133 boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
135 // Disable screenshots if not allowed.
136 if (!allowScreenshots) {
137 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
141 setTheme(R.style.PrivacyBrowser);
143 // Run the default commands.
144 super.onCreate(savedInstanceState);
146 // Get the intent that launched the activity.
147 Intent launchingIntent = getIntent();
149 // Store the current URL and title.
150 String currentUrl = launchingIntent.getStringExtra("current_url");
151 String currentTitle = launchingIntent.getStringExtra("current_title");
153 // Set the current folder variable.
154 if (launchingIntent.getStringExtra("current_folder") != null) { // Set the current folder from the intent.
155 currentFolder = launchingIntent.getStringExtra("current_folder");
156 } else { // Set the current folder to be `""`, which is the home folder.
160 // Get the favorite icon byte array.
161 favoriteIconByteArray = launchingIntent.getByteArrayExtra("favorite_icon_byte_array");
163 // Remove the incorrect lint warning that the favorite icon byte array might be null.
164 assert favoriteIconByteArray != null;
166 // Convert the favorite icon byte array to a bitmap.
167 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
169 // Set the content view.
170 setContentView(R.layout.bookmarks_coordinatorlayout);
172 // The AndroidX toolbar must be used until the minimum API is >= 21.
173 final Toolbar toolbar = findViewById(R.id.bookmarks_toolbar);
174 setSupportActionBar(toolbar);
176 // Get handles for the views.
177 appBar = getSupportActionBar();
178 bookmarksListView = findViewById(R.id.bookmarks_listview);
180 // Remove the incorrect lint warning that `appBar` might be null.
181 assert appBar != null;
183 // Display the home arrow on the app bar.
184 appBar.setDisplayHomeAsUpEnabled(true);
186 // Initialize the database helper. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`.
187 // The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
188 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
190 // Load the home folder.
193 // Set a listener so that tapping a list item loads the URL or folder.
194 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
195 // Convert the id from long to int to match the format of the bookmarks database.
196 int databaseId = (int) id;
198 // Get the bookmark cursor for this ID.
199 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
201 // Move the cursor to the first entry.
202 bookmarkCursor.moveToFirst();
204 // Act upon the bookmark according to the type.
205 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
206 // Update the current folder.
207 currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
209 // Load the new folder.
211 } else { // The selected bookmark is not a folder.
212 // Instantiate the edit bookmark dialog.
213 DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, favoriteIconBitmap);
216 editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark));
220 bookmarkCursor.close();
223 // Handle long presses on the list view.
224 bookmarksListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
225 // Instantiate the common variables.
226 MenuItem editBookmarkMenuItem;
227 MenuItem deleteBookmarksMenuItem;
228 MenuItem selectAllBookmarksMenuItem;
229 boolean deletingBookmarks;
232 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
233 // Inflate the menu for the contextual app bar.
234 getMenuInflater().inflate(R.menu.bookmarks_context_menu, menu);
237 if (currentFolder.isEmpty()) { // Use `R.string.bookmarks` if in the home folder.
238 mode.setTitle(R.string.bookmarks);
239 } else { // Use the current folder name as the title.
240 mode.setTitle(currentFolder);
243 // Get handles for menu items that need to be selectively disabled.
244 moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up);
245 moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down);
246 editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark);
247 deleteBookmarksMenuItem = menu.findItem(R.id.delete_bookmark);
248 selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks);
250 // Disable the delete bookmarks menu item if a delete is pending.
251 deleteBookmarksMenuItem.setEnabled(!deletingBookmarks);
253 // Store a handle for the contextual action mode so it can be closed programatically.
254 contextualActionMode = mode;
261 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
262 // Get a handle for the move to folder menu item.
263 MenuItem moveToFolderMenuItem = menu.findItem(R.id.move_to_folder);
265 // Get a Cursor with all of the folders.
266 Cursor folderCursor = bookmarksDatabaseHelper.getAllFolders();
268 // Enable the move to folder menu item if at least one folder exists.
269 moveToFolderMenuItem.setVisible(folderCursor.getCount() > 0);
276 public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
277 // Get the number of selected bookmarks.
278 int numberOfSelectedBookmarks = bookmarksListView.getCheckedItemCount();
280 // Only process commands if at least one bookmark is selected. Otherwise, a context menu with 0 selected bookmarks is briefly displayed.
281 if (numberOfSelectedBookmarks > 0) {
282 // Adjust the ActionMode and the menu according to the number of selected bookmarks.
283 if (numberOfSelectedBookmarks == 1) { // One bookmark is selected.
284 // List the number of selected bookmarks in the subtitle.
285 mode.setSubtitle(getString(R.string.selected) + " 1");
287 // Show the `Move Up`, `Move Down`, and `Edit` options.
288 moveBookmarkUpMenuItem.setVisible(true);
289 moveBookmarkDownMenuItem.setVisible(true);
290 editBookmarkMenuItem.setVisible(true);
292 // Update the enabled status of the move icons.
294 } else { // More than one bookmark is selected.
295 // List the number of selected bookmarks in the subtitle.
296 mode.setSubtitle(getString(R.string.selected) + " " + numberOfSelectedBookmarks);
298 // Hide non-applicable `MenuItems`.
299 moveBookmarkUpMenuItem.setVisible(false);
300 moveBookmarkDownMenuItem.setVisible(false);
301 editBookmarkMenuItem.setVisible(false);
304 // Show the select all menu item if all the bookmarks are not selected.
305 selectAllBookmarksMenuItem.setVisible(bookmarksListView.getCheckedItemCount() != bookmarksListView.getCount());
310 public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
311 // Declare the common variables.
312 int selectedBookmarkNewPosition;
313 final SparseBooleanArray selectedBookmarksPositionsSparseBooleanArray;
315 // Initialize the selected bookmark position.
316 int selectedBookmarkPosition = 0;
318 // Get a handle for the menu item ID.
319 int menuItemId = menuItem.getItemId();
321 // Run the commands according to the selected action item.
322 if (menuItemId == R.id.move_bookmark_up) { // Move the bookmark up.
323 // Get the array of checked bookmark positions.
324 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
326 // 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`.
327 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
328 // Check to see if the value for the bookmark is true, meaning it is currently selected.
329 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
330 // Only one bookmark should have a value of `true` when move bookmark up is enabled.
331 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
335 // Calculate the new position of the selected bookmark.
336 selectedBookmarkNewPosition = selectedBookmarkPosition - 1;
338 // Iterate through the bookmarks.
339 for (int i = 0; i < bookmarksListView.getCount(); i++) {
340 // Get the database ID for the current bookmark.
341 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
343 // Update the display order for the current bookmark.
344 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
345 // Move the current bookmark up one.
346 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1);
347 } else if ((i + 1) == selectedBookmarkPosition) { // The current bookmark is immediately above the selected bookmark.
348 // Move the current bookmark down one.
349 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1);
350 } else { // The current bookmark is not changing positions.
351 // Move `bookmarksCursor` to the current bookmark position.
352 bookmarksCursor.moveToPosition(i);
354 // Update the display order only if it is not correct in the database.
355 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
356 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
361 // Update the bookmarks cursor with the current contents of the bookmarks database.
362 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
364 // Update the list view.
365 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
367 // Scroll with the bookmark.
368 scrollBookmarks(selectedBookmarkNewPosition);
370 // Update the enabled status of the move icons.
372 } else if (menuItemId == R.id.move_bookmark_down) { // Move the bookmark down.
373 // Get the array of checked bookmark positions.
374 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
376 // 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`.
377 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
378 // Check to see if the value for the bookmark is true, meaning it is currently selected.
379 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
380 // Only one bookmark should have a value of `true` when move bookmark down is enabled.
381 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
385 // Calculate the new position of the selected bookmark.
386 selectedBookmarkNewPosition = selectedBookmarkPosition + 1;
388 // Iterate through the bookmarks.
389 for (int i = 0; i < bookmarksListView.getCount(); i++) {
390 // Get the database ID for the current bookmark.
391 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
393 // Update the display order for the current bookmark.
394 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
395 // Move the current bookmark down one.
396 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1);
397 } else if ((i - 1) == selectedBookmarkPosition) { // The current bookmark is immediately below the selected bookmark.
398 // Move the bookmark below the selected bookmark up one.
399 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1);
400 } else { // The current bookmark is not changing positions.
401 // Move `bookmarksCursor` to the current bookmark position.
402 bookmarksCursor.moveToPosition(i);
404 // Update the display order only if it is not correct in the database.
405 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
406 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
411 // Update the bookmarks cursor with the current contents of the bookmarks database.
412 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
414 // Update the list view.
415 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
417 // Scroll with the bookmark.
418 scrollBookmarks(selectedBookmarkNewPosition);
420 // Update the enabled status of the move icons.
422 } else if (menuItemId == R.id.move_to_folder) { // Move to folder.
423 // Store the checked item IDs for use by the alert dialog.
424 checkedItemIds = bookmarksListView.getCheckedItemIds();
426 // Instantiate the move to folder alert dialog.
427 DialogFragment moveToFolderDialog = new MoveToFolderDialog();
429 // Show the move to folder alert dialog.
430 moveToFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.move_to_folder));
431 } else if (menuItemId == R.id.edit_bookmark) {
432 // Get the array of checked bookmark positions.
433 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
435 // 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`.
436 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
437 // Check to see if the value for the bookmark is true, meaning it is currently selected.
438 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
439 // Only one bookmark should have a value of `true` when move edit bookmark is enabled.
440 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
444 // Move the cursor to the selected position.
445 bookmarksCursor.moveToPosition(selectedBookmarkPosition);
447 // Find out if this bookmark is a folder.
448 boolean isFolder = (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1);
450 // Get the selected bookmark database ID.
451 int databaseId = bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper._ID));
453 // Show the edit bookmark or edit bookmark folder dialog.
455 // Save the current folder name, which is used in `onSaveBookmarkFolder()`.
456 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
458 // Instantiate the edit bookmark folder dialog.
459 DialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, favoriteIconBitmap);
462 editFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_folder));
464 // Instantiate the edit bookmark dialog.
465 DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, favoriteIconBitmap);
468 editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark));
470 } else if (menuItemId == R.id.delete_bookmark) { // Delete bookmark.
471 // Set the deleting bookmarks flag, which prevents the delete menu item from being enabled until the current process finishes.
472 deletingBookmarks = true;
474 // Get an array of the selected row IDs.
475 final long[] selectedBookmarksIdsLongArray = bookmarksListView.getCheckedItemIds();
477 // 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.
478 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions().clone();
480 // Update the bookmarks cursor with the current contents of the bookmarks database except for the specified database IDs.
481 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(selectedBookmarksIdsLongArray, currentFolder);
483 // Update the list view.
484 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
486 // Create a Snackbar with the number of deleted bookmarks.
487 bookmarksDeletedSnackbar = Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.bookmarks_deleted) + " " + selectedBookmarksIdsLongArray.length,
488 Snackbar.LENGTH_LONG)
489 .setAction(R.string.undo, view -> {
490 // Do nothing because everything will be handled by `onDismissed()` below.
492 .addCallback(new Snackbar.Callback() {
493 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
495 public void onDismissed(Snackbar snackbar, int event) {
496 if (event == Snackbar.Callback.DISMISS_EVENT_ACTION) { // The user pushed the undo button.
497 // Update the bookmarks cursor with the current contents of the bookmarks database, including the "deleted" bookmarks.
498 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
500 // Update the list view.
501 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
503 // Re-select the previously selected bookmarks.
504 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
505 bookmarksListView.setItemChecked(selectedBookmarksPositionsSparseBooleanArray.keyAt(i), true);
507 } else { // The snackbar was dismissed without the undo button being pushed.
508 // Delete each selected bookmark.
509 for (long databaseIdLong : selectedBookmarksIdsLongArray) {
510 // Convert `databaseIdLong` to an int.
511 int databaseIdInt = (int) databaseIdLong;
513 // Delete the contents of the folder if the selected bookmark is a folder.
514 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
515 deleteBookmarkFolderContents(databaseIdInt);
518 // Delete the selected bookmark.
519 bookmarksDatabaseHelper.deleteBookmark(databaseIdInt);
522 // Update the display order.
523 for (int i = 0; i < bookmarksListView.getCount(); i++) {
524 // Get the database ID for the current bookmark.
525 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
527 // Move `bookmarksCursor` to the current bookmark position.
528 bookmarksCursor.moveToPosition(i);
530 // Update the display order only if it is not correct in the database.
531 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
532 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
537 // Reset the deleting bookmarks flag.
538 deletingBookmarks = false;
540 // Enable the delete bookmarks menu item.
541 deleteBookmarksMenuItem.setEnabled(true);
543 // Close the activity if back has been pressed.
544 if (closeActivityAfterDismissingSnackbar) {
551 bookmarksDeletedSnackbar.show();
552 } else if (menuItemId == R.id.context_menu_select_all_bookmarks) { // Select all.
553 // Get the total number of bookmarks.
554 int numberOfBookmarks = bookmarksListView.getCount();
557 for (int i = 0; i < numberOfBookmarks; i++) {
558 bookmarksListView.setItemChecked(i, true);
562 // Consume the click.
567 public void onDestroyActionMode(ActionMode mode) {
572 // Get handles for the floating action buttons.
573 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
574 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
576 // Set the create new bookmark folder FAB to display the `AlertDialog`.
577 createBookmarkFolderFab.setOnClickListener(v -> {
578 // Create a create bookmark folder dialog.
579 DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(favoriteIconBitmap);
581 // Show the create bookmark folder dialog.
582 createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
585 // Set the create new bookmark FAB to display the alert dialog.
586 createBookmarkFab.setOnClickListener(view -> {
587 // Remove the incorrect lint warning below.
588 assert currentUrl != null;
589 assert currentTitle != null;
591 // Instantiate the create bookmark dialog.
592 DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentUrl, currentTitle, favoriteIconBitmap);
594 // Display the create bookmark dialog.
595 createBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_bookmark));
598 // Restore the state if the app has been restarted.
599 if (savedInstanceState != null) {
600 // Update the bookmarks list view after it has loaded.
601 bookmarksListView.post(() -> {
602 // Get the checked bookmarks array list.
603 ArrayList<Integer> checkedBookmarksArrayList = savedInstanceState.getIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST);
605 // Check each previously checked bookmark in the list view. When the minimum API >= 24 a `forEach()` command can be used instead.
606 if (checkedBookmarksArrayList != null) {
607 for (int i = 0; i < checkedBookmarksArrayList.size(); i++) {
608 bookmarksListView.setItemChecked(checkedBookmarksArrayList.get(i), true);
616 public void onRestart() {
617 // Run the default commands.
620 // Update the list view if returning from the bookmarks database view activity.
621 if (restartFromBookmarksDatabaseViewActivity) {
622 // Load the current folder in the list view.
625 // Reset `restartFromBookmarksDatabaseViewActivity`.
626 restartFromBookmarksDatabaseViewActivity = false;
631 public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
632 // Run the default commands.
633 super.onSaveInstanceState(savedInstanceState);
635 // Get the array of the checked items.
636 SparseBooleanArray checkedBookmarksSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
638 // Create a checked items array list.
639 ArrayList<Integer> checkedBookmarksArrayList = new ArrayList<>();
641 // Add each checked bookmark position to the array list.
642 for (int i = 0; i < checkedBookmarksSparseBooleanArray.size(); i++) {
643 // 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.
644 if (checkedBookmarksSparseBooleanArray.valueAt(i)) {
645 // Add the bookmark position to the checked bookmarks array list.
646 checkedBookmarksArrayList.add(checkedBookmarksSparseBooleanArray.keyAt(i));
650 // Store the checked items array list in the saved instance state.
651 savedInstanceState.putIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST, checkedBookmarksArrayList);
655 public boolean onCreateOptionsMenu(Menu menu) {
657 getMenuInflater().inflate(R.menu.bookmarks_options_menu, menu);
664 public boolean onOptionsItemSelected(MenuItem menuItem) {
665 // Get a handle for the menu item ID.
666 int menuItemId = menuItem.getItemId();
668 // Run the command according to the selected option.
669 if (menuItemId == android.R.id.home) { // Home. The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
670 if (currentFolder.isEmpty()) { // Currently in the home folder.
671 // Run the back commands.
673 } else { // Currently in a subfolder.
674 // Place the former parent folder in `currentFolder`.
675 currentFolder = bookmarksDatabaseHelper.getParentFolderName(currentFolder);
677 // Load the new folder.
680 } else if (menuItemId == R.id.options_menu_select_all_bookmarks) { // Select all.
681 // Get the total number of bookmarks.
682 int numberOfBookmarks = bookmarksListView.getCount();
685 for (int i = 0; i < numberOfBookmarks; i++) {
686 bookmarksListView.setItemChecked(i, true);
688 } else if (menuItemId == R.id.bookmarks_database_view) {
689 // Create an intent to launch the bookmarks database view activity.
690 Intent bookmarksDatabaseViewIntent = new Intent(this, BookmarksDatabaseViewActivity.class);
692 // Include the favorite icon byte array to the intent.
693 bookmarksDatabaseViewIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
696 startActivity(bookmarksDatabaseViewIntent);
702 public void onBackPressed() {
703 // 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.
704 if ((bookmarksDeletedSnackbar != null) && bookmarksDeletedSnackbar.isShown()) { // Close the bookmarks deleted snackbar before going home.
705 // Set the close flag.
706 closeActivityAfterDismissingSnackbar = true;
708 // Dismiss the snackbar.
709 bookmarksDeletedSnackbar.dismiss();
710 } else { // Go home immediately.
711 // Update the bookmarks folder for the bookmarks drawer in the main WebView activity.
712 MainWebViewActivity.currentBookmarksFolder = currentFolder;
714 // Close the bookmarks drawer and reload the bookmarks ListView when returning to the main WebView activity.
715 MainWebViewActivity.restartFromBookmarksActivity = true;
717 // Exit the bookmarks activity.
718 super.onBackPressed();
723 public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
724 // Get the alert dialog from the fragment.
725 Dialog dialog = dialogFragment.getDialog();
727 // Remove the incorrect lint warning below that the dialog might be null.
728 assert dialog != null;
730 // Get the views from the dialog fragment.
731 EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext);
732 EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext);
734 // Extract the strings from the edit texts.
735 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
736 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
738 // Create a favorite icon byte array output stream.
739 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
741 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
742 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
744 // Convert the favorite icon byte array stream to a byte array.
745 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
747 // Display the new bookmark below the current items in the (0 indexed) list.
748 int newBookmarkDisplayOrder = bookmarksListView.getCount();
750 // Create the bookmark.
751 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
753 // Update the bookmarks cursor with the current contents of this folder.
754 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
756 // Update the `ListView`.
757 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
759 // Scroll to the new bookmark.
760 bookmarksListView.setSelection(newBookmarkDisplayOrder);
764 public void onCreateBookmarkFolder(DialogFragment dialogFragment, @NonNull Bitmap favoriteIconBitmap) {
765 // Get the dialog from the dialog fragment.
766 Dialog dialog = dialogFragment.getDialog();
768 // Remove the incorrect lint warning below that the dialog might be null.
769 assert dialog != null;
771 // Get handles for the views in the dialog fragment.
772 EditText createFolderNameEditText = dialog.findViewById(R.id.create_folder_name_edittext);
773 RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.create_folder_default_icon_radiobutton);
774 ImageView folderIconImageView = dialog.findViewById(R.id.create_folder_default_icon);
776 // Get new folder name string.
777 String folderNameString = createFolderNameEditText.getText().toString();
779 // Create a folder icon bitmap.
780 Bitmap folderIconBitmap;
782 // Set the folder icon bitmap according to the dialog.
783 if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
784 // Get the default folder icon drawable.
785 Drawable folderIconDrawable = folderIconImageView.getDrawable();
787 // Convert the folder icon drawable to a bitmap drawable.
788 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
790 // Convert the folder icon bitmap drawable to a bitmap.
791 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
792 } else { // Use the WebView favorite icon.
793 // Copy the favorite icon bitmap to the folder icon bitmap.
794 folderIconBitmap = favoriteIconBitmap;
797 // Create a folder icon byte array output stream.
798 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
800 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
801 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
803 // Convert the folder icon byte array stream to a byte array.
804 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
806 // Move all the bookmarks down one in the display order.
807 for (int i = 0; i < bookmarksListView.getCount(); i++) {
808 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
809 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
812 // Create the folder, which will be placed at the top of the `ListView`.
813 bookmarksDatabaseHelper.createFolder(folderNameString, currentFolder, folderIconByteArray);
815 // Update the bookmarks cursor with the current contents of this folder.
816 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
818 // Update the `ListView`.
819 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
821 // Scroll to the new folder.
822 bookmarksListView.setSelection(0);
826 public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
827 // Get the dialog from the dialog fragment.
828 Dialog dialog = dialogFragment.getDialog();
830 // Remove the incorrect lint warning below that the dialog might be null.
831 assert dialog != null;
833 // Get handles for the views from `dialogFragment`.
834 EditText editBookmarkNameEditText = dialog.findViewById(R.id.edit_bookmark_name_edittext);
835 EditText editBookmarkUrlEditText = dialog.findViewById(R.id.edit_bookmark_url_edittext);
836 RadioButton currentBookmarkIconRadioButton = dialog.findViewById(R.id.edit_bookmark_current_icon_radiobutton);
838 // Store the bookmark strings.
839 String bookmarkNameString = editBookmarkNameEditText.getText().toString();
840 String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
842 // Update the bookmark.
843 if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
844 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
845 } else { // Update the bookmark using the WebView favorite icon.
846 // Create a favorite icon byte array output stream.
847 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
849 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
850 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
852 // Convert the favorite icon byte array stream to a byte array.
853 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
855 // Update the bookmark and the favorite icon.
856 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
859 // Check to see if the contextual action mode has been created.
860 if (contextualActionMode != null) {
861 // Close the contextual action mode if it is open.
862 contextualActionMode.finish();
865 // Update the bookmarks cursor with the contents of the current folder.
866 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
868 // Update the `ListView`.
869 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
873 public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) {
874 // Get the dialog from the dialog fragment.
875 Dialog dialog = dialogFragment.getDialog();
877 // Remove the incorrect lint warning below that the dialog might be null.
878 assert dialog != null;
880 // Get handles for the views from `dialogFragment`.
881 RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_current_icon_radiobutton);
882 RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_default_icon_radiobutton);
883 ImageView defaultFolderIconImageView = dialog.findViewById(R.id.edit_folder_default_icon_imageview);
884 EditText editFolderNameEditText = dialog.findViewById(R.id.edit_folder_name_edittext);
886 // Get the new folder name.
887 String newFolderNameString = editFolderNameEditText.getText().toString();
889 // Check if the favorite icon has changed.
890 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
891 // Update the name in the database.
892 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
893 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
894 // Create the new folder icon Bitmap.
895 Bitmap folderIconBitmap;
897 // Populate the new folder icon bitmap.
898 if (defaultFolderIconRadioButton.isChecked()) {
899 // Get the default folder icon drawable.
900 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
902 // Convert the folder icon drawable to a bitmap drawable.
903 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
905 // Convert the folder icon bitmap drawable to a bitmap.
906 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
907 } else { // Use the WebView favorite icon.
908 // Copy the favorite icon bitmap to the folder icon bitmap.
909 folderIconBitmap = favoriteIconBitmap;
912 // Create a folder icon byte array output stream.
913 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
915 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
916 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
918 // Convert the folder icon byte array stream to a byte array.
919 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
921 // Update the folder icon in the database.
922 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
923 } else { // The folder icon and the name have changed.
924 // Instantiate the new folder icon `Bitmap`.
925 Bitmap folderIconBitmap;
927 // Populate the new folder icon bitmap.
928 if (defaultFolderIconRadioButton.isChecked()) {
929 // Get the default folder icon drawable.
930 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
932 // Convert the folder icon drawable to a bitmap drawable.
933 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
935 // Convert the folder icon bitmap drawable to a bitmap.
936 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
937 } else { // Use the WebView favorite icon.
938 // Copy the favorite icon bitmap to the folder icon bitmap.
939 folderIconBitmap = favoriteIconBitmap;
942 // Create a folder icon byte array output stream.
943 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
945 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
946 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
948 // Convert the folder icon byte array stream to a byte array.
949 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
951 // Update the folder name and icon in the database.
952 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
955 // Update the bookmarks cursor with the current contents of this folder.
956 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
958 // Update the `ListView`.
959 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
961 // Close the contextual action mode.
962 contextualActionMode.finish();
966 public void onMoveToFolder(DialogFragment dialogFragment) {
967 // Get the dialog from the dialog fragment.
968 Dialog dialog = dialogFragment.getDialog();
970 // Remove the incorrect lint warning below that the dialog might be null.
971 assert dialog != null;
973 // Get a handle for the list view from the dialog.
974 ListView folderListView = dialog.findViewById(R.id.move_to_folder_listview);
976 // Store a long array of the selected folders.
977 long[] newFolderLongArray = folderListView.getCheckedItemIds();
979 // Get the new folder database ID. Only one folder will be selected.
980 int newFolderDatabaseId = (int) newFolderLongArray[0];
982 // Instantiate `newFolderName`.
983 String newFolderName;
985 // Set the new folder name.
986 if (newFolderDatabaseId == 0) {
987 // The new folder is the home folder, represented as `""` in the database.
990 // Get the new folder name from the database.
991 newFolderName = bookmarksDatabaseHelper.getFolderName(newFolderDatabaseId);
994 // Get a long array with the the database ID of the selected bookmarks.
995 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
997 // Move each of the selected bookmarks to the new folder.
998 for (long databaseIdLong : selectedBookmarksLongArray) {
999 // Get `databaseIdInt` for each selected bookmark.
1000 int databaseIdInt = (int) databaseIdLong;
1002 // Move the selected bookmark to the new folder.
1003 bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderName);
1006 // Update the bookmarks cursor with the current contents of this folder.
1007 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
1009 // Update the `ListView`.
1010 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
1012 // Close the contextual app bar.
1013 contextualActionMode.finish();
1016 private void deleteBookmarkFolderContents(int databaseId) {
1017 // Get the name of the folder.
1018 String folderName = bookmarksDatabaseHelper.getFolderName(databaseId);
1020 // Get the contents of the folder.
1021 Cursor folderCursor = bookmarksDatabaseHelper.getBookmarkIDs(folderName);
1023 // Delete each of the bookmarks in the folder.
1024 for (int i = 0; i < folderCursor.getCount(); i++) {
1025 // Move `folderCursor` to the current row.
1026 folderCursor.moveToPosition(i);
1028 // Get the database ID of the item.
1029 int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndex(BookmarksDatabaseHelper._ID));
1031 // If this is a folder, recursively delete the contents first.
1032 if (bookmarksDatabaseHelper.isFolder(itemDatabaseId)) {
1033 deleteBookmarkFolderContents(itemDatabaseId);
1036 // Delete the bookmark.
1037 bookmarksDatabaseHelper.deleteBookmark(itemDatabaseId);
1041 private void updateMoveIcons() {
1042 // Get a long array of the selected bookmarks.
1043 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
1045 // Get the database IDs for the first, last, and selected bookmarks.
1046 int selectedBookmarkDatabaseId = (int) selectedBookmarksLongArray[0];
1047 int firstBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(0);
1048 // bookmarksListView is 0 indexed.
1049 int lastBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(bookmarksListView.getCount() - 1);
1051 // Get the current theme status.
1052 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
1054 // Update the move bookmark up `MenuItem`.
1055 if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) { // The selected bookmark is in the first position.
1056 // Disable the move bookmark up `MenuItem`.
1057 moveBookmarkUpMenuItem.setEnabled(false);
1059 // Set the move bookmark up icon to be ghosted.
1060 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_disabled);
1061 } else { // The selected bookmark is not in the first position.
1062 // Enable the move bookmark up menu item.
1063 moveBookmarkUpMenuItem.setEnabled(true);
1065 // Set the icon according to the theme.
1066 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
1067 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled_night);
1069 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled_day);
1073 // Update the move bookmark down `MenuItem`.
1074 if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) { // The selected bookmark is in the last position.
1075 // Disable the move bookmark down `MenuItem`.
1076 moveBookmarkDownMenuItem.setEnabled(false);
1078 // Set the move bookmark down icon to be ghosted.
1079 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_disabled);
1080 } else { // The selected bookmark is not in the last position.
1081 // Enable the move bookmark down `MenuItem`.
1082 moveBookmarkDownMenuItem.setEnabled(true);
1084 // Set the icon according to the theme.
1085 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
1086 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled_night);
1088 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled_day);
1093 private void scrollBookmarks(int selectedBookmarkPosition) {
1094 // Get the first and last visible bookmark positions.
1095 int firstVisibleBookmarkPosition = bookmarksListView.getFirstVisiblePosition();
1096 int lastVisibleBookmarkPosition = bookmarksListView.getLastVisiblePosition();
1098 // Calculate the number of bookmarks per screen.
1099 int numberOfBookmarksPerScreen = lastVisibleBookmarkPosition - firstVisibleBookmarkPosition;
1101 // Scroll with the moved bookmark if necessary.
1102 if (selectedBookmarkPosition <= firstVisibleBookmarkPosition) { // The selected bookmark position is at or above the top of the screen.
1103 // Scroll to the selected bookmark position.
1104 bookmarksListView.setSelection(selectedBookmarkPosition);
1105 } else if (selectedBookmarkPosition >= (lastVisibleBookmarkPosition - 1)) { // The selected bookmark is at or below the bottom of the screen.
1106 // 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.
1107 // `+1` assures that the entire bookmark will be displayed in situations where only a partial bookmark fits at the bottom of the list view.
1108 bookmarksListView.setSelection(selectedBookmarkPosition - numberOfBookmarksPerScreen + 1);
1112 private void loadFolder() {
1113 // Update bookmarks cursor with the contents of the bookmarks database for the current folder.
1114 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
1116 // Setup a `CursorAdapter`. `this` specifies the `Context`. `false` disables `autoRequery`.
1117 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
1119 public View newView(Context context, Cursor cursor, ViewGroup parent) {
1120 // Inflate the individual item layout. `false` does not attach it to the root.
1121 return getLayoutInflater().inflate(R.layout.bookmarks_activity_item_linearlayout, parent, false);
1125 public void bindView(View view, Context context, Cursor cursor) {
1126 // Get handles for the views.
1127 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
1128 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
1130 // Get the favorite icon byte array from the `Cursor`.
1131 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
1133 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
1134 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
1136 // Display the bitmap in `bookmarkFavoriteIcon`.
1137 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
1139 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
1140 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
1141 bookmarkNameTextView.setText(bookmarkNameString);
1143 // Make the font bold for folders.
1144 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
1145 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
1146 } else { // Reset the font to default for normal bookmarks.
1147 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
1152 // Populate the list view with the adapter.
1153 bookmarksListView.setAdapter(bookmarksCursorAdapter);
1155 // Set the `AppBar` title.
1156 if (currentFolder.isEmpty()) {
1157 appBar.setTitle(R.string.bookmarks);
1159 appBar.setTitle(currentFolder);
1164 public void onDestroy() {
1165 // Close the bookmarks cursor and database.
1166 bookmarksCursor.close();
1167 bookmarksDatabaseHelper.close();
1169 // Run the default commands.