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 // Only process commands if at least one bookmark is selected. Otherwise, a context menu with 0 selected bookmarks is briefly displayed.
291 if (numberOfSelectedBookmarks > 0) {
292 // Adjust the ActionMode and the menu according to the number of selected bookmarks.
293 if (numberOfSelectedBookmarks == 1) { // One bookmark is selected.
294 // List the number of selected bookmarks in the subtitle.
295 mode.setSubtitle(getString(R.string.selected) + " 1");
297 // Show the `Move Up`, `Move Down`, and `Edit` options.
298 moveBookmarkUpMenuItem.setVisible(true);
299 moveBookmarkDownMenuItem.setVisible(true);
300 editBookmarkMenuItem.setVisible(true);
302 // Update the enabled status of the move icons.
304 } else { // More than one bookmark is selected.
305 // List the number of selected bookmarks in the subtitle.
306 mode.setSubtitle(getString(R.string.selected) + " " + numberOfSelectedBookmarks);
308 // Hide non-applicable `MenuItems`.
309 moveBookmarkUpMenuItem.setVisible(false);
310 moveBookmarkDownMenuItem.setVisible(false);
311 editBookmarkMenuItem.setVisible(false);
314 // Do not show the select all menu item if all the bookmarks are already checked.
315 if (bookmarksListView.getCheckedItemCount() == bookmarksListView.getCount()) {
316 selectAllBookmarksMenuItem.setVisible(false);
318 selectAllBookmarksMenuItem.setVisible(true);
324 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
325 // Declare the common variables.
326 int selectedBookmarkNewPosition;
327 final SparseBooleanArray selectedBookmarksPositionsSparseBooleanArray;
329 // Initialize the selected bookmark position.
330 int selectedBookmarkPosition = 0;
332 switch (item.getItemId()) {
333 case R.id.move_bookmark_up:
334 // Get the array of checked bookmark positions.
335 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
337 // 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`.
338 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
339 // Check to see if the value for the bookmark is true, meaning it is currently selected.
340 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
341 // Only one bookmark should have a value of `true` when move bookmark up is enabled.
342 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
346 // Calculate the new position of the selected bookmark.
347 selectedBookmarkNewPosition = selectedBookmarkPosition - 1;
349 // Iterate through the bookmarks.
350 for (int i = 0; i < bookmarksListView.getCount(); i++) {
351 // Get the database ID for the current bookmark.
352 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
354 // Update the display order for the current bookmark.
355 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
356 // Move the current bookmark up one.
357 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1);
358 } else if ((i + 1) == selectedBookmarkPosition){ // The current bookmark is immediately above the selected bookmark.
359 // Move the current bookmark down one.
360 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1);
361 } else { // The current bookmark is not changing positions.
362 // Move `bookmarksCursor` to the current bookmark position.
363 bookmarksCursor.moveToPosition(i);
365 // Update the display order only if it is not correct in the database.
366 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
367 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
372 // Update the bookmarks cursor with the current contents of the bookmarks database.
373 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
375 // Update the `ListView`.
376 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
378 // Scroll with the bookmark.
379 scrollBookmarks(selectedBookmarkNewPosition);
381 // Update the enabled status of the move icons.
385 case R.id.move_bookmark_down:
386 // Get the array of checked bookmark positions.
387 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
389 // 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`.
390 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
391 // Check to see if the value for the bookmark is true, meaning it is currently selected.
392 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
393 // Only one bookmark should have a value of `true` when move bookmark down is enabled.
394 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
398 // Calculate the new position of the selected bookmark.
399 selectedBookmarkNewPosition = selectedBookmarkPosition + 1;
401 // Iterate through the bookmarks.
402 for (int i = 0; i <bookmarksListView.getCount(); i++) {
403 // Get the database ID for the current bookmark.
404 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
406 // Update the display order for the current bookmark.
407 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
408 // Move the current bookmark down one.
409 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1);
410 } else if ((i - 1) == selectedBookmarkPosition) { // The current bookmark is immediately below the selected bookmark.
411 // Move the bookmark below the selected bookmark up one.
412 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1);
413 } else { // The current bookmark is not changing positions.
414 // Move `bookmarksCursor` to the current bookmark position.
415 bookmarksCursor.moveToPosition(i);
417 // Update the display order only if it is not correct in the database.
418 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
419 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
424 // Update the bookmarks cursor with the current contents of the bookmarks database.
425 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
427 // Update the `ListView`.
428 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
430 // Scroll with the bookmark.
431 scrollBookmarks(selectedBookmarkNewPosition);
433 // Update the enabled status of the move icons.
437 case R.id.move_to_folder:
438 // Store `checkedItemIds` for use by the `AlertDialog`.
439 checkedItemIds = bookmarksListView.getCheckedItemIds();
441 // Show the `MoveToFolderDialog` `AlertDialog` and name the instance `@string/move_to_folder
442 DialogFragment moveToFolderDialog = new MoveToFolderDialog();
443 moveToFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.move_to_folder));
446 case R.id.edit_bookmark:
447 // Get the array of checked bookmark positions.
448 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
450 // 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`.
451 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
452 // Check to see if the value for the bookmark is true, meaning it is currently selected.
453 if (selectedBookmarksPositionsSparseBooleanArray.valueAt(i)) {
454 // Only one bookmark should have a value of `true` when move edit bookmark is enabled.
455 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(i);
459 // Move the `Cursor` to the selected position and find out if it is a folder.
460 bookmarksCursor.moveToPosition(selectedBookmarkPosition);
461 boolean isFolder = (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1);
463 // Get the selected bookmark database ID.
464 int databaseId = bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper._ID));
466 // Show the edit bookmark or edit bookmark folder dialog.
468 // Save the current folder name, which is used in `onSaveBookmarkFolder()`.
469 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
471 // Show the edit bookmark folder dialog.
472 DialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, favoriteIconBitmap);
473 editFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_folder));
475 // Show the edit bookmark dialog.
476 DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, favoriteIconBitmap);
477 editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark));
481 case R.id.delete_bookmark:
482 // Set the deleting bookmarks flag, which prevents the delete menu item from being enabled until the current process finishes.
483 deletingBookmarks = true;
485 // Get an array of the selected row IDs.
486 final long[] selectedBookmarksIdsLongArray = bookmarksListView.getCheckedItemIds();
488 // 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.
489 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions().clone();
491 // Update the bookmarks cursor with the current contents of the bookmarks database except for the specified database IDs.
492 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(selectedBookmarksIdsLongArray, currentFolder);
494 // Update the list view.
495 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
497 // Create a Snackbar with the number of deleted bookmarks.
498 bookmarksDeletedSnackbar = Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.bookmarks_deleted) + " " + selectedBookmarksIdsLongArray.length,
499 Snackbar.LENGTH_LONG)
500 .setAction(R.string.undo, view -> {
501 // Do nothing because everything will be handled by `onDismissed()` below.
503 .addCallback(new Snackbar.Callback() {
504 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
506 public void onDismissed(Snackbar snackbar, int event) {
507 if (event == Snackbar.Callback.DISMISS_EVENT_ACTION) { // The user pushed the undo button.
508 // Update the bookmarks cursor with the current contents of the bookmarks database, including the "deleted" bookmarks.
509 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
511 // Update the list view.
512 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
514 // Re-select the previously selected bookmarks.
515 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
516 bookmarksListView.setItemChecked(selectedBookmarksPositionsSparseBooleanArray.keyAt(i), true);
518 } else { // The snackbar was dismissed without the undo button being pushed.
519 // Delete each selected bookmark.
520 for (long databaseIdLong : selectedBookmarksIdsLongArray) {
521 // Convert `databaseIdLong` to an int.
522 int databaseIdInt = (int) databaseIdLong;
524 // Delete the contents of the folder if the selected bookmark is a folder.
525 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
526 deleteBookmarkFolderContents(databaseIdInt);
529 // Delete the selected bookmark.
530 bookmarksDatabaseHelper.deleteBookmark(databaseIdInt);
533 // Update the display order.
534 for (int i = 0; i < bookmarksListView.getCount(); i++) {
535 // Get the database ID for the current bookmark.
536 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
538 // Move `bookmarksCursor` to the current bookmark position.
539 bookmarksCursor.moveToPosition(i);
541 // Update the display order only if it is not correct in the database.
542 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
543 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
548 // Reset the deleting bookmarks flag.
549 deletingBookmarks = false;
551 // Enable the delete bookmarks menu item.
552 deleteBookmarksMenuItem.setEnabled(true);
554 // Close the activity if back has been pressed.
555 if (closeActivityAfterDismissingSnackbar) {
562 bookmarksDeletedSnackbar.show();
565 case R.id.context_menu_select_all_bookmarks:
566 // Get the total number of bookmarks.
567 int numberOfBookmarks = bookmarksListView.getCount();
570 for (int i = 0; i < numberOfBookmarks; i++) {
571 bookmarksListView.setItemChecked(i, true);
576 // Consume the click.
581 public void onDestroyActionMode(ActionMode mode) {
586 // Get handles for the `FloatingActionButtons`.
587 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
588 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
590 // Set the create new bookmark folder FAB to display the `AlertDialog`.
591 createBookmarkFolderFab.setOnClickListener(v -> {
592 // Create a create bookmark folder dialog.
593 DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(favoriteIconBitmap);
595 // Show the create bookmark folder dialog.
596 createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
599 // Set the create new bookmark FAB to display the `AlertDialog`.
600 createBookmarkFab.setOnClickListener(view -> {
601 // Remove the incorrect lint warning below.
602 assert currentUrl != null;
603 assert currentTitle != null;
605 // Instantiate the create bookmark dialog.
606 DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentUrl, currentTitle, favoriteIconBitmap);
608 // Display the create bookmark dialog.
609 createBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_bookmark));
612 // Restore the state if the app has been restarted.
613 if (savedInstanceState != null) {
614 // Update the bookmarks list view after it has loaded.
615 bookmarksListView.post(() -> {
616 // Get the checked bookmarks array list.
617 ArrayList<Integer> checkedBookmarksArrayList = savedInstanceState.getIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST);
619 // Check each previously checked bookmark in the list view. When the minimum API >= 24 a `forEach()` command can be used instead.
620 if (checkedBookmarksArrayList != null) {
621 for (int i = 0; i < checkedBookmarksArrayList.size(); i++) {
622 bookmarksListView.setItemChecked(checkedBookmarksArrayList.get(i), true);
630 public void onRestart() {
631 // Run the default commands.
634 // Update the list view if returning from the bookmarks database view activity.
635 if (restartFromBookmarksDatabaseViewActivity) {
636 // Load the current folder in the list view.
639 // Reset `restartFromBookmarksDatabaseViewActivity`.
640 restartFromBookmarksDatabaseViewActivity = false;
645 public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
646 // Run the default commands.
647 super.onSaveInstanceState(savedInstanceState);
649 // Get the array of the checked items.
650 SparseBooleanArray checkedBookmarksSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
652 // Create a checked items array list.
653 ArrayList<Integer> checkedBookmarksArrayList = new ArrayList<>();
655 // Add each checked bookmark position to the array list.
656 for (int i = 0; i < checkedBookmarksSparseBooleanArray.size(); i++) {
657 // 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.
658 if (checkedBookmarksSparseBooleanArray.valueAt(i)) {
659 // Add the bookmark position to the checked bookmarks array list.
660 checkedBookmarksArrayList.add(checkedBookmarksSparseBooleanArray.keyAt(i));
664 // Store the checked items array list in the saved instance state.
665 savedInstanceState.putIntegerArrayList(CHECKED_BOOKMARKS_ARRAY_LIST, checkedBookmarksArrayList);
669 public boolean onCreateOptionsMenu(Menu menu) {
671 getMenuInflater().inflate(R.menu.bookmarks_options_menu, menu);
678 public boolean onOptionsItemSelected(MenuItem menuItem) {
679 switch (menuItem.getItemId()) {
680 case android.R.id.home: // The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
681 if (currentFolder.isEmpty()) { // Currently in the home folder.
682 // Run the back commands.
684 } else { // Currently in a subfolder.
685 // Place the former parent folder in `currentFolder`.
686 currentFolder = bookmarksDatabaseHelper.getParentFolderName(currentFolder);
688 // Load the new folder.
693 case R.id.options_menu_select_all_bookmarks:
694 // Get the total number of bookmarks.
695 int numberOfBookmarks = bookmarksListView.getCount();
698 for (int i = 0; i < numberOfBookmarks; i++) {
699 bookmarksListView.setItemChecked(i, true);
703 case R.id.bookmarks_database_view:
704 // Create an intent to launch the bookmarks database view activity.
705 Intent bookmarksDatabaseViewIntent = new Intent(this, BookmarksDatabaseViewActivity.class);
707 // Include the favorite icon byte array to the intent.
708 bookmarksDatabaseViewIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
711 startActivity(bookmarksDatabaseViewIntent);
718 public void onBackPressed() {
719 // 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.
720 if ((bookmarksDeletedSnackbar != null) && bookmarksDeletedSnackbar.isShown()) { // Close the bookmarks deleted snackbar before going home.
721 // Set the close flag.
722 closeActivityAfterDismissingSnackbar = true;
724 // Dismiss the snackbar.
725 bookmarksDeletedSnackbar.dismiss();
726 } else { // Go home immediately.
727 // Update the bookmarks folder for the bookmarks drawer in the main WebView activity.
728 MainWebViewActivity.currentBookmarksFolder = currentFolder;
730 // Close the bookmarks drawer and reload the bookmarks ListView when returning to the main WebView activity.
731 MainWebViewActivity.restartFromBookmarksActivity = true;
733 // Exit the bookmarks activity.
734 super.onBackPressed();
739 public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
740 // Get the alert dialog from the fragment.
741 Dialog dialog = dialogFragment.getDialog();
743 // Remove the incorrect lint warning below that the dialog might be null.
744 assert dialog != null;
746 // Get the views from the dialog fragment.
747 EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext);
748 EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext);
750 // Extract the strings from the edit texts.
751 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
752 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
754 // Create a favorite icon byte array output stream.
755 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
757 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
758 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
760 // Convert the favorite icon byte array stream to a byte array.
761 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
763 // Display the new bookmark below the current items in the (0 indexed) list.
764 int newBookmarkDisplayOrder = bookmarksListView.getCount();
766 // Create the bookmark.
767 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
769 // Update the bookmarks cursor with the current contents of this folder.
770 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
772 // Update the `ListView`.
773 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
775 // Scroll to the new bookmark.
776 bookmarksListView.setSelection(newBookmarkDisplayOrder);
780 public void onCreateBookmarkFolder(DialogFragment dialogFragment, @NonNull Bitmap favoriteIconBitmap) {
781 // Get the dialog from the dialog fragment.
782 Dialog dialog = dialogFragment.getDialog();
784 // Remove the incorrect lint warning below that the dialog might be null.
785 assert dialog != null;
787 // Get handles for the views in the dialog fragment.
788 EditText createFolderNameEditText = dialog.findViewById(R.id.create_folder_name_edittext);
789 RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.create_folder_default_icon_radiobutton);
790 ImageView folderIconImageView = dialog.findViewById(R.id.create_folder_default_icon);
792 // Get new folder name string.
793 String folderNameString = createFolderNameEditText.getText().toString();
795 // Create a folder icon bitmap.
796 Bitmap folderIconBitmap;
798 // Set the folder icon bitmap according to the dialog.
799 if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
800 // Get the default folder icon drawable.
801 Drawable folderIconDrawable = folderIconImageView.getDrawable();
803 // Convert the folder icon drawable to a bitmap drawable.
804 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
806 // Convert the folder icon bitmap drawable to a bitmap.
807 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
808 } else { // Use the WebView favorite icon.
809 // Copy the favorite icon bitmap to the folder icon bitmap.
810 folderIconBitmap = favoriteIconBitmap;
813 // Create a folder icon byte array output stream.
814 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
816 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
817 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
819 // Convert the folder icon byte array stream to a byte array.
820 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
822 // Move all the bookmarks down one in the display order.
823 for (int i = 0; i < bookmarksListView.getCount(); i++) {
824 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
825 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
828 // Create the folder, which will be placed at the top of the `ListView`.
829 bookmarksDatabaseHelper.createFolder(folderNameString, currentFolder, folderIconByteArray);
831 // Update the bookmarks cursor with the current contents of this folder.
832 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
834 // Update the `ListView`.
835 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
837 // Scroll to the new folder.
838 bookmarksListView.setSelection(0);
842 public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
843 // Get the dialog from the dialog fragment.
844 Dialog dialog = dialogFragment.getDialog();
846 // Remove the incorrect lint warning below that the dialog might be null.
847 assert dialog != null;
849 // Get handles for the views from `dialogFragment`.
850 EditText editBookmarkNameEditText = dialog.findViewById(R.id.edit_bookmark_name_edittext);
851 EditText editBookmarkUrlEditText = dialog.findViewById(R.id.edit_bookmark_url_edittext);
852 RadioButton currentBookmarkIconRadioButton = dialog.findViewById(R.id.edit_bookmark_current_icon_radiobutton);
854 // Store the bookmark strings.
855 String bookmarkNameString = editBookmarkNameEditText.getText().toString();
856 String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
858 // Update the bookmark.
859 if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
860 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
861 } else { // Update the bookmark using the WebView favorite icon.
862 // Create a favorite icon byte array output stream.
863 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
865 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
866 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
868 // Convert the favorite icon byte array stream to a byte array.
869 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
871 // Update the bookmark and the favorite icon.
872 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
875 // Close the contextual action mode.
876 contextualActionMode.finish();
878 // Update the bookmarks cursor with the contents of the current folder.
879 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
881 // Update the `ListView`.
882 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
886 public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) {
887 // Get the dialog from the dialog fragment.
888 Dialog dialog = dialogFragment.getDialog();
890 // Remove the incorrect lint warning below that the dialog might be null.
891 assert dialog != null;
893 // Get handles for the views from `dialogFragment`.
894 RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_current_icon_radiobutton);
895 RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_default_icon_radiobutton);
896 ImageView defaultFolderIconImageView = dialog.findViewById(R.id.edit_folder_default_icon_imageview);
897 EditText editFolderNameEditText = dialog.findViewById(R.id.edit_folder_name_edittext);
899 // Get the new folder name.
900 String newFolderNameString = editFolderNameEditText.getText().toString();
902 // Check if the favorite icon has changed.
903 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
904 // Update the name in the database.
905 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
906 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
907 // Create the new folder icon Bitmap.
908 Bitmap folderIconBitmap;
910 // Populate the new folder icon bitmap.
911 if (defaultFolderIconRadioButton.isChecked()) {
912 // Get the default folder icon drawable.
913 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
915 // Convert the folder icon drawable to a bitmap drawable.
916 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
918 // Convert the folder icon bitmap drawable to a bitmap.
919 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
920 } else { // Use the WebView favorite icon.
921 // Copy the favorite icon bitmap to the folder icon bitmap.
922 folderIconBitmap = favoriteIconBitmap;
925 // Create a folder icon byte array output stream.
926 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
928 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
929 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
931 // Convert the folder icon byte array stream to a byte array.
932 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
934 // Update the folder icon in the database.
935 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
936 } else { // The folder icon and the name have changed.
937 // Instantiate the new folder icon `Bitmap`.
938 Bitmap folderIconBitmap;
940 // Populate the new folder icon bitmap.
941 if (defaultFolderIconRadioButton.isChecked()) {
942 // Get the default folder icon drawable.
943 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
945 // Convert the folder icon drawable to a bitmap drawable.
946 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
948 // Convert the folder icon bitmap drawable to a bitmap.
949 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
950 } else { // Use the WebView favorite icon.
951 // Copy the favorite icon bitmap to the folder icon bitmap.
952 folderIconBitmap = favoriteIconBitmap;
955 // Create a folder icon byte array output stream.
956 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
958 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
959 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
961 // Convert the folder icon byte array stream to a byte array.
962 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
964 // Update the folder name and icon in the database.
965 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
968 // Update the bookmarks cursor with the current contents of this folder.
969 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
971 // Update the `ListView`.
972 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
974 // Close the contextual action mode.
975 contextualActionMode.finish();
979 public void onMoveToFolder(DialogFragment dialogFragment) {
980 // Get the dialog from the dialog fragment.
981 Dialog dialog = dialogFragment.getDialog();
983 // Remove the incorrect lint warning below that the dialog might be null.
984 assert dialog != null;
986 // Get a handle for the list view from the dialog.
987 ListView folderListView = dialog.findViewById(R.id.move_to_folder_listview);
989 // Store a long array of the selected folders.
990 long[] newFolderLongArray = folderListView.getCheckedItemIds();
992 // Get the new folder database ID. Only one folder will be selected.
993 int newFolderDatabaseId = (int) newFolderLongArray[0];
995 // Instantiate `newFolderName`.
996 String newFolderName;
998 // Set the new folder name.
999 if (newFolderDatabaseId == 0) {
1000 // The new folder is the home folder, represented as `""` in the database.
1003 // Get the new folder name from the database.
1004 newFolderName = bookmarksDatabaseHelper.getFolderName(newFolderDatabaseId);
1007 // Get a long array with the the database ID of the selected bookmarks.
1008 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
1010 // Move each of the selected bookmarks to the new folder.
1011 for (long databaseIdLong : selectedBookmarksLongArray) {
1012 // Get `databaseIdInt` for each selected bookmark.
1013 int databaseIdInt = (int) databaseIdLong;
1015 // Move the selected bookmark to the new folder.
1016 bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderName);
1019 // Update the bookmarks cursor with the current contents of this folder.
1020 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
1022 // Update the `ListView`.
1023 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
1025 // Close the contextual app bar.
1026 contextualActionMode.finish();
1029 private void deleteBookmarkFolderContents(int databaseId) {
1030 // Get the name of the folder.
1031 String folderName = bookmarksDatabaseHelper.getFolderName(databaseId);
1033 // Get the contents of the folder.
1034 Cursor folderCursor = bookmarksDatabaseHelper.getBookmarkIDs(folderName);
1036 // Delete each of the bookmarks in the folder.
1037 for (int i = 0; i < folderCursor.getCount(); i++) {
1038 // Move `folderCursor` to the current row.
1039 folderCursor.moveToPosition(i);
1041 // Get the database ID of the item.
1042 int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndex(BookmarksDatabaseHelper._ID));
1044 // If this is a folder, recursively delete the contents first.
1045 if (bookmarksDatabaseHelper.isFolder(itemDatabaseId)) {
1046 deleteBookmarkFolderContents(itemDatabaseId);
1049 // Delete the bookmark.
1050 bookmarksDatabaseHelper.deleteBookmark(itemDatabaseId);
1054 private void updateMoveIcons() {
1055 // Get a long array of the selected bookmarks.
1056 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
1058 // Get the database IDs for the first, last, and selected bookmarks.
1059 int selectedBookmarkDatabaseId = (int) selectedBookmarksLongArray[0];
1060 int firstBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(0);
1061 // bookmarksListView is 0 indexed.
1062 int lastBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(bookmarksListView.getCount() - 1);
1064 // Get the current theme status.
1065 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
1067 // Update the move bookmark up `MenuItem`.
1068 if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) { // The selected bookmark is in the first position.
1069 // Disable the move bookmark up `MenuItem`.
1070 moveBookmarkUpMenuItem.setEnabled(false);
1072 // Set the move bookmark up icon to be ghosted.
1073 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_disabled);
1074 } else { // The selected bookmark is not in the first position.
1075 // Enable the move bookmark up menu item.
1076 moveBookmarkUpMenuItem.setEnabled(true);
1078 // Set the icon according to the theme.
1079 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
1080 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled_night);
1082 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled_day);
1086 // Update the move bookmark down `MenuItem`.
1087 if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) { // The selected bookmark is in the last position.
1088 // Disable the move bookmark down `MenuItem`.
1089 moveBookmarkDownMenuItem.setEnabled(false);
1091 // Set the move bookmark down icon to be ghosted.
1092 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_disabled);
1093 } else { // The selected bookmark is not in the last position.
1094 // Enable the move bookmark down `MenuItem`.
1095 moveBookmarkDownMenuItem.setEnabled(true);
1097 // Set the icon according to the theme.
1098 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
1099 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled_night);
1101 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled_day);
1106 private void scrollBookmarks(int selectedBookmarkPosition) {
1107 // Get the first and last visible bookmark positions.
1108 int firstVisibleBookmarkPosition = bookmarksListView.getFirstVisiblePosition();
1109 int lastVisibleBookmarkPosition = bookmarksListView.getLastVisiblePosition();
1111 // Calculate the number of bookmarks per screen.
1112 int numberOfBookmarksPerScreen = lastVisibleBookmarkPosition - firstVisibleBookmarkPosition;
1114 // Scroll with the moved bookmark if necessary.
1115 if (selectedBookmarkPosition <= firstVisibleBookmarkPosition) { // The selected bookmark position is at or above the top of the screen.
1116 // Scroll to the selected bookmark position.
1117 bookmarksListView.setSelection(selectedBookmarkPosition);
1118 } else if (selectedBookmarkPosition >= (lastVisibleBookmarkPosition - 1)) { // The selected bookmark is at or below the bottom of the screen.
1119 // 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.
1120 // `+1` assures that the entire bookmark will be displayed in situations where only a partial bookmark fits at the bottom of the list view.
1121 bookmarksListView.setSelection(selectedBookmarkPosition - numberOfBookmarksPerScreen + 1);
1125 private void loadFolder() {
1126 // Update bookmarks cursor with the contents of the bookmarks database for the current folder.
1127 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
1129 // Setup a `CursorAdapter`. `this` specifies the `Context`. `false` disables `autoRequery`.
1130 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
1132 public View newView(Context context, Cursor cursor, ViewGroup parent) {
1133 // Inflate the individual item layout. `false` does not attach it to the root.
1134 return getLayoutInflater().inflate(R.layout.bookmarks_activity_item_linearlayout, parent, false);
1138 public void bindView(View view, Context context, Cursor cursor) {
1139 // Get handles for the views.
1140 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
1141 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
1143 // Get the favorite icon byte array from the `Cursor`.
1144 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
1146 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
1147 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
1149 // Display the bitmap in `bookmarkFavoriteIcon`.
1150 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
1152 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
1153 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
1154 bookmarkNameTextView.setText(bookmarkNameString);
1156 // Make the font bold for folders.
1157 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
1158 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
1159 } else { // Reset the font to default for normal bookmarks.
1160 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
1165 // Populate the list view with the adapter.
1166 bookmarksListView.setAdapter(bookmarksCursorAdapter);
1168 // Set the `AppBar` title.
1169 if (currentFolder.isEmpty()) {
1170 appBar.setTitle(R.string.bookmarks);
1172 appBar.setTitle(currentFolder);
1177 public void onDestroy() {
1178 // Close the bookmarks cursor and database.
1179 bookmarksCursor.close();
1180 bookmarksDatabaseHelper.close();
1182 // Run the default commands.