OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.suggestions; |
| 6 |
| 7 import android.app.Activity; |
| 8 import android.net.Uri; |
| 9 import android.os.SystemClock; |
| 10 |
| 11 import org.chromium.base.Callback; |
| 12 import org.chromium.base.ObserverList; |
| 13 import org.chromium.base.ThreadUtils; |
| 14 import org.chromium.base.metrics.RecordHistogram; |
| 15 import org.chromium.base.metrics.RecordUserAction; |
| 16 import org.chromium.chrome.browser.ChromeFeatureList; |
| 17 import org.chromium.chrome.browser.UrlConstants; |
| 18 import org.chromium.chrome.browser.bookmarks.BookmarkUtils; |
| 19 import org.chromium.chrome.browser.download.DownloadUtils; |
| 20 import org.chromium.chrome.browser.favicon.FaviconHelper; |
| 21 import org.chromium.chrome.browser.favicon.FaviconHelper.FaviconImageCallback; |
| 22 import org.chromium.chrome.browser.favicon.FaviconHelper.IconAvailabilityCallbac
k; |
| 23 import org.chromium.chrome.browser.favicon.LargeIconBridge; |
| 24 import org.chromium.chrome.browser.favicon.LargeIconBridge.LargeIconCallback; |
| 25 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils; |
| 26 import org.chromium.chrome.browser.ntp.NewTabPage.DestructionObserver; |
| 27 import org.chromium.chrome.browser.ntp.NewTabPageUma; |
| 28 import org.chromium.chrome.browser.ntp.snippets.KnownCategories; |
| 29 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle; |
| 30 import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge; |
| 31 import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource; |
| 32 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; |
| 33 import org.chromium.chrome.browser.offlinepages.OfflinePageUtils; |
| 34 import org.chromium.chrome.browser.preferences.PrefServiceBridge; |
| 35 import org.chromium.chrome.browser.profiles.Profile; |
| 36 import org.chromium.chrome.browser.tab.Tab; |
| 37 import org.chromium.chrome.browser.tabmodel.TabModel; |
| 38 import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType; |
| 39 import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
| 40 import org.chromium.chrome.browser.tabmodel.TabModelUtils; |
| 41 import org.chromium.chrome.browser.tabmodel.document.TabDelegate; |
| 42 import org.chromium.content_public.browser.LoadUrlParams; |
| 43 import org.chromium.content_public.common.Referrer; |
| 44 import org.chromium.ui.base.PageTransition; |
| 45 import org.chromium.ui.mojom.WindowOpenDisposition; |
| 46 |
| 47 import java.util.HashSet; |
| 48 import java.util.Set; |
| 49 import java.util.concurrent.TimeUnit; |
| 50 |
| 51 /** |
| 52 * {@link ContentSuggestionsManager} implementation. |
| 53 */ |
| 54 public class SuggestionsManagerImpl implements ContentSuggestionsManager { |
| 55 private static final String CHROME_CONTENT_SUGGESTIONS_REFERRER = |
| 56 "https://www.googleapis.com/auth/chrome-content-suggestions"; |
| 57 |
| 58 private final ObserverList<DestructionObserver> mDestructionObservers = new
ObserverList<>(); |
| 59 private final SuggestionsSource mSuggestionsSource; |
| 60 private SnippetsBridge mSnippetsBridge; |
| 61 private final Activity mActivity; |
| 62 private final Profile mProfile; |
| 63 |
| 64 private final Tab mTab; |
| 65 private final TabModelSelector mTabModelSelector; |
| 66 |
| 67 private FaviconHelper mFaviconHelper; |
| 68 private LargeIconBridge mLargeIconBridge; |
| 69 |
| 70 private boolean mIsDestroyed; |
| 71 |
| 72 public SuggestionsManagerImpl(SuggestionsSource suggestionsSource, Activity
activity, |
| 73 Profile profile, Tab currentTab, TabModelSelector tabModelSelector)
{ |
| 74 mSuggestionsSource = suggestionsSource; |
| 75 mActivity = activity; |
| 76 mProfile = profile; |
| 77 mTab = currentTab; |
| 78 mTabModelSelector = tabModelSelector; |
| 79 mSnippetsBridge = (SnippetsBridge) suggestionsSource; |
| 80 } |
| 81 |
| 82 @Override |
| 83 public boolean isOpenInNewWindowEnabled() { |
| 84 return MultiWindowUtils.getInstance().isOpenInOtherWindowSupported(mActi
vity); |
| 85 } |
| 86 |
| 87 @Override |
| 88 public boolean isOpenInIncognitoEnabled() { |
| 89 return PrefServiceBridge.getInstance().isIncognitoModeEnabled(); |
| 90 } |
| 91 |
| 92 @Override |
| 93 public void navigateToBookmarks() { |
| 94 if (mIsDestroyed) return; |
| 95 RecordUserAction.record("MobileNTPSwitchToBookmarks"); |
| 96 BookmarkUtils.showBookmarkManager(mActivity); |
| 97 } |
| 98 |
| 99 @Override |
| 100 public void navigateToRecentTabs() { |
| 101 if (mIsDestroyed) return; |
| 102 RecordUserAction.record("MobileNTPSwitchToOpenTabs"); |
| 103 mTab.loadUrl(new LoadUrlParams(UrlConstants.RECENT_TABS_URL)); |
| 104 } |
| 105 |
| 106 @Override |
| 107 public void navigateToDownloadManager() { |
| 108 if (mIsDestroyed) return; |
| 109 assert DownloadUtils.isDownloadHomeEnabled(); |
| 110 |
| 111 RecordUserAction.record("MobileNTPSwitchToDownloadManager"); |
| 112 DownloadUtils.showDownloadManager(mActivity, mTab); |
| 113 } |
| 114 |
| 115 @Override |
| 116 public void onLearnMoreClicked() { |
| 117 if (mIsDestroyed) return; |
| 118 NewTabPageUma.recordAction(NewTabPageUma.ACTION_CLICKED_LEARN_MORE); |
| 119 String url = "https://support.google.com/chrome/?p=new_tab"; |
| 120 // TODO(mastiz): Change this to LINK? |
| 121 openUrl(WindowOpenDisposition.CURRENT_TAB, |
| 122 new LoadUrlParams(url, PageTransition.AUTO_BOOKMARK)); |
| 123 } |
| 124 |
| 125 @Override |
| 126 public void openSnippet(int windowOpenDisposition, SnippetArticle article) { |
| 127 mSnippetsBridge.onSuggestionOpened(article, windowOpenDisposition); |
| 128 NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_SNIPPET); |
| 129 |
| 130 if (article.mIsAssetDownload) { |
| 131 assert windowOpenDisposition == WindowOpenDisposition.CURRENT_TAB |
| 132 || windowOpenDisposition == WindowOpenDisposition.NEW_WINDOW |
| 133 || windowOpenDisposition == WindowOpenDisposition.NEW_FOREGR
OUND_TAB; |
| 134 DownloadUtils.openFile( |
| 135 article.getAssetDownloadFile(), article.getAssetDownloadMime
Type(), false); |
| 136 return; |
| 137 } |
| 138 |
| 139 if (article.isRecentTab()) { |
| 140 assert windowOpenDisposition == WindowOpenDisposition.CURRENT_TAB; |
| 141 // TODO(vitaliii): Add a debug check that the result is true after c
rbug.com/662924 |
| 142 // is resolved. |
| 143 openRecentTabSnippet(article); |
| 144 return; |
| 145 } |
| 146 |
| 147 // TODO(treib): Also track other dispositions. crbug.com/665915 |
| 148 if (windowOpenDisposition == WindowOpenDisposition.CURRENT_TAB) { |
| 149 NewTabPageUma.monitorContentSuggestionVisit(mTab, article.mCategory)
; |
| 150 } |
| 151 |
| 152 LoadUrlParams loadUrlParams; |
| 153 // We explicitly open an offline page only for offline page downloads. F
or all other |
| 154 // sections the URL is opened and it is up to Offline Pages whether to o
pen its offline |
| 155 // page (e.g. when offline). |
| 156 if (article.isDownload() && !article.mIsAssetDownload) { |
| 157 assert article.getOfflinePageOfflineId() != null; |
| 158 assert windowOpenDisposition == WindowOpenDisposition.CURRENT_TAB |
| 159 || windowOpenDisposition == WindowOpenDisposition.NEW_WINDOW |
| 160 || windowOpenDisposition == WindowOpenDisposition.NEW_FOREGR
OUND_TAB; |
| 161 loadUrlParams = OfflinePageUtils.getLoadUrlParamsForOpeningOfflineVe
rsion( |
| 162 article.mUrl, article.getOfflinePageOfflineId()); |
| 163 // Extra headers are not read in loadUrl, but verbatim headers are. |
| 164 loadUrlParams.setVerbatimHeaders(loadUrlParams.getExtraHeadersString
()); |
| 165 } else { |
| 166 loadUrlParams = new LoadUrlParams(article.mUrl, PageTransition.AUTO_
BOOKMARK); |
| 167 } |
| 168 |
| 169 // For article suggestions, we set the referrer. This is exploited |
| 170 // to filter out these history entries for NTP tiles. |
| 171 // TODO(mastiz): Extend this with support for other categories. |
| 172 if (article.mCategory == KnownCategories.ARTICLES) { |
| 173 loadUrlParams.setReferrer(new Referrer( |
| 174 CHROME_CONTENT_SUGGESTIONS_REFERRER, Referrer.REFERRER_POLIC
Y_ALWAYS)); |
| 175 } |
| 176 |
| 177 openUrl(windowOpenDisposition, loadUrlParams); |
| 178 } |
| 179 |
| 180 @Override |
| 181 public void openUrl(int windowOpenDisposition, LoadUrlParams loadUrlParams)
{ |
| 182 assert !mIsDestroyed; |
| 183 switch (windowOpenDisposition) { |
| 184 case WindowOpenDisposition.CURRENT_TAB: |
| 185 mTab.loadUrl(loadUrlParams); |
| 186 break; |
| 187 case WindowOpenDisposition.NEW_FOREGROUND_TAB: |
| 188 openUrlInNewTab(loadUrlParams, false); |
| 189 break; |
| 190 case WindowOpenDisposition.OFF_THE_RECORD: |
| 191 openUrlInNewTab(loadUrlParams, true); |
| 192 break; |
| 193 case WindowOpenDisposition.NEW_WINDOW: |
| 194 openUrlInNewWindow(loadUrlParams); |
| 195 break; |
| 196 case WindowOpenDisposition.SAVE_TO_DISK: |
| 197 saveUrlForOffline(loadUrlParams.getUrl()); |
| 198 break; |
| 199 default: |
| 200 assert false; |
| 201 } |
| 202 } |
| 203 |
| 204 @Override |
| 205 public void trackSnippetsPageImpression(int[] categories, int[] suggestionsP
erCategory) { |
| 206 mSnippetsBridge.onPageShown(categories, suggestionsPerCategory); |
| 207 } |
| 208 |
| 209 @Override |
| 210 public void trackSnippetImpression(SnippetArticle article) { |
| 211 mSnippetsBridge.onSuggestionShown(article); |
| 212 } |
| 213 |
| 214 @Override |
| 215 public void trackSnippetMenuOpened(SnippetArticle article) { |
| 216 mSnippetsBridge.onSuggestionMenuOpened(article); |
| 217 } |
| 218 |
| 219 @Override |
| 220 public void trackSnippetCategoryActionImpression(int category, int position)
{ |
| 221 mSnippetsBridge.onMoreButtonShown(category, position); |
| 222 } |
| 223 |
| 224 @Override |
| 225 public void trackSnippetCategoryActionClick(int category, int position) { |
| 226 mSnippetsBridge.onMoreButtonClicked(category, position); |
| 227 switch (category) { |
| 228 case KnownCategories.BOOKMARKS: |
| 229 NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_BOOKMARKS
_MANAGER); |
| 230 break; |
| 231 // MORE button in both categories leads to the recent tabs manager |
| 232 case KnownCategories.FOREIGN_TABS: |
| 233 case KnownCategories.RECENT_TABS: |
| 234 NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_RECENT_TA
BS_MANAGER); |
| 235 break; |
| 236 case KnownCategories.DOWNLOADS: |
| 237 NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_DOWNLOADS
_MANAGER); |
| 238 break; |
| 239 default: |
| 240 // No action associated |
| 241 break; |
| 242 } |
| 243 } |
| 244 |
| 245 @Override |
| 246 public void getLocalFaviconImageForURL( |
| 247 String url, int size, FaviconImageCallback faviconCallback) { |
| 248 if (mIsDestroyed) return; |
| 249 if (mFaviconHelper == null) mFaviconHelper = new FaviconHelper(); |
| 250 mFaviconHelper.getLocalFaviconImageForURL(mProfile, url, size, faviconCa
llback); |
| 251 } |
| 252 |
| 253 @Override |
| 254 public void getLargeIconForUrl(String url, int size, LargeIconCallback callb
ack) { |
| 255 if (mIsDestroyed) return; |
| 256 if (mLargeIconBridge == null) mLargeIconBridge = new LargeIconBridge(mPr
ofile); |
| 257 mLargeIconBridge.getLargeIconForUrl(url, size, callback); |
| 258 } |
| 259 |
| 260 @Override |
| 261 public void ensureIconIsAvailable(String pageUrl, String iconUrl, boolean is
LargeIcon, |
| 262 boolean isTemporary, IconAvailabilityCallback callback) { |
| 263 if (mIsDestroyed) return; |
| 264 if (mFaviconHelper == null) mFaviconHelper = new FaviconHelper(); |
| 265 mFaviconHelper.ensureIconIsAvailable(mProfile, mTab.getWebContents(), pa
geUrl, iconUrl, |
| 266 isLargeIcon, isTemporary, callback); |
| 267 } |
| 268 |
| 269 @Override |
| 270 public void getUrlsAvailableOffline( |
| 271 Set<String> pageUrls, final Callback<Set<String>> callback) { |
| 272 final Set<String> urlsAvailableOffline = new HashSet<>(); |
| 273 if (mIsDestroyed || !isNtpOfflinePagesEnabled()) { |
| 274 callback.onResult(urlsAvailableOffline); |
| 275 return; |
| 276 } |
| 277 |
| 278 HashSet<String> urlsToCheckForOfflinePage = new HashSet<>(); |
| 279 |
| 280 for (String pageUrl : pageUrls) { |
| 281 if (isLocalUrl(pageUrl)) { |
| 282 urlsAvailableOffline.add(pageUrl); |
| 283 } else { |
| 284 urlsToCheckForOfflinePage.add(pageUrl); |
| 285 } |
| 286 } |
| 287 |
| 288 final long offlineQueryStartTime = SystemClock.elapsedRealtime(); |
| 289 |
| 290 OfflinePageBridge offlinePageBridge = OfflinePageBridge.getForProfile(mP
rofile); |
| 291 |
| 292 // TODO(dewittj): Remove this code by making the NTP badging available a
fter the NTP is |
| 293 // fully loaded. |
| 294 if (offlinePageBridge == null || !offlinePageBridge.isOfflinePageModelLo
aded()) { |
| 295 // Posting a task to avoid potential re-entrancy issues. |
| 296 ThreadUtils.postOnUiThread(new Runnable() { |
| 297 @Override |
| 298 public void run() { |
| 299 callback.onResult(urlsAvailableOffline); |
| 300 } |
| 301 }); |
| 302 return; |
| 303 } |
| 304 |
| 305 offlinePageBridge.checkPagesExistOffline( |
| 306 urlsToCheckForOfflinePage, new Callback<Set<String>>() { |
| 307 @Override |
| 308 public void onResult(Set<String> urlsWithOfflinePages) { |
| 309 urlsAvailableOffline.addAll(urlsWithOfflinePages); |
| 310 callback.onResult(urlsAvailableOffline); |
| 311 RecordHistogram.recordTimesHistogram("NewTabPage.Offline
UrlsLoadTime", |
| 312 SystemClock.elapsedRealtime() - offlineQueryStar
tTime, |
| 313 TimeUnit.MILLISECONDS); |
| 314 } |
| 315 }); |
| 316 } |
| 317 |
| 318 @Override |
| 319 public SuggestionsSource getSuggestionsSource() { |
| 320 return mSuggestionsSource; |
| 321 } |
| 322 |
| 323 @Override |
| 324 public void addDestructionObserver(DestructionObserver destructionObserver)
{ |
| 325 mDestructionObservers.addObserver(destructionObserver); |
| 326 } |
| 327 |
| 328 public void onDestroy() { |
| 329 for (DestructionObserver observer : mDestructionObservers) { |
| 330 observer.onDestroy(); |
| 331 } |
| 332 mIsDestroyed = true; |
| 333 } |
| 334 |
| 335 private boolean openRecentTabSnippet(SnippetArticle article) { |
| 336 TabModel tabModel = mTabModelSelector.getModel(false); |
| 337 int tabId = Integer.parseInt(article.getRecentTabId()); |
| 338 int tabIndex = TabModelUtils.getTabIndexById(tabModel, tabId); |
| 339 if (tabIndex == TabModel.INVALID_TAB_INDEX) return false; |
| 340 TabModelUtils.setIndex(tabModel, tabIndex); |
| 341 return true; |
| 342 } |
| 343 |
| 344 private void openUrlInNewWindow(LoadUrlParams loadUrlParams) { |
| 345 TabDelegate tabDelegate = new TabDelegate(false); |
| 346 tabDelegate.createTabInOtherWindow(loadUrlParams, mActivity, mTab.getPar
entId()); |
| 347 } |
| 348 |
| 349 private void openUrlInNewTab(LoadUrlParams loadUrlParams, boolean incognito)
{ |
| 350 mTabModelSelector.openNewTab( |
| 351 loadUrlParams, TabLaunchType.FROM_LONGPRESS_BACKGROUND, mTab, in
cognito); |
| 352 } |
| 353 |
| 354 private void saveUrlForOffline(String url) { |
| 355 OfflinePageBridge.getForProfile(mProfile).savePageLater( |
| 356 url, "ntp_suggestions", true /* userRequested */); |
| 357 } |
| 358 |
| 359 private boolean isNtpOfflinePagesEnabled() { |
| 360 return ChromeFeatureList.isEnabled(ChromeFeatureList.NTP_OFFLINE_PAGES_F
EATURE_NAME); |
| 361 } |
| 362 |
| 363 private boolean isLocalUrl(String url) { |
| 364 return "file".equals(Uri.parse(url).getScheme()); |
| 365 } |
| 366 } |
OLD | NEW |