2 * Copyright © 2016-2019 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.content.Context;
25 import android.content.Intent;
26 import android.database.Cursor;
27 import android.database.CursorWindow;
28 import android.database.sqlite.SQLiteCursor;
29 import android.graphics.Bitmap;
30 import android.graphics.BitmapFactory;
31 import android.graphics.Typeface;
32 import android.graphics.drawable.BitmapDrawable;
33 import android.graphics.drawable.Drawable;
34 import android.os.Build;
35 import android.os.Bundle;
36 import android.util.SparseBooleanArray;
37 import android.view.ActionMode;
38 import android.view.Menu;
39 import android.view.MenuItem;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.view.WindowManager;
43 import android.widget.AbsListView;
44 import android.widget.CursorAdapter;
45 import android.widget.EditText;
46 import android.widget.ImageView;
47 import android.widget.ListView;
48 import android.widget.RadioButton;
49 import android.widget.TextView;
51 import androidx.appcompat.app.ActionBar;
52 import androidx.appcompat.app.AppCompatActivity;
53 import androidx.appcompat.widget.Toolbar; // The AndroidX toolbar must be used until the minimum API is >= 21.
54 import androidx.core.app.NavUtils;
55 import androidx.fragment.app.DialogFragment;
57 import com.google.android.material.floatingactionbutton.FloatingActionButton;
58 import com.google.android.material.snackbar.Snackbar;
60 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
61 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
62 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
63 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
64 import com.stoutner.privacybrowser.dialogs.MoveToFolderDialog;
65 import com.stoutner.privacybrowser.R;
66 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
68 import java.io.ByteArrayOutputStream;
70 public class BookmarksActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, EditBookmarkDialog.EditBookmarkListener,
71 EditBookmarkFolderDialog.EditBookmarkFolderListener, MoveToFolderDialog.MoveToFolderListener {
73 // `currentFolder` is public static so it can be accessed from `MoveToFolderDialog` and `BookmarksDatabaseViewActivity`.
74 // It is used in `onCreate`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`,
75 // and `loadFolder()`.
76 public static String currentFolder;
78 // `checkedItemIds` is public static so it can be accessed from `MoveToFolderDialog`. It is also used in `onCreate()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`,
79 // and `updateMoveIcons()`.
80 public static long[] checkedItemIds;
82 // `restartFromBookmarksDatabaseViewActivity` is public static so it can be accessed from `BookmarksDatabaseViewActivity`. It is also used in `onRestart()`.
83 public static boolean restartFromBookmarksDatabaseViewActivity;
86 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onOptionsItemSelected()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`,
87 // `onMoveToFolder()`, `deleteBookmarkFolderContents()`, `loadFolder()`, and `onDestroy()`.
88 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
90 // `bookmarksListView` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`,
91 // `updateMoveIcons()`, `scrollBookmarks()`, and `loadFolder()`.
92 private ListView bookmarksListView;
94 // `bookmarksCursor` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, `deleteBookmarkFolderContents()`,
95 // `loadFolder()`, and `onDestroy()`.
96 // TODO This should be switched back to a `Cursor` after the release of 2.17.1.
97 private SQLiteCursor bookmarksCursor;
99 // `bookmarksCursorAdapter` is used in `onCreate(), `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, and `onLoadFolder()`.
100 private CursorAdapter bookmarksCursorAdapter;
102 // `contextualActionMode` is used in `onCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()` and `onMoveToFolder()`.
103 private ActionMode contextualActionMode;
105 // `appBar` is used in `onCreate()` and `loadFolder()`.
106 private ActionBar appBar;
108 // `oldFolderName` is used in `onCreate()` and `onSaveBookmarkFolder()`.
109 private String oldFolderNameString;
111 // `moveBookmarkUpMenuItem` is used in `onCreate()` and `updateMoveIcons()`.
112 private MenuItem moveBookmarkUpMenuItem;
114 // `moveBookmarkDownMenuItem` is used in `onCreate()` and `updateMoveIcons()`.
115 private MenuItem moveBookmarkDownMenuItem;
117 // `bookmarksDeletedSnackbar` is used in `onCreate()`, `onOptionsItemSelected()`, and `onBackPressed()`.
118 private Snackbar bookmarksDeletedSnackbar;
120 // `closeActivityAfterDismissingSnackbar` is used in `onCreate()`, `onOptionsItemSelected()`, and `onBackPressed()`.
121 private boolean closeActivityAfterDismissingSnackbar;
124 protected void onCreate(Bundle savedInstanceState) {
125 // Disable screenshots if not allowed.
126 if (!MainWebViewActivity.allowScreenshots) {
127 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
130 // Set the activity theme.
131 if (MainWebViewActivity.darkTheme) {
132 setTheme(R.style.PrivacyBrowserDark_SecondaryActivity);
134 setTheme(R.style.PrivacyBrowserLight_SecondaryActivity);
137 // Run the default commands.
138 super.onCreate(savedInstanceState);
140 // Get the intent that launched the activity.
141 Intent launchingIntent = getIntent();
143 // Set the current folder variable.
144 if (launchingIntent.getStringExtra("Current Folder") != null) { // Set the current folder from the intent.
145 currentFolder = launchingIntent.getStringExtra("Current Folder");
146 } else { // Set the current folder to be `""`, which is the home folder.
150 // Set the content view.
151 setContentView(R.layout.bookmarks_coordinatorlayout);
153 // The AndroidX toolbar must be used until the minimum API is >= 21.
154 final Toolbar toolbar = findViewById(R.id.bookmarks_toolbar);
155 setSupportActionBar(toolbar);
157 // Get a handle for the activity, the app bar, and the ListView.
158 final Activity bookmarksActivity = this;
159 appBar = getSupportActionBar();
160 bookmarksListView = findViewById(R.id.bookmarks_listview);
162 // Remove the incorrect lint warning that `appBar` might be null.
163 assert appBar != null;
165 // Display the home arrow on the app bar.
166 appBar.setDisplayHomeAsUpEnabled(true);
168 // Initialize the database helper. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`.
169 // The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
170 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
172 // Load the home folder.
175 // Set a listener so that tapping a list item loads the URL or folder.
176 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
177 // Convert the id from long to int to match the format of the bookmarks database.
178 int databaseID = (int) id;
180 // Get the bookmark cursor for this ID and move it to the first row.
181 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
182 bookmarkCursor.moveToFirst();
184 // Act upon the bookmark according to the type.
185 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
186 // Update the current folder.
187 currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
189 // Load the new folder.
191 } else { // The selected bookmark is not a folder.
192 // Get the bookmark URL and assign it to `formattedUrlString`.
193 MainWebViewActivity.formattedUrlString = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL));
195 // Set `MainWebViewActivity` to load the new URL on restart.
196 MainWebViewActivity.loadUrlOnRestart = true;
198 // Update the bookmarks folder for the bookmarks drawer in `MainWebViewActivity`.
199 MainWebViewActivity.currentBookmarksFolder = currentFolder;
201 // Close the bookmarks drawer and reload the bookmarks `ListView` when returning to `MainWebViewActivity`.
202 MainWebViewActivity.restartFromBookmarksActivity = true;
204 // Return to `MainWebViewActivity`.
205 NavUtils.navigateUpFromSameTask(bookmarksActivity);
208 // Close the `Cursor`.
209 bookmarkCursor.close();
212 // Handle long presses on the list view.
213 bookmarksListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
214 // Instantiate the common variables.
215 MenuItem editBookmarkMenuItem;
216 MenuItem deleteBookmarksMenuItem;
217 MenuItem selectAllBookmarksMenuItem;
218 boolean deletingBookmarks;
221 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
222 // Inflate the menu for the contextual app bar.
223 getMenuInflater().inflate(R.menu.bookmarks_context_menu, menu);
226 if (currentFolder.isEmpty()) { // Use `R.string.bookmarks` if in the home folder.
227 mode.setTitle(R.string.bookmarks);
228 } else { // Use the current folder name as the title.
229 mode.setTitle(currentFolder);
232 // Get handles for menu items that need to be selectively disabled.
233 moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up);
234 moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down);
235 editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark);
236 deleteBookmarksMenuItem = menu.findItem(R.id.delete_bookmark);
237 selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks);
239 // Disable the delete bookmarks menu item if a delete is pending.
240 deleteBookmarksMenuItem.setEnabled(!deletingBookmarks);
242 // Store a handle for the contextual action mode so it can be closed programatically.
243 contextualActionMode = mode;
250 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
251 // Get a handle for the move to folder menu item.
252 MenuItem moveToFolderMenuItem = menu.findItem(R.id.move_to_folder);
254 // Get a Cursor with all of the folders.
255 Cursor folderCursor = bookmarksDatabaseHelper.getAllFolders();
257 // Enable the move to folder menu item if at least one folder exists.
258 moveToFolderMenuItem.setVisible(folderCursor.getCount() > 0);
265 public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
266 // Get the number of selected bookmarks.
267 int numberOfSelectedBookmarks = bookmarksListView.getCheckedItemCount();
269 // Adjust the ActionMode and the menu according to the number of selected bookmarks.
270 if (numberOfSelectedBookmarks == 1) { // One bookmark is selected.
271 // List the number of selected bookmarks in the subtitle.
272 mode.setSubtitle(getString(R.string.selected) + " 1");
274 // Show the `Move Up`, `Move Down`, and `Edit` options.
275 moveBookmarkUpMenuItem.setVisible(true);
276 moveBookmarkDownMenuItem.setVisible(true);
277 editBookmarkMenuItem.setVisible(true);
279 // Update the enabled status of the move icons.
281 } else { // More than one bookmark is selected.
282 // List the number of selected bookmarks in the subtitle.
283 mode.setSubtitle(getString(R.string.selected) + " " + numberOfSelectedBookmarks);
285 // Hide non-applicable `MenuItems`.
286 moveBookmarkUpMenuItem.setVisible(false);
287 moveBookmarkDownMenuItem.setVisible(false);
288 editBookmarkMenuItem.setVisible(false);
291 // Do not show the select all menu item if all the bookmarks are already checked.
292 if (bookmarksListView.getCheckedItemCount() == bookmarksListView.getCount()) {
293 selectAllBookmarksMenuItem.setVisible(false);
295 selectAllBookmarksMenuItem.setVisible(true);
300 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
301 // Instantiate the common variables.
302 int selectedBookmarkPosition;
303 int selectedBookmarkNewPosition;
304 final SparseBooleanArray selectedBookmarksPositionsSparseBooleanArray;
306 switch (item.getItemId()) {
307 case R.id.move_bookmark_up:
308 // Get the array of checked bookmark positions.
309 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
311 // Store the position of the selected bookmark. Only one bookmark is selected when `move_bookmark_up` is enabled.
312 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(0);
314 // Calculate the new position of the selected bookmark.
315 selectedBookmarkNewPosition = selectedBookmarkPosition - 1;
317 // Iterate through the bookmarks.
318 for (int i = 0; i < bookmarksListView.getCount(); i++) {
319 // Get the database ID for the current bookmark.
320 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
322 // Update the display order for the current bookmark.
323 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
324 // Move the current bookmark up one.
325 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1);
326 } else if ((i + 1) == selectedBookmarkPosition){ // The current bookmark is immediately above the selected bookmark.
327 // Move the current bookmark down one.
328 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1);
329 } else { // The current bookmark is not changing positions.
330 // Move `bookmarksCursor` to the current bookmark position.
331 bookmarksCursor.moveToPosition(i);
333 // Update the display order only if it is not correct in the database.
334 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
335 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
340 // Update the bookmarks cursor with the current contents of the bookmarks database.
341 // TODO Change this back to a `Cursor` after 2.17.1 is released.
342 bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
344 // Update the `ListView`.
345 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
347 // Scroll with the bookmark.
348 scrollBookmarks(selectedBookmarkNewPosition);
350 // Update the enabled status of the move icons.
354 case R.id.move_bookmark_down:
355 // Get the array of checked bookmark positions.
356 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
358 // Store the position of the selected bookmark. Only one bookmark is selected when `move_bookmark_down` is enabled.
359 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(0);
361 // Calculate the new position of the selected bookmark.
362 selectedBookmarkNewPosition = selectedBookmarkPosition + 1;
364 // Iterate through the bookmarks.
365 for (int i = 0; i <bookmarksListView.getCount(); i++) {
366 // Get the database ID for the current bookmark.
367 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
369 // Update the display order for the current bookmark.
370 if (i == selectedBookmarkPosition) { // The current bookmark is the selected bookmark.
371 // Move the current bookmark down one.
372 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i + 1);
373 } else if ((i - 1) == selectedBookmarkPosition) { // The current bookmark is immediately below the selected bookmark.
374 // Move the bookmark below the selected bookmark up one.
375 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i - 1);
376 } else { // The current bookmark is not changing positions.
377 // Move `bookmarksCursor` to the current bookmark position.
378 bookmarksCursor.moveToPosition(i);
380 // Update the display order only if it is not correct in the database.
381 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
382 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
387 // Update the bookmarks cursor with the current contents of the bookmarks database.
388 // TODO Change this back to a `Cursor` after 2.17.1 is released.
389 bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
391 // Update the `ListView`.
392 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
394 // Scroll with the bookmark.
395 scrollBookmarks(selectedBookmarkNewPosition);
397 // Update the enabled status of the move icons.
401 case R.id.move_to_folder:
402 // Store `checkedItemIds` for use by the `AlertDialog`.
403 checkedItemIds = bookmarksListView.getCheckedItemIds();
405 // Show the `MoveToFolderDialog` `AlertDialog` and name the instance `@string/move_to_folder
406 DialogFragment moveToFolderDialog = new MoveToFolderDialog();
407 moveToFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.move_to_folder));
410 case R.id.edit_bookmark:
411 // Get the array of checked bookmark positions.
412 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
414 // Get the position of the selected bookmark. Only one bookmark is selected when `edit_bookmark_down` is enabled.
415 selectedBookmarkPosition = selectedBookmarksPositionsSparseBooleanArray.keyAt(0);
417 // Move the `Cursor` to the selected position and find out if it is a folder.
418 bookmarksCursor.moveToPosition(selectedBookmarkPosition);
419 boolean isFolder = (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1);
421 // Get the selected bookmark database ID.
422 int databaseId = bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper._ID));
424 // Show the edit bookmark or edit bookmark folder dialog.
426 // Save the current folder name, which is used in `onSaveBookmarkFolder()`.
427 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
429 // Show the edit bookmark folder dialog.
430 DialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
431 editFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_folder));
433 // Show the edit bookmark dialog.
434 DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
435 editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark));
439 case R.id.delete_bookmark:
440 // Set the deleting bookmarks flag, which prevents the delete menu item from being enabled until the current process finishes.
441 deletingBookmarks = true;
443 // Get an array of the selected row IDs.
444 final long[] selectedBookmarksIdsLongArray = bookmarksListView.getCheckedItemIds();
446 // 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.
447 selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions().clone();
449 // Update the bookmarks cursor with the current contents of the bookmarks database except for the specified database IDs.
450 // TODO Change this back to a `Cursor` after 2.17.1 is released.
451 bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(selectedBookmarksIdsLongArray, currentFolder);
453 // Update the list view.
454 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
456 // Show a Snackbar with the number of deleted bookmarks.
457 bookmarksDeletedSnackbar = Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.bookmarks_deleted) + " " + selectedBookmarksIdsLongArray.length,
458 Snackbar.LENGTH_LONG)
459 .setAction(R.string.undo, view -> {
460 // Do nothing because everything will be handled by `onDismissed()` below.
462 .addCallback(new Snackbar.Callback() {
463 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
465 public void onDismissed(Snackbar snackbar, int event) {
467 // The user pushed the `Undo` button.
468 case Snackbar.Callback.DISMISS_EVENT_ACTION:
469 // Update the bookmarks cursor with the current contents of the bookmarks database, including the "deleted" bookmarks.
470 // TODO Change this back to a `Cursor` after 2.17.1 is released.
471 bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
473 // Update the list view.
474 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
476 // Re-select the previously selected bookmarks.
477 for (int i = 0; i < selectedBookmarksPositionsSparseBooleanArray.size(); i++) {
478 bookmarksListView.setItemChecked(selectedBookmarksPositionsSparseBooleanArray.keyAt(i), true);
482 // The Snackbar was dismissed without the `Undo` button being pushed.
484 // Delete each selected bookmark.
485 for (long databaseIdLong : selectedBookmarksIdsLongArray) {
486 // Convert `databaseIdLong` to an int.
487 int databaseIdInt = (int) databaseIdLong;
489 // Delete the contents of the folder if the selected bookmark is a folder.
490 if (bookmarksDatabaseHelper.isFolder(databaseIdInt)) {
491 deleteBookmarkFolderContents(databaseIdInt);
494 // Delete the selected bookmark.
495 bookmarksDatabaseHelper.deleteBookmark(databaseIdInt);
498 // Update the display order.
499 for (int i = 0; i < bookmarksListView.getCount(); i++) {
500 // Get the database ID for the current bookmark.
501 int currentBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i);
503 // Move `bookmarksCursor` to the current bookmark position.
504 bookmarksCursor.moveToPosition(i);
506 // Update the display order only if it is not correct in the database.
507 if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)) != i) {
508 bookmarksDatabaseHelper.updateDisplayOrder(currentBookmarkDatabaseId, i);
513 // Reset the deleting bookmarks flag.
514 deletingBookmarks = false;
516 // Enable the delete bookmarks menu item.
517 deleteBookmarksMenuItem.setEnabled(true);
519 // Close the activity if back has been pressed.
520 if (closeActivityAfterDismissingSnackbar) {
527 bookmarksDeletedSnackbar.show();
530 case R.id.context_menu_select_all_bookmarks:
531 // Get the total number of bookmarks.
532 int numberOfBookmarks = bookmarksListView.getCount();
535 for (int i = 0; i < numberOfBookmarks; i++) {
536 bookmarksListView.setItemChecked(i, true);
541 // Consume the click.
546 public void onDestroyActionMode(ActionMode mode) {
551 // Get handles for the `FloatingActionButtons`.
552 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
553 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
555 // Set the create new bookmark folder FAB to display the `AlertDialog`.
556 createBookmarkFolderFab.setOnClickListener(v -> {
557 // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`.
558 DialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
559 createBookmarkFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_folder));
562 // Set the create new bookmark FAB to display the `AlertDialog`.
563 createBookmarkFab.setOnClickListener(view -> {
564 // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`.
565 DialogFragment createBookmarkDialog = new CreateBookmarkDialog();
566 createBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_bookmark));
571 public void onRestart() {
572 // Run the default commands.
575 // Update the list view if returning from the bookmarks database view activity.
576 if (restartFromBookmarksDatabaseViewActivity) {
577 // Load the current folder in the list view.
580 // Reset `restartFromBookmarksDatabaseViewActivity`.
581 restartFromBookmarksDatabaseViewActivity = false;
586 public boolean onCreateOptionsMenu(Menu menu) {
588 getMenuInflater().inflate(R.menu.bookmarks_options_menu, menu);
595 public boolean onOptionsItemSelected(MenuItem menuItem) {
596 switch (menuItem.getItemId()) {
597 case android.R.id.home: // The home arrow is identified as `android.R.id.home`, not just `R.id.home`.
598 if (currentFolder.isEmpty()) { // Currently in the home folder.
599 // Run the back commands.
601 } else { // Currently in a subfolder.
602 // Place the former parent folder in `currentFolder`.
603 currentFolder = bookmarksDatabaseHelper.getParentFolderName(currentFolder);
605 // Load the new folder.
610 case R.id.options_menu_select_all_bookmarks:
611 // Get the total number of bookmarks.
612 int numberOfBookmarks = bookmarksListView.getCount();
615 for (int i = 0; i < numberOfBookmarks; i++) {
616 bookmarksListView.setItemChecked(i, true);
620 case R.id.bookmarks_database_view:
621 // Launch `BookmarksDatabaseViewActivity`.
622 Intent bookmarksDatabaseViewIntent = new Intent(this, BookmarksDatabaseViewActivity.class);
623 startActivity(bookmarksDatabaseViewIntent);
630 public void onBackPressed() {
631 // 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.
632 if ((bookmarksDeletedSnackbar != null) && bookmarksDeletedSnackbar.isShown()) { // Close the bookmarks deleted snackbar before going home.
633 // Set the close flag.
634 closeActivityAfterDismissingSnackbar = true;
636 // Dismiss the snackbar.
637 bookmarksDeletedSnackbar.dismiss();
638 } else { // Go home immediately.
639 // Update the bookmarks folder for the bookmarks drawer in the main WebView activity.
640 MainWebViewActivity.currentBookmarksFolder = currentFolder;
642 // Close the bookmarks drawer and reload the bookmarks ListView when returning to the main WebView activity.
643 MainWebViewActivity.restartFromBookmarksActivity = true;
645 // Exit the bookmarks activity.
646 super.onBackPressed();
651 public void onCreateBookmark(DialogFragment dialogFragment) {
652 // Get the views from the dialog fragment.
653 EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
654 EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
656 // Extract the strings from the edit texts.
657 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
658 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
660 // Get a copy of the favorite icon bitmap.
661 Bitmap favoriteIcon = MainWebViewActivity.favoriteIconBitmap;
663 // Scale the favorite icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation.
664 if ((favoriteIcon.getHeight() > 256) || (favoriteIcon.getWidth() > 256)) {
665 favoriteIcon = Bitmap.createScaledBitmap(favoriteIcon, 256, 256, true);
668 // Create a favorite icon byte array output stream.
669 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
671 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
672 favoriteIcon.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
674 // Convert the favorite icon byte array stream to a byte array.
675 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
677 // Display the new bookmark below the current items in the (0 indexed) list.
678 int newBookmarkDisplayOrder = bookmarksListView.getCount();
680 // Create the bookmark.
681 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
683 // Update the bookmarks cursor with the current contents of this folder.
684 // TODO Change this back to a `Cursor` after 2.17.1 is released.
685 bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
687 // Update the `ListView`.
688 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
690 // Scroll to the new bookmark.
691 bookmarksListView.setSelection(newBookmarkDisplayOrder);
695 public void onCreateBookmarkFolder(DialogFragment dialogFragment) {
696 // Get handles for the views in the dialog fragment.
697 EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
698 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
699 ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
701 // Get new folder name string.
702 String folderNameString = createFolderNameEditText.getText().toString();
704 // Create a folder icon bitmap.
705 Bitmap folderIconBitmap;
707 // Set the folder icon bitmap according to the dialog.
708 if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
709 // Get the default folder icon drawable.
710 Drawable folderIconDrawable = folderIconImageView.getDrawable();
712 // Convert the folder icon drawable to a bitmap drawable.
713 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
715 // Convert the folder icon bitmap drawable to a bitmap.
716 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
717 } else { // Use the WebView favorite icon.
718 // Get a copy of the favorite icon bitmap.
719 folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
721 // Scale the folder icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation.
722 if ((folderIconBitmap.getHeight() > 256) || (folderIconBitmap.getWidth() > 256)) {
723 folderIconBitmap = Bitmap.createScaledBitmap(folderIconBitmap, 256, 256, true);
727 // Create a folder icon byte array output stream.
728 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
730 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
731 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
733 // Convert the folder icon byte array stream to a byte array.
734 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
736 // Move all the bookmarks down one in the display order.
737 for (int i = 0; i < bookmarksListView.getCount(); i++) {
738 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
739 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
742 // Create the folder, which will be placed at the top of the `ListView`.
743 bookmarksDatabaseHelper.createFolder(folderNameString, currentFolder, folderIconByteArray);
745 // Update the bookmarks cursor with the current contents of this folder.
746 // TODO Change this back to a `Cursor` after 2.17.1 is released.
747 bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
749 // Update the `ListView`.
750 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
752 // Scroll to the new folder.
753 bookmarksListView.setSelection(0);
757 public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
758 // Get handles for the views from `dialogFragment`.
759 EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
760 EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
761 RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
763 // Store the bookmark strings.
764 String bookmarkNameString = editBookmarkNameEditText.getText().toString();
765 String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
767 // Update the bookmark.
768 if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
769 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
770 } else { // Update the bookmark using the `WebView` favorite icon.
771 // Get a copy of the favorite icon bitmap.
772 Bitmap favoriteIconBitmap = MainWebViewActivity.favoriteIconBitmap;
774 // Scale the favorite icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation.
775 if ((favoriteIconBitmap.getHeight() > 256) || (favoriteIconBitmap.getWidth() > 256)) {
776 favoriteIconBitmap = Bitmap.createScaledBitmap(favoriteIconBitmap, 256, 256, true);
779 // Create a favorite icon byte array output stream.
780 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
782 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
783 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
785 // Convert the favorite icon byte array stream to a byte array.
786 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
788 // Update the bookmark and the favorite icon.
789 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
792 // Close the contextual action mode.
793 contextualActionMode.finish();
795 // Update the bookmarks cursor with the contents of the current folder.
796 // TODO Change this back to a `Cursor` after 2.17.1 is released.
797 bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
799 // Update the `ListView`.
800 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
804 public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId) {
805 // Get handles for the views from `dialogFragment`.
806 RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
807 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
808 ImageView defaultFolderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
809 EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
811 // Get the new folder name.
812 String newFolderNameString = editFolderNameEditText.getText().toString();
814 // Check if the favorite icon has changed.
815 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
816 // Update the name in the database.
817 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
818 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
819 // Create the new folder icon Bitmap.
820 Bitmap folderIconBitmap;
822 // Populate the new folder icon bitmap.
823 if (defaultFolderIconRadioButton.isChecked()) {
824 // Get the default folder icon drawable.
825 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
827 // Convert the folder icon drawable to a bitmap drawable.
828 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
830 // Convert the folder icon bitmap drawable to a bitmap.
831 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
832 } else { // Use the WebView favorite icon.
833 // Get a copy of the favorite icon bitmap.
834 folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
836 // Scale the folder icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation.
837 if ((folderIconBitmap.getHeight() > 256) || (folderIconBitmap.getWidth() > 256)) {
838 folderIconBitmap = Bitmap.createScaledBitmap(folderIconBitmap, 256, 256, true);
842 // Create a folder icon byte array output stream.
843 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
845 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
846 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
848 // Convert the folder icon byte array stream to a byte array.
849 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
851 // Update the folder icon in the database.
852 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
853 } else { // The folder icon and the name have changed.
854 // Instantiate the new folder icon `Bitmap`.
855 Bitmap folderIconBitmap;
857 // Populate the new folder icon bitmap.
858 if (defaultFolderIconRadioButton.isChecked()) {
859 // Get the default folder icon drawable.
860 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
862 // Convert the folder icon drawable to a bitmap drawable.
863 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
865 // Convert the folder icon bitmap drawable to a bitmap.
866 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
867 } else { // Use the WebView favorite icon.
868 // Get a copy of the favorite icon bitmap.
869 folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
871 // Scale the folder icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation.
872 if ((folderIconBitmap.getHeight() > 256) || (folderIconBitmap.getWidth() > 256)) {
873 folderIconBitmap = Bitmap.createScaledBitmap(folderIconBitmap, 256, 256, true);
877 // Create a folder icon byte array output stream.
878 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
880 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
881 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
883 // Convert the folder icon byte array stream to a byte array.
884 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
886 // Update the folder name and icon in the database.
887 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
890 // Update the bookmarks cursor with the current contents of this folder.
891 // TODO Change this back to a `Cursor` after 2.17.1 is released.
892 bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
894 // Update the `ListView`.
895 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
897 // Close the contextual action mode.
898 contextualActionMode.finish();
902 public void onMoveToFolder(DialogFragment dialogFragment) {
903 // Get a handle for the `ListView` from `dialogFragment`.
904 ListView folderListView = dialogFragment.getDialog().findViewById(R.id.move_to_folder_listview);
906 // Store a long array of the selected folders.
907 long[] newFolderLongArray = folderListView.getCheckedItemIds();
909 // Get the new folder database ID. Only one folder will be selected.
910 int newFolderDatabaseId = (int) newFolderLongArray[0];
912 // Instantiate `newFolderName`.
913 String newFolderName;
915 // Set the new folder name.
916 if (newFolderDatabaseId == 0) {
917 // The new folder is the home folder, represented as `""` in the database.
920 // Get the new folder name from the database.
921 newFolderName = bookmarksDatabaseHelper.getFolderName(newFolderDatabaseId);
924 // Get a long array with the the database ID of the selected bookmarks.
925 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
927 // Move each of the selected bookmarks to the new folder.
928 for (long databaseIdLong : selectedBookmarksLongArray) {
929 // Get `databaseIdInt` for each selected bookmark.
930 int databaseIdInt = (int) databaseIdLong;
932 // Move the selected bookmark to the new folder.
933 bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderName);
936 // Update the bookmarks cursor with the current contents of this folder.
937 // TODO Change this back to a `Cursor` after 2.17.1 is released.
938 bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
940 // Update the `ListView`.
941 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
943 // Close the contextual app bar.
944 contextualActionMode.finish();
947 private void deleteBookmarkFolderContents(int databaseId) {
948 // Get the name of the folder.
949 String folderName = bookmarksDatabaseHelper.getFolderName(databaseId);
951 // Get the contents of the folder.
952 Cursor folderCursor = bookmarksDatabaseHelper.getBookmarkIDs(folderName);
954 // Delete each of the bookmarks in the folder.
955 for (int i = 0; i < folderCursor.getCount(); i++) {
956 // Move `folderCursor` to the current row.
957 folderCursor.moveToPosition(i);
959 // Get the database ID of the item.
960 int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndex(BookmarksDatabaseHelper._ID));
962 // If this is a folder, recursively delete the contents first.
963 if (bookmarksDatabaseHelper.isFolder(itemDatabaseId)) {
964 deleteBookmarkFolderContents(itemDatabaseId);
967 // Delete the bookmark.
968 bookmarksDatabaseHelper.deleteBookmark(itemDatabaseId);
972 private void updateMoveIcons() {
973 // Get a long array of the selected bookmarks.
974 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
976 // Get the database IDs for the first, last, and selected bookmarks.
977 int selectedBookmarkDatabaseId = (int) selectedBookmarksLongArray[0];
978 int firstBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(0);
979 // bookmarksListView is 0 indexed.
980 int lastBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(bookmarksListView.getCount() - 1);
982 // Update the move bookmark up `MenuItem`.
983 if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) { // The selected bookmark is in the first position.
984 // Disable the move bookmark up `MenuItem`.
985 moveBookmarkUpMenuItem.setEnabled(false);
987 // Set the move bookmark up icon to be ghosted.
988 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_disabled);
989 } else { // The selected bookmark is not in the first position.
990 // Enable the move bookmark up `MenuItem`.
991 moveBookmarkUpMenuItem.setEnabled(true);
993 // Set the icon according to the theme.
994 if (MainWebViewActivity.darkTheme) {
995 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled_dark);
997 moveBookmarkUpMenuItem.setIcon(R.drawable.move_up_enabled_light);
1001 // Update the move bookmark down `MenuItem`.
1002 if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) { // The selected bookmark is in the last position.
1003 // Disable the move bookmark down `MenuItem`.
1004 moveBookmarkDownMenuItem.setEnabled(false);
1006 // Set the move bookmark down icon to be ghosted.
1007 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_disabled);
1008 } else { // The selected bookmark is not in the last position.
1009 // Enable the move bookmark down `MenuItem`.
1010 moveBookmarkDownMenuItem.setEnabled(true);
1012 // Set the icon according to the theme.
1013 if (MainWebViewActivity.darkTheme) {
1014 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled_dark);
1016 moveBookmarkDownMenuItem.setIcon(R.drawable.move_down_enabled_light);
1021 private void scrollBookmarks(int selectedBookmarkPosition) {
1022 // Get the first and last visible bookmark positions.
1023 int firstVisibleBookmarkPosition = bookmarksListView.getFirstVisiblePosition();
1024 int lastVisibleBookmarkPosition = bookmarksListView.getLastVisiblePosition();
1026 // Calculate the number of bookmarks per screen.
1027 int numberOfBookmarksPerScreen = lastVisibleBookmarkPosition - firstVisibleBookmarkPosition;
1029 // Scroll with the moved bookmark if necessary.
1030 if (selectedBookmarkPosition <= firstVisibleBookmarkPosition) { // The selected bookmark position is at or above the top of the screen.
1031 // Scroll to the selected bookmark position.
1032 bookmarksListView.setSelection(selectedBookmarkPosition);
1033 } else if (selectedBookmarkPosition >= (lastVisibleBookmarkPosition - 1)) { // The selected bookmark is at or below the bottom of the screen.
1034 // 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.
1035 // `+1` assures that the entire bookmark will be displayed in situations where only a partial bookmark fits at the bottom of the list view.
1036 bookmarksListView.setSelection(selectedBookmarkPosition - numberOfBookmarksPerScreen + 1);
1040 private void loadFolder() {
1041 // Update bookmarks cursor with the contents of the bookmarks database for the current folder.
1042 // TODO Change this back to a `Cursor` after 2.17.1 is released.
1043 bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
1045 // TODO Remove after the release of 2.17.1.
1046 if (Build.VERSION.SDK_INT >= 28) {
1047 // Create a big cursor window.
1048 CursorWindow bigCursorWindow = new CursorWindow("Big Cursor Window", 4194304);
1050 bookmarksCursor.setWindow(bigCursorWindow);
1053 // Setup a `CursorAdapter`. `this` specifies the `Context`. `false` disables `autoRequery`.
1054 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
1056 public View newView(Context context, Cursor cursor, ViewGroup parent) {
1057 // Inflate the individual item layout. `false` does not attach it to the root.
1058 return getLayoutInflater().inflate(R.layout.bookmarks_activity_item_linearlayout, parent, false);
1062 public void bindView(View view, Context context, Cursor cursor) {
1063 // Get handles for the views.
1064 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
1065 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
1067 // Get the favorite icon byte array from the `Cursor`.
1068 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
1070 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
1071 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
1073 // Display the bitmap in `bookmarkFavoriteIcon`.
1074 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
1076 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
1077 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
1078 bookmarkNameTextView.setText(bookmarkNameString);
1080 // Make the font bold for folders.
1081 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
1082 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
1083 } else { // Reset the font to default for normal bookmarks.
1084 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
1089 // Populate the list view with the adapter.
1090 bookmarksListView.setAdapter(bookmarksCursorAdapter);
1092 // Set the `AppBar` title.
1093 if (currentFolder.isEmpty()) {
1094 appBar.setTitle(R.string.bookmarks);
1096 appBar.setTitle(currentFolder);
1101 public void onDestroy() {
1102 // Close the bookmarks cursor and database.
1103 bookmarksCursor.close();
1104 bookmarksDatabaseHelper.close();
1106 // Run the default commands.