2 * Copyright 2016 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;
22 import android.app.Activity;
23 import android.app.DialogFragment;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.database.Cursor;
27 import android.graphics.Bitmap;
28 import android.graphics.BitmapFactory;
29 import android.graphics.Typeface;
30 import android.graphics.drawable.BitmapDrawable;
31 import android.graphics.drawable.Drawable;
32 import android.os.Bundle;
33 import android.support.design.widget.FloatingActionButton;
34 import android.support.design.widget.Snackbar;
35 import android.support.v4.app.NavUtils;
36 import android.support.v7.app.ActionBar;
37 import android.support.v7.app.AppCompatActivity;
38 import android.support.v7.widget.Toolbar;
39 import android.util.SparseBooleanArray;
40 import android.view.ActionMode;
41 import android.view.Menu;
42 import android.view.MenuItem;
43 import android.view.View;
44 import android.view.ViewGroup;
45 import android.widget.AbsListView;
46 import android.widget.AdapterView;
47 import android.widget.CursorAdapter;
48 import android.widget.EditText;
49 import android.widget.ImageView;
50 import android.widget.ListView;
51 import android.widget.RadioButton;
52 import android.widget.TextView;
54 import java.io.ByteArrayOutputStream;
56 public class BookmarksActivity extends AppCompatActivity implements CreateBookmark.CreateBookmarkListener,
57 CreateBookmarkFolder.CreateBookmarkFolderListener, EditBookmark.EditBookmarkListener,
58 EditBookmarkFolder.EditBookmarkFolderListener, MoveToFolder.MoveToFolderListener {
60 // `bookmarksDatabaseHandler` is public static so it can be accessed from `EditBookmark` and `MoveToFolder`. It is also used in `onCreate()`,
61 // `onCreateBookmarkCreate()`, `updateBookmarksListView()`, and `updateBookmarksListViewExcept()`.
62 public static BookmarksDatabaseHandler bookmarksDatabaseHandler;
64 // `currentFolder` is public static so it can be accessed from `MoveToFolder`.
65 // It is used in `onCreate`, `onOptionsItemSelected()`, `onCreateBookmarkCreate`, `onCreateBookmarkFolderCreate`, and `onEditBookmarkSave`.
66 public static String currentFolder;
68 // `checkedItemIds` is public static so it can be accessed from `EditBookmark`, `EditBookmarkFolder`, and `MoveToFolder`.
69 // It is also used in `onActionItemClicked`.
70 public static long[] checkedItemIds;
73 // `bookmarksListView` is used in `onCreate()`, `updateBookmarksListView()`, and `updateBookmarksListViewExcept()`.
74 private ListView bookmarksListView;
76 // `contextualActionMode` is used in `onCreate()` and `onEditBookmarkSave()`.
77 private ActionMode contextualActionMode;
79 // `selectedBookmarkPosition` is used in `onCreate()` and `onEditBookmarkSave()`.
80 private int selectedBookmarkPosition;
82 // `appBar` is used in `onCreate()` and `updateBookmarksListView()`.
83 private ActionBar appBar;
85 // `bookmarksCursor` is used in `onCreate()`, `updateBookmarksListView()`, and `updateBookmarksListViewExcept()`.
86 private Cursor bookmarksCursor;
88 // `oldFolderName` is used in `onCreate()` and `onEditBookmarkFolderSave()`.
89 private String oldFolderNameString;
92 protected void onCreate(Bundle savedInstanceState) {
93 super.onCreate(savedInstanceState);
94 setContentView(R.layout.bookmarks_coordinatorlayout);
96 // We need to use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21.
97 final Toolbar bookmarksAppBar = (Toolbar) findViewById(R.id.bookmarks_toolbar);
98 setSupportActionBar(bookmarksAppBar);
100 // Display the home arrow on `SupportActionBar`.
101 appBar = getSupportActionBar();
102 assert appBar != null;// This assert removes the incorrect warning in Android Studio on the following line that appBar might be null.
103 appBar.setDisplayHomeAsUpEnabled(true);
106 // Initialize the database handler and the ListView.
107 // `this` specifies the context. The two `null`s do not specify the database name or a `CursorFactory`.
108 // The `0` is to specify a database version, but that is set instead using a constant in `BookmarksDatabaseHandler`.
109 bookmarksDatabaseHandler = new BookmarksDatabaseHandler(this, null, null, 0);
110 bookmarksListView = (ListView) findViewById(R.id.bookmarks_listview);
112 // Set currentFolder to the home folder, which is null in the database.
115 // Display the bookmarks in the ListView.
116 updateBookmarksListView(currentFolder);
118 // Set a listener so that tapping a list item loads the URL. We need to store the activity
119 // in a variable so that we can return to the parent activity after loading the URL.
120 final Activity bookmarksActivity = this;
121 bookmarksListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
123 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
124 // Convert the id from long to int to match the format of the bookmarks database.
125 int databaseID = (int) id;
127 // Get the bookmark `Cursor` and move it to the first row.
128 Cursor bookmarkCursor = bookmarksDatabaseHandler.getBookmarkCursor(databaseID);
129 bookmarkCursor.moveToFirst();
131 // If the bookmark is a folder load its contents into the ListView.
132 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHandler.IS_FOLDER)) == 1) {
133 // Update `currentFolder`.
134 currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHandler.BOOKMARK_NAME));
136 // Reload the ListView with `currentFolder`.
137 updateBookmarksListView(currentFolder);
138 } else { // Load the URL into `mainWebView`.
139 // Get the bookmark URL and assign it to formattedUrlString. `mainWebView` will automatically reload when `BookmarksActivity` closes.
140 MainWebViewActivity.formattedUrlString = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHandler.BOOKMARK_URL));
142 NavUtils.navigateUpFromSameTask(bookmarksActivity);
145 // Close the `Cursor`.
146 bookmarkCursor.close();
150 // `MultiChoiceModeListener` handles long clicks.
151 bookmarksListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
152 // `moveBookmarkUpMenuItem` is used in `onCreateActionMode()` and `onItemCheckedStateChanged`.
153 MenuItem moveBookmarkUpMenuItem;
155 // `moveBookmarkDownMenuItem` is used in `onCreateActionMode()` and `onItemCheckedStateChanged`.
156 MenuItem moveBookmarkDownMenuItem;
158 // `editBookmarkMenuItem` is used in `onCreateActionMode()` and `onItemCheckedStateChanged`.
159 MenuItem editBookmarkMenuItem;
161 // `selectAllBookmarks` is used in `onCreateActionMode()` and `onItemCheckedStateChanges`.
162 MenuItem selectAllBookmarksMenuItem;
165 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
166 // Inflate the menu for the contextual app bar and set the title.
167 getMenuInflater().inflate(R.menu.bookmarks_context_menu, menu);
170 if (currentFolder.isEmpty()) {
171 // Use `R.string.bookmarks` if we are in the home folder.
172 mode.setTitle(R.string.bookmarks);
173 } else { // Use the current folder name as the title.
174 mode.setTitle(currentFolder);
177 // Get a handle for MenuItems we need to selectively disable.
178 moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up);
179 moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down);
180 editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark);
181 selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks);
183 // Get a handle for `contextualActionMode` so we can close it programatically.
184 contextualActionMode = mode;
190 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
195 public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
196 // Get an array of the selected bookmarks.
197 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
199 // Calculate the number of selected bookmarks.
200 int numberOfSelectedBookmarks = selectedBookmarksLongArray.length;
202 // Sometimes Android forgets to close the contextual app bar when all the items are deselected.
203 if (numberOfSelectedBookmarks == 0) {
207 // List the number of selected bookmarks in the subtitle.
208 mode.setSubtitle(numberOfSelectedBookmarks + " " + getString(R.string.selected));
210 if (numberOfSelectedBookmarks == 1) {
211 // Show the `Move Up`, `Move Down`, and `Edit` option only if 1 bookmark is selected.
212 moveBookmarkUpMenuItem.setVisible(true);
213 moveBookmarkDownMenuItem.setVisible(true);
214 editBookmarkMenuItem.setVisible(true);
216 // Get the database IDs for the bookmarks.
217 int selectedBookmarkDatabaseId = (int) selectedBookmarksLongArray[0];
218 int firstBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(0);
219 // bookmarksListView is 0 indexed.
220 int lastBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(bookmarksListView.getCount() - 1);
222 // Disable `moveBookmarkUpMenuItem` if the selected bookmark is at the top of the ListView.
223 if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) {
224 moveBookmarkUpMenuItem.setEnabled(false);
225 moveBookmarkUpMenuItem.setIcon(R.drawable.move_bookmark_up_disabled);
226 } else { // Otherwise enable `moveBookmarkUpMenuItem`.
227 moveBookmarkUpMenuItem.setEnabled(true);
228 moveBookmarkUpMenuItem.setIcon(R.drawable.move_bookmark_up_enabled);
231 // Disable `moveBookmarkDownMenuItem` if the selected bookmark is at the bottom of the ListView.
232 if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) {
233 moveBookmarkDownMenuItem.setEnabled(false);
234 moveBookmarkDownMenuItem.setIcon(R.drawable.move_bookmark_down_disabled);
235 } else { // Otherwise enable `moveBookmarkDownMenuItem`.
236 moveBookmarkDownMenuItem.setEnabled(true);
237 moveBookmarkDownMenuItem.setIcon(R.drawable.move_bookmark_down_enabled);
239 } else { // Hide the MenuItems because more than one bookmark is selected.
240 moveBookmarkUpMenuItem.setVisible(false);
241 moveBookmarkDownMenuItem.setVisible(false);
242 editBookmarkMenuItem.setVisible(false);
245 // Do not show `Select All` if all the bookmarks are already checked.
246 if (bookmarksListView.getCheckedItemIds().length == bookmarksListView.getCount()) {
247 selectAllBookmarksMenuItem.setVisible(false);
249 selectAllBookmarksMenuItem.setVisible(true);
254 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
255 int menuItemId = item.getItemId();
257 // `numberOfBookmarks` is used in `R.id.move_bookmark_up_enabled`, `R.id.move_bookmark_down_enabled`, and `R.id.context_menu_select_all_bookmarks`.
258 int numberOfBookmarks;
260 // `selectedBookmarkLongArray` is used in `R.id.move_bookmark_up`, `R.id.move_bookmark_down`, and `R.id.edit_bookmark`.
261 long[]selectedBookmarkLongArray;
262 // `selectedBookmarkDatabaseId` is used in `R.id.move_bookmark_up`, `R.id.move_bookmark_down`, and `R.id.edit_bookmark`.
263 int selectedBookmarkDatabaseId;
264 // `selectedBookmarkNewPosition` is used in `R.id.move_bookmark_up` and `R.id.move_bookmark_down`.
265 int selectedBookmarkNewPosition;
266 // `bookmarkPositionSparseBooleanArray` is used in `R.id.edit_bookmark` and `R.id.delete_bookmark`.
267 SparseBooleanArray bookmarkPositionSparseBooleanArray;
269 switch (menuItemId) {
270 case R.id.move_bookmark_up:
271 // Get the selected bookmark database ID.
272 selectedBookmarkLongArray = bookmarksListView.getCheckedItemIds();
273 selectedBookmarkDatabaseId = (int) selectedBookmarkLongArray[0];
275 // Initialize `selectedBookmarkNewPosition`.
276 selectedBookmarkNewPosition = 0;
278 for (int i = 0; i < bookmarksListView.getCount(); i++) {
279 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
280 int nextBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i + 1);
282 if (databaseId == selectedBookmarkDatabaseId || nextBookmarkDatabaseId == selectedBookmarkDatabaseId) {
283 if (databaseId == selectedBookmarkDatabaseId) {
284 // Move the selected bookmark up one and store the new bookmark position.
285 bookmarksDatabaseHandler.updateBookmarkDisplayOrder(databaseId, i - 1);
286 selectedBookmarkNewPosition = i - 1;
287 } else { // Move the bookmark above the selected bookmark down one.
288 bookmarksDatabaseHandler.updateBookmarkDisplayOrder(databaseId, i + 1);
291 // Reset the rest of the bookmarks' DISPLAY_ORDER to match the position in the ListView.
292 // This isn't necessary, but it clears out any stray values that might have crept into the database.
293 bookmarksDatabaseHandler.updateBookmarkDisplayOrder(databaseId, i);
297 // Refresh the ListView.
298 updateBookmarksListView(currentFolder);
300 // Select the previously selected bookmark in the new location.
301 bookmarksListView.setItemChecked(selectedBookmarkNewPosition, true);
303 bookmarksListView.setSelection(selectedBookmarkNewPosition - 5);
307 case R.id.move_bookmark_down:
308 // Get the selected bookmark database ID.
309 selectedBookmarkLongArray = bookmarksListView.getCheckedItemIds();
310 selectedBookmarkDatabaseId = (int) selectedBookmarkLongArray[0];
312 // Initialize `selectedBookmarkNewPosition`.
313 selectedBookmarkNewPosition = 0;
315 for (int i = 0; i <bookmarksListView.getCount(); i++) {
316 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
317 int previousBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i - 1);
319 if (databaseId == selectedBookmarkDatabaseId || previousBookmarkDatabaseId == selectedBookmarkDatabaseId) {
320 if (databaseId == selectedBookmarkDatabaseId) {
321 // Move the selected bookmark down one and store the new bookmark position.
322 bookmarksDatabaseHandler.updateBookmarkDisplayOrder(databaseId, i + 1);
323 selectedBookmarkNewPosition = i + 1;
324 } else { // Move the bookmark below the selected bookmark up one.
325 bookmarksDatabaseHandler.updateBookmarkDisplayOrder(databaseId, i - 1);
328 // Reset the rest of the bookmark' DISPLAY_ORDER to match the position in the ListView.
329 // This isn't necessary, but it clears out any stray values that might have crept into the database.
330 bookmarksDatabaseHandler.updateBookmarkDisplayOrder(databaseId, i);
334 // Refresh the ListView.
335 updateBookmarksListView(currentFolder);
337 // Select the previously selected bookmark in the new location.
338 bookmarksListView.setItemChecked(selectedBookmarkNewPosition, true);
340 bookmarksListView.setSelection(selectedBookmarkNewPosition - 5);
343 case R.id.move_to_folder:
344 // Store `checkedItemIds` for use by the `AlertDialog`.
345 checkedItemIds = bookmarksListView.getCheckedItemIds();
347 // Show the `MoveToFolder` `AlertDialog` and name the instance `@string/move_to_folder
348 DialogFragment moveToFolderDialog = new MoveToFolder();
349 moveToFolderDialog.show(getFragmentManager(), getResources().getString(R.string.move_to_folder));
352 case R.id.edit_bookmark:
353 // Get a handle for `selectedBookmarkPosition` so we can scroll to it after refreshing the ListView.
354 bookmarkPositionSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
355 for (int i = 0; i < bookmarkPositionSparseBooleanArray.size(); i++) {
356 // Find the bookmark that is selected and save the position to `selectedBookmarkPosition`.
357 if (bookmarkPositionSparseBooleanArray.valueAt(i))
358 selectedBookmarkPosition = bookmarkPositionSparseBooleanArray.keyAt(i);
361 // Move to the selected database ID and find out if it is a folder.
362 bookmarksCursor.moveToPosition(selectedBookmarkPosition);
363 boolean isFolder = (bookmarksCursor.getInt(bookmarksCursor.getColumnIndex(BookmarksDatabaseHandler.IS_FOLDER)) == 1);
365 // Store `checkedItemIds` for use by the `AlertDialog`.
366 checkedItemIds = bookmarksListView.getCheckedItemIds();
369 // Save the current folder name.
370 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHandler.BOOKMARK_NAME));
372 // Show the `EditBookmarkFolder` `AlertDialog` and name the instance `@string/edit_folder`.
373 DialogFragment editFolderDialog = new EditBookmarkFolder();
374 editFolderDialog.show(getFragmentManager(), getResources().getString(R.string.edit_folder));
376 // Show the `EditBookmark` `AlertDialog` and name the instance `@string/edit_bookmark`.
377 DialogFragment editBookmarkDialog = new EditBookmark();
378 editBookmarkDialog.show(getFragmentManager(), getResources().getString(R.string.edit_bookmark));
382 case R.id.delete_bookmark:
383 // Get an array of the selected rows.
384 final long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
386 // Get a handle for `selectedBookmarkPosition` so we can scroll to it after refreshing the ListView.
387 bookmarkPositionSparseBooleanArray = bookmarksListView.getCheckedItemPositions();
388 for (int i = 0; i < bookmarkPositionSparseBooleanArray.size(); i++) {
389 // Find the bookmark that is selected and save the position to `selectedBookmarkPosition`.
390 if (bookmarkPositionSparseBooleanArray.valueAt(i))
391 selectedBookmarkPosition = bookmarkPositionSparseBooleanArray.keyAt(i);
394 updateBookmarksListViewExcept(selectedBookmarksLongArray, currentFolder);
396 // Scroll to where the deleted bookmark was located.
397 bookmarksListView.setSelection(selectedBookmarkPosition - 5);
399 String snackbarMessage;
401 // Determine how many items are in the array and prepare an appropriate Snackbar message.
402 if (selectedBookmarksLongArray.length == 1) {
403 snackbarMessage = getString(R.string.one_bookmark_deleted);
405 snackbarMessage = selectedBookmarksLongArray.length + " " + getString(R.string.bookmarks_deleted);
409 Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), snackbarMessage, Snackbar.LENGTH_LONG)
410 .setAction(R.string.undo, new View.OnClickListener() {
412 public void onClick(View view) {
413 // Do nothing because everything will be handled by `onDismissed()` below.
416 .setCallback(new Snackbar.Callback() {
418 public void onDismissed(Snackbar snackbar, int event) {
419 // Android Studio wants to see entries for every possible `Snackbar.Callback` even if they aren't used.
421 // The user pushed the "Undo" button.
422 case Snackbar.Callback.DISMISS_EVENT_ACTION:
423 // Refresh the ListView to show the rows again.
424 updateBookmarksListView(currentFolder);
426 // Scroll to where the deleted bookmark was located.
427 bookmarksListView.setSelection(selectedBookmarkPosition - 5);
431 case Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE:
432 // Do nothing and let the default behavior run.
434 case Snackbar.Callback.DISMISS_EVENT_MANUAL:
435 // Do nothing and let the default behavior run.
437 case Snackbar.Callback.DISMISS_EVENT_SWIPE:
438 // Do nothing and let the default behavior run.
440 case Snackbar.Callback.DISMISS_EVENT_TIMEOUT:
441 // Do nothing and let the default behavior run.
443 // The Snackbar was dismissed without the "Undo" button being pushed.
445 // Delete each selected row.
446 for (long databaseIdLong : selectedBookmarksLongArray) {
447 // Convert `databaseIdLong` to an int.
448 int databaseIdInt = (int) databaseIdLong;
450 if (bookmarksDatabaseHandler.isFolder(databaseIdInt)) {
451 deleteBookmarkFolderContents(databaseIdInt);
454 // Delete `databaseIdInt`.
455 bookmarksDatabaseHandler.deleteBookmark(databaseIdInt);
463 // Close the contextual app bar.
467 case R.id.context_menu_select_all_bookmarks:
468 numberOfBookmarks = bookmarksListView.getCount();
470 for (int i = 0; i < numberOfBookmarks; i++) {
471 bookmarksListView.setItemChecked(i, true);
475 // Consume the click.
480 public void onDestroyActionMode(ActionMode mode) {
485 // Set a FloatingActionButton for creating new bookmarks.
486 FloatingActionButton createBookmarkFAB = (FloatingActionButton) findViewById(R.id.create_bookmark_fab);
487 createBookmarkFAB.setOnClickListener(new View.OnClickListener() {
489 public void onClick(View view) {
490 // Show the `CreateBookmark` `AlertDialog` and name the instance `@string/create_bookmark`.
491 DialogFragment createBookmarkDialog = new CreateBookmark();
492 createBookmarkDialog.show(getFragmentManager(), getResources().getString(R.string.create_bookmark));
498 public boolean onCreateOptionsMenu(Menu menu) {
500 getMenuInflater().inflate(R.menu.bookmarks_options_menu, menu);
506 public boolean onPrepareOptionsMenu(Menu menu) {
507 super.onPrepareOptionsMenu(menu);
513 public boolean onOptionsItemSelected(MenuItem menuItem) {
514 int menuItemId = menuItem.getItemId();
516 switch (menuItemId) {
517 case android.R.id.home:
518 // Exit BookmarksActivity if currently at the home folder.
519 if (currentFolder.isEmpty()) {
520 NavUtils.navigateUpFromSameTask(this);
521 } else { // Navigate up one folder.
522 // Place the former parent folder in `currentFolder`.
523 currentFolder = bookmarksDatabaseHandler.getParentFolder(currentFolder);
525 updateBookmarksListView(currentFolder);
529 case R.id.create_folder:
530 // Show the `CreateBookmarkFolder` `AlertDialog` and name the instance `@string/create_folder`.
531 DialogFragment createBookmarkFolderDialog = new CreateBookmarkFolder();
532 createBookmarkFolderDialog.show(getFragmentManager(), getResources().getString(R.string.create_folder));
535 case R.id.options_menu_select_all_bookmarks:
536 int numberOfBookmarks = bookmarksListView.getCount();
538 for (int i = 0; i < numberOfBookmarks; i++) {
539 bookmarksListView.setItemChecked(i, true);
543 case R.id.bookmarks_database_view:
544 // Launch `BookmarksDatabaseViewActivity`.
545 Intent bookmarksDatabaseViewIntent = new Intent(this, BookmarksDatabaseViewActivity.class);
546 startActivity(bookmarksDatabaseViewIntent);
553 public void onCreateBookmark(DialogFragment dialogFragment) {
554 // Get the `EditText`s from the `createBookmarkDialogFragment` and extract the strings.
555 EditText createBookmarkNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
556 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
557 EditText createBookmarkUrlEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
558 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
560 // Convert the favoriteIcon Bitmap to a byte array.
561 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
562 // `0` is for lossless compression (the only option for a PNG).
563 MainWebViewActivity.favoriteIcon.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
564 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
566 // Display the new bookmark below the current items in the (0 indexed) list.
567 int newBookmarkDisplayOrder = bookmarksListView.getCount();
569 // Create the bookmark.
570 bookmarksDatabaseHandler.createBookmark(bookmarkNameString, bookmarkUrlString, newBookmarkDisplayOrder, currentFolder, favoriteIconByteArray);
572 // Refresh the ListView. `setSelection` scrolls to the bottom of the list.
573 updateBookmarksListView(currentFolder);
574 bookmarksListView.setSelection(newBookmarkDisplayOrder);
578 public void onCreateBookmarkFolder(DialogFragment dialogFragment) {
579 // Get `create_folder_name_edit_text` and extract the string.
580 EditText createFolderNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
581 String folderNameString = createFolderNameEditText.getText().toString();
583 // Check to see if the folder already exists.
584 Cursor bookmarkFolderCursor = bookmarksDatabaseHandler.getFolderCursor(folderNameString);
585 int existingFoldersWithNewName = bookmarkFolderCursor.getCount();
586 bookmarkFolderCursor.close();
587 if (folderNameString.isEmpty() || (existingFoldersWithNewName > 0)) {
588 String cannotCreateFolder = getResources().getString(R.string.cannot_create_folder) + " \"" + folderNameString + "\"";
589 Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), cannotCreateFolder, Snackbar.LENGTH_INDEFINITE).show();
590 } else { // Create the folder.
591 // Get the new folder icon `Bitmap`.
592 RadioButton defaultFolderIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
593 Bitmap folderIconBitmap;
594 if (defaultFolderIconRadioButton.isChecked()) {
595 // Get the default folder icon `ImageView` from the `Dialog` and convert it to a `Bitmap`.
596 ImageView folderIconImageView = (ImageView) dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
597 Drawable folderIconDrawable = folderIconImageView.getDrawable();
598 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
599 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
600 } else { // Assign `favoriteIcon` from the `WebView`.
601 folderIconBitmap = MainWebViewActivity.favoriteIcon;
604 // Convert `folderIconBitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
605 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
606 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
607 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
609 // Move all the bookmarks down one in the display order.
610 for (int i = 0; i < bookmarksListView.getCount(); i++) {
611 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
612 bookmarksDatabaseHandler.updateBookmarkDisplayOrder(databaseId, i + 1);
615 // Create the folder, placing it at the top of the ListView
616 bookmarksDatabaseHandler.createFolder(folderNameString, 0, currentFolder, folderIconByteArray);
618 // Refresh the ListView.
619 updateBookmarksListView(currentFolder);
624 public void onSaveEditBookmark(DialogFragment dialogFragment) {
625 // Get a long array with the the databaseId of the selected bookmark and convert it to an `int`.
626 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
627 int selectedBookmarkDatabaseId = (int) selectedBookmarksLongArray[0];
629 // Get the `EditText`s from the `editBookmarkDialogFragment` and extract the strings.
630 EditText editBookmarkNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
631 String bookmarkNameString = editBookmarkNameEditText.getText().toString();
632 EditText editBookmarkUrlEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
633 String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
635 // Get `edit_bookmark_current_icon_radiobutton`.
636 RadioButton currentBookmarkIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
638 if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
639 bookmarksDatabaseHandler.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
640 } else { // Update the bookmark using the `WebView` favorite icon.
641 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
642 MainWebViewActivity.favoriteIcon.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
643 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
645 // Update the bookmark and the favorite icon.
646 bookmarksDatabaseHandler.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
649 // Close the contextual action mode.
650 contextualActionMode.finish();
652 // Refresh the `ListView`. `setSelection` scrolls to the position of the bookmark that was edited.
653 updateBookmarksListView(currentFolder);
654 bookmarksListView.setSelection(selectedBookmarkPosition);
658 public void onSaveEditBookmarkFolder(DialogFragment dialogFragment) {
659 // Get the new folder name.
660 EditText editFolderNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
661 String newFolderNameString = editFolderNameEditText.getText().toString();
663 // Check to see if the new folder name is unique.
664 Cursor bookmarkFolderCursor = bookmarksDatabaseHandler.getFolderCursor(newFolderNameString);
665 int existingFoldersWithNewName = bookmarkFolderCursor.getCount();
666 bookmarkFolderCursor.close();
667 if ( ((existingFoldersWithNewName == 0) || newFolderNameString.equals(oldFolderNameString)) && !newFolderNameString.isEmpty()) {
668 // Get a long array with the the database ID of the selected folder and convert it to an `int`.
669 long[] selectedFolderLongArray = bookmarksListView.getCheckedItemIds();
670 int selectedFolderDatabaseId = (int) selectedFolderLongArray[0];
672 // Get the `RadioButtons` from the `Dialog`.
673 RadioButton currentFolderIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
674 RadioButton defaultFolderIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
676 // Check if the favorite icon has changed.
677 if (currentFolderIconRadioButton.isChecked()) {
678 // Update the folder name if it has changed without modifying the favorite icon.
679 if (!newFolderNameString.equals(oldFolderNameString)) {
680 bookmarksDatabaseHandler.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
682 // Refresh the `ListView`. `setSelection` scrolls to the position of the folder that was edited.
683 updateBookmarksListView(currentFolder);
684 bookmarksListView.setSelection(selectedBookmarkPosition);
686 } else { // Update the folder icon.
687 // Get the new folder icon `Bitmap`.
688 Bitmap folderIconBitmap;
689 if (defaultFolderIconRadioButton.isChecked()) {
690 // Get the default folder icon `ImageView` from the `Drawable` and convert it to a `Bitmap`.
691 ImageView folderIconImageView = (ImageView) dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon);
692 Drawable folderIconDrawable = folderIconImageView.getDrawable();
693 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
694 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
695 } else { // Get the web page icon `ImageView` from the `Dialog`.
696 folderIconBitmap = MainWebViewActivity.favoriteIcon;
699 // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
700 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
701 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
702 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
704 bookmarksDatabaseHandler.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray);
706 // Refresh the `ListView`. `setSelection` scrolls to the position of the folder that was edited.
707 updateBookmarksListView(currentFolder);
708 bookmarksListView.setSelection(selectedBookmarkPosition);
710 } else { // Don't edit the folder because the new name is not unique.
711 String cannot_rename_folder = getResources().getString(R.string.cannot_rename_folder) + " \"" + newFolderNameString + "\"";
712 Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), cannot_rename_folder, Snackbar.LENGTH_INDEFINITE).show();
715 // Close the contextual action mode.
716 contextualActionMode.finish();
720 public void onMoveToFolder(DialogFragment dialogFragment) {
721 // Get the new folder database id.
722 ListView folderListView = (ListView) dialogFragment.getDialog().findViewById(R.id.move_to_folder_listview);
723 long[] newFolderLongArray = folderListView.getCheckedItemIds();
725 if (newFolderLongArray.length == 0) { // No new folder was selected.
726 Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.cannot_move_bookmarks), Snackbar.LENGTH_LONG).show();
727 } else { // Move the selected bookmarks.
728 // Get the new folder database ID.
729 int newFolderDatabaseId = (int) newFolderLongArray[0];
731 // Instantiate `newFolderName`.
732 String newFolderName;
734 if (newFolderDatabaseId == 0) {
735 // The new folder is the home folder, represented as `""` in the database.
738 // Get the new folder name from the database.
739 newFolderName = bookmarksDatabaseHandler.getFolderName(newFolderDatabaseId);
742 // Get a long array with the the database ID of the selected bookmarks.
743 long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds();
744 for (long databaseIdLong : selectedBookmarksLongArray) {
745 // Get `databaseIdInt` for each selected bookmark.
746 int databaseIdInt = (int) databaseIdLong;
748 // Move the selected bookmark to the new folder.
749 bookmarksDatabaseHandler.moveToFolder(databaseIdInt, newFolderName);
752 // Refresh the `ListView`.
753 updateBookmarksListView(currentFolder);
755 // Close the contextual app bar.
756 contextualActionMode.finish();
760 private void updateBookmarksListView(String folderName) {
761 // Get a `Cursor` with the current contents of the bookmarks database.
762 bookmarksCursor = bookmarksDatabaseHandler.getAllBookmarksCursorByDisplayOrder(folderName);
764 // Setup `bookmarksCursorAdapter` with `this` context. `false` disables autoRequery.
765 CursorAdapter bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
767 public View newView(Context context, Cursor cursor, ViewGroup parent) {
768 // Inflate the individual item layout. `false` does not attach it to the root.
769 return getLayoutInflater().inflate(R.layout.bookmarks_item_linearlayout, parent, false);
773 public void bindView(View view, Context context, Cursor cursor) {
774 // Get the favorite icon byte array from the `Cursor`.
775 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHandler.FAVORITE_ICON));
777 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
778 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
780 // Display the bitmap in `bookmarkFavoriteIcon`.
781 ImageView bookmarkFavoriteIcon = (ImageView) view.findViewById(R.id.bookmark_favorite_icon);
782 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
785 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
786 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHandler.BOOKMARK_NAME));
787 TextView bookmarkNameTextView = (TextView) view.findViewById(R.id.bookmark_name);
788 bookmarkNameTextView.setText(bookmarkNameString);
790 // Make the font bold for folders.
791 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHandler.IS_FOLDER)) == 1) {
792 // The first argument is `null` because we don't want to change the font.
793 bookmarkNameTextView.setTypeface(null, Typeface.BOLD);
794 } else { // Reset the font to default.
795 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
800 // Update the ListView.
801 bookmarksListView.setAdapter(bookmarksCursorAdapter);
803 // Set the AppBar title.
804 if (currentFolder.isEmpty()) {
805 appBar.setTitle(R.string.bookmarks);
807 appBar.setTitle(currentFolder);
811 private void updateBookmarksListViewExcept(long[] exceptIdLongArray, String folderName) {
812 // Get a `Cursor` with the current contents of the bookmarks database except for the specified database IDs.
813 bookmarksCursor = bookmarksDatabaseHandler.getBookmarksCursorExcept(exceptIdLongArray, folderName);
815 // Setup `bookmarksCursorAdapter` with `this` context. `false` disables autoRequery.
816 CursorAdapter bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
818 public View newView(Context context, Cursor cursor, ViewGroup parent) {
819 // Inflate the individual item layout. `false` does not attach it to the root.
820 return getLayoutInflater().inflate(R.layout.bookmarks_item_linearlayout, parent, false);
824 public void bindView(View view, Context context, Cursor cursor) {
825 // Get the favorite icon byte array from the cursor.
826 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHandler.FAVORITE_ICON));
828 // Convert the byte array to a Bitmap beginning at the first byte and ending at the last.
829 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
831 // Display the bitmap in `bookmarkFavoriteIcon`.
832 ImageView bookmarkFavoriteIcon = (ImageView) view.findViewById(R.id.bookmark_favorite_icon);
833 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
836 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
837 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHandler.BOOKMARK_NAME));
838 TextView bookmarkNameTextView = (TextView) view.findViewById(R.id.bookmark_name);
839 bookmarkNameTextView.setText(bookmarkNameString);
841 // Make the font bold for folders.
842 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHandler.IS_FOLDER)) == 1) {
843 // The first argument is `null` because we don't want to change the font.
844 bookmarkNameTextView.setTypeface(null, Typeface.BOLD);
845 } else { // Reset the font to default.
846 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
851 // Update the ListView.
852 bookmarksListView.setAdapter(bookmarksCursorAdapter);
855 private void deleteBookmarkFolderContents(int databaseId) {
856 // Get the name of the folder.
857 String folderName = bookmarksDatabaseHandler.getFolderName(databaseId);
859 // Get the contents of the folder.
860 Cursor folderCursor = bookmarksDatabaseHandler.getAllBookmarksCursorByDisplayOrder(folderName);
862 for (int i = 0; i < folderCursor.getCount(); i++) {
863 // Move `folderCursor` to the current row.
864 folderCursor.moveToPosition(i);
866 // Get the database ID of the item.
867 int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndex(BookmarksDatabaseHandler._ID));
869 // If this is a folder, delete the contents first.
870 if (bookmarksDatabaseHandler.isFolder(itemDatabaseId)) {
871 deleteBookmarkFolderContents(itemDatabaseId);
874 bookmarksDatabaseHandler.deleteBookmark(itemDatabaseId);