| Index: chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsManagerImpl.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsManagerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsManagerImpl.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..39492cbcebe7aeb5d1dc4490588c631b64d5d093
|
| --- /dev/null
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsManagerImpl.java
|
| @@ -0,0 +1,366 @@
|
| +// Copyright 2017 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.suggestions;
|
| +
|
| +import android.app.Activity;
|
| +import android.net.Uri;
|
| +import android.os.SystemClock;
|
| +
|
| +import org.chromium.base.Callback;
|
| +import org.chromium.base.ObserverList;
|
| +import org.chromium.base.ThreadUtils;
|
| +import org.chromium.base.metrics.RecordHistogram;
|
| +import org.chromium.base.metrics.RecordUserAction;
|
| +import org.chromium.chrome.browser.ChromeFeatureList;
|
| +import org.chromium.chrome.browser.UrlConstants;
|
| +import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
|
| +import org.chromium.chrome.browser.download.DownloadUtils;
|
| +import org.chromium.chrome.browser.favicon.FaviconHelper;
|
| +import org.chromium.chrome.browser.favicon.FaviconHelper.FaviconImageCallback;
|
| +import org.chromium.chrome.browser.favicon.FaviconHelper.IconAvailabilityCallback;
|
| +import org.chromium.chrome.browser.favicon.LargeIconBridge;
|
| +import org.chromium.chrome.browser.favicon.LargeIconBridge.LargeIconCallback;
|
| +import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
|
| +import org.chromium.chrome.browser.ntp.NewTabPage.DestructionObserver;
|
| +import org.chromium.chrome.browser.ntp.NewTabPageUma;
|
| +import org.chromium.chrome.browser.ntp.snippets.KnownCategories;
|
| +import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
|
| +import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge;
|
| +import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
|
| +import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
|
| +import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
|
| +import org.chromium.chrome.browser.preferences.PrefServiceBridge;
|
| +import org.chromium.chrome.browser.profiles.Profile;
|
| +import org.chromium.chrome.browser.tab.Tab;
|
| +import org.chromium.chrome.browser.tabmodel.TabModel;
|
| +import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType;
|
| +import org.chromium.chrome.browser.tabmodel.TabModelSelector;
|
| +import org.chromium.chrome.browser.tabmodel.TabModelUtils;
|
| +import org.chromium.chrome.browser.tabmodel.document.TabDelegate;
|
| +import org.chromium.content_public.browser.LoadUrlParams;
|
| +import org.chromium.content_public.common.Referrer;
|
| +import org.chromium.ui.base.PageTransition;
|
| +import org.chromium.ui.mojom.WindowOpenDisposition;
|
| +
|
| +import java.util.HashSet;
|
| +import java.util.Set;
|
| +import java.util.concurrent.TimeUnit;
|
| +
|
| +/**
|
| + * {@link ContentSuggestionsManager} implementation.
|
| + */
|
| +public class SuggestionsManagerImpl implements ContentSuggestionsManager {
|
| + private static final String CHROME_CONTENT_SUGGESTIONS_REFERRER =
|
| + "https://www.googleapis.com/auth/chrome-content-suggestions";
|
| +
|
| + private final ObserverList<DestructionObserver> mDestructionObservers = new ObserverList<>();
|
| + private final SuggestionsSource mSuggestionsSource;
|
| + private SnippetsBridge mSnippetsBridge;
|
| + private final Activity mActivity;
|
| + private final Profile mProfile;
|
| +
|
| + private final Tab mTab;
|
| + private final TabModelSelector mTabModelSelector;
|
| +
|
| + private FaviconHelper mFaviconHelper;
|
| + private LargeIconBridge mLargeIconBridge;
|
| +
|
| + private boolean mIsDestroyed;
|
| +
|
| + public SuggestionsManagerImpl(SuggestionsSource suggestionsSource, Activity activity,
|
| + Profile profile, Tab currentTab, TabModelSelector tabModelSelector) {
|
| + mSuggestionsSource = suggestionsSource;
|
| + mActivity = activity;
|
| + mProfile = profile;
|
| + mTab = currentTab;
|
| + mTabModelSelector = tabModelSelector;
|
| + mSnippetsBridge = (SnippetsBridge) suggestionsSource;
|
| + }
|
| +
|
| + @Override
|
| + public boolean isOpenInNewWindowEnabled() {
|
| + return MultiWindowUtils.getInstance().isOpenInOtherWindowSupported(mActivity);
|
| + }
|
| +
|
| + @Override
|
| + public boolean isOpenInIncognitoEnabled() {
|
| + return PrefServiceBridge.getInstance().isIncognitoModeEnabled();
|
| + }
|
| +
|
| + @Override
|
| + public void navigateToBookmarks() {
|
| + if (mIsDestroyed) return;
|
| + RecordUserAction.record("MobileNTPSwitchToBookmarks");
|
| + BookmarkUtils.showBookmarkManager(mActivity);
|
| + }
|
| +
|
| + @Override
|
| + public void navigateToRecentTabs() {
|
| + if (mIsDestroyed) return;
|
| + RecordUserAction.record("MobileNTPSwitchToOpenTabs");
|
| + mTab.loadUrl(new LoadUrlParams(UrlConstants.RECENT_TABS_URL));
|
| + }
|
| +
|
| + @Override
|
| + public void navigateToDownloadManager() {
|
| + if (mIsDestroyed) return;
|
| + assert DownloadUtils.isDownloadHomeEnabled();
|
| +
|
| + RecordUserAction.record("MobileNTPSwitchToDownloadManager");
|
| + DownloadUtils.showDownloadManager(mActivity, mTab);
|
| + }
|
| +
|
| + @Override
|
| + public void onLearnMoreClicked() {
|
| + if (mIsDestroyed) return;
|
| + NewTabPageUma.recordAction(NewTabPageUma.ACTION_CLICKED_LEARN_MORE);
|
| + String url = "https://support.google.com/chrome/?p=new_tab";
|
| + // TODO(mastiz): Change this to LINK?
|
| + openUrl(WindowOpenDisposition.CURRENT_TAB,
|
| + new LoadUrlParams(url, PageTransition.AUTO_BOOKMARK));
|
| + }
|
| +
|
| + @Override
|
| + public void openSnippet(int windowOpenDisposition, SnippetArticle article) {
|
| + mSnippetsBridge.onSuggestionOpened(article, windowOpenDisposition);
|
| + NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_SNIPPET);
|
| +
|
| + if (article.mIsAssetDownload) {
|
| + assert windowOpenDisposition == WindowOpenDisposition.CURRENT_TAB
|
| + || windowOpenDisposition == WindowOpenDisposition.NEW_WINDOW
|
| + || windowOpenDisposition == WindowOpenDisposition.NEW_FOREGROUND_TAB;
|
| + DownloadUtils.openFile(
|
| + article.getAssetDownloadFile(), article.getAssetDownloadMimeType(), false);
|
| + return;
|
| + }
|
| +
|
| + if (article.isRecentTab()) {
|
| + assert windowOpenDisposition == WindowOpenDisposition.CURRENT_TAB;
|
| + // TODO(vitaliii): Add a debug check that the result is true after crbug.com/662924
|
| + // is resolved.
|
| + openRecentTabSnippet(article);
|
| + return;
|
| + }
|
| +
|
| + // TODO(treib): Also track other dispositions. crbug.com/665915
|
| + if (windowOpenDisposition == WindowOpenDisposition.CURRENT_TAB) {
|
| + NewTabPageUma.monitorContentSuggestionVisit(mTab, article.mCategory);
|
| + }
|
| +
|
| + LoadUrlParams loadUrlParams;
|
| + // We explicitly open an offline page only for offline page downloads. For all other
|
| + // sections the URL is opened and it is up to Offline Pages whether to open its offline
|
| + // page (e.g. when offline).
|
| + if (article.isDownload() && !article.mIsAssetDownload) {
|
| + assert article.getOfflinePageOfflineId() != null;
|
| + assert windowOpenDisposition == WindowOpenDisposition.CURRENT_TAB
|
| + || windowOpenDisposition == WindowOpenDisposition.NEW_WINDOW
|
| + || windowOpenDisposition == WindowOpenDisposition.NEW_FOREGROUND_TAB;
|
| + loadUrlParams = OfflinePageUtils.getLoadUrlParamsForOpeningOfflineVersion(
|
| + article.mUrl, article.getOfflinePageOfflineId());
|
| + // Extra headers are not read in loadUrl, but verbatim headers are.
|
| + loadUrlParams.setVerbatimHeaders(loadUrlParams.getExtraHeadersString());
|
| + } else {
|
| + loadUrlParams = new LoadUrlParams(article.mUrl, PageTransition.AUTO_BOOKMARK);
|
| + }
|
| +
|
| + // For article suggestions, we set the referrer. This is exploited
|
| + // to filter out these history entries for NTP tiles.
|
| + // TODO(mastiz): Extend this with support for other categories.
|
| + if (article.mCategory == KnownCategories.ARTICLES) {
|
| + loadUrlParams.setReferrer(new Referrer(
|
| + CHROME_CONTENT_SUGGESTIONS_REFERRER, Referrer.REFERRER_POLICY_ALWAYS));
|
| + }
|
| +
|
| + openUrl(windowOpenDisposition, loadUrlParams);
|
| + }
|
| +
|
| + @Override
|
| + public void openUrl(int windowOpenDisposition, LoadUrlParams loadUrlParams) {
|
| + assert !mIsDestroyed;
|
| + switch (windowOpenDisposition) {
|
| + case WindowOpenDisposition.CURRENT_TAB:
|
| + mTab.loadUrl(loadUrlParams);
|
| + break;
|
| + case WindowOpenDisposition.NEW_FOREGROUND_TAB:
|
| + openUrlInNewTab(loadUrlParams, false);
|
| + break;
|
| + case WindowOpenDisposition.OFF_THE_RECORD:
|
| + openUrlInNewTab(loadUrlParams, true);
|
| + break;
|
| + case WindowOpenDisposition.NEW_WINDOW:
|
| + openUrlInNewWindow(loadUrlParams);
|
| + break;
|
| + case WindowOpenDisposition.SAVE_TO_DISK:
|
| + saveUrlForOffline(loadUrlParams.getUrl());
|
| + break;
|
| + default:
|
| + assert false;
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void trackSnippetsPageImpression(int[] categories, int[] suggestionsPerCategory) {
|
| + mSnippetsBridge.onPageShown(categories, suggestionsPerCategory);
|
| + }
|
| +
|
| + @Override
|
| + public void trackSnippetImpression(SnippetArticle article) {
|
| + mSnippetsBridge.onSuggestionShown(article);
|
| + }
|
| +
|
| + @Override
|
| + public void trackSnippetMenuOpened(SnippetArticle article) {
|
| + mSnippetsBridge.onSuggestionMenuOpened(article);
|
| + }
|
| +
|
| + @Override
|
| + public void trackSnippetCategoryActionImpression(int category, int position) {
|
| + mSnippetsBridge.onMoreButtonShown(category, position);
|
| + }
|
| +
|
| + @Override
|
| + public void trackSnippetCategoryActionClick(int category, int position) {
|
| + mSnippetsBridge.onMoreButtonClicked(category, position);
|
| + switch (category) {
|
| + case KnownCategories.BOOKMARKS:
|
| + NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_BOOKMARKS_MANAGER);
|
| + break;
|
| + // MORE button in both categories leads to the recent tabs manager
|
| + case KnownCategories.FOREIGN_TABS:
|
| + case KnownCategories.RECENT_TABS:
|
| + NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_RECENT_TABS_MANAGER);
|
| + break;
|
| + case KnownCategories.DOWNLOADS:
|
| + NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_DOWNLOADS_MANAGER);
|
| + break;
|
| + default:
|
| + // No action associated
|
| + break;
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void getLocalFaviconImageForURL(
|
| + String url, int size, FaviconImageCallback faviconCallback) {
|
| + if (mIsDestroyed) return;
|
| + if (mFaviconHelper == null) mFaviconHelper = new FaviconHelper();
|
| + mFaviconHelper.getLocalFaviconImageForURL(mProfile, url, size, faviconCallback);
|
| + }
|
| +
|
| + @Override
|
| + public void getLargeIconForUrl(String url, int size, LargeIconCallback callback) {
|
| + if (mIsDestroyed) return;
|
| + if (mLargeIconBridge == null) mLargeIconBridge = new LargeIconBridge(mProfile);
|
| + mLargeIconBridge.getLargeIconForUrl(url, size, callback);
|
| + }
|
| +
|
| + @Override
|
| + public void ensureIconIsAvailable(String pageUrl, String iconUrl, boolean isLargeIcon,
|
| + boolean isTemporary, IconAvailabilityCallback callback) {
|
| + if (mIsDestroyed) return;
|
| + if (mFaviconHelper == null) mFaviconHelper = new FaviconHelper();
|
| + mFaviconHelper.ensureIconIsAvailable(mProfile, mTab.getWebContents(), pageUrl, iconUrl,
|
| + isLargeIcon, isTemporary, callback);
|
| + }
|
| +
|
| + @Override
|
| + public void getUrlsAvailableOffline(
|
| + Set<String> pageUrls, final Callback<Set<String>> callback) {
|
| + final Set<String> urlsAvailableOffline = new HashSet<>();
|
| + if (mIsDestroyed || !isNtpOfflinePagesEnabled()) {
|
| + callback.onResult(urlsAvailableOffline);
|
| + return;
|
| + }
|
| +
|
| + HashSet<String> urlsToCheckForOfflinePage = new HashSet<>();
|
| +
|
| + for (String pageUrl : pageUrls) {
|
| + if (isLocalUrl(pageUrl)) {
|
| + urlsAvailableOffline.add(pageUrl);
|
| + } else {
|
| + urlsToCheckForOfflinePage.add(pageUrl);
|
| + }
|
| + }
|
| +
|
| + final long offlineQueryStartTime = SystemClock.elapsedRealtime();
|
| +
|
| + OfflinePageBridge offlinePageBridge = OfflinePageBridge.getForProfile(mProfile);
|
| +
|
| + // TODO(dewittj): Remove this code by making the NTP badging available after the NTP is
|
| + // fully loaded.
|
| + if (offlinePageBridge == null || !offlinePageBridge.isOfflinePageModelLoaded()) {
|
| + // Posting a task to avoid potential re-entrancy issues.
|
| + ThreadUtils.postOnUiThread(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + callback.onResult(urlsAvailableOffline);
|
| + }
|
| + });
|
| + return;
|
| + }
|
| +
|
| + offlinePageBridge.checkPagesExistOffline(
|
| + urlsToCheckForOfflinePage, new Callback<Set<String>>() {
|
| + @Override
|
| + public void onResult(Set<String> urlsWithOfflinePages) {
|
| + urlsAvailableOffline.addAll(urlsWithOfflinePages);
|
| + callback.onResult(urlsAvailableOffline);
|
| + RecordHistogram.recordTimesHistogram("NewTabPage.OfflineUrlsLoadTime",
|
| + SystemClock.elapsedRealtime() - offlineQueryStartTime,
|
| + TimeUnit.MILLISECONDS);
|
| + }
|
| + });
|
| + }
|
| +
|
| + @Override
|
| + public SuggestionsSource getSuggestionsSource() {
|
| + return mSuggestionsSource;
|
| + }
|
| +
|
| + @Override
|
| + public void addDestructionObserver(DestructionObserver destructionObserver) {
|
| + mDestructionObservers.addObserver(destructionObserver);
|
| + }
|
| +
|
| + public void onDestroy() {
|
| + for (DestructionObserver observer : mDestructionObservers) {
|
| + observer.onDestroy();
|
| + }
|
| + mIsDestroyed = true;
|
| + }
|
| +
|
| + private boolean openRecentTabSnippet(SnippetArticle article) {
|
| + TabModel tabModel = mTabModelSelector.getModel(false);
|
| + int tabId = Integer.parseInt(article.getRecentTabId());
|
| + int tabIndex = TabModelUtils.getTabIndexById(tabModel, tabId);
|
| + if (tabIndex == TabModel.INVALID_TAB_INDEX) return false;
|
| + TabModelUtils.setIndex(tabModel, tabIndex);
|
| + return true;
|
| + }
|
| +
|
| + private void openUrlInNewWindow(LoadUrlParams loadUrlParams) {
|
| + TabDelegate tabDelegate = new TabDelegate(false);
|
| + tabDelegate.createTabInOtherWindow(loadUrlParams, mActivity, mTab.getParentId());
|
| + }
|
| +
|
| + private void openUrlInNewTab(LoadUrlParams loadUrlParams, boolean incognito) {
|
| + mTabModelSelector.openNewTab(
|
| + loadUrlParams, TabLaunchType.FROM_LONGPRESS_BACKGROUND, mTab, incognito);
|
| + }
|
| +
|
| + private void saveUrlForOffline(String url) {
|
| + OfflinePageBridge.getForProfile(mProfile).savePageLater(
|
| + url, "ntp_suggestions", true /* userRequested */);
|
| + }
|
| +
|
| + private boolean isNtpOfflinePagesEnabled() {
|
| + return ChromeFeatureList.isEnabled(ChromeFeatureList.NTP_OFFLINE_PAGES_FEATURE_NAME);
|
| + }
|
| +
|
| + private boolean isLocalUrl(String url) {
|
| + return "file".equals(Uri.parse(url).getScheme());
|
| + }
|
| +}
|
|
|