blob: 36dd8e3595d1eda2f771becd7e17a9d3222aeff4 [file] [log] [blame]
// 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.support.v7.widget.RecyclerView;
import org.chromium.base.Callback;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.browser.ntp.NewTabPage;
import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
import org.chromium.chrome.browser.ntp.snippets.FaviconFetchResult;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.tab.Tab;
import java.util.concurrent.TimeUnit;
/**
* Exposes methods to report suggestions related events, for UMA or Fetch scheduling purposes.
*/
public abstract class SuggestionsMetrics {
private SuggestionsMetrics() {}
// UI Element interactions
public static void recordSurfaceVisible() {
if (!ChromePreferenceManager.getInstance().readBoolean(
ChromePreferenceManager.CONTENT_SUGGESTIONS_SHOWN_KEY, false)) {
RecordUserAction.record("Suggestions.FirstTimeSurfaceVisible");
ChromePreferenceManager.getInstance().writeBoolean(
ChromePreferenceManager.CONTENT_SUGGESTIONS_SHOWN_KEY, true);
}
RecordUserAction.record("Suggestions.SurfaceVisible");
}
public static void recordSurfaceHidden() {
RecordUserAction.record("Suggestions.SurfaceHidden");
}
public static void recordTileTapped() {
RecordUserAction.record("Suggestions.Tile.Tapped");
}
public static void recordExpandableHeaderTapped(boolean expanded) {
if (expanded) {
RecordUserAction.record("Suggestions.ExpandableHeader.Expanded");
} else {
RecordUserAction.record("Suggestions.ExpandableHeader.Collapsed");
}
}
public static void recordCardTapped() {
RecordUserAction.record("Suggestions.Card.Tapped");
}
public static void recordCardActionTapped() {
RecordUserAction.record("Suggestions.Card.ActionTapped");
}
public static void recordCardSwipedAway() {
RecordUserAction.record("Suggestions.Card.SwipedAway");
}
// Effect/Purpose of the interactions. Most are recorded in |content_suggestions_metrics.h|
public static void recordActionViewAll() {
RecordUserAction.record("Suggestions.Category.ViewAll");
}
/**
* Records metrics for the visit to the provided content suggestion, such as the time spent on
* the website, or if the user comes back to the starting point.
* @param tab The tab we want to record the visit on. It should have a live WebContents.
* @param suggestion The suggestion that prompted the visit.
*/
public static void recordVisit(Tab tab, SnippetArticle suggestion) {
@CategoryInt
final int category = suggestion.mCategory;
NavigationRecorder.record(tab, visit -> {
if (NewTabPage.isNTPUrl(visit.endUrl)) {
RecordUserAction.record("MobileNTP.Snippets.VisitEndBackInNTP");
}
RecordUserAction.record("MobileNTP.Snippets.VisitEnd");
SuggestionsEventReporterBridge.onSuggestionTargetVisited(category, visit.duration);
});
}
// Histogram recordings
/**
* Records whether article suggestions are set visible by user.
*/
public static void recordArticlesListVisible() {
RecordHistogram.recordBooleanHistogram("NewTabPage.ContentSuggestions.ArticlesListVisible",
PrefServiceBridge.getInstance().getBoolean(Pref.NTP_ARTICLES_LIST_VISIBLE));
}
/**
* Records the time it took to fetch a favicon for an article.
*
* @param fetchTime The time it took to fetch the favicon.
*/
public static void recordArticleFaviconFetchTime(long fetchTime) {
RecordHistogram.recordMediumTimesHistogram(
"NewTabPage.ContentSuggestions.ArticleFaviconFetchTime", fetchTime,
TimeUnit.MILLISECONDS);
}
/**
* Records the result from a favicon fetch for an article.
*
* @param result {@link FaviconFetchResult} The result from the fetch.
*/
public static void recordArticleFaviconFetchResult(@FaviconFetchResult int result) {
RecordHistogram.recordEnumeratedHistogram(
"NewTabPage.ContentSuggestions.ArticleFaviconFetchResult", result,
FaviconFetchResult.COUNT);
}
/**
* Records which tiles are available offline once the site suggestions finished loading.
* @param tileIndex index of a tile whose URL is available offline.
*/
public static void recordTileOfflineAvailability(int tileIndex) {
RecordHistogram.recordEnumeratedHistogram("NewTabPage.TileOfflineAvailable", tileIndex,
MostVisitedSitesBridge.MAX_TILE_COUNT);
}
/**
* @return A {@link DurationTracker} to notify to report how long the spinner is visible
* for.
*/
public static DurationTracker getSpinnerVisibilityReporter() {
return new DurationTracker((duration) -> {
RecordHistogram.recordTimesHistogram(
"ContentSuggestions.FetchPendingSpinner.VisibleDuration", duration,
TimeUnit.MILLISECONDS);
});
}
/**
* Measures the amount of time it takes for date formatting in order to track StrictMode
* violations.
* See https://crbug.com/639877
* @param duration Duration of date formatting.
*/
static void recordDateFormattingDuration(long duration) {
RecordHistogram.recordTimesHistogram(
"Android.StrictMode.SnippetUIBuildTime", duration, TimeUnit.MILLISECONDS);
}
/**
* One-shot reporter that records the first time the user scrolls a {@link RecyclerView}. If it
* should be reused, call {@link #reset()} to rearm it.
*/
public static class ScrollEventReporter extends RecyclerView.OnScrollListener {
private boolean mFired;
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (mFired) return;
if (newState != RecyclerView.SCROLL_STATE_DRAGGING) return;
RecordUserAction.record("Suggestions.ScrolledAfterOpen");
mFired = true;
}
public void reset() {
mFired = false;
}
}
/**
* Utility class to track the duration of an event. Call {@link #startTracking()} and
* {@link #endTracking()} to notify about the key moments. These methods are no-ops when called
* while tracking is not in the expected state.
*/
public static class DurationTracker {
private long mTrackingStartTimeMs;
private final Callback<Long> mTrackingCompleteCallback;
private DurationTracker(Callback<Long> trackingCompleteCallback) {
mTrackingCompleteCallback = trackingCompleteCallback;
}
public void startTracking() {
if (isTracking()) return;
mTrackingStartTimeMs = System.currentTimeMillis();
}
public void endTracking() {
if (!isTracking()) return;
mTrackingCompleteCallback.onResult(System.currentTimeMillis() - mTrackingStartTimeMs);
mTrackingStartTimeMs = 0;
}
private boolean isTracking() {
return mTrackingStartTimeMs > 0;
}
}
}