// Copyright 2015 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;
import android.os.SystemClock;
import android.support.annotation.IntDef;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.browser.UrlConstants;
import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge;
import org.chromium.chrome.browser.rappor.RapporServiceBridge;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.util.UrlUtilities;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.base.PageTransition;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.TimeUnit;
/**
* Records UMA stats for which actions the user takes on the NTP in the
* "NewTabPage.ActionAndroid" histogram.
*/
public final class NewTabPageUma {
private NewTabPageUma() {}
// Possible actions taken by the user on the NTP. These values are also defined in
// histograms.xml. WARNING: these values must stay in sync with histograms.xml.
// User performed a search using the omnibox
private static final int ACTION_SEARCHED_USING_OMNIBOX = 0;
// User navigated to Google search homepage using the omnibox
private static final int ACTION_NAVIGATED_TO_GOOGLE_HOMEPAGE = 1;
// User navigated to any other page using the omnibox
private static final int ACTION_NAVIGATED_USING_OMNIBOX = 2;
// User opened a most visited page
public static final int ACTION_OPENED_MOST_VISITED_ENTRY = 3;
// User opened a recently closed tab
public static final int ACTION_OPENED_RECENTLY_CLOSED_ENTRY = 4;
// User opened a bookmark
public static final int ACTION_OPENED_BOOKMARK = 5;
// User opened a foreign session (from recent tabs section)
public static final int ACTION_OPENED_FOREIGN_SESSION = 6;
// User navigated to the webpage for a snippet shown on the NTP.
public static final int ACTION_OPENED_SNIPPET = 7;
// User clicked on an interest item.
public static final int ACTION_CLICKED_INTEREST = 8;
// User clicked on the "learn more" link in the footer.
public static final int ACTION_CLICKED_LEARN_MORE = 9;
// The number of possible actions
private static final int NUM_ACTIONS = 10;
// User navigated to a page using the omnibox.
private static final int RAPPOR_ACTION_NAVIGATED_USING_OMNIBOX = 0;
// User navigated to a page using one of the suggested tiles.
public static final int RAPPOR_ACTION_VISITED_SUGGESTED_TILE = 1;
// Regular NTP impression (usually when a new tab is opened)
public static final int NTP_IMPRESSION_REGULAR = 0;
// Potential NTP impressions (instead of blank page if no tab is open)
public static final int NTP_IMPESSION_POTENTIAL_NOTAB = 1;
// The number of possible NTP impression types
private static final int NUM_NTP_IMPRESSION = 2;
/** Possible interactions with the snippets.
* Do not remove or change existing values other than NUM_SNIPPETS_ACTIONS. */
@IntDef({SNIPPETS_ACTION_SHOWN, SNIPPETS_ACTION_SCROLLED, SNIPPETS_ACTION_CLICKED,
SNIPPETS_ACTION_DISMISSED_OBSOLETE, SNIPPETS_ACTION_DISMISSED_VISITED,
SNIPPETS_ACTION_DISMISSED_UNVISITED})
@Retention(RetentionPolicy.SOURCE)
public @interface SnippetsAction {}
/** Snippets are enabled and are being shown to the user. */
public static final int SNIPPETS_ACTION_SHOWN = 0;
/** The snippet list has been scrolled. */
public static final int SNIPPETS_ACTION_SCROLLED = 1;
/** A snippet has been clicked. */
public static final int SNIPPETS_ACTION_CLICKED = 2;
/** A snippet has been dismissed, made obsolete by the next two actions. */
public static final int SNIPPETS_ACTION_DISMISSED_OBSOLETE = 3;
/** A snippet has been swiped away, it had been viewed by the user (on this device). */
public static final int SNIPPETS_ACTION_DISMISSED_VISITED = 4;
/** A snippet has been swiped away, it had not been viewed by the user (on this device). */
public static final int SNIPPETS_ACTION_DISMISSED_UNVISITED = 5;
/** Obsolete. The snippet list has been scrolled below the fold (once per NTP load). */
// public static final int SNIPPETS_ACTION_SCROLLED_BELOW_THE_FOLD_ONCE = 6;
/** The number of possible actions. */
private static final int NUM_SNIPPETS_ACTIONS = 7;
/** Possible ways to follow the link provided by a snippet.
* Do not remove or change existing values other than NUM_OPEN_SNIPPET_METHODS. */
@IntDef({OPEN_SNIPPET_METHODS_PLAIN_CLICK, OPEN_SNIPPET_METHODS_NEW_WINDOW,
OPEN_SNIPPET_METHODS_NEW_TAB, OPEN_SNIPPET_METHODS_INCOGNITO,
OPEN_SNIPPET_METHODS_SAVE_FOR_OFFLINE, NUM_OPEN_SNIPPET_METHODS})
@Retention(RetentionPolicy.SOURCE)
public @interface OpenSnippetMethod {}
/** The article was opened taking over the tab. */
public static final int OPEN_SNIPPET_METHODS_PLAIN_CLICK = 0;
/** The article was opened in a new window. */
public static final int OPEN_SNIPPET_METHODS_NEW_WINDOW = 1;
/** The article was opened in a new tab, */
public static final int OPEN_SNIPPET_METHODS_NEW_TAB = 2;
/** The article was opened in an incognito tab. */
public static final int OPEN_SNIPPET_METHODS_INCOGNITO = 3;
/** The article was saved to be viewed offline. */
public static final int OPEN_SNIPPET_METHODS_SAVE_FOR_OFFLINE = 4;
/** The number of ways an article can be viewed. */
public static final int NUM_OPEN_SNIPPET_METHODS = 5;
/**
* Records an action taken by the user on the NTP.
* @param action One of the ACTION_* values defined in this class.
*/
public static void recordAction(int action) {
assert action >= 0;
assert action < NUM_ACTIONS;
switch (action) {
case ACTION_OPENED_MOST_VISITED_ENTRY:
RecordUserAction.record("MobileNTPMostVisited");
break;
case ACTION_OPENED_RECENTLY_CLOSED_ENTRY:
RecordUserAction.record("MobileNTPRecentlyClosed");
break;
case ACTION_OPENED_BOOKMARK:
RecordUserAction.record("MobileNTPBookmark");
break;
case ACTION_OPENED_FOREIGN_SESSION:
RecordUserAction.record("MobileNTPForeignSession");
break;
default:
// No UMA action associated with this type.
break;
}
RecordHistogram.recordEnumeratedHistogram("NewTabPage.ActionAndroid", action, NUM_ACTIONS);
}
/**
* Record that the user has navigated away from the NTP using the omnibox.
* @param destinationUrl The URL to which the user navigated.
* @param transitionType The transition type of the navigation, from PageTransition.java.
*/
public static void recordOmniboxNavigation(String destinationUrl, int transitionType) {
if ((transitionType & PageTransition.CORE_MASK) == PageTransition.GENERATED) {
recordAction(ACTION_SEARCHED_USING_OMNIBOX);
} else {
if (UrlUtilities.nativeIsGoogleHomePageUrl(destinationUrl)) {
recordAction(ACTION_NAVIGATED_TO_GOOGLE_HOMEPAGE);
} else {
recordAction(ACTION_NAVIGATED_USING_OMNIBOX);
}
recordExplicitUserNavigation(destinationUrl, RAPPOR_ACTION_NAVIGATED_USING_OMNIBOX);
}
}
/**
* Record the eTLD+1 for a website explicitly visited by the user, using Rappor.
*/
public static void recordExplicitUserNavigation(String destinationUrl, int rapporMetric) {
switch (rapporMetric) {
case RAPPOR_ACTION_NAVIGATED_USING_OMNIBOX:
RapporServiceBridge.sampleDomainAndRegistryFromURL(
"NTP.ExplicitUserAction.PageNavigation.OmniboxNonSearch", destinationUrl);
return;
case RAPPOR_ACTION_VISITED_SUGGESTED_TILE:
RapporServiceBridge.sampleDomainAndRegistryFromURL(
"NTP.ExplicitUserAction.PageNavigation.NTPTileClick", destinationUrl);
return;
default:
return;
}
}
/**
* Records important events related to snippets.
* @param action action key, one of {@link SnippetsAction}'s values.
*/
public static void recordSnippetAction(@SnippetsAction int action) {
RecordHistogram.recordEnumeratedHistogram(
"NewTabPage.Snippets.Interactions", action, NUM_SNIPPETS_ACTIONS);
}
/**
* Records how the article linked from a snippet was viewed.
* @param method method key, one of {@link OpenSnippetMethod}'s values.
*/
public static void recordOpenSnippetMethod(@OpenSnippetMethod int method) {
RecordHistogram.recordEnumeratedHistogram(
"NewTabPage.Snippets.OpenMethod", method, NUM_OPEN_SNIPPET_METHODS);
}
/**
* Record a NTP impression (even potential ones to make informed product decisions).
* @param impressionType Type of the impression from NewTabPageUma.java
*/
public static void recordNTPImpression(int impressionType) {
assert impressionType >= 0;
assert impressionType < NUM_NTP_IMPRESSION;
RecordHistogram.recordEnumeratedHistogram(
"Android.NTP.Impression", impressionType, NUM_NTP_IMPRESSION);
}
/**
* Records stats related to content suggestion visits, such as the time spent on the website, or
* if the user comes back to the NTP.
* @param tab Tab opened to load a content suggestion.
* @param category The category of the content suggestion.
*/
public static void monitorContentSuggestionVisit(Tab tab, int category) {
tab.addObserver(new SnippetVisitRecorder(category));
}
/**
* Records stats related to content suggestion visits, such as the time spent on the website, or
* if the user comes back to the NTP. Use through
* {@link NewTabPageUma#monitorContentSuggestionVisit(Tab, int)}.
*/
private static class SnippetVisitRecorder extends EmptyTabObserver {
private final int mCategory;
private final long mStartTimeMs = SystemClock.elapsedRealtime();
private SnippetVisitRecorder(int category) {
mCategory = category;
}
@Override
public void onHidden(Tab tab) {
endRecording(tab);
}
@Override
public void onDestroyed(Tab tab) {
endRecording(null);
}
@Override
public void onUpdateUrl(Tab tab, String url) {
// onLoadUrl below covers many exit conditions to stop recording but not all,
// such as navigating back. We therefore stop recording if a URL change
// indicates some non-Web page was visited.
if (!url.startsWith(UrlConstants.CHROME_SCHEME)
&& !url.startsWith(UrlConstants.CHROME_NATIVE_SCHEME)) {
assert !NewTabPage.isNTPUrl(url);
return;
}
if (NewTabPage.isNTPUrl(url)) {
RecordUserAction.record("MobileNTP.Snippets.VisitEndBackInNTP");
}
endRecording(tab);
}
@Override
public void onLoadUrl(Tab tab, LoadUrlParams params, int loadType) {
// End recording if a new URL gets loaded e.g. after entering a new query in
// the omnibox. This doesn't cover the navigate-back case so we also need
// onUpdateUrl.
int transitionTypeMask = PageTransition.FROM_ADDRESS_BAR | PageTransition.HOME_PAGE
| PageTransition.CHAIN_START | PageTransition.CHAIN_END;
if ((params.getTransitionType() & transitionTypeMask) != 0) endRecording(tab);
}
private void endRecording(Tab removeObserverFromTab) {
if (removeObserverFromTab != null) removeObserverFromTab.removeObserver(this);
RecordUserAction.record("MobileNTP.Snippets.VisitEnd");
long visitTimeMs = SystemClock.elapsedRealtime() - mStartTimeMs;
RecordHistogram.recordLongTimesHistogram(
"NewTabPage.Snippets.VisitDuration", visitTimeMs, TimeUnit.MILLISECONDS);
SnippetsBridge.onSuggestionTargetVisited(mCategory, visitTimeMs);
}
}
}