// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.bookmarks;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.chromium.base.ObserverList;
import org.chromium.base.ResettersForTesting;
import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.components.bookmarks.BookmarkId;
import org.chromium.components.bookmarks.BookmarkItem;
import org.chromium.components.bookmarks.BookmarkType;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A class that encapsulates {@link BookmarkBridge} and provides extra features such as undo, large
* icon fetching, reader mode url redirecting, etc. This class should serve as the single class for
* the UI to acquire data from the backend.
*/
public class BookmarkModel extends BookmarkBridge {
private static BookmarkModel sInstanceForTesting;
/** Set an instance for testing. */
public static void setInstanceForTesting(BookmarkModel bookmarkModel) {
sInstanceForTesting = bookmarkModel;
ResettersForTesting.register(() -> sInstanceForTesting = null);
}
/**
* Observer that listens to delete event. This interface is used by undo controllers to know
* which bookmarks were deleted. Note this observer only listens to events that go through
* bookmark model.
*/
public interface BookmarkDeleteObserver {
/**
* Callback being triggered immediately before bookmarks are deleted.
*
* @param titles All titles of the bookmarks to be deleted.
* @param isUndoable Whether the deletion is undoable.
*/
void onDeleteBookmarks(String[] titles, boolean isUndoable);
}
private ObserverList<BookmarkDeleteObserver> mDeleteObservers = new ObserverList<>();
/**
* Provides an instance of the bookmark model for the provided profile.
*
* @param profile A profile for which the bookmark model is provided.
* @return An instance of the bookmark model.
*/
public static final BookmarkModel getForProfile(@NonNull Profile profile) {
assert profile != null;
if (sInstanceForTesting != null) {
return sInstanceForTesting;
}
ThreadUtils.assertOnUiThread();
return BookmarkBridge.getForProfile(profile);
}
BookmarkModel(long nativeBookmarkBridge) {
super(nativeBookmarkBridge);
}
/**
* Add an observer that listens to delete events that go through the bookmark model.
*
* @param observer The observer to add.
*/
void addDeleteObserver(BookmarkDeleteObserver observer) {
mDeleteObservers.addObserver(observer);
}
/**
* Remove the observer from listening to bookmark deleting events.
*
* @param observer The observer to remove.
*/
void removeDeleteObserver(BookmarkDeleteObserver observer) {
mDeleteObservers.removeObserver(observer);
}
/**
* Delete one or multiple bookmarks from model. If more than one bookmarks are passed here, this
* method will group these delete operations into one undo bundle so that later if the user
* clicks undo, all bookmarks deleted here will be restored.
*
* @param bookmarks Bookmarks to delete. Note this array should not contain a folder and its
* children, because deleting folder will also remove all its children, and deleting
* children once more will cause errors.
*/
public void deleteBookmarks(BookmarkId... bookmarks) {
assert bookmarks != null && bookmarks.length > 0;
// Store all titles of bookmarks.
List<String> titles = new ArrayList<>();
boolean isUndoable = true;
startGroupingUndos();
for (BookmarkId bookmarkId : bookmarks) {
BookmarkItem bookmarkItem = getBookmarkById(bookmarkId);
if (bookmarkItem == null) continue;
isUndoable &= (bookmarkId.getType() == BookmarkType.NORMAL);
titles.add(bookmarkItem.getTitle());
deleteBookmark(bookmarkId);
}
endGroupingUndos();
for (BookmarkDeleteObserver observer : mDeleteObservers) {
observer.onDeleteBookmarks(titles.toArray(new String[titles.size()]), isUndoable);
}
}
/**
* Calls {@link BookmarkBridge#moveBookmark(BookmarkId, BookmarkId, int)} for the given bookmark
* list. The bookmarks are appended at the end.
*/
public void moveBookmarks(List<BookmarkId> bookmarkIds, BookmarkId newParentId) {
Set<BookmarkId> existingChildren = new HashSet<>(getChildIds(newParentId));
int appendIndex = getChildCount(newParentId);
for (BookmarkId child : bookmarkIds) {
if (!existingChildren.contains(child)) {
moveBookmark(child, newParentId, appendIndex++);
}
}
}
/**
* @see org.chromium.chrome.browser.bookmarks.BookmarkItem#getTitle()
*/
public String getBookmarkTitle(BookmarkId bookmarkId) {
BookmarkItem bookmarkItem = getBookmarkById(bookmarkId);
if (bookmarkItem == null) return "";
return bookmarkItem.getTitle();
}
/**
* @return The id of the default folder to view bookmarks.
*/
public BookmarkId getDefaultFolderViewLocation() {
return getRootFolderId();
}
/**
* @param tab Tab whose current URL is checked against.
* @return {@code true} if the current tab URL has a bookmark associated with it. If the
* bookmark backend is not loaded, return {@code false}.
*/
public boolean hasBookmarkIdForTab(@Nullable Tab tab) {
if (tab == null) return false;
return isBookmarked(tab.getOriginalUrl());
}
/**
* @param tab Tab whose current URL is checked against.
* @return BookmarkId or {@link null} if bookmark backend is not loaded.
*/
public @Nullable BookmarkId getUserBookmarkIdForTab(@Nullable Tab tab) {
if (tab == null) return null;
return getMostRecentlyAddedUserBookmarkIdForUrl(tab.getOriginalUrl());
}
}