chromium/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelMetrics.java

// Copyright 2015 The Chromium Authors
// 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.compositor.bottombar.contextualsearch;

import org.chromium.base.TimeUtils;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.PanelState;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChangeReason;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchHeuristics;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchUma;
import org.chromium.chrome.browser.contextualsearch.QuickActionCategory;
import org.chromium.chrome.browser.contextualsearch.ResolvedSearchTerm;
import org.chromium.chrome.browser.profiles.Profile;

/**
 * This class is responsible for all the logging triggered by activity of the {@link
 * ContextualSearchPanel}. Typically this consists of tracking user activity logging that to UMA
 * when the interaction ends as the panel is dismissed.
 */
public class ContextualSearchPanelMetrics {
    // Flags for logging.
    private boolean mDidSearchInvolvePromo;
    private boolean mWasSearchContentViewSeen;
    private boolean mIsPromoActive;
    private boolean mHasExitedPeeking;
    private boolean mHasExitedExpanded;
    private boolean mHasExitedMaximized;
    private boolean mIsSerpNavigation;
    private boolean mWasActivatedByTap;
    @ResolvedSearchTerm.CardTag private int mCardTag;
    private boolean mWasQuickActionShown;
    private int mQuickActionCategory;
    private boolean mWasQuickActionClicked;
    // Time when the panel was triggered (not reset by a chained search).
    // Panel transitions are animated so mPanelTriggerTimeNs will be less than mFirstPeekTimeNs.
    private long mPanelTriggerTimeFromTapNs;
    // Time when the panel peeks into view (not reset by a chained search).
    // Used to log total time the panel is showing (not closed).
    private long mFirstPeekTimeNs;
    // Time when a search request was started. Reset by chained searches.
    // Used to log the time it takes for a Search Result to become available.
    private long mSearchRequestStartTimeNs;
    // The current set of heuristics that should be logged with results seen when the panel closes.
    private ContextualSearchHeuristics mResultsSeenExperiments;

    /** Whether the Search was prefetched or not. */
    private boolean mWasPrefetch;

    /**
     * Whether we were able to start painting the document in the Content View so the user could
     * actually see the SRP.
     */
    private boolean mDidFirstNonEmptyPaint;

    /**
     * Log information when the panel's state has changed.
     * @param fromState The state the panel is transitioning from.
     * @param toState The state that the panel is transitioning to.
     * @param reason The reason for the state change.
     * @param profile The current {@link Profile}.
     */
    public void onPanelStateChanged(
            @PanelState int fromState,
            @PanelState int toState,
            @StateChangeReason int reason,
            Profile profile) {
        // Note: the logging within this function includes the promo, unless specifically
        // excluded.
        boolean isStartingSearch = isStartingNewContextualSearch(toState, reason);
        boolean isEndingSearch = isEndingContextualSearch(fromState, toState, isStartingSearch);
        boolean isChained = isStartingSearch && isOngoingContextualSearch(fromState);
        boolean isSameState = fromState == toState;
        boolean isFirstExitFromPeeking =
                fromState == PanelState.PEEKED
                        && !mHasExitedPeeking
                        && (!isSameState || isStartingSearch);
        boolean isFirstExitFromExpanded =
                fromState == PanelState.EXPANDED && !mHasExitedExpanded && !isSameState;
        boolean isFirstExitFromMaximized =
                fromState == PanelState.MAXIMIZED && !mHasExitedMaximized && !isSameState;

        if (isEndingSearch) {
            long panelViewDurationMs =
                    (System.nanoTime() - mFirstPeekTimeNs) / TimeUtils.NANOSECONDS_PER_MILLISECOND;
            ContextualSearchUma.logPanelViewDurationAction(panelViewDurationMs);
            if (!mIsPromoActive) {
                ContextualSearchUma.logResultsSeen(mWasSearchContentViewSeen, mWasActivatedByTap);
            }

            ContextualSearchUma.logCardTagSeen(mWasSearchContentViewSeen, mCardTag);
            if (mWasQuickActionShown) {
                ContextualSearchUma.logQuickActionResultsSeen(
                        mWasSearchContentViewSeen, mQuickActionCategory);
                ContextualSearchUma.logQuickActionClicked(
                        mWasQuickActionClicked, mQuickActionCategory);
            }

            if (mResultsSeenExperiments != null) {
                mResultsSeenExperiments.logResultsSeen(
                        mWasSearchContentViewSeen, mWasActivatedByTap);
                if (!isChained) mResultsSeenExperiments = null;
            }

            if (mWasActivatedByTap) {
                ContextualSearchUma.logTapResultsSeen(mWasSearchContentViewSeen, profile);
            }
            ContextualSearchUma.logAllResultsSeen(mWasSearchContentViewSeen);
            if (mWasSearchContentViewSeen) {
                ContextualSearchUma.logAllSearches(/* wasRelatedSearches= */ false);
            }
            ContextualSearchUma.logCountedSearches(
                    mWasSearchContentViewSeen, mDidFirstNonEmptyPaint, mWasPrefetch);
        }

        if (isStartingSearch) {
            mFirstPeekTimeNs = System.nanoTime();
            mWasActivatedByTap = reason == StateChangeReason.TEXT_SELECT_TAP;
        }

        @StateChangeReason
        int reasonForLogging = mIsSerpNavigation ? StateChangeReason.SERP_NAVIGATION : reason;
        // Log individual user actions so they can be sequenced.
        ContextualSearchUma.logPanelStateUserAction(toState, reasonForLogging);

        // We can now modify the state.
        if (isFirstExitFromPeeking) {
            mHasExitedPeeking = true;
        } else if (isFirstExitFromExpanded) {
            mHasExitedExpanded = true;
        } else if (isFirstExitFromMaximized) {
            mHasExitedMaximized = true;
        }

        if (reason == StateChangeReason.SERP_NAVIGATION) {
            mIsSerpNavigation = true;
        }

        if (isEndingSearch) {
            mDidSearchInvolvePromo = false;
            mWasSearchContentViewSeen = false;
            mHasExitedPeeking = false;
            mHasExitedExpanded = false;
            mHasExitedMaximized = false;
            mIsSerpNavigation = false;
            mWasQuickActionShown = false;
            mQuickActionCategory = QuickActionCategory.NONE;
            mCardTag = ResolvedSearchTerm.CardTag.CT_NONE;
            mWasQuickActionClicked = false;
            mPanelTriggerTimeFromTapNs = 0;
            mDidFirstNonEmptyPaint = false;
            mWasPrefetch = false;
        }
    }

    /** Sets that the contextual search involved the promo. */
    public void setDidSearchInvolvePromo() {
        mDidSearchInvolvePromo = true;
    }

    /** Sets that the Search Content View was seen. */
    public void setWasSearchContentViewSeen() {
        mWasSearchContentViewSeen = true;
    }

    /** Sets whether the promo is active. */
    public void setIsPromoActive(boolean shown) {
        mIsPromoActive = shown;
    }

    /**
     * @param cardTag The indicator tag for the kind of card shown.
     */
    public void setCardShown(@ResolvedSearchTerm.CardTag int cardTag) {
        mCardTag = cardTag;
    }

    /**
     * @param wasQuickActionShown Whether a quick action was shown in the Contextual Search Bar.
     * @param quickActionCategory The {@link QuickActionCategory} for the quick action.
     */
    public void setWasQuickActionShown(boolean wasQuickActionShown, int quickActionCategory) {
        mWasQuickActionShown = wasQuickActionShown;
        if (mWasQuickActionShown) mQuickActionCategory = quickActionCategory;
    }

    /** Sets |mWasQuickActionClicked| to true. */
    public void setWasQuickActionClicked() {
        mWasQuickActionClicked = true;
    }

    /** Should be called when the panel first starts showing due to a tap. */
    public void onPanelTriggeredFromTap() {
        mPanelTriggerTimeFromTapNs = System.nanoTime();
    }

    /** Called to record the time when a search request started, for resolve and prefetch timing. */
    public void onSearchRequestStarted() {
        mSearchRequestStartTimeNs = System.nanoTime();
    }

    /**
     * Notifies that we were able to start painting the document in the Content View so the user can
     * actually see the SRP.
     * @param didPrefetch Whether this was a prefetched SRP.
     */
    public void onFirstNonEmptyPaint(boolean didPrefetch) {
        mDidFirstNonEmptyPaint = true;
        mWasPrefetch = didPrefetch;
    }

    /**
     * Sets the experiments to log with results seen.
     * @param resultsSeenExperiments The experiments to log when the panel results are known.
     */
    public void setResultsSeenExperiments(ContextualSearchHeuristics resultsSeenExperiments) {
        mResultsSeenExperiments = resultsSeenExperiments;
    }

    /**
     * Determine whether a new contextual search is starting.
     * @param toState The contextual search state that will be transitioned to.
     * @param reason The reason for the search state transition.
     * @return Whether a new contextual search is starting.
     */
    private boolean isStartingNewContextualSearch(
            @PanelState int toState, @StateChangeReason int reason) {
        return toState == PanelState.PEEKED
                && (reason == StateChangeReason.TEXT_SELECT_TAP
                        || reason == StateChangeReason.TEXT_SELECT_LONG_PRESS);
    }

    /**
     * Determine whether a contextual search is ending.
     * @param fromState The contextual search state that will be transitioned from.
     * @param toState The contextual search state that will be transitioned to.
     * @param isStartingSearch Whether a new contextual search is starting.
     * @return Whether a contextual search is ending.
     */
    private boolean isEndingContextualSearch(
            @PanelState int fromState, @PanelState int toState, boolean isStartingSearch) {
        return isOngoingContextualSearch(fromState)
                && (toState == PanelState.CLOSED || isStartingSearch);
    }

    /**
     * @param fromState The state the panel is transitioning from.
     * @return Whether there is an ongoing contextual search.
     */
    private boolean isOngoingContextualSearch(@PanelState int fromState) {
        return fromState != PanelState.UNDEFINED && fromState != PanelState.CLOSED;
    }
}