// Copyright 2016 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.ntp.snippets;
import android.graphics.Bitmap;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.ntp.NewTabPageUma;
import org.chromium.chrome.browser.ntp.cards.NewTabPageItem;
import org.chromium.chrome.browser.ntp.cards.NewTabPageViewHolder;
import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsCardLayout.ContentSuggestionsCardLayoutEnum;
/**
* Represents the data for an article card on the NTP.
*/
public class SnippetArticle implements NewTabPageItem {
/** The category of this article. */
public final int mCategory;
/** The identifier for this article within the category - not necessarily unique globally. */
public final String mIdWithinCategory;
/** The title of this article. */
public final String mTitle;
/** The canonical publisher name (e.g., New York Times). */
public final String mPublisher;
/** The snippet preview text. */
public final String mPreviewText;
/** The URL of this article. */
public final String mUrl;
/** the AMP url for this article (possible for this to be empty). */
public final String mAmpUrl;
/** The time when this article was published. */
public final long mPublishTimestampMilliseconds;
/** The score expressing relative quality of the article for the user. */
public final float mScore;
/** The position of this article within its section. */
public final int mPosition;
/** The position of this article in the complete list. Populated by NewTabPageAdapter.*/
public int mGlobalPosition = -1;
/** The layout that should be used to display the snippet. */
@ContentSuggestionsCardLayoutEnum
public final int mCardLayout;
/** Bitmap of the thumbnail, fetched lazily, when the RecyclerView wants to show the snippet. */
private Bitmap mThumbnailBitmap;
/** Stores whether impression of this article has been tracked already. */
private boolean mImpressionTracked;
/** Specifies ranges of positions for which we store position-specific sub-histograms. */
private static final int[] HISTOGRAM_FOR_POSITIONS = {0, 2, 4, 9};
/**
* Creates a SnippetArticleListItem object that will hold the data.
*/
public SnippetArticle(int category, String idWithinCategory, String title, String publisher,
String previewText, String url, String ampUrl, long timestamp, float score,
int position, @ContentSuggestionsCardLayoutEnum int cardLayout) {
mCategory = category;
mIdWithinCategory = idWithinCategory;
mTitle = title;
mPublisher = publisher;
mPreviewText = previewText;
mUrl = url;
mAmpUrl = ampUrl;
mPublishTimestampMilliseconds = timestamp;
mScore = score;
mPosition = position;
mCardLayout = cardLayout;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof SnippetArticle)) return false;
SnippetArticle rhs = (SnippetArticle) other;
return mCategory == rhs.mCategory && mIdWithinCategory.equals(rhs.mIdWithinCategory);
}
@Override
public int hashCode() {
return mCategory ^ mIdWithinCategory.hashCode();
}
@Override
public int getType() {
return NewTabPageItem.VIEW_TYPE_SNIPPET;
}
@Override
public void onBindViewHolder(NewTabPageViewHolder holder) {
assert holder instanceof SnippetArticleViewHolder;
((SnippetArticleViewHolder) holder).onBindViewHolder(this);
}
/**
* Returns this article's thumbnail as a {@link Bitmap}. Can return {@code null} as it is
* initially unset.
*/
public Bitmap getThumbnailBitmap() {
return mThumbnailBitmap;
}
/** Sets the thumbnail bitmap for this article. */
public void setThumbnailBitmap(Bitmap bitmap) {
mThumbnailBitmap = bitmap;
}
/** Tracks click on this NTP snippet in UMA. */
public void trackClick() {
// To compare against NewTabPage.Snippets.CardShown for each position.
RecordHistogram.recordSparseSlowlyHistogram("NewTabPage.Snippets.CardClicked", mPosition);
// To compare against all snippets actions.
NewTabPageUma.recordSnippetAction(NewTabPageUma.SNIPPETS_ACTION_CLICKED);
// To compare how the user views the article linked to from a snippet (eg. as opposed to
// opening in a new tab).
NewTabPageUma.recordOpenSnippetMethod(NewTabPageUma.OPEN_SNIPPET_METHODS_PLAIN_CLICK);
// To see how users left the NTP.
NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_SNIPPET);
// To see whether users click on more recent snippets and whether our suggestion algorithm
// is accurate.
recordAgeAndScore("NewTabPage.Snippets.CardClicked");
}
/** Tracks impression of this NTP snippet. */
public boolean trackImpression() {
// Track UMA only upon the first impression per life-time of this object.
if (mImpressionTracked) return false;
RecordHistogram.recordSparseSlowlyHistogram("NewTabPage.Snippets.CardShown", mPosition);
recordAgeAndScore("NewTabPage.Snippets.CardShown");
mImpressionTracked = true;
return true;
}
/** Returns whether impression of this SnippetArticleListItem has already been tracked. */
public boolean impressionTracked() {
return mImpressionTracked;
}
public void recordAgeAndScore(String histogramPrefix) {
// Track how the (approx.) position relates to age / score of the snippet that is clicked.
int ageInMinutes =
(int) ((System.currentTimeMillis() - mPublishTimestampMilliseconds) / 60000L);
String histogramAge = histogramPrefix + "Age";
String histogramScore = histogramPrefix + "ScoreNew";
recordAge(histogramAge, ageInMinutes);
recordScore(histogramScore, mScore);
int startPosition = 0;
for (int endPosition : HISTOGRAM_FOR_POSITIONS) {
if (mPosition >= startPosition && mPosition <= endPosition) {
String suffix = "_" + startPosition + "_" + endPosition;
recordAge(histogramAge + suffix, ageInMinutes);
recordScore(histogramScore + suffix, mScore);
break;
}
startPosition = endPosition + 1;
}
}
private static void recordAge(String histogramName, int ageInMinutes) {
// Negative values (when the time of the device is set inappropriately) provide no value.
if (ageInMinutes >= 0) {
// If the max value below (72 hours) were to be changed, the histogram should be renamed
// since it will change the shape of buckets.
RecordHistogram.recordCustomCountHistogram(histogramName, ageInMinutes, 1, 72 * 60, 50);
}
}
private static void recordScore(String histogramName, float score) {
int recordedScore = Math.min((int) Math.ceil(score), 100000);
RecordHistogram.recordCustomCountHistogram(histogramName, recordedScore, 1, 100000, 50);
}
@Override
public String toString() {
// For debugging purposes. Displays the first 42 characters of the title.
return String.format("{%s, %1.42s}", getClass().getSimpleName(), mTitle);
}
}