| Index: chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java
|
| index aeb54cc9520624a47da71ff387df5f4d48746d72..d809a34af399f5c05583e3241ab53b2d79c4b9ec 100644
|
| --- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java
|
| @@ -5,6 +5,7 @@
|
| package org.chromium.chrome.browser.ntp.snippets;
|
|
|
| import android.annotation.SuppressLint;
|
| +import android.content.res.ColorStateList;
|
| import android.content.res.Resources;
|
| import android.graphics.Bitmap;
|
| import android.graphics.drawable.BitmapDrawable;
|
| @@ -14,6 +15,7 @@ import android.media.ThumbnailUtils;
|
| import android.os.StrictMode;
|
| import android.os.SystemClock;
|
| import android.support.v4.text.BidiFormatter;
|
| +import android.text.TextUtils;
|
| import android.text.format.DateUtils;
|
| import android.view.View;
|
| import android.view.View.MeasureSpec;
|
| @@ -25,6 +27,10 @@ import org.chromium.base.ApiCompatibilityUtils;
|
| import org.chromium.base.Callback;
|
| import org.chromium.base.metrics.RecordHistogram;
|
| import org.chromium.chrome.R;
|
| +import org.chromium.chrome.browser.download.DownloadUtils;
|
| +import org.chromium.chrome.browser.download.ui.DownloadFilter;
|
| +import org.chromium.chrome.browser.download.ui.ThumbnailProvider;
|
| +import org.chromium.chrome.browser.download.ui.ThumbnailProviderImpl;
|
| import org.chromium.chrome.browser.favicon.FaviconHelper.FaviconImageCallback;
|
| import org.chromium.chrome.browser.favicon.FaviconHelper.IconAvailabilityCallback;
|
| import org.chromium.chrome.browser.ntp.ContextMenuManager;
|
| @@ -36,6 +42,7 @@ import org.chromium.chrome.browser.ntp.cards.NewTabPageRecyclerView;
|
| import org.chromium.chrome.browser.ntp.cards.NewTabPageViewHolder;
|
| import org.chromium.chrome.browser.ntp.cards.SuggestionsCategoryInfo;
|
| import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegate;
|
| +import org.chromium.chrome.browser.widget.TintedImageView;
|
| import org.chromium.chrome.browser.widget.displaystyle.DisplayStyleObserver;
|
| import org.chromium.chrome.browser.widget.displaystyle.DisplayStyleObserverAdapter;
|
| import org.chromium.chrome.browser.widget.displaystyle.HorizontalDisplayStyle;
|
| @@ -68,21 +75,25 @@ public class SnippetArticleViewHolder extends CardViewHolder implements Impressi
|
| private final TextView mHeadlineTextView;
|
| private final TextView mPublisherTextView;
|
| private final TextView mArticleSnippetTextView;
|
| - private final ImageView mThumbnailView;
|
| + private final TintedImageView mThumbnailView;
|
| private final ImageView mOfflineBadge;
|
| private final View mPublisherBar;
|
|
|
| + private final boolean mUseFaviconService;
|
| + private final UiConfig mUiConfig;
|
| +
|
| + private final ColorStateList mIconForegroundColorList;
|
| + private final int mIconBackgroundColor;
|
| + private final ThumbnailProvider mThumbnailProvider;
|
| +
|
| private FetchImageCallback mImageCallback;
|
| private SnippetArticle mArticle;
|
| private SuggestionsCategoryInfo mCategoryInfo;
|
| private int mPublisherFaviconSizePx;
|
|
|
| - private final boolean mUseFaviconService;
|
| - private final UiConfig mUiConfig;
|
| -
|
| /**
|
| * Constructs a {@link SnippetArticleViewHolder} item used to display snippets.
|
| - * @param parent The NewTabPageRecyclerView that is going to contain the newly created view.
|
| + * @param parent The NewTabPageRecyclerView that is going to contain the newly created view.
|
| * @param contextMenuManager The manager responsible for the context menu.
|
| * @param uiDelegate The delegate object used to open an article, fetch thumbnails, etc.
|
| * @param uiConfig The NTP UI configuration object used to adjust the article UI.
|
| @@ -93,7 +104,7 @@ public class SnippetArticleViewHolder extends CardViewHolder implements Impressi
|
| super(R.layout.new_tab_page_snippets_card, parent, uiConfig, contextMenuManager);
|
|
|
| mUiDelegate = uiDelegate;
|
| - mThumbnailView = (ImageView) itemView.findViewById(R.id.article_thumbnail);
|
| + mThumbnailView = (TintedImageView) itemView.findViewById(R.id.article_thumbnail);
|
| mHeadlineTextView = (TextView) itemView.findViewById(R.id.article_headline);
|
| mPublisherTextView = (TextView) itemView.findViewById(R.id.article_publisher);
|
| mArticleSnippetTextView = (TextView) itemView.findViewById(R.id.article_snippet);
|
| @@ -111,6 +122,11 @@ public class SnippetArticleViewHolder extends CardViewHolder implements Impressi
|
| });
|
|
|
| mUseFaviconService = CardsVariationParameters.isFaviconServiceEnabled();
|
| +
|
| + mIconBackgroundColor = DownloadUtils.getIconBackgroundColor(parent.getContext());
|
| + mIconForegroundColorList = DownloadUtils.getIconForegroundColorList(parent.getContext());
|
| + mThumbnailProvider = new ThumbnailProviderImpl(
|
| + Math.min(mThumbnailView.getMaxWidth(), mThumbnailView.getMaxHeight()));
|
| }
|
|
|
| @Override
|
| @@ -153,6 +169,44 @@ public class SnippetArticleViewHolder extends CardViewHolder implements Impressi
|
| }
|
|
|
| /**
|
| + * Updates ViewHolder with data.
|
| + * @param article The snippet to take the data from.
|
| + * @param categoryInfo The info of the category which the snippet belongs to.
|
| + */
|
| + public void onBindViewHolder(SnippetArticle article, SuggestionsCategoryInfo categoryInfo) {
|
| + super.onBindViewHolder();
|
| +
|
| + mArticle = article;
|
| + mCategoryInfo = categoryInfo;
|
| + updateLayout();
|
| +
|
| + mHeadlineTextView.setText(mArticle.mTitle);
|
| + mPublisherTextView.setText(getAttributionString(mArticle));
|
| +
|
| + // The favicon of the publisher should match the TextView height.
|
| + int widthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
| + int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
| + mPublisherTextView.measure(widthSpec, heightSpec);
|
| + mPublisherFaviconSizePx = mPublisherTextView.getMeasuredHeight();
|
| +
|
| + mArticleSnippetTextView.setText(mArticle.mPreviewText);
|
| +
|
| + setThumbnail();
|
| +
|
| + // Set the favicon of the publisher.
|
| + try {
|
| + fetchFaviconFromLocalCache(new URI(mArticle.mUrl), true);
|
| + } catch (URISyntaxException e) {
|
| + setDefaultFaviconOnView();
|
| + }
|
| +
|
| + mOfflineBadge.setVisibility(View.GONE);
|
| + refreshOfflineBadgeVisibility();
|
| +
|
| + mRecyclerView.onSnippetBound(itemView);
|
| + }
|
| +
|
| + /**
|
| * Updates the layout taking into account screen dimensions and the type of snippet displayed.
|
| */
|
| private void updateLayout() {
|
| @@ -237,87 +291,84 @@ public class SnippetArticleViewHolder extends CardViewHolder implements Impressi
|
| BidiFormatter.getInstance().unicodeWrap(article.mPublisher), relativeTimeSpan);
|
| }
|
|
|
| - public void onBindViewHolder(SnippetArticle article, SuggestionsCategoryInfo categoryInfo) {
|
| - super.onBindViewHolder();
|
| + private void setThumbnailFromBitmap(Bitmap thumbnail) {
|
| + assert thumbnail != null && !thumbnail.isRecycled();
|
| + mThumbnailView.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
| + mThumbnailView.setBackground(null);
|
| + mThumbnailView.setImageBitmap(thumbnail);
|
| + mThumbnailView.setTint(null);
|
| + }
|
|
|
| - mArticle = article;
|
| - mCategoryInfo = categoryInfo;
|
| - updateLayout();
|
| + private void setThumbnailFromFileType(int fileType) {
|
| + mThumbnailView.setScaleType(ImageView.ScaleType.CENTER);
|
| + mThumbnailView.setBackgroundColor(mIconBackgroundColor);
|
| + mThumbnailView.setImageResource(DownloadUtils.getIconResId(fileType));
|
| + mThumbnailView.setTint(mIconForegroundColorList);
|
| + }
|
|
|
| - mHeadlineTextView.setText(mArticle.mTitle);
|
| - mPublisherTextView.setText(getAttributionString(mArticle));
|
| + private void setDownloadThumbnail() {
|
| + assert mArticle.isDownload();
|
| + if (mArticle.isAssetDownload()) {
|
| + int fileType = DownloadFilter.fromMimeType(mArticle.getAssetDownloadMimeType());
|
| + setThumbnailFromFileType(fileType);
|
|
|
| - // The favicon of the publisher should match the TextView height.
|
| - int widthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
| - int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
| - mPublisherTextView.measure(widthSpec, heightSpec);
|
| - mPublisherFaviconSizePx = mPublisherTextView.getMeasuredHeight();
|
| + if (fileType != DownloadFilter.FILTER_IMAGE) return;
|
| + if (mImageCallback != null) {
|
| + mThumbnailProvider.cancelRetrieval(mImageCallback);
|
| + mImageCallback = null;
|
| + }
|
| + mImageCallback = new FetchImageCallback(this, mArticle);
|
| + mArticle.setThumbnailBitmap(null);
|
| + Bitmap thumbnail = mThumbnailProvider.getThumbnail(mImageCallback);
|
| + if (thumbnail == null || thumbnail.isRecycled()) return;
|
| + mArticle.setThumbnailBitmap(thumbnail);
|
| + setThumbnailFromBitmap(thumbnail);
|
| +
|
| + return;
|
| + }
|
|
|
| - mArticleSnippetTextView.setText(mArticle.mPreviewText);
|
| + setThumbnailFromFileType(DownloadFilter.FILTER_PAGE);
|
| + }
|
|
|
| + private void setThumbnail() {
|
| // If there's still a pending thumbnail fetch, cancel it.
|
| cancelImageFetch();
|
|
|
| - // If the article has a thumbnail already, reuse it. Otherwise start a fetch.
|
| // mThumbnailView's visibility is modified in updateLayout().
|
| - if (mThumbnailView.getVisibility() == View.VISIBLE) {
|
| - if (mArticle.getThumbnailBitmap() != null) {
|
| - mThumbnailView.setImageBitmap(mArticle.getThumbnailBitmap());
|
| - } else {
|
| - mThumbnailView.setImageResource(R.drawable.ic_snippet_thumbnail_placeholder);
|
| - mImageCallback = new FetchImageCallback(this, mArticle);
|
| - mUiDelegate.getSuggestionsSource().fetchSuggestionImage(mArticle, mImageCallback);
|
| - }
|
| + if (mThumbnailView.getVisibility() != View.VISIBLE) return;
|
| + if (mArticle.getThumbnailBitmap() != null && !mArticle.getThumbnailBitmap().isRecycled()) {
|
| + setThumbnailFromBitmap(mArticle.getThumbnailBitmap());
|
| + return;
|
| }
|
|
|
| - // Set the favicon of the publisher.
|
| - try {
|
| - fetchFaviconFromLocalCache(new URI(mArticle.mUrl), true);
|
| - } catch (URISyntaxException e) {
|
| - setDefaultFaviconOnView();
|
| + if (mArticle.isDownload()) {
|
| + setDownloadThumbnail();
|
| + return;
|
| }
|
|
|
| - mOfflineBadge.setVisibility(View.GONE);
|
| - refreshOfflineBadgeVisibility();
|
| -
|
| - mRecyclerView.onSnippetBound(itemView);
|
| + // Temporarily set placeholder and then fetch the thumbnail from a provider.
|
| + mThumbnailView.setBackground(null);
|
| + mThumbnailView.setImageResource(R.drawable.ic_snippet_thumbnail_placeholder);
|
| + mThumbnailView.setTint(null);
|
| + mImageCallback = new FetchImageCallback(this, mArticle);
|
| + mUiDelegate.getSuggestionsSource().fetchSuggestionImage(mArticle, mImageCallback);
|
| }
|
|
|
| /** Updates the visibility of the card's offline badge by checking the bound article's info. */
|
| private void refreshOfflineBadgeVisibility() {
|
| if (!SnippetsConfig.isOfflineBadgeEnabled()) return;
|
| - boolean visible = mArticle.getOfflinePageOfflineId() != null || mArticle.mIsAssetDownload;
|
| + boolean visible = mArticle.getOfflinePageOfflineId() != null || mArticle.isAssetDownload();
|
| if (visible == (mOfflineBadge.getVisibility() == View.VISIBLE)) return;
|
| mOfflineBadge.setVisibility(visible ? View.VISIBLE : View.GONE);
|
| }
|
|
|
| - private static class FetchImageCallback extends Callback<Bitmap> {
|
| - private SnippetArticleViewHolder mViewHolder;
|
| - private final SnippetArticle mSnippet;
|
| -
|
| - public FetchImageCallback(
|
| - SnippetArticleViewHolder viewHolder, SnippetArticle snippet) {
|
| - mViewHolder = viewHolder;
|
| - mSnippet = snippet;
|
| - }
|
| -
|
| - @Override
|
| - public void onResult(Bitmap image) {
|
| - if (mViewHolder == null) return;
|
| - mViewHolder.fadeThumbnailIn(mSnippet, image);
|
| - }
|
| -
|
| - public void cancel() {
|
| - // TODO(treib): Pass the "cancel" on to the actual image fetcher.
|
| - mViewHolder = null;
|
| - }
|
| - }
|
| -
|
| private void cancelImageFetch() {
|
| - if (mImageCallback != null) {
|
| - mImageCallback.cancel();
|
| - mImageCallback = null;
|
| + if (mImageCallback == null) return;
|
| + mImageCallback.cancel();
|
| + if (mArticle.isAssetDownload()) {
|
| + mThumbnailProvider.cancelRetrieval(mImageCallback);
|
| }
|
| + mImageCallback = null;
|
| }
|
|
|
| private void fadeThumbnailIn(SnippetArticle snippet, Bitmap thumbnail) {
|
| @@ -340,7 +391,10 @@ public class SnippetArticleViewHolder extends CardViewHolder implements Impressi
|
| Drawable[] layers = {mThumbnailView.getDrawable(),
|
| new BitmapDrawable(mThumbnailView.getResources(), scaledThumbnail)};
|
| TransitionDrawable transitionDrawable = new TransitionDrawable(layers);
|
| + mThumbnailView.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
| + mThumbnailView.setBackground(null);
|
| mThumbnailView.setImageDrawable(transitionDrawable);
|
| + mThumbnailView.setTint(null);
|
| transitionDrawable.setCrossFadeEnabled(true);
|
| transitionDrawable.startTransition(FADE_IN_ANIMATION_TIME_MS);
|
| }
|
| @@ -415,6 +469,41 @@ public class SnippetArticleViewHolder extends CardViewHolder implements Impressi
|
| mPublisherTextView.setVisibility(View.VISIBLE);
|
| }
|
|
|
| + private static class FetchImageCallback
|
| + extends Callback<Bitmap> implements ThumbnailProvider.ThumbnailRequest {
|
| + private SnippetArticleViewHolder mViewHolder;
|
| + private final SnippetArticle mSnippet;
|
| +
|
| + public FetchImageCallback(SnippetArticleViewHolder viewHolder, SnippetArticle snippet) {
|
| + mViewHolder = viewHolder;
|
| + mSnippet = snippet;
|
| + }
|
| +
|
| + @Override
|
| + public void onResult(Bitmap image) {
|
| + if (mViewHolder == null) return;
|
| + mViewHolder.fadeThumbnailIn(mSnippet, image);
|
| + }
|
| +
|
| + @Override
|
| + public String getFilePath() {
|
| + return mSnippet == null ? null : mSnippet.getAssetDownloadFile().getAbsolutePath();
|
| + }
|
| +
|
| + @Override
|
| + public void onThumbnailRetrieved(String filePath, Bitmap thumbnail) {
|
| + if (TextUtils.equals(getFilePath(), filePath) && thumbnail != null
|
| + && thumbnail.getWidth() > 0 && thumbnail.getHeight() > 0) {
|
| + onResult(thumbnail);
|
| + }
|
| + }
|
| +
|
| + public void cancel() {
|
| + // TODO(treib): Pass the "cancel" on to the actual image fetcher.
|
| + mViewHolder = null;
|
| + }
|
| + }
|
| +
|
| /**
|
| * Callback to refresh the offline badge visibility.
|
| */
|
|
|