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.Activity;
24 import android.app.Dialog;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.SharedPreferences;
28 import android.content.res.Configuration;
29 import android.database.Cursor;
30 import android.graphics.Bitmap;
31 import android.graphics.BitmapFactory;
32 import android.graphics.Typeface;
33 import android.graphics.drawable.BitmapDrawable;
34 import android.graphics.drawable.Drawable;
35 import android.os.Bundle;
36 import android.preference.PreferenceManager;
37 import android.util.SparseBooleanArray;
38 import android.view.ActionMode;
39 import android.view.Menu;
40 import android.view.MenuItem;
41 import android.view.View;
42 import android.view.ViewGroup;
43 import android.view.WindowManager;
44 import android.widget.AbsListView;
45 import android.widget.CursorAdapter;
46 import android.widget.EditText;
47 import android.widget.ImageView;
48 import android.widget.ListView;
49 import android.widget.RadioButton;
50 import android.widget.TextView;
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.core.app.NavUtils;
57 import androidx.fragment.app.DialogFragment;
59 import com.google.android.material.floatingactionbutton.FloatingActionButton;
60 import com.google.android.material.snackbar.Snackbar;
62 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
63 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
64 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
65 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
66 import com.stoutner.privacybrowser.dialogs.MoveToFolderDialog;
67 import com.stoutner.privacybrowser.R;
68 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
70 import java.io.ByteArrayOutputStream;
71 import java.util.ArrayList;
73 public class BookmarksActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, EditBookmarkDialog.EditBookmarkListener,
74 EditBookmarkFolderDialog.EditBookmarkFolderListener, MoveToFolderDialog.MoveToFolderListener {
76 // `currentFolder` is public static so it can be accessed from `MoveToFolderDialog` and `BookmarksDatabaseViewActivity`.
77 // It is used in `onCreate`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`,
78 // and `loadFolder()`.
79 public static String currentFolder;
81 // `checkedItemIds` is public static so it can be accessed from `MoveToFolderDialog`. It is also used in `onCreate()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`,
82 // and `updateMoveIcons()`.
83 public static long[] checkedItemIds;
85 // `restartFromBookmarksDatabaseViewActivity` is public static so it can be accessed from `BookmarksDatabaseViewActivity`. It is also used in `onRestart()`.
86 public static boolean restartFromBookmarksDatabaseViewActivity;
89 // Define the saved instance state constants.
90 private final String CHECKED_BOOKMARKS_ARRAY_LIST = "checked_bookmarks_array_list";
92 // Define the class menu items.
93 private MenuItem moveBookmarkUpMenuItem;
94 private MenuItem moveBookmarkDownMenuItem;
96 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`,
97 // `onMoveToFolder()`, `deleteBookmarkFolderContents()`, `loadFolder()`, and `onDestroy()`.
98 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
100 // `bookmarksListView` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`,
101 // `updateMoveIcons()`, `scrollBookmarks()`, and `loadFolder()`.
102 private ListView bookmarksListView;
104 // `bookmarksCursor` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, `deleteBookmarkFolderContents()`,
105 // `loadFolder()`, and `onDestroy()`.
106 private Cursor bookmarksCursor;
108 // `bookmarksCursorAdapter` is used in `onCreate(), `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, and `onLoadFolder()`.
109 private CursorAdapter bookmarksCursorAdapter;
111 // `contextualActionMode` is used in `onCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()` and `onMoveToFolder()`.
112 private ActionMode contextualActionMode;
114 // `appBar` is used in `onCreate()` and `loadFolder()`.
115 private ActionBar appBar;
117 // `oldFolderName` is used in `onCreate()` and `onSaveBookmarkFolder()`.
118 private String oldFolderNameString;
120 // `bookmarksDeletedSnackbar` is used in `onCreate()`, `onOptionsItemSelected()`, and `onBackPressed()`.
121 private Snackbar bookmarksDeletedSnackbar;
123 // `closeActivityAfterDismissingSnackbar` is used in `onCreate()`, `onOptionsItemSelected()`, and `onBackPressed()`.
124 private boolean closeActivityAfterDismissingSnackbar;
126 // The favorite icon byte array is populated in `onCreate()` and used in `onOptionsItemSelected()`.
127 private byte[] favoriteIconByteArray;
130 protected void onCreate(Bundle savedInstanceState) {
131 // Get a handle for the shared preferences.
132 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
134 // Get the screenshot preference.
135 boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
137 // Disable screenshots if not allowed.
138 if (!allowScreenshots) {
139 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
143 setTheme(R.style.PrivacyBrowser);
145 // Run the default commands.
146 super.onCreate(savedInstanceState);
148 // Get the intent that launched the activity.
149 Intent launchingIntent = getIntent();
151 // Store the current URL and title.
152 String currentUrl = launchingIntent.getStringExtra("current_url");
153 String currentTitle = launchingIntent.getStringExtra("current_title");
155 // Set the current folder variable.
156 if (launchingIntent.getStringExtra("current_folder") != null) { // Set the current folder from the intent.
157 currentFolder = launchingIntent.getStringExtra("current_folder");
158 } else { // Set the current folder to be `""`, which is the home folder.
162 // Get the favorite icon byte array.
163 favoriteIconByteArray = launchingIntent.getByteArrayExtra("favorite_icon_byte_array");
165 // Remove the incorrect lint warning that the favorite icon byte array might be null.
166 assert favoriteIconByteArray != null;
168 // Convert the favorite icon byte array to a bitmap.
169 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
171 // Set the content view.
172 setContentView(R.layout.bookmarks_coordinatorlayout);
174 // The AndroidX toolbar must be used until the minimum API is >= 21.
175 final Toolbar toolbar = findViewById(R.id.bookmarks_toolbar);
176 setSupportActionBar(toolbar);
178 // Get a handle for the activity, the app bar, and the ListView.
179 final Activity bookmarksActivity = this;
180 appBar = getSupportActionBar();
181 bookmarksListView = findViewById(R.id.bookmarks_listview);
183 // Remove the incorrect lint warning that `appBar` might be null.
184 assert appBar != null;
186 // Display the home arrow on the app bar.
187 appBar.setDisplayHomeAsUpEnabled(true);
189 // Initialize the database helper. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`.
190 // The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
191 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
193 // Load the home folder.
196 // Set a listener so that tapping a list item loads the URL or folder.
197 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
198 // Convert the id from long to int to match the format of the bookmarks database.
199 int databaseID = (int) id;
201 // Get the bookmark cursor for this ID and move it to the first row.
202 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
203 bookmarkCursor.moveToFirst();
205 // Act upon the bookmark according to the type.
206 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
207 // Update the current folder.
208 currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
210 // Load the new folder.
212 } else { // The selected bookmark is not a folder.
213 // Get the bookmark URL and assign it to `formattedUrlString`.
214 MainWebViewActivity.urlToLoadOnRestart = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL));
216 // Set `MainWebViewActivity` to load the new URL on restart.
217 MainWebViewActivity.loadUrlOnRestart = true;
219 // Update the bookmarks folder for the bookmarks drawer in `MainWebViewActivity`.
220 MainWebViewActivity.currentBookmarksFolder = currentFolder;
222 // Close the bookmarks drawer and reload the bookmarks `ListView` when returning to `MainWebViewActivity`.
223 MainWebViewActivity.restartFromBookmarksActivity = true;
225 // Return to `MainWebViewActivity`.
226 NavUtils.navigateUpFromSameTask(bookmarksActivity);
229 // Close the `Cursor`.
230 bookmarkCursor.close();
233 // Handle long presses on the list view.
234 bookmarksListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
235 // Instantiate the common variables.
236 MenuItem editBookmarkMenuItem;
237 MenuItem deleteBookmarksMenuItem;
238 MenuItem selectAllBookmarksMenuItem;
239 boolean deletingBookmarks;
242 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
243 // Inflate the menu for the contextual app bar.
244 getMenuInflater().inflate(R.menu.bookmarks_context_menu, menu);
247 if (currentFolder.isEmpty()) { // Use `R.string.bookmarks` if in the home folder.
248 mode.setTitle(R.string.bookmarks);
249 } else { // Use the current folder name as the title.
250 mode.setTitle(currentFolder);
253 // Get handles for menu items that need to be selectively disabled.
254 moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up);
255 moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down);
256 editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark);
257 deleteBookmarksMenuItem = menu.findItem(R.id.delete_bookmark);
258 selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks);
260 // Disable the delete bookmarks menu item if a delete is pending.
261 deleteBookmarksMenuItem.setEnabled(!deletingBookmarks);
263 // Store a handle for the contextual action mode so it can be closed programatically.
264 contextualActionMode = mode;
271 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
272 // Get a handle for the move to folder menu item.
273 MenuItem moveToFolderMenuItem = menu.findItem(R.id.move_to_folder);
275 // Get a Cursor with all of the folders.
276 Cursor folderCursor = bookmarksDatabaseHelper.getAllFolders();
278 // Enable the move to folder menu item if at least one folder exists.
279 moveToFolderMenuItem.setVisible(folderCursor.getCount() > 0);
286 public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
287 // Get the number of selected bookmarks.
288 int numberOfSelectedBookmarks = bookmarksListView.getCheckedItemCount();
290 // Adjust the ActionMode and the menu according to the number of selected bookmarks.
291 if (numberOfSelectedBookmarks == 1) { // One bookmark is selected.
292 // List the number of selected bookmarks in the subtitle.
293 mode.setSubtitle(getString(R.string.selected) + " 1");
295 // Show the `Move Up`, `Move Down`, and `Edit` options.
296 moveBookmarkUpMenuItem.setVisible(true);
297 moveBookmarkDownMenuItem.setVisible(true);
298 editBookmarkMenuItem.setVisible(true);
300 // Update the enabled status of the move icons.
302 } else { // More than one bookmark is selected.
303 // List the number of selected bookmarks in the subtitle.
304 mode.setSubtitle(getString(R.string.selected) + " " + numberOfSelectedBookmarks);
306 // Hide non-applicable `MenuItems`.
307 moveBookmarkUpMenuItem.setVisible(false);
308 moveBookmarkDownMenuItem.setVisible(false);
309 editBookmarkMenuItem.setVisible(false);
312 // Do not show the select all menu item if all the bookmarks are already checked.
313 if (bookmarksListView.getCheckedItemCount() == bookmarksListView.getCount()) {
314 selectAllBookmarksMenuItem.setVisible(false);
316 selectAllBookmarksMenuItem.setVisible(true);
321 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
322 // Declare the common variables.
323 int selectedBookmarkNewPosition;
324 final SparseBooleanArray selectedBookmarksPositionsSparseBooleanArray;
326 // Initialize the selected bookmark position.
327 int selectedBookmarkPosition = 0;
329 switch (item.getItemId()) {
330 case R.id.move_bookmark_up:
331 // Get the array of checked bookmark positions.
332 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
334 // 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`.
335 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
336 // Check to see if the value for the bookmark is true, meaning it is currently selected.
337 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
338 // Only one bookmark should have a value of `true` when move bookmark up is enabled.
339 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
343 // Calculate the new position of the selected bookmark.
344 selectedBookmarkNewPosition = selectedBookmarkPosition - 1;
346 // Iterate through the bookmarks.
347 for (int i = 0; i < bookmarksListView.getCount(); i++) {
348 // Get the database ID for the current bookmark.
349 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
351 // Update the display order for the current bookmark.
352 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
353 // Move the current bookmark up one.
354 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1);
355 } else if ((i + 1) == selectedBookmarkPosition){ // The current bookmark is immediately above the selected bookmark.
356 // Move the current bookmark down one.
357 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1);
358 } else { // The current bookmark is not changing positions.
359 // Move `bookmarksCursor` to the current bookmark position.
360 bookmarksCursor.moveToPosition(i);
362 // Update the display order only if it is not correct in the database.
363 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
364 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
369 // Update the bookmarks cursor with the current contents of the bookmarks database.
370 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
372 // Update the `ListView`.
373 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
375 // Scroll with the bookmark.
376 scrollBookmarks(selectedBookmarkNewPosition);
378 // Update the enabled status of the move icons.
382 case R.id.move_bookmark_down:
383 // Get the array of checked bookmark positions.
384 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
386 // 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`.
387 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
388 // Check to see if the value for the bookmark is true, meaning it is currently selected.
389 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
390 // Only one bookmark should have a value of `true` when move bookmark down is enabled.
391 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
395 // Calculate the new position of the selected bookmark.
396 selectedBookmarkNewPosition = selectedBookmarkPosition + 1;
398 // Iterate through the bookmarks.
399 for (int i = 0; i <bookmarksListView.getCount(); i++) {
400 // Get the database ID for the current bookmark.
401 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
403 // Update the display order for the current bookmark.
404 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
405 // Move the current bookmark down one.
406 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1);
407 } else if ((i - 1) == selectedBookmarkPosition) { // The current bookmark is immediately below the selected bookmark.
408 // Move the bookmark below the selected bookmark up one.
409 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1);
410 } else { // The current bookmark is not changing positions.
411 // Move `bookmarksCursor` to the current bookmark position.
412 bookmarksCursor.moveToPosition(i);
414 // Update the display order only if it is not correct in the database.
415 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
416 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
421 // Update the bookmarks cursor with the current contents of the bookmarks database.
422 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
424 // Update the `ListView`.
425 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
427 // Scroll with the bookmark.
428 scrollBookmarks(selectedBookmarkNewPosition);
430 // Update the enabled status of the move icons.
434 case R.id.move_to_folder:
435 // Store `checkedItemIds` for use by the `AlertDialog`.
436 checkedItemIds = bookmarksListView.getCheckedItemIds();
438 // Show the `MoveToFolderDialog` `AlertDialog` and name the instance `@string/move_to_folder
439 DialogFragment moveToFolderDialog = new MoveToFolderDialog();
440 moveToFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.move_to_folder));
443 case R.id.edit_bookmark:
444 // Get the array of checked bookmark positions.
445 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
447 // 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`.
448 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
449 // Check to see if the value for the bookmark is true, meaning it is currently selected.
450 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
451 // Only one bookmark should have a value of `true` when move edit bookmark is enabled.
452 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
456 // Move the `Cursor` to the selected position and find out if it is a folder.
457 bookmarksCursor.moveToPosition(selectedBookmarkPosition);
458 boolean isFolder = (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1);
460 // Get the selected bookmark database ID.
461 int databaseId = bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper._ID));
463 // Show the edit bookmark or edit bookmark folder dialog.
465 // Save the current folder name, which is used in `onSaveBookmarkFolder()`.
466 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
468 // Show the edit bookmark folder dialog.
469 DialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, favoriteIconBitmap);
470 editFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_folder));
472 // Show the edit bookmark dialog.
473 DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, favoriteIconBitmap);
474 editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark));
478 case R.id.delete_bookmark:
479 // Set the deleting bookmarks flag, which prevents the delete menu item from being enabled until the current process finishes.
480 deletingBookmarks = true;
482 // Get an array of the selected row IDs.
483 final long[] selectedBookmarksIdsLongArray = bookmarksListView.getCheckedItemIds();
485 // 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.
486 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions().clone();
488 // Update the bookmarks cursor with the current contents of the bookmarks database except for the specified database IDs.
489 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(selectedBookmarksIdsLongArray, currentFolder);
491 // Update the list view.
492 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
494 // Create a Snackbar with the number of deleted bookmarks.
495 bookmarksDeletedSnackbar = Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.bookmarks_deleted) + " " + selectedBookmarksIdsLongArray.length,
496 Snackbar.LENGTH_LONG)
497 .setAction(R.string.undo, view -> {
498 // Do nothing because everything will be handled by `onDismissed()` below.
500 .addCallback(new Snackbar.Callback() {
501 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
503 public void onDismissed(Snackbar snackbar, int event) {
504 if (event == Snackbar.Callback.DISMISS_EVENT_ACTION) { // The user pushed the undo button.
505 // Update the bookmarks cursor with the current contents of the bookmarks database, including the "deleted" bookmarks.
506 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
508 // Update the list view.
509 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
511 // Re-select the previously selected bookmarks.
512 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
513 bookmarksListView.setItemChecked(selectedBookmarksPositionsSparseBooleanArray.keyAt(i), true);
515 } else { // The snackbar was dismissed without the undo button being pushed.
516 // Delete each selected bookmark.
517 for (long databaseIdLong : selectedBookmarksIdsLongArray) {
518 // Convert `databaseIdLong` to an int.
519 int databaseIdInt = (int) databaseIdLong;
521 // Delete the contents of the folder if the selected bookmark is a folder.
522 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
523 deleteBookmarkFolderContents(databaseIdInt);
526 // Delete the selected bookmark.
527 bookmarksDatabaseHelper.deleteBookmark(databaseIdInt);
530 // Update the display order.
531 for (int i = 0; i < bookmarksListView.getCount(); i++) {
532 // Get the database ID for the current bookmark.
533 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
535 // Move `bookmarksCursor` to the current bookmark position.
536 bookmarksCursor.moveToPosition(i);
538 // Update the display order only if it is not correct in the database.
539 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
540 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
545 // Reset the deleting bookmarks flag.
546 deletingBookmarks = false;
548 // Enable the delete bookmarks menu item.
549 deleteBookmarksMenuItem.setEnabled(true);
551 // Close the activity if back has been pressed.
552 if (closeActivityAfterDismissingSnackbar) {
559 bookmarksDeletedSnackbar.show();
562 case R.id.context_menu_select_all_bookmarks:
563 // Get the total number of bookmarks.
564 int numberOfBookmarks = bookmarksListView.getCount();
567 for (int i = 0; i < numberOfBookmarks; i++) {
568 bookmarksListView.setItemChecked(i, true);
573 // Consume the click.
578 public void onDestroyActionMode(ActionMode mode) {
583 // Get handles for the `FloatingActionButtons`.
584 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
585 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
587 // Set the create new bookmark folder FAB to display the `AlertDialog`.
588 createBookmarkFolderFab.setOnClickListener(v -> {
589 // Create a create bookmark folder dialog.
590 DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(favoriteIconBitmap);
592 // Show the create bookmark folder dialog.
593 createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
596 // Set the create new bookmark FAB to display the `AlertDialog`.
597 createBookmarkFab.setOnClickListener(view -> {
598 // Remove the incorrect lint warning below.
599 assert currentUrl != null;
600 assert currentTitle != null;
602 // Instantiate the create bookmark dialog.
603 DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentUrl, currentTitle, favoriteIconBitmap);
605 // Display the create bookmark dialog.
606 createBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_bookmark));
609 // Restore the state if the app has been restarted.
610 if (savedInstanceState != null) {
611 // Update the bookmarks list view after it has loaded.
612 bookmarksListView.post(() -> {
613 // Get the checked bookmarks array list.
614 ArrayList<Integer> checkedBookmarksArrayList = savedInstanceState.getIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST);
616 // Check each previously checked bookmark in the list view. When the minimum API >= 24 a `forEach()` command can be used instead.
617 if (checkedBookmarksArrayList != null) {
618 for (int i = 0; i < checkedBookmarksArrayList.size(); i++) {
619 bookmarksListView.setItemChecked(checkedBookmarksArrayList.get(i), true);
627 public void onRestart() {
628 // Run the default commands.
631 // Update the list view if returning from the bookmarks database view activity.
632 if (restartFromBookmarksDatabaseViewActivity) {
633 // Load the current folder in the list view.
636 // Reset `restartFromBookmarksDatabaseViewActivity`.
637 restartFromBookmarksDatabaseViewActivity = false;
642 public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
643 // Run the default commands.
644 super.onSaveInstanceState(savedInstanceState);
646 // Get the array of the checked items.
647 SparseBooleanArray checkedBookmarksSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
649 // Create a checked items array list.
650 ArrayList<Integer> checkedBookmarksArrayList = new ArrayList<>();
652 // Add each checked bookmark position to the array list.
653 for (int i = 0; i < checkedBookmarksSparseBooleanArray.size(); i++) {
654 // 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.
655 if (checkedBookmarksSparseBooleanArray.valueAt(i)) {
656 // Add the bookmark position to the checked bookmarks array list.
657 checkedBookmarksArrayList.add(checkedBookmarksSparseBooleanArray.keyAt(i));
661 // Store the checked items array list in the saved instance state.
662 savedInstanceState.putIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST, checkedBookmarksArrayList);
666 public boolean onCreateOptionsMenu(Menu menu) {
668 getMenuInflater().inflate(R.menu.bookmarks_options_menu, menu);
675 public boolean onOptionsItemSelected(MenuItem menuItem) {
676 switch (menuItem.getItemId()) {
677 case android.R.id.home: // The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
678 if (currentFolder.isEmpty()) { // Currently in the home folder.
679 // Run the back commands.
681 } else { // Currently in a subfolder.
682 // Place the former parent folder in `currentFolder`.
683 currentFolder = bookmarksDatabaseHelper.getParentFolderName(currentFolder);
685 // Load the new folder.
690 case R.id.options_menu_select_all_bookmarks:
691 // Get the total number of bookmarks.
692 int numberOfBookmarks = bookmarksListView.getCount();
695 for (int i = 0; i < numberOfBookmarks; i++) {
696 bookmarksListView.setItemChecked(i, true);
700 case R.id.bookmarks_database_view:
701 // Create an intent to launch the bookmarks database view activity.
702 Intent bookmarksDatabaseViewIntent = new Intent(this, BookmarksDatabaseViewActivity.class);
704 // Include the favorite icon byte array to the intent.
705 bookmarksDatabaseViewIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
708 startActivity(bookmarksDatabaseViewIntent);
715 public void onBackPressed() {
716 // 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.
717 if ((bookmarksDeletedSnackbar != null) && bookmarksDeletedSnackbar.isShown()) { // Close the bookmarks deleted snackbar before going home.
718 // Set the close flag.
719 closeActivityAfterDismissingSnackbar = true;
721 // Dismiss the snackbar.
722 bookmarksDeletedSnackbar.dismiss();
723 } else { // Go home immediately.
724 // Update the bookmarks folder for the bookmarks drawer in the main WebView activity.
725 MainWebViewActivity.currentBookmarksFolder = currentFolder;
727 // Close the bookmarks drawer and reload the bookmarks ListView when returning to the main WebView activity.
728 MainWebViewActivity.restartFromBookmarksActivity = true;
730 // Exit the bookmarks activity.
731 super.onBackPressed();
736 public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
737 // Get the alert dialog from the fragment.
738 Dialog dialog = dialogFragment.getDialog();
740 // Remove the incorrect lint warning below that the dialog might be null.
741 assert dialog != null;
743 // Get the views from the dialog fragment.
744 EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext);
745 EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext);
747 // Extract the strings from the edit texts.
748 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
749 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
751 // Create a favorite icon byte array output stream.
752 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
754 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
755 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
757 // Convert the favorite icon byte array stream to a byte array.
758 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
760 // Display the new bookmark below the current items in the (0 indexed) list.
761 int newBookmarkDisplayOrder = bookmarksListView.getCount();
763 // Create the bookmark.
764 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
766 // Update the bookmarks cursor with the current contents of this folder.
767 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
769 // Update the `ListView`.
770 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
772 // Scroll to the new bookmark.
773 bookmarksListView.setSelection(newBookmarkDisplayOrder);
777 public void onCreateBookmarkFolder(DialogFragment dialogFragment, @NonNull Bitmap favoriteIconBitmap) {
778 // Get the dialog from the dialog fragment.
779 Dialog dialog = dialogFragment.getDialog();
781 // Remove the incorrect lint warning below that the dialog might be null.
782 assert dialog != null;
784 // Get handles for the views in the dialog fragment.
785 EditText createFolderNameEditText = dialog.findViewById(R.id.create_folder_name_edittext);
786 RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.create_folder_default_icon_radiobutton);
787 ImageView folderIconImageView = dialog.findViewById(R.id.create_folder_default_icon);
789 // Get new folder name string.
790 String folderNameString = createFolderNameEditText.getText().toString();
792 // Create a folder icon bitmap.
793 Bitmap folderIconBitmap;
795 // Set the folder icon bitmap according to the dialog.
796 if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
797 // Get the default folder icon drawable.
798 Drawable folderIconDrawable = folderIconImageView.getDrawable();
800 // Convert the folder icon drawable to a bitmap drawable.
801 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
803 // Convert the folder icon bitmap drawable to a bitmap.
804 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
805 } else { // Use the WebView favorite icon.
806 // Copy the favorite icon bitmap to the folder icon bitmap.
807 folderIconBitmap = favoriteIconBitmap;
810 // Create a folder icon byte array output stream.
811 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
813 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
814 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
816 // Convert the folder icon byte array stream to a byte array.
817 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
819 // Move all the bookmarks down one in the display order.
820 for (int i = 0; i < bookmarksListView.getCount(); i++) {
821 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
822 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
825 // Create the folder, which will be placed at the top of the `ListView`.
826 bookmarksDatabaseHelper.createFolder(folderNameString, currentFolder, folderIconByteArray);
828 // Update the bookmarks cursor with the current contents of this folder.
829 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
831 // Update the `ListView`.
832 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
834 // Scroll to the new folder.
835 bookmarksListView.setSelection(0);
839 public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
840 // Get the dialog from the dialog fragment.
841 Dialog dialog = dialogFragment.getDialog();
843 // Remove the incorrect lint warning below that the dialog might be null.
844 assert dialog != null;
846 // Get handles for the views from `dialogFragment`.
847 EditText editBookmarkNameEditText = dialog.findViewById(R.id.edit_bookmark_name_edittext);
848 EditText editBookmarkUrlEditText = dialog.findViewById(R.id.edit_bookmark_url_edittext);
849 RadioButton currentBookmarkIconRadioButton = dialog.findViewById(R.id.edit_bookmark_current_icon_radiobutton);
851 // Store the bookmark strings.
852 String bookmarkNameString = editBookmarkNameEditText.getText().toString();
853 String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
855 // Update the bookmark.
856 if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
857 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
858 } else { // Update the bookmark using the WebView favorite icon.
859 // Create a favorite icon byte array output stream.
860 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
862 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
863 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
865 // Convert the favorite icon byte array stream to a byte array.
866 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
868 // Update the bookmark and the favorite icon.
869 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
872 // Close the contextual action mode.
873 contextualActionMode.finish();
875 // Update the bookmarks cursor with the contents of the current folder.
876 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
878 // Update the `ListView`.
879 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
883 public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) {
884 // Get the dialog from the dialog fragment.
885 Dialog dialog = dialogFragment.getDialog();
887 // Remove the incorrect lint warning below that the dialog might be null.
888 assert dialog != null;
890 // Get handles for the views from `dialogFragment`.
891 RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_current_icon_radiobutton);
892 RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_default_icon_radiobutton);
893 ImageView defaultFolderIconImageView = dialog.findViewById(R.id.edit_folder_default_icon_imageview);
894 EditText editFolderNameEditText = dialog.findViewById(R.id.edit_folder_name_edittext);
896 // Get the new folder name.
897 String newFolderNameString = editFolderNameEditText.getText().toString();
899 // Check if the favorite icon has changed.
900 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
901 // Update the name in the database.
902 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
903 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
904 // Create the new folder icon Bitmap.
905 Bitmap folderIconBitmap;
907 // Populate the new folder icon bitmap.
908 if (defaultFolderIconRadioButton.isChecked()) {
909 // Get the default folder icon drawable.
910 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
912 // Convert the folder icon drawable to a bitmap drawable.
913 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
915 // Convert the folder icon bitmap drawable to a bitmap.
916 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
917 } else { // Use the WebView favorite icon.
918 // Copy the favorite icon bitmap to the folder icon bitmap.
919 folderIconBitmap = favoriteIconBitmap;
922 // Create a folder icon byte array output stream.
923 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
925 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
926 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
928 // Convert the folder icon byte array stream to a byte array.
929 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
931 // Update the folder icon in the database.
932 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
933 } else { // The folder icon and the name have changed.
934 // Instantiate the new folder icon `Bitmap`.
935 Bitmap folderIconBitmap;
937 // Populate the new folder icon bitmap.
938 if (defaultFolderIconRadioButton.isChecked()) {
939 // Get the default folder icon drawable.
940 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
942 // Convert the folder icon drawable to a bitmap drawable.
943 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
945 // Convert the folder icon bitmap drawable to a bitmap.
946 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
947 } else { // Use the WebView favorite icon.
948 // Copy the favorite icon bitmap to the folder icon bitmap.
949 folderIconBitmap = favoriteIconBitmap;
952 // Create a folder icon byte array output stream.
953 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
955 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
956 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
958 // Convert the folder icon byte array stream to a byte array.
959 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
961 // Update the folder name and icon in the database.
962 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
965 // Update the bookmarks cursor with the current contents of this folder.
966 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
968 // Update the `ListView`.
969 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
971 // Close the contextual action mode.
972 contextualActionMode.finish();
976 public void onMoveToFolder(DialogFragment dialogFragment) {
977 // Get the dialog from the dialog fragment.
978 Dialog dialog = dialogFragment.getDialog();
980 // Remove the incorrect lint warning below that the dialog might be null.
981 assert dialog != null;
983 // Get a handle for the list view from the dialog.
984 ListView folderListView = dialog.findViewById(R.id.move_to_folder_listview);
986 // Store a long array of the selected folders.
987 long[] newFolderLongArray = folderListView.getCheckedItemIds();
989 // Get the new folder database ID. Only one folder will be selected.
990 int newFolderDatabaseId = (int) newFolderLongArray[0];
992 // Instantiate `newFolderName`.
993 String newFolderName;
995 // Set the new folder name.
996 if (newFolderDatabaseId == 0) {
997 // The new folder is the home folder, represented as `""` in the database.
1000 // Get the new folder name from the database.
1001 newFolderName = bookmarksDatabaseHelper.getFolderName(newFolderDatabaseId);
1004 // Get a long array with the the database ID of the selected bookmarks.
1005 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
1007 // Move each of the selected bookmarks to the new folder.
1008 for (long databaseIdLong : selectedBookmarksLongArray) {
1009 // Get `databaseIdInt` for each selected bookmark.
1010 int databaseIdInt = (int) databaseIdLong;
1012 // Move the selected bookmark to the new folder.
1013 bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderName);
1016 // Update the bookmarks cursor with the current contents of this folder.
1017 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
1019 // Update the `ListView`.
1020 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
1022 // Close the contextual app bar.
1023 contextualActionMode.finish();
1026 private void deleteBookmarkFolderContents(int databaseId) {
1027 // Get the name of the folder.
1028 String folderName = bookmarksDatabaseHelper.getFolderName(databaseId);
1030 // Get the contents of the folder.
1031 Cursor folderCursor = bookmarksDatabaseHelper.getBookmarkIDs(folderName);
1033 // Delete each of the bookmarks in the folder.
1034 for (int i = 0; i < folderCursor.getCount(); i++) {
1035 // Move `folderCursor` to the current row.
1036 folderCursor.moveToPosition(i);
1038 // Get the database ID of the item.
1039 int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndex(BookmarksDatabaseHelper._ID));
1041 // If this is a folder, recursively delete the contents first.
1042 if (bookmarksDatabaseHelper.isFolder(itemDatabaseId)) {
1043 deleteBookmarkFolderContents(itemDatabaseId);
1046 // Delete the bookmark.
1047 bookmarksDatabaseHelper.deleteBookmark(itemDatabaseId);
1051 private void updateMoveIcons() {
1052 // Get a long array of the selected bookmarks.
1053 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
1055 // Get the database IDs for the first, last, and selected bookmarks.
1056 int selectedBookmarkDatabaseId = (int) selectedBookmarksLongArray[0];
1057 int firstBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(0);
1058 // bookmarksListView is 0 indexed.
1059 int lastBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(bookmarksListView.getCount() - 1);
1061 // Get the current theme status.
1062 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
1064 // Update the move bookmark up `MenuItem`.
1065 if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) { // The selected bookmark is in the first position.
1066 // Disable the move bookmark up `MenuItem`.
1067 moveBookmarkUpMenuItem.setEnabled(false);
1069 // Set the move bookmark up icon to be ghosted.
1070 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_disabled);
1071 } else { // The selected bookmark is not in the first position.
1072 // Enable the move bookmark up menu item.
1073 moveBookmarkUpMenuItem.setEnabled(true);
1075 // Set the icon according to the theme.
1076 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
1077 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled_night);
1079 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled_day);
1083 // Update the move bookmark down `MenuItem`.
1084 if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) { // The selected bookmark is in the last position.
1085 // Disable the move bookmark down `MenuItem`.
1086 moveBookmarkDownMenuItem.setEnabled(false);
1088 // Set the move bookmark down icon to be ghosted.
1089 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_disabled);
1090 } else { // The selected bookmark is not in the last position.
1091 // Enable the move bookmark down `MenuItem`.
1092 moveBookmarkDownMenuItem.setEnabled(true);
1094 // Set the icon according to the theme.
1095 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
1096 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled_night);
1098 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled_day);
1103 private void scrollBookmarks(int selectedBookmarkPosition) {
1104 // Get the first and last visible bookmark positions.
1105 int firstVisibleBookmarkPosition = bookmarksListView.getFirstVisiblePosition();
1106 int lastVisibleBookmarkPosition = bookmarksListView.getLastVisiblePosition();
1108 // Calculate the number of bookmarks per screen.
1109 int numberOfBookmarksPerScreen = lastVisibleBookmarkPosition - firstVisibleBookmarkPosition;
1111 // Scroll with the moved bookmark if necessary.
1112 if (selectedBookmarkPosition <= firstVisibleBookmarkPosition) { // The selected bookmark position is at or above the top of the screen.
1113 // Scroll to the selected bookmark position.
1114 bookmarksListView.setSelection(selectedBookmarkPosition);
1115 } else if (selectedBookmarkPosition >= (lastVisibleBookmarkPosition - 1)) { // The selected bookmark is at or below the bottom of the screen.
1116 // 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.
1117 // `+1` assures that the entire bookmark will be displayed in situations where only a partial bookmark fits at the bottom of the list view.
1118 bookmarksListView.setSelection(selectedBookmarkPosition - numberOfBookmarksPerScreen + 1);
1122 private void loadFolder() {
1123 // Update bookmarks cursor with the contents of the bookmarks database for the current folder.
1124 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
1126 // Setup a `CursorAdapter`. `this` specifies the `Context`. `false` disables `autoRequery`.
1127 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
1129 public View newView(Context context, Cursor cursor, ViewGroup parent) {
1130 // Inflate the individual item layout. `false` does not attach it to the root.
1131 return getLayoutInflater().inflate(R.layout.bookmarks_activity_item_linearlayout, parent, false);
1135 public void bindView(View view, Context context, Cursor cursor) {
1136 // Get handles for the views.
1137 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
1138 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
1140 // Get the favorite icon byte array from the `Cursor`.
1141 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
1143 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
1144 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
1146 // Display the bitmap in `bookmarkFavoriteIcon`.
1147 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
1149 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
1150 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
1151 bookmarkNameTextView.setText(bookmarkNameString);
1153 // Make the font bold for folders.
1154 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
1155 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
1156 } else { // Reset the font to default for normal bookmarks.
1157 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
1162 // Populate the list view with the adapter.
1163 bookmarksListView.setAdapter(bookmarksCursorAdapter);
1165 // Set the `AppBar` title.
1166 if (currentFolder.isEmpty()) {
1167 appBar.setTitle(R.string.bookmarks);
1169 appBar.setTitle(currentFolder);
1174 public void onDestroy() {
1175 // Close the bookmarks cursor and database.
1176 bookmarksCursor.close();
1177 bookmarksDatabaseHelper.close();
1179 // Run the default commands.