| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chrome.browser.ntp.snippets; | 5 package org.chromium.chrome.browser.ntp.snippets; |
| 6 | 6 |
| 7 import android.annotation.SuppressLint; | 7 import android.annotation.SuppressLint; |
| 8 import android.content.res.Resources; | 8 import android.content.res.Resources; |
| 9 import android.graphics.Bitmap; | 9 import android.graphics.Bitmap; |
| 10 import android.graphics.drawable.BitmapDrawable; | 10 import android.graphics.drawable.BitmapDrawable; |
| (...skipping 13 matching lines...) Expand all Loading... |
| 24 import org.chromium.base.ApiCompatibilityUtils; | 24 import org.chromium.base.ApiCompatibilityUtils; |
| 25 import org.chromium.base.Callback; | 25 import org.chromium.base.Callback; |
| 26 import org.chromium.base.metrics.RecordHistogram; | 26 import org.chromium.base.metrics.RecordHistogram; |
| 27 import org.chromium.chrome.R; | 27 import org.chromium.chrome.R; |
| 28 import org.chromium.chrome.browser.favicon.FaviconHelper.FaviconImageCallback; | 28 import org.chromium.chrome.browser.favicon.FaviconHelper.FaviconImageCallback; |
| 29 import org.chromium.chrome.browser.favicon.FaviconHelper.IconAvailabilityCallbac
k; | 29 import org.chromium.chrome.browser.favicon.FaviconHelper.IconAvailabilityCallbac
k; |
| 30 import org.chromium.chrome.browser.ntp.ContextMenuManager; | 30 import org.chromium.chrome.browser.ntp.ContextMenuManager; |
| 31 import org.chromium.chrome.browser.ntp.ContextMenuManager.ContextMenuItemId; | 31 import org.chromium.chrome.browser.ntp.ContextMenuManager.ContextMenuItemId; |
| 32 import org.chromium.chrome.browser.ntp.ContextMenuManager.Delegate; | 32 import org.chromium.chrome.browser.ntp.ContextMenuManager.Delegate; |
| 33 import org.chromium.chrome.browser.ntp.DisplayStyleObserver; | 33 import org.chromium.chrome.browser.ntp.DisplayStyleObserver; |
| 34 import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager; | |
| 35 import org.chromium.chrome.browser.ntp.UiConfig; | 34 import org.chromium.chrome.browser.ntp.UiConfig; |
| 36 import org.chromium.chrome.browser.ntp.cards.CardViewHolder; | 35 import org.chromium.chrome.browser.ntp.cards.CardViewHolder; |
| 37 import org.chromium.chrome.browser.ntp.cards.CardsVariationParameters; | 36 import org.chromium.chrome.browser.ntp.cards.CardsVariationParameters; |
| 38 import org.chromium.chrome.browser.ntp.cards.DisplayStyleObserverAdapter; | 37 import org.chromium.chrome.browser.ntp.cards.DisplayStyleObserverAdapter; |
| 39 import org.chromium.chrome.browser.ntp.cards.ImpressionTracker; | 38 import org.chromium.chrome.browser.ntp.cards.ImpressionTracker; |
| 40 import org.chromium.chrome.browser.ntp.cards.NewTabPageRecyclerView; | 39 import org.chromium.chrome.browser.ntp.cards.NewTabPageRecyclerView; |
| 41 import org.chromium.chrome.browser.ntp.cards.SuggestionsCategoryInfo; | 40 import org.chromium.chrome.browser.ntp.cards.SuggestionsCategoryInfo; |
| 41 import org.chromium.chrome.browser.suggestions.ContentSuggestionsManager; |
| 42 import org.chromium.ui.mojom.WindowOpenDisposition; | 42 import org.chromium.ui.mojom.WindowOpenDisposition; |
| 43 | 43 |
| 44 import java.net.URI; | 44 import java.net.URI; |
| 45 import java.net.URISyntaxException; | 45 import java.net.URISyntaxException; |
| 46 import java.util.List; | 46 import java.util.List; |
| 47 import java.util.concurrent.TimeUnit; | 47 import java.util.concurrent.TimeUnit; |
| 48 | 48 |
| 49 /** | 49 /** |
| 50 * A class that represents the view for a single card snippet. | 50 * A class that represents the view for a single card snippet. |
| 51 */ | 51 */ |
| 52 public class SnippetArticleViewHolder | 52 public class SnippetArticleViewHolder |
| 53 extends CardViewHolder implements ImpressionTracker.Listener, ContextMen
uManager.Delegate { | 53 extends CardViewHolder implements ImpressionTracker.Listener, ContextMen
uManager.Delegate { |
| 54 private static final String PUBLISHER_FORMAT_STRING = "%s - %s"; | 54 private static final String PUBLISHER_FORMAT_STRING = "%s - %s"; |
| 55 private static final int FADE_IN_ANIMATION_TIME_MS = 300; | 55 private static final int FADE_IN_ANIMATION_TIME_MS = 300; |
| 56 private static final int[] FAVICON_SERVICE_SUPPORTED_SIZES = {16, 24, 32, 48
, 64}; | 56 private static final int[] FAVICON_SERVICE_SUPPORTED_SIZES = {16, 24, 32, 48
, 64}; |
| 57 private static final String FAVICON_SERVICE_FORMAT = | 57 private static final String FAVICON_SERVICE_FORMAT = |
| 58 "https://s2.googleusercontent.com/s2/favicons?domain=%s&src=chrome_n
ewtab_mobile&sz=%d&alt=404"; | 58 "https://s2.googleusercontent.com/s2/favicons?domain=%s&src=chrome_n
ewtab_mobile&sz=%d&alt=404"; |
| 59 | 59 |
| 60 public static final int PARTIAL_UPDATE_OFFLINE_ID = 1; | 60 public static final int PARTIAL_UPDATE_OFFLINE_ID = 1; |
| 61 | 61 |
| 62 private final NewTabPageManager mNewTabPageManager; | 62 private final ContentSuggestionsManager mContentSuggestionsManager; |
| 63 private final TextView mHeadlineTextView; | 63 private final TextView mHeadlineTextView; |
| 64 private final TextView mPublisherTextView; | 64 private final TextView mPublisherTextView; |
| 65 private final TextView mArticleSnippetTextView; | 65 private final TextView mArticleSnippetTextView; |
| 66 private final ImageView mThumbnailView; | 66 private final ImageView mThumbnailView; |
| 67 private final ImageView mOfflineBadge; | 67 private final ImageView mOfflineBadge; |
| 68 private final View mPublisherBar; | 68 private final View mPublisherBar; |
| 69 | 69 |
| 70 private FetchImageCallback mImageCallback; | 70 private FetchImageCallback mImageCallback; |
| 71 private SnippetArticle mArticle; | 71 private SnippetArticle mArticle; |
| 72 private SuggestionsCategoryInfo mCategoryInfo; | 72 private SuggestionsCategoryInfo mCategoryInfo; |
| 73 private int mPublisherFaviconSizePx; | 73 private int mPublisherFaviconSizePx; |
| 74 | 74 |
| 75 private final boolean mUseFaviconService; | 75 private final boolean mUseFaviconService; |
| 76 private final UiConfig mUiConfig; | 76 private final UiConfig mUiConfig; |
| 77 | 77 |
| 78 /** | 78 /** |
| 79 * Constructs a {@link SnippetArticleViewHolder} item used to display snippe
ts. | 79 * Constructs a {@link SnippetArticleViewHolder} item used to display snippe
ts. |
| 80 * | 80 * @param parent The NewTabPageRecyclerView that is going to contain the ne
wly created view. |
| 81 * @param parent The NewTabPageRecyclerView that is going to contain the new
ly created view. | 81 * @param contextMenuManager |
| 82 * @param manager The NewTabPageManager object used to open an article. | 82 * @param manager The NewTabPageManager object used to open an article. |
| 83 * @param uiConfig The NTP UI configuration object used to adjust the articl
e UI. | 83 * @param uiConfig The NTP UI configuration object used to adjust the articl
e UI. |
| 84 */ | 84 */ |
| 85 public SnippetArticleViewHolder(NewTabPageRecyclerView parent, NewTabPageMan
ager manager, | 85 public SnippetArticleViewHolder(NewTabPageRecyclerView parent, |
| 86 ContextMenuManager contextMenuManager, ContentSuggestionsManager man
ager, |
| 86 UiConfig uiConfig) { | 87 UiConfig uiConfig) { |
| 87 super(R.layout.new_tab_page_snippets_card, parent, uiConfig, manager); | 88 super(R.layout.new_tab_page_snippets_card, parent, uiConfig, contextMenu
Manager); |
| 88 | 89 |
| 89 mNewTabPageManager = manager; | 90 mContentSuggestionsManager = manager; |
| 90 mThumbnailView = (ImageView) itemView.findViewById(R.id.article_thumbnai
l); | 91 mThumbnailView = (ImageView) itemView.findViewById(R.id.article_thumbnai
l); |
| 91 mHeadlineTextView = (TextView) itemView.findViewById(R.id.article_headli
ne); | 92 mHeadlineTextView = (TextView) itemView.findViewById(R.id.article_headli
ne); |
| 92 mPublisherTextView = (TextView) itemView.findViewById(R.id.article_publi
sher); | 93 mPublisherTextView = (TextView) itemView.findViewById(R.id.article_publi
sher); |
| 93 mArticleSnippetTextView = (TextView) itemView.findViewById(R.id.article_
snippet); | 94 mArticleSnippetTextView = (TextView) itemView.findViewById(R.id.article_
snippet); |
| 94 mPublisherBar = itemView.findViewById(R.id.publisher_bar); | 95 mPublisherBar = itemView.findViewById(R.id.publisher_bar); |
| 95 mOfflineBadge = (ImageView) itemView.findViewById(R.id.offline_icon); | 96 mOfflineBadge = (ImageView) itemView.findViewById(R.id.offline_icon); |
| 96 | 97 |
| 97 new ImpressionTracker(itemView, this); | 98 new ImpressionTracker(itemView, this); |
| 98 | 99 |
| 99 mUiConfig = uiConfig; | 100 mUiConfig = uiConfig; |
| 100 new DisplayStyleObserverAdapter(itemView, uiConfig, new DisplayStyleObse
rver() { | 101 new DisplayStyleObserverAdapter(itemView, uiConfig, new DisplayStyleObse
rver() { |
| 101 @Override | 102 @Override |
| 102 public void onDisplayStyleChanged(@UiConfig.DisplayStyle int newDisp
layStyle) { | 103 public void onDisplayStyleChanged(@UiConfig.DisplayStyle int newDisp
layStyle) { |
| 103 updateLayout(); | 104 updateLayout(); |
| 104 } | 105 } |
| 105 }); | 106 }); |
| 106 | 107 |
| 107 mUseFaviconService = CardsVariationParameters.isFaviconServiceEnabled(); | 108 mUseFaviconService = CardsVariationParameters.isFaviconServiceEnabled(); |
| 108 } | 109 } |
| 109 | 110 |
| 110 @Override | 111 @Override |
| 111 public void onImpression() { | 112 public void onImpression() { |
| 112 if (mArticle != null && mArticle.trackImpression()) { | 113 if (mArticle != null && mArticle.trackImpression()) { |
| 113 mNewTabPageManager.trackSnippetImpression(mArticle); | 114 mContentSuggestionsManager.trackSnippetImpression(mArticle); |
| 114 mRecyclerView.onSnippetImpression(); | 115 mRecyclerView.onSnippetImpression(); |
| 115 } | 116 } |
| 116 } | 117 } |
| 117 | 118 |
| 118 @Override | 119 @Override |
| 119 public void onCardTapped() { | 120 public void onCardTapped() { |
| 120 mNewTabPageManager.openSnippet(WindowOpenDisposition.CURRENT_TAB, mArtic
le); | 121 mContentSuggestionsManager.openSnippet(WindowOpenDisposition.CURRENT_TAB
, mArticle); |
| 121 } | 122 } |
| 122 | 123 |
| 123 @Override | 124 @Override |
| 124 public void openItem(int windowDisposition) { | 125 public void openItem(int windowDisposition) { |
| 125 mNewTabPageManager.openSnippet(windowDisposition, mArticle); | 126 mContentSuggestionsManager.openSnippet(windowDisposition, mArticle); |
| 126 } | 127 } |
| 127 | 128 |
| 128 @Override | 129 @Override |
| 129 public void removeItem() { | 130 public void removeItem() { |
| 130 getRecyclerView().dismissItemWithAnimation(this); | 131 getRecyclerView().dismissItemWithAnimation(this); |
| 131 } | 132 } |
| 132 | 133 |
| 133 @Override | 134 @Override |
| 134 public String getUrl() { | 135 public String getUrl() { |
| 135 return mArticle.mUrl; | 136 return mArticle.mUrl; |
| 136 } | 137 } |
| 137 | 138 |
| 138 @Override | 139 @Override |
| 139 public boolean isItemSupported(@ContextMenuItemId int menuItemId) { | 140 public boolean isItemSupported(@ContextMenuItemId int menuItemId) { |
| 140 if (mArticle.isDownload()) { | 141 if (mArticle.isDownload()) { |
| 141 if (menuItemId == ContextMenuManager.ID_OPEN_IN_INCOGNITO_TAB) retur
n false; | 142 if (menuItemId == ContextMenuManager.ID_OPEN_IN_INCOGNITO_TAB) retur
n false; |
| 142 if (menuItemId == ContextMenuManager.ID_SAVE_FOR_OFFLINE) return fal
se; | 143 if (menuItemId == ContextMenuManager.ID_SAVE_FOR_OFFLINE) return fal
se; |
| 143 return true; | 144 return true; |
| 144 } | 145 } |
| 145 if (mArticle.isRecentTab()) { | 146 if (mArticle.isRecentTab()) { |
| 146 if (menuItemId == ContextMenuManager.ID_REMOVE) return true; | 147 if (menuItemId == ContextMenuManager.ID_REMOVE) return true; |
| 147 return false; | 148 return false; |
| 148 } | 149 } |
| 149 return true; | 150 return true; |
| 150 } | 151 } |
| 151 | 152 |
| 152 @Override | 153 @Override |
| 153 public void onContextMenuCreated() { | 154 public void onContextMenuCreated() { |
| 154 mNewTabPageManager.trackSnippetMenuOpened(mArticle); | 155 mContentSuggestionsManager.trackSnippetMenuOpened(mArticle); |
| 155 } | 156 } |
| 156 | 157 |
| 157 @Override | 158 @Override |
| 158 protected Delegate getContextMenuDelegate() { | 159 protected Delegate getContextMenuDelegate() { |
| 159 return this; | 160 return this; |
| 160 } | 161 } |
| 161 | 162 |
| 162 /** | 163 /** |
| 163 * Updates the layout taking into account screen dimensions and the type of
snippet displayed. | 164 * Updates the layout taking into account screen dimensions and the type of
snippet displayed. |
| 164 */ | 165 */ |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 251 cancelImageFetch(); | 252 cancelImageFetch(); |
| 252 | 253 |
| 253 // If the article has a thumbnail already, reuse it. Otherwise start a f
etch. | 254 // If the article has a thumbnail already, reuse it. Otherwise start a f
etch. |
| 254 // mThumbnailView's visibility is modified in updateLayout(). | 255 // mThumbnailView's visibility is modified in updateLayout(). |
| 255 if (mThumbnailView.getVisibility() == View.VISIBLE) { | 256 if (mThumbnailView.getVisibility() == View.VISIBLE) { |
| 256 if (mArticle.getThumbnailBitmap() != null) { | 257 if (mArticle.getThumbnailBitmap() != null) { |
| 257 mThumbnailView.setImageBitmap(mArticle.getThumbnailBitmap()); | 258 mThumbnailView.setImageBitmap(mArticle.getThumbnailBitmap()); |
| 258 } else { | 259 } else { |
| 259 mThumbnailView.setImageResource(R.drawable.ic_snippet_thumbnail_
placeholder); | 260 mThumbnailView.setImageResource(R.drawable.ic_snippet_thumbnail_
placeholder); |
| 260 mImageCallback = new FetchImageCallback(this, mArticle); | 261 mImageCallback = new FetchImageCallback(this, mArticle); |
| 261 mNewTabPageManager.getSuggestionsSource() | 262 mContentSuggestionsManager.getSuggestionsSource().fetchSuggestio
nImage( |
| 262 .fetchSuggestionImage(mArticle, mImageCallback); | 263 mArticle, mImageCallback); |
| 263 } | 264 } |
| 264 } | 265 } |
| 265 | 266 |
| 266 // Set the favicon of the publisher. | 267 // Set the favicon of the publisher. |
| 267 try { | 268 try { |
| 268 fetchFaviconFromLocalCache(new URI(mArticle.mUrl), true); | 269 fetchFaviconFromLocalCache(new URI(mArticle.mUrl), true); |
| 269 } catch (URISyntaxException e) { | 270 } catch (URISyntaxException e) { |
| 270 setDefaultFaviconOnView(); | 271 setDefaultFaviconOnView(); |
| 271 } | 272 } |
| 272 | 273 |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 337 // image may have transparency and we don't want the previous image show
ing up behind. | 338 // image may have transparency and we don't want the previous image show
ing up behind. |
| 338 Drawable[] layers = {mThumbnailView.getDrawable(), | 339 Drawable[] layers = {mThumbnailView.getDrawable(), |
| 339 new BitmapDrawable(mThumbnailView.getResources(), scaledThumbnai
l)}; | 340 new BitmapDrawable(mThumbnailView.getResources(), scaledThumbnai
l)}; |
| 340 TransitionDrawable transitionDrawable = new TransitionDrawable(layers); | 341 TransitionDrawable transitionDrawable = new TransitionDrawable(layers); |
| 341 mThumbnailView.setImageDrawable(transitionDrawable); | 342 mThumbnailView.setImageDrawable(transitionDrawable); |
| 342 transitionDrawable.setCrossFadeEnabled(true); | 343 transitionDrawable.setCrossFadeEnabled(true); |
| 343 transitionDrawable.startTransition(FADE_IN_ANIMATION_TIME_MS); | 344 transitionDrawable.startTransition(FADE_IN_ANIMATION_TIME_MS); |
| 344 } | 345 } |
| 345 | 346 |
| 346 private void fetchFaviconFromLocalCache(final URI snippetUri, final boolean
fallbackToService) { | 347 private void fetchFaviconFromLocalCache(final URI snippetUri, final boolean
fallbackToService) { |
| 347 mNewTabPageManager.getLocalFaviconImageForURL( | 348 mContentSuggestionsManager.getLocalFaviconImageForURL( |
| 348 getSnippetDomain(snippetUri), mPublisherFaviconSizePx, new Favic
onImageCallback() { | 349 getSnippetDomain(snippetUri), mPublisherFaviconSizePx, new Favic
onImageCallback() { |
| 349 @Override | 350 @Override |
| 350 public void onFaviconAvailable(Bitmap image, String iconUrl)
{ | 351 public void onFaviconAvailable(Bitmap image, String iconUrl)
{ |
| 351 if (image == null && fallbackToService) { | 352 if (image == null && fallbackToService) { |
| 352 fetchFaviconFromService(snippetUri); | 353 fetchFaviconFromService(snippetUri); |
| 353 return; | 354 return; |
| 354 } | 355 } |
| 355 setFaviconOnView(image); | 356 setFaviconOnView(image); |
| 356 } | 357 } |
| 357 }); | 358 }); |
| 358 } | 359 } |
| 359 | 360 |
| 360 // TODO(crbug.com/635567): Fix this properly. | 361 // TODO(crbug.com/635567): Fix this properly. |
| 361 @SuppressLint("DefaultLocale") | 362 @SuppressLint("DefaultLocale") |
| 362 private void fetchFaviconFromService(final URI snippetUri) { | 363 private void fetchFaviconFromService(final URI snippetUri) { |
| 363 // Show the default favicon immediately. | 364 // Show the default favicon immediately. |
| 364 setDefaultFaviconOnView(); | 365 setDefaultFaviconOnView(); |
| 365 | 366 |
| 366 if (!mUseFaviconService) return; | 367 if (!mUseFaviconService) return; |
| 367 int sizePx = getFaviconServiceSupportedSize(); | 368 int sizePx = getFaviconServiceSupportedSize(); |
| 368 if (sizePx == 0) return; | 369 if (sizePx == 0) return; |
| 369 | 370 |
| 370 // Replace the default icon by another one from the service when it is f
etched. | 371 // Replace the default icon by another one from the service when it is f
etched. |
| 371 mNewTabPageManager.ensureIconIsAvailable( | 372 mContentSuggestionsManager.ensureIconIsAvailable( |
| 372 getSnippetDomain(snippetUri), // Store to the cache for the whol
e domain. | 373 getSnippetDomain(snippetUri), // Store to the cache for the whol
e domain. |
| 373 String.format(FAVICON_SERVICE_FORMAT, snippetUri.getHost(), size
Px), | 374 String.format(FAVICON_SERVICE_FORMAT, snippetUri.getHost(), size
Px), |
| 374 /*useLargeIcon=*/false, /*isTemporary=*/true, new IconAvailabili
tyCallback() { | 375 /*useLargeIcon=*/false, /*isTemporary=*/true, new IconAvailabili
tyCallback() { |
| 375 @Override | 376 @Override |
| 376 public void onIconAvailabilityChecked(boolean newlyAvailable
) { | 377 public void onIconAvailabilityChecked(boolean newlyAvailable
) { |
| 377 if (!newlyAvailable) return; | 378 if (!newlyAvailable) return; |
| 378 // The download succeeded, the favicon is in the cache;
fetch it. | 379 // The download succeeded, the favicon is in the cache;
fetch it. |
| 379 fetchFaviconFromLocalCache(snippetUri, /*fallbackToServi
ce=*/false); | 380 fetchFaviconFromLocalCache(snippetUri, /*fallbackToServi
ce=*/false); |
| 380 } | 381 } |
| 381 }); | 382 }); |
| (...skipping 29 matching lines...) Expand all Loading... |
| 411 ApiCompatibilityUtils.setCompoundDrawablesRelative( | 412 ApiCompatibilityUtils.setCompoundDrawablesRelative( |
| 412 mPublisherTextView, drawable, null, null, null); | 413 mPublisherTextView, drawable, null, null, null); |
| 413 mPublisherTextView.setVisibility(View.VISIBLE); | 414 mPublisherTextView.setVisibility(View.VISIBLE); |
| 414 } | 415 } |
| 415 | 416 |
| 416 @Override | 417 @Override |
| 417 public boolean isDismissable() { | 418 public boolean isDismissable() { |
| 418 return !isPeeking(); | 419 return !isPeeking(); |
| 419 } | 420 } |
| 420 } | 421 } |
| OLD | NEW |