// Copyright 2015 The Chromium Authors. All rights reserved.
// 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 android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.provider.Browser;
import android.text.TextUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.UrlConstants;
import org.chromium.chrome.browser.document.ChromeLauncherActivity;
import org.chromium.chrome.browser.ntp.NewTabPageUma;
import org.chromium.chrome.browser.snackbar.Snackbar;
import org.chromium.chrome.browser.snackbar.SnackbarManager;
import org.chromium.chrome.browser.snackbar.SnackbarManager.SnackbarController;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.util.IntentUtils;
import org.chromium.components.bookmarks.BookmarkId;
import org.chromium.components.bookmarks.BookmarkType;
import org.chromium.ui.base.DeviceFormFactor;
/**
* A class holding static util functions for bookmark.
*/
public class BookmarkUtils {
private static final String PREF_LAST_USED_URL = "enhanced_bookmark_last_used_url";
private static final String PREF_LAST_USED_PARENT = "enhanced_bookmark_last_used_parent_folder";
/**
* If the tab has already been bookmarked, start {@link BookmarkEditActivity} for the
* bookmark. If not, add the bookmark to bookmarkmodel, and show a snackbar notifying the user.
*
* Note: Takes ownership of bookmarkModel, and will call |destroy| on it when finished.
*
* @param existingBookmarkId The bookmark ID if the tab has already been bookmarked.
* @param bookmarkModel The bookmark model.
* @param tab The tab to add or edit a bookmark.
* @param snackbarManager The SnackbarManager used to show the snackbar.
* @param activity Current activity.
* @return Bookmark ID of the bookmark. Could be <code>null</code> if bookmark didn't exist
* and bookmark model failed to create it.
*/
public static BookmarkId addOrEditBookmark(long existingBookmarkId, BookmarkModel bookmarkModel,
Tab tab, SnackbarManager snackbarManager, Activity activity) {
if (existingBookmarkId != Tab.INVALID_BOOKMARK_ID) {
BookmarkId bookmarkId = new BookmarkId(existingBookmarkId, BookmarkType.NORMAL);
startEditActivity(activity, bookmarkId);
bookmarkModel.destroy();
return bookmarkId;
}
BookmarkId parent = getLastUsedParent(activity);
if (parent == null || !bookmarkModel.doesBookmarkExist(parent)) {
parent = bookmarkModel.getDefaultFolder();
}
String url = tab.getOriginalUrl();
BookmarkId bookmarkId = bookmarkModel.addBookmark(parent,
bookmarkModel.getChildCount(parent), tab.getTitle(), url);
Snackbar snackbar = null;
if (bookmarkId == null) {
snackbar = Snackbar.make(activity.getString(R.string.bookmark_page_failed),
new SnackbarController() {
@Override
public void onDismissNoAction(Object actionData) { }
@Override
public void onAction(Object actionData) { }
}, Snackbar.TYPE_NOTIFICATION, Snackbar.UMA_BOOKMARK_ADDED)
.setSingleLine(false);
RecordUserAction.record("EnhancedBookmarks.AddingFailed");
} else {
String folderName = bookmarkModel.getBookmarkTitle(
bookmarkModel.getBookmarkById(bookmarkId).getParentId());
SnackbarController snackbarController =
createSnackbarControllerForEditButton(activity, bookmarkId);
if (getLastUsedParent(activity) == null) {
snackbar = Snackbar.make(activity.getString(R.string.bookmark_page_saved),
snackbarController, Snackbar.TYPE_ACTION, Snackbar.UMA_BOOKMARK_ADDED);
} else {
snackbar = Snackbar.make(folderName, snackbarController, Snackbar.TYPE_ACTION,
Snackbar.UMA_BOOKMARK_ADDED)
.setTemplateText(activity.getString(R.string.bookmark_page_saved_folder));
}
snackbar.setSingleLine(false).setAction(activity.getString(R.string.bookmark_item_edit),
null);
}
snackbarManager.showSnackbar(snackbar);
bookmarkModel.destroy();
return bookmarkId;
}
/**
* Adds a bookmark with the given title and url to the last used parent folder. Provides
* no visual feedback that a bookmark has been added.
*
* @param title The title of the bookmark.
* @param url The URL of the new bookmark.
*/
public static BookmarkId addBookmarkSilently(
Context context, BookmarkModel bookmarkModel, String title, String url) {
BookmarkId parent = getLastUsedParent(context);
if (parent == null || !bookmarkModel.doesBookmarkExist(parent)) {
parent = bookmarkModel.getDefaultFolder();
}
return bookmarkModel.addBookmark(parent, bookmarkModel.getChildCount(parent), title, url);
}
/**
* Creates a snackbar controller for a case where "Edit" button is shown to edit the newly
* created bookmark.
*/
private static SnackbarController createSnackbarControllerForEditButton(
final Activity activity, final BookmarkId bookmarkId) {
return new SnackbarController() {
@Override
public void onDismissNoAction(Object actionData) {
RecordUserAction.record("EnhancedBookmarks.EditAfterCreateButtonNotClicked");
}
@Override
public void onAction(Object actionData) {
RecordUserAction.record("EnhancedBookmarks.EditAfterCreateButtonClicked");
startEditActivity(activity, bookmarkId);
}
};
}
/**
* Shows bookmark main UI.
*/
public static void showBookmarkManager(Activity activity) {
String url = getFirstUrlToLoad(activity);
if (DeviceFormFactor.isTablet(activity)) {
openUrl(activity, url, activity.getComponentName());
} else {
Intent intent = new Intent(activity, BookmarkActivity.class);
intent.setData(Uri.parse(url));
intent.putExtra(IntentHandler.EXTRA_PARENT_COMPONENT, activity.getComponentName());
activity.startActivity(intent);
}
}
/**
* The initial url the bookmark manager shows depends some experiments we run.
*/
private static String getFirstUrlToLoad(Activity activity) {
String lastUsedUrl = getLastUsedUrl(activity);
return TextUtils.isEmpty(lastUsedUrl) ? UrlConstants.BOOKMARKS_URL : lastUsedUrl;
}
/**
* Saves the last used url to preference. The saved url will be later queried by
* {@link #getLastUsedUrl(Context)}
*/
static void setLastUsedUrl(Context context, String url) {
ContextUtils.getAppSharedPreferences().edit()
.putString(PREF_LAST_USED_URL, url).apply();
}
/**
* Fetches url representing the user's state last time they close the bookmark manager.
*/
@VisibleForTesting
static String getLastUsedUrl(Context context) {
return ContextUtils.getAppSharedPreferences().getString(
PREF_LAST_USED_URL, UrlConstants.BOOKMARKS_URL);
}
/**
* Save the last used {@link BookmarkId} as a folder to put new bookmarks to.
*/
static void setLastUsedParent(Context context, BookmarkId bookmarkId) {
ContextUtils.getAppSharedPreferences().edit()
.putString(PREF_LAST_USED_PARENT, bookmarkId.toString()).apply();
}
/**
* @return The parent {@link BookmarkId} that the user used the last time or null if the user
* has never selected a parent folder to use.
*/
static BookmarkId getLastUsedParent(Context context) {
SharedPreferences preferences = ContextUtils.getAppSharedPreferences();
if (!preferences.contains(PREF_LAST_USED_PARENT)) return null;
return BookmarkId.getBookmarkIdFromString(
preferences.getString(PREF_LAST_USED_PARENT, null));
}
/** Starts an {@link BookmarkEditActivity} for the given {@link BookmarkId}. */
public static void startEditActivity(Context context, BookmarkId bookmarkId) {
Intent intent = new Intent(context, BookmarkEditActivity.class);
intent.putExtra(BookmarkEditActivity.INTENT_BOOKMARK_ID, bookmarkId.toString());
if (context instanceof BookmarkActivity) {
((BookmarkActivity) context).startActivityForResult(
intent, BookmarkActivity.EDIT_BOOKMARK_REQUEST_CODE);
} else {
context.startActivity(intent);
}
}
/**
* Opens a bookmark and reports UMA.
* @param model Bookmarks model to manage the bookmark.
* @param activity Activity requesting to open the bookmark.
* @param bookmarkId ID of the bookmark to be opened.
* @param launchLocation Location from which the bookmark is being opened.
* @return Whether the bookmark was successfully opened.
*/
public static boolean openBookmark(BookmarkModel model, Activity activity,
BookmarkId bookmarkId, int launchLocation) {
if (model.getBookmarkById(bookmarkId) == null) return false;
String url = model.getBookmarkById(bookmarkId).getUrl();
NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_BOOKMARK);
RecordHistogram.recordEnumeratedHistogram(
"Stars.LaunchLocation", launchLocation, BookmarkLaunchLocation.COUNT);
if (DeviceFormFactor.isTablet(activity)) {
// For tablets, the bookmark manager is open in a tab in the ChromeActivity. Use
// the ComponentName of the ChromeActivity passed into this method.
openUrl(activity, url, activity.getComponentName());
} else {
// For phones, the bookmark manager is a separate activity. When the activity is
// launched, an intent extra is set specifying the parent component.
ComponentName parentComponent = IntentUtils.safeGetParcelableExtra(
activity.getIntent(), IntentHandler.EXTRA_PARENT_COMPONENT);
openUrl(activity, url, parentComponent);
}
return true;
}
private static void openUrl(Activity activity, String url, ComponentName componentName) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.putExtra(Browser.EXTRA_APPLICATION_ID,
activity.getApplicationContext().getPackageName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (componentName != null) {
intent.setComponent(componentName);
} else {
// If the bookmark manager is shown in a tab on a phone (rather than in a separate
// activity) the component name may be null. Send the intent through
// ChromeLauncherActivity instead to avoid crashing. See crbug.com/615012.
intent.setClass(activity, ChromeLauncherActivity.class);
}
IntentHandler.startActivityForTrustedIntent(intent, activity);
}
/**
* Closes the {@link BookmarkActivity} on Phone. Does nothing on tablet.
*/
public static void finishActivityOnPhone(Context context) {
if (context instanceof BookmarkActivity) {
((Activity) context).finish();
}
}
}