// 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.contextualsearch; import android.util.Pair; import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.metrics.RecordUserAction; import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.PanelState; import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChangeReason; import org.chromium.chrome.browser.contextualsearch.ContextualSearchBlacklist.BlacklistReason; import org.chromium.chrome.browser.preferences.PrefServiceBridge; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; /** * Centralizes UMA data collection for Contextual Search. All calls must be made from the UI thread. */ public class ContextualSearchUma { // Constants to use for the original selection gesture private static final boolean LONG_PRESS = false; private static final boolean TAP = true; // Constants used to log UMA "enum" histograms about the Contextual Search's preference state. private static final int PREFERENCE_UNINITIALIZED = 0; private static final int PREFERENCE_ENABLED = 1; private static final int PREFERENCE_DISABLED = 2; private static final int PREFERENCE_HISTOGRAM_BOUNDARY = 3; // Constants used to log UMA "enum" histograms about whether search results were seen. private static final int RESULTS_SEEN = 0; private static final int RESULTS_NOT_SEEN = 1; private static final int RESULTS_SEEN_BOUNDARY = 2; // Constants used to log UMA "enum" histograms about whether the selection is valid. private static final int SELECTION_VALID = 0; private static final int SELECTION_INVALID = 1; private static final int SELECTION_BOUNDARY = 2; // Constants used to log UMA "enum" histograms about a request's outcome. private static final int REQUEST_NOT_FAILED = 0; private static final int REQUEST_FAILED = 1; private static final int REQUEST_BOUNDARY = 2; // Constants used to log UMA "enum" histograms about the panel's state transitions. // Entry code: first entry into CLOSED. private static final int ENTER_CLOSED_FROM_OTHER = 0; private static final int ENTER_CLOSED_FROM_PEEKED_BACK_PRESS = 1; private static final int ENTER_CLOSED_FROM_PEEKED_BASE_PAGE_SCROLL = 2; private static final int ENTER_CLOSED_FROM_PEEKED_TEXT_SELECT_TAP = 3; private static final int ENTER_CLOSED_FROM_EXPANDED_BACK_PRESS = 4; private static final int ENTER_CLOSED_FROM_EXPANDED_BASE_PAGE_TAP = 5; private static final int ENTER_CLOSED_FROM_EXPANDED_FLING = 6; private static final int ENTER_CLOSED_FROM_MAXIMIZED_BACK_PRESS = 7; private static final int ENTER_CLOSED_FROM_MAXIMIZED_FLING = 8; private static final int ENTER_CLOSED_FROM_MAXIMIZED_TAB_PROMOTION = 9; private static final int ENTER_CLOSED_FROM_MAXIMIZED_SERP_NAVIGATION = 10; private static final int ENTER_CLOSED_FROM_BOUNDARY = 11; // Entry code: first entry into PEEKED. private static final int ENTER_PEEKED_FROM_OTHER = 0; private static final int ENTER_PEEKED_FROM_CLOSED_TEXT_SELECT_TAP = 1; private static final int ENTER_PEEKED_FROM_CLOSED_EXT_SELECT_LONG_PRESS = 2; private static final int ENTER_PEEKED_FROM_PEEKED_TEXT_SELECT_TAP = 3; private static final int ENTER_PEEKED_FROM_PEEKED_TEXT_SELECT_LONG_PRESS = 4; private static final int ENTER_PEEKED_FROM_EXPANDED_SEARCH_BAR_TAP = 5; private static final int ENTER_PEEKED_FROM_EXPANDED_SWIPE = 6; private static final int ENTER_PEEKED_FROM_EXPANDED_FLING = 7; private static final int ENTER_PEEKED_FROM_MAXIMIZED_SWIPE = 8; private static final int ENTER_PEEKED_FROM_MAXIMIZED_FLING = 9; private static final int ENTER_PEEKED_FROM_BOUNDARY = 10; // Entry code: first entry into EXPANDED. private static final int ENTER_EXPANDED_FROM_OTHER = 0; private static final int ENTER_EXPANDED_FROM_PEEKED_SEARCH_BAR_TAP = 1; private static final int ENTER_EXPANDED_FROM_PEEKED_SWIPE = 2; private static final int ENTER_EXPANDED_FROM_PEEKED_FLING = 3; private static final int ENTER_EXPANDED_FROM_MAXIMIZED_SWIPE = 4; private static final int ENTER_EXPANDED_FROM_MAXIMIZED_FLING = 5; private static final int ENTER_EXPANDED_FROM_BOUNDARY = 6; // Entry code: first entry into MAXIMIZED. private static final int ENTER_MAXIMIZED_FROM_OTHER = 0; private static final int ENTER_MAXIMIZED_FROM_PEEKED_SWIPE = 1; private static final int ENTER_MAXIMIZED_FROM_PEEKED_FLING = 2; private static final int ENTER_MAXIMIZED_FROM_EXPANDED_SWIPE = 3; private static final int ENTER_MAXIMIZED_FROM_EXPANDED_FLING = 4; private static final int ENTER_MAXIMIZED_FROM_EXPANDED_SERP_NAVIGATION = 5; private static final int ENTER_MAXIMIZED_FROM_BOUNDARY = 6; // Exit code: first exit from CLOSED (or UNDEFINED). private static final int EXIT_CLOSED_TO_OTHER = 0; private static final int EXIT_CLOSED_TO_PEEKED_TEXT_SELECT_TAP = 1; private static final int EXIT_CLOSED_TO_PEEKED_TEXT_SELECT_LONG_PRESS = 2; private static final int EXIT_CLOSED_TO_BOUNDARY = 3; // Exit code: first exit from PEEKED. private static final int EXIT_PEEKED_TO_OTHER = 0; private static final int EXIT_PEEKED_TO_CLOSED_BACK_PRESS = 1; private static final int EXIT_PEEKED_TO_CLOSED_BASE_PAGE_SCROLL = 2; private static final int EXIT_PEEKED_TO_CLOSED_TEXT_SELECT_TAP = 3; private static final int EXIT_PEEKED_TO_PEEKED_TEXT_SELECT_TAP = 4; private static final int EXIT_PEEKED_TO_PEEKED_TEXT_SELECT_LONG_PRESS = 5; private static final int EXIT_PEEKED_TO_EXPANDED_SEARCH_BAR_TAP = 6; private static final int EXIT_PEEKED_TO_EXPANDED_SWIPE = 7; private static final int EXIT_PEEKED_TO_EXPANDED_FLING = 8; private static final int EXIT_PEEKED_TO_MAXIMIZED_SWIPE = 9; private static final int EXIT_PEEKED_TO_MAXIMIZED_FLING = 10; private static final int EXIT_PEEKED_TO_BOUNDARY = 11; // Exit code: first exit from EXPANDED. private static final int EXIT_EXPANDED_TO_OTHER = 0; private static final int EXIT_EXPANDED_TO_CLOSED_BACK_PRESS = 1; private static final int EXIT_EXPANDED_TO_CLOSED_BASE_PAGE_TAP = 2; private static final int EXIT_EXPANDED_TO_CLOSED_FLING = 3; private static final int EXIT_EXPANDED_TO_PEEKED_SEARCH_BAR_TAP = 4; private static final int EXIT_EXPANDED_TO_PEEKED_SWIPE = 5; private static final int EXIT_EXPANDED_TO_PEEKED_FLING = 6; private static final int EXIT_EXPANDED_TO_MAXIMIZED_SWIPE = 7; private static final int EXIT_EXPANDED_TO_MAXIMIZED_FLING = 8; private static final int EXIT_EXPANDED_TO_MAXIMIZED_SERP_NAVIGATION = 9; private static final int EXIT_EXPANDED_TO_BOUNDARY = 10; // Exit code: first exit from MAXIMIZED. private static final int EXIT_MAXIMIZED_TO_OTHER = 0; private static final int EXIT_MAXIMIZED_TO_CLOSED_BACK_PRESS = 1; private static final int EXIT_MAXIMIZED_TO_CLOSED_FLING = 2; private static final int EXIT_MAXIMIZED_TO_CLOSED_TAB_PROMOTION = 3; private static final int EXIT_MAXIMIZED_TO_CLOSED_SERP_NAVIGATION = 4; private static final int EXIT_MAXIMIZED_TO_PEEKED_SWIPE = 5; private static final int EXIT_MAXIMIZED_TO_PEEKED_FLING = 6; private static final int EXIT_MAXIMIZED_TO_EXPANDED_SWIPE = 7; private static final int EXIT_MAXIMIZED_TO_EXPANDED_FLING = 8; private static final int EXIT_MAXIMIZED_TO_BOUNDARY = 9; // Constants used to log UMA "enum" histograms with details about whether search results // were seen, and what the original triggering gesture was. private static final int RESULTS_SEEN_FROM_TAP = 0; private static final int RESULTS_NOT_SEEN_FROM_TAP = 1; private static final int RESULTS_SEEN_FROM_LONG_PRESS = 2; private static final int RESULTS_NOT_SEEN_FROM_LONG_PRESS = 3; private static final int RESULTS_BY_GESTURE_BOUNDARY = 4; // Constants used to log UMA "enum" histograms with details about whether search results // were seen, and whether any existing tap suppression heuristics were satisfied. private static final int RESULTS_SEEN_SUPPRESSION_HEURSTIC_SATISFIED = 0; private static final int RESULTS_NOT_SEEN_SUPPRESSION_HEURSTIC_SATISFIED = 1; private static final int RESULTS_SEEN_SUPPRESSION_HEURSTIC_NOT_SATISFIED = 2; private static final int RESULTS_NOT_SEEN_SUPPRESSION_HEURSTIC_NOT_SATISFIED = 3; private static final int RESULTS_SEEN_SUPPRESSION_BOUNDARY = 4; // Constants used to log UMA "enum" histograms with details about the Peek Promo Outcome. private static final int PEEK_PROMO_OUTCOME_SEEN_OPENED = 0; private static final int PEEK_PROMO_OUTCOME_SEEN_NOT_OPENED = 1; private static final int PEEK_PROMO_OUTCOME_NOT_SEEN_OPENED = 2; private static final int PEEK_PROMO_OUTCOME_NOT_SEEN_NOT_OPENED = 3; private static final int PEEK_PROMO_OUTCOME_BOUNDARY = 4; // Constants used to log UMA "enum" histograms with details about whether search results // were seen, and what the original triggering gesture was. private static final int PROMO_ENABLED_FROM_TAP = 0; private static final int PROMO_DISABLED_FROM_TAP = 1; private static final int PROMO_UNDECIDED_FROM_TAP = 2; private static final int PROMO_ENABLED_FROM_LONG_PRESS = 3; private static final int PROMO_DISABLED_FROM_LONG_PRESS = 4; private static final int PROMO_UNDECIDED_FROM_LONG_PRESS = 5; private static final int PROMO_BY_GESTURE_BOUNDARY = 6; // Constants used to log UMA "enum" histograms with summary counts for SERP loading times. private static final int PREFETCHED_PARIALLY_LOADED = 0; private static final int PREFETCHED_FULLY_LOADED = 1; private static final int NOT_PREFETCHED = 2; private static final int PREFETCH_BOUNDARY = 3; // Constants used to log UMA "enum" histograms for HTTP / HTTPS. private static final int PROTOCOL_IS_HTTP = 0; private static final int PROTOCOL_NOT_HTTP = 1; private static final int PROTOCOL_BOUNDARY = 2; // Constants used to log UMA "enum" histograms for single / multi-word. private static final int RESOLVED_SINGLE_WORD = 0; private static final int RESOLVED_MULTI_WORD = 1; private static final int RESOLVED_BOUNDARY = 2; // Constants used to log UMA "enum" histograms for partially / fully loaded. private static final int PARTIALLY_LOADED = 0; private static final int FULLY_LOADED = 1; private static final int LOADED_BOUNDARY = 2; // Constants used to log UMA "enum" histograms for triggering the Translate Onebox. private static final int DID_FORCE_TRANSLATE = 0; private static final int WOULD_FORCE_TRANSLATE = 1; private static final int FORCE_TRANSLATE_BOUNDARY = 2; // Constants used to log UMA "enum" histograms with details about whether the search // provider sprite icon was animated, whether search results were seen and the triggering // gesture. All new values should be inserted right before ICON_SPRITE_BOUNDARY. private static final int ICON_SPRITE_ANIMATED_RESULTS_SEEN_FROM_TAP = 0; private static final int ICON_SPRITE_ANIMATED_RESULTS_NOT_SEEN_FROM_TAP = 1; private static final int ICON_SPRITE_NOT_ANIMATED_RESULTS_SEEN_FROM_TAP = 2; private static final int ICON_SPRITE_NOT_ANIMATED_RESULTS_NOT_SEEN_FROM_TAP = 3; private static final int ICON_SPRITE_ANIMATED_RESULTS_SEEN_FROM_LONG_PRESS = 4; private static final int ICON_SPRITE_ANIMATED_RESULTS_NOT_SEEN_FROM_LONG_PRESS = 5; private static final int ICON_SPRITE_NOT_ANIMATED_RESULTS_SEEN_FROM_LONG_PRESS = 6; private static final int ICON_SPRITE_NOT_ANIMATED_RESULTS_NOT_SEEN_FROM_LONG_PRESS = 7; private static final int ICON_SPRITE_BOUNDARY = 8; // Constants used to log UMA "enum" histograms for any kind of Tap suppression. private static final int TAP_SUPPRESSED = 0; private static final int NOT_TAP_SUPPRESSED = 1; private static final int TAP_SUPPRESSED_BOUNDARY = 2; // Constants used to log UMA "enum" histograms for Quick Answers. private static final int QUICK_ANSWER_ACTIVATED_WAS_AN_ANSWER_SEEN = 0; private static final int QUICK_ANSWER_ACTIVATED_WAS_AN_ANSWER_NOT_SEEN = 1; private static final int QUICK_ANSWER_ACTIVATED_NOT_AN_ANSWER_SEEN = 2; private static final int QUICK_ANSWER_ACTIVATED_NOT_AN_ANSWER_NOT_SEEN = 3; private static final int QUICK_ANSWER_NOT_ACTIVATED_SEEN = 4; private static final int QUICK_ANSWER_NOT_ACTIVATED_NOT_SEEN = 5; private static final int QUICK_ANSWER_SEEN_BOUNDARY = 6; // Constants for "Bar Overlap" with triggering gesture, and whether the results were seen. private static final int BAR_OVERLAP_RESULTS_SEEN_FROM_TAP = 0; private static final int BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_TAP = 1; private static final int NO_BAR_OVERLAP_RESULTS_SEEN_FROM_TAP = 2; private static final int NO_BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_TAP = 3; private static final int BAR_OVERLAP_RESULTS_SEEN_FROM_LONG_PRESS = 4; private static final int BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_LONG_PRESS = 5; private static final int NO_BAR_OVERLAP_RESULTS_SEEN_FROM_LONG_PRESS = 6; private static final int NO_BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_LONG_PRESS = 7; private static final int BAR_OVERLAP_RESULTS_BOUNDARY = 8; /** * Key used in maps from {state, reason} to state entry (exit) logging code. */ static class StateChangeKey { final PanelState mState; final StateChangeReason mReason; final int mHashCode; StateChangeKey(PanelState state, StateChangeReason reason) { mState = state; mReason = reason; mHashCode = 31 * state.hashCode() + reason.hashCode(); } @Override public boolean equals(Object obj) { if (!(obj instanceof StateChangeKey)) { return false; } if (obj == this) { return true; } StateChangeKey other = (StateChangeKey) obj; return mState.equals(other.mState) && mReason.equals(other.mReason); } @Override public int hashCode() { return mHashCode; } } static class IconSpriteAnimationKey { final boolean mWasIconSpriteAnimated; final boolean mWasPanelSeen; final boolean mWasTap; final int mHashCode; IconSpriteAnimationKey(boolean wasIconSpriteAnimated, boolean wasPanelSeen, boolean wasTap) { mWasIconSpriteAnimated = wasIconSpriteAnimated; mWasPanelSeen = wasPanelSeen; mWasTap = wasTap; // HashCode logic generated by Eclipse. final int prime = 31; int result = 1; result = prime * result + (mWasIconSpriteAnimated ? 1231 : 1237); result = prime * result + (mWasPanelSeen ? 1231 : 1237); result = prime * result + (mWasTap ? 1231 : 1237); mHashCode = result; } @Override public boolean equals(Object obj) { if (!(obj instanceof IconSpriteAnimationKey)) { return false; } if (obj == this) { return true; } IconSpriteAnimationKey other = (IconSpriteAnimationKey) obj; return other.mWasIconSpriteAnimated == mWasIconSpriteAnimated && other.mWasPanelSeen == mWasPanelSeen && other.mWasTap == mWasTap; } @Override public int hashCode() { return mHashCode; } } // TODO(donnd): switch from using Maps to some method that does not require creation of a key. // Entry code map: first entry into CLOSED. private static final Map<StateChangeKey, Integer> ENTER_CLOSED_STATE_CHANGE_CODES; static { Map<StateChangeKey, Integer> codes = new HashMap<StateChangeKey, Integer>(); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.BACK_PRESS), ENTER_CLOSED_FROM_PEEKED_BACK_PRESS); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.BASE_PAGE_SCROLL), ENTER_CLOSED_FROM_PEEKED_BASE_PAGE_SCROLL); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_TAP), ENTER_CLOSED_FROM_PEEKED_TEXT_SELECT_TAP); codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.BACK_PRESS), ENTER_CLOSED_FROM_EXPANDED_BACK_PRESS); codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.BASE_PAGE_TAP), ENTER_CLOSED_FROM_EXPANDED_BASE_PAGE_TAP); codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.FLING), ENTER_CLOSED_FROM_EXPANDED_FLING); codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.BACK_PRESS), ENTER_CLOSED_FROM_MAXIMIZED_BACK_PRESS); codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.FLING), ENTER_CLOSED_FROM_MAXIMIZED_FLING); codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.TAB_PROMOTION), ENTER_CLOSED_FROM_MAXIMIZED_TAB_PROMOTION); codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SERP_NAVIGATION), ENTER_CLOSED_FROM_MAXIMIZED_SERP_NAVIGATION); ENTER_CLOSED_STATE_CHANGE_CODES = Collections.unmodifiableMap(codes); } // Entry code map: first entry into PEEKED. private static final Map<StateChangeKey, Integer> ENTER_PEEKED_STATE_CHANGE_CODES; static { Map<StateChangeKey, Integer> codes = new HashMap<StateChangeKey, Integer>(); // Note: we don't distinguish entering PEEKED from UNDEFINED / CLOSED. codes.put(new StateChangeKey(PanelState.UNDEFINED, StateChangeReason.TEXT_SELECT_TAP), ENTER_PEEKED_FROM_CLOSED_TEXT_SELECT_TAP); codes.put(new StateChangeKey(PanelState.UNDEFINED, StateChangeReason.TEXT_SELECT_LONG_PRESS), ENTER_PEEKED_FROM_CLOSED_EXT_SELECT_LONG_PRESS); codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.TEXT_SELECT_TAP), ENTER_PEEKED_FROM_CLOSED_TEXT_SELECT_TAP); codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.TEXT_SELECT_LONG_PRESS), ENTER_PEEKED_FROM_CLOSED_EXT_SELECT_LONG_PRESS); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_TAP), ENTER_PEEKED_FROM_PEEKED_TEXT_SELECT_TAP); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_LONG_PRESS), ENTER_PEEKED_FROM_PEEKED_TEXT_SELECT_LONG_PRESS); codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SEARCH_BAR_TAP), ENTER_PEEKED_FROM_EXPANDED_SEARCH_BAR_TAP); codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SWIPE), ENTER_PEEKED_FROM_EXPANDED_SWIPE); codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.FLING), ENTER_PEEKED_FROM_EXPANDED_FLING); codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SWIPE), ENTER_PEEKED_FROM_MAXIMIZED_SWIPE); codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.FLING), ENTER_PEEKED_FROM_MAXIMIZED_FLING); ENTER_PEEKED_STATE_CHANGE_CODES = Collections.unmodifiableMap(codes); } // Entry code map: first entry into EXPANDED. private static final Map<StateChangeKey, Integer> ENTER_EXPANDED_STATE_CHANGE_CODES; static { Map<StateChangeKey, Integer> codes = new HashMap<StateChangeKey, Integer>(); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SEARCH_BAR_TAP), ENTER_EXPANDED_FROM_PEEKED_SEARCH_BAR_TAP); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SWIPE), ENTER_EXPANDED_FROM_PEEKED_SWIPE); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.FLING), ENTER_EXPANDED_FROM_PEEKED_FLING); codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SWIPE), ENTER_EXPANDED_FROM_MAXIMIZED_SWIPE); codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.FLING), ENTER_EXPANDED_FROM_MAXIMIZED_FLING); ENTER_EXPANDED_STATE_CHANGE_CODES = Collections.unmodifiableMap(codes); } // Entry code map: first entry into MAXIMIZED. private static final Map<StateChangeKey, Integer> ENTER_MAXIMIZED_STATE_CHANGE_CODES; static { Map<StateChangeKey, Integer> codes = new HashMap<StateChangeKey, Integer>(); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SWIPE), ENTER_MAXIMIZED_FROM_PEEKED_SWIPE); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.FLING), ENTER_MAXIMIZED_FROM_PEEKED_FLING); codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SWIPE), ENTER_MAXIMIZED_FROM_EXPANDED_SWIPE); codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.FLING), ENTER_MAXIMIZED_FROM_EXPANDED_FLING); codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SERP_NAVIGATION), ENTER_MAXIMIZED_FROM_EXPANDED_SERP_NAVIGATION); ENTER_MAXIMIZED_STATE_CHANGE_CODES = Collections.unmodifiableMap(codes); } // Exit code map: first exit from CLOSED. private static final Map<StateChangeKey, Integer> EXIT_CLOSED_TO_STATE_CHANGE_CODES; static { Map<StateChangeKey, Integer> codes = new HashMap<StateChangeKey, Integer>(); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_TAP), EXIT_CLOSED_TO_PEEKED_TEXT_SELECT_TAP); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_LONG_PRESS), EXIT_CLOSED_TO_PEEKED_TEXT_SELECT_LONG_PRESS); EXIT_CLOSED_TO_STATE_CHANGE_CODES = Collections.unmodifiableMap(codes); } // Exit code map: first exit from PEEKED. private static final Map<StateChangeKey, Integer> EXIT_PEEKED_TO_STATE_CHANGE_CODES; static { Map<StateChangeKey, Integer> codes = new HashMap<StateChangeKey, Integer>(); codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BACK_PRESS), EXIT_PEEKED_TO_CLOSED_BACK_PRESS); codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BASE_PAGE_SCROLL), EXIT_PEEKED_TO_CLOSED_BASE_PAGE_SCROLL); codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BASE_PAGE_TAP), EXIT_PEEKED_TO_CLOSED_TEXT_SELECT_TAP); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_TAP), EXIT_PEEKED_TO_PEEKED_TEXT_SELECT_TAP); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_LONG_PRESS), EXIT_PEEKED_TO_PEEKED_TEXT_SELECT_LONG_PRESS); codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SEARCH_BAR_TAP), EXIT_PEEKED_TO_EXPANDED_SEARCH_BAR_TAP); codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SWIPE), EXIT_PEEKED_TO_EXPANDED_SWIPE); codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.FLING), EXIT_PEEKED_TO_EXPANDED_FLING); codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SWIPE), EXIT_PEEKED_TO_MAXIMIZED_SWIPE); codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.FLING), EXIT_PEEKED_TO_MAXIMIZED_FLING); EXIT_PEEKED_TO_STATE_CHANGE_CODES = Collections.unmodifiableMap(codes); } // Exit code map: first exit from EXPANDED. private static final Map<StateChangeKey, Integer> EXIT_EXPANDED_TO_STATE_CHANGE_CODES; static { Map<StateChangeKey, Integer> codes = new HashMap<StateChangeKey, Integer>(); codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BACK_PRESS), EXIT_EXPANDED_TO_CLOSED_BACK_PRESS); codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BASE_PAGE_TAP), EXIT_EXPANDED_TO_CLOSED_BASE_PAGE_TAP); codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.FLING), EXIT_EXPANDED_TO_CLOSED_FLING); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SEARCH_BAR_TAP), EXIT_EXPANDED_TO_PEEKED_SEARCH_BAR_TAP); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SWIPE), EXIT_EXPANDED_TO_PEEKED_SWIPE); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.FLING), EXIT_EXPANDED_TO_PEEKED_FLING); codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SWIPE), EXIT_EXPANDED_TO_MAXIMIZED_SWIPE); codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.FLING), EXIT_EXPANDED_TO_MAXIMIZED_FLING); codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SERP_NAVIGATION), EXIT_EXPANDED_TO_MAXIMIZED_SERP_NAVIGATION); EXIT_EXPANDED_TO_STATE_CHANGE_CODES = Collections.unmodifiableMap(codes); } // Exit code map: first exit from MAXIMIZED. private static final Map<StateChangeKey, Integer> EXIT_MAXIMIZED_TO_STATE_CHANGE_CODES; static { Map<StateChangeKey, Integer> codes = new HashMap<StateChangeKey, Integer>(); codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BACK_PRESS), EXIT_MAXIMIZED_TO_CLOSED_BACK_PRESS); codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.FLING), EXIT_MAXIMIZED_TO_CLOSED_FLING); codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.TAB_PROMOTION), EXIT_MAXIMIZED_TO_CLOSED_TAB_PROMOTION); codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.SERP_NAVIGATION), EXIT_MAXIMIZED_TO_CLOSED_SERP_NAVIGATION); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SWIPE), EXIT_MAXIMIZED_TO_PEEKED_SWIPE); codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.FLING), EXIT_MAXIMIZED_TO_PEEKED_FLING); codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SWIPE), EXIT_MAXIMIZED_TO_EXPANDED_SWIPE); codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.FLING), EXIT_MAXIMIZED_TO_EXPANDED_FLING); EXIT_MAXIMIZED_TO_STATE_CHANGE_CODES = Collections.unmodifiableMap(codes); } // "Seen by gesture" code map: logged on first exit from expanded panel, or promo, // broken down by gesture. private static final Map<Pair<Boolean, Boolean>, Integer> SEEN_BY_GESTURE_CODES; static { final boolean unseen = false; final boolean seen = true; Map<Pair<Boolean, Boolean>, Integer> codes = new HashMap<Pair<Boolean, Boolean>, Integer>(); codes.put(new Pair<Boolean, Boolean>(seen, TAP), RESULTS_SEEN_FROM_TAP); codes.put(new Pair<Boolean, Boolean>(unseen, TAP), RESULTS_NOT_SEEN_FROM_TAP); codes.put(new Pair<Boolean, Boolean>(seen, LONG_PRESS), RESULTS_SEEN_FROM_LONG_PRESS); codes.put(new Pair<Boolean, Boolean>(unseen, LONG_PRESS), RESULTS_NOT_SEEN_FROM_LONG_PRESS); SEEN_BY_GESTURE_CODES = Collections.unmodifiableMap(codes); } // "Promo outcome by gesture" code map: logged on exit from promo, broken down by gesture. private static final Map<Pair<Integer, Boolean>, Integer> PROMO_BY_GESTURE_CODES; static { Map<Pair<Integer, Boolean>, Integer> codes = new HashMap<Pair<Integer, Boolean>, Integer>(); codes.put(new Pair<Integer, Boolean>(PREFERENCE_ENABLED, TAP), PROMO_ENABLED_FROM_TAP); codes.put(new Pair<Integer, Boolean>(PREFERENCE_DISABLED, TAP), PROMO_DISABLED_FROM_TAP); codes.put(new Pair<Integer, Boolean>(PREFERENCE_UNINITIALIZED, TAP), PROMO_UNDECIDED_FROM_TAP); codes.put(new Pair<Integer, Boolean>(PREFERENCE_ENABLED, LONG_PRESS), PROMO_ENABLED_FROM_LONG_PRESS); codes.put(new Pair<Integer, Boolean>(PREFERENCE_DISABLED, LONG_PRESS), PROMO_DISABLED_FROM_LONG_PRESS); codes.put(new Pair<Integer, Boolean>(PREFERENCE_UNINITIALIZED, LONG_PRESS), PROMO_UNDECIDED_FROM_LONG_PRESS); PROMO_BY_GESTURE_CODES = Collections.unmodifiableMap(codes); } // Icon sprite animation code mapped: logged when ending a contextual search. private static final Map<IconSpriteAnimationKey, Integer> ICON_SPRITE_ANIMATION_CODES; static { Map<IconSpriteAnimationKey, Integer> codes = new HashMap<IconSpriteAnimationKey, Integer>(); codes.put(new IconSpriteAnimationKey(true, true, true), ICON_SPRITE_ANIMATED_RESULTS_SEEN_FROM_TAP); codes.put(new IconSpriteAnimationKey(true, false, true), ICON_SPRITE_ANIMATED_RESULTS_NOT_SEEN_FROM_TAP); codes.put(new IconSpriteAnimationKey(false, true, true), ICON_SPRITE_NOT_ANIMATED_RESULTS_SEEN_FROM_TAP); codes.put(new IconSpriteAnimationKey(false, false, true), ICON_SPRITE_NOT_ANIMATED_RESULTS_NOT_SEEN_FROM_TAP); codes.put(new IconSpriteAnimationKey(true, true, false), ICON_SPRITE_ANIMATED_RESULTS_SEEN_FROM_LONG_PRESS); codes.put(new IconSpriteAnimationKey(true, false, false), ICON_SPRITE_ANIMATED_RESULTS_NOT_SEEN_FROM_LONG_PRESS); codes.put(new IconSpriteAnimationKey(false, true, false), ICON_SPRITE_NOT_ANIMATED_RESULTS_SEEN_FROM_LONG_PRESS); codes.put(new IconSpriteAnimationKey(false, false, false), ICON_SPRITE_NOT_ANIMATED_RESULTS_NOT_SEEN_FROM_LONG_PRESS); ICON_SPRITE_ANIMATION_CODES = Collections.unmodifiableMap(codes); } /** * Logs the state of the Contextual Search preference. This function should be called if the * Contextual Search feature is active, and will track the different preference settings * (disabled, enabled or uninitialized). Calling more than once is fine. */ public static void logPreferenceState() { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchPreferenceState", getPreferenceValue(), PREFERENCE_HISTOGRAM_BOUNDARY); } /** * Logs the given number of promo taps remaining. Should be called only for users that * are still undecided. * @param promoTapsRemaining The number of taps remaining (should not be negative). */ public static void logPromoTapsRemaining(int promoTapsRemaining) { if (promoTapsRemaining >= 0) { RecordHistogram.recordCountHistogram("Search.ContextualSearchPromoTapsRemaining", promoTapsRemaining); } } /** * Logs the historic number of times that a Tap gesture triggered the peeking promo * for users that have never opened the panel. This should be called periodically for * undecided users only. * @param promoTaps The historic number of taps that have caused the peeking bar for the promo, * for users that have never opened the panel. */ public static void logPromoTapsForNeverOpened(int promoTaps) { RecordHistogram.recordCountHistogram("Search.ContextualSearchPromoTapsForNeverOpened", promoTaps); } /** * Logs the historic number of times that a Tap gesture triggered the peeking promo before * the user ever opened the panel. This should be called periodically for all users. * @param promoTaps The historic number of taps that have caused the peeking bar for the promo * before the first open of the panel, for all users that have ever opened the panel. */ public static void logPromoTapsBeforeFirstOpen(int promoTaps) { RecordHistogram.recordCountHistogram("Search.ContextualSearchPromoTapsBeforeFirstOpen", promoTaps); } /** * Records the total count of times the promo panel has *ever* been opened. This should only * be called when the user is still undecided. * @param count The total historic count of times the panel has ever been opened for the * current user. */ public static void logPromoOpenCount(int count) { RecordHistogram.recordCountHistogram("Search.ContextualSearchPromoOpenCount", count); } /** * Logs the number of taps that have been counted since the user last opened the panel, for * undecided users. * @param tapsSinceOpen The number of taps to log. */ public static void logTapsSinceOpenForUndecided(int tapsSinceOpen) { RecordHistogram.recordCountHistogram("Search.ContextualSearchTapsSinceOpenUndecided", tapsSinceOpen); } /** * Logs the number of taps that have been counted since the user last opened the panel, for * decided users. * @param tapsSinceOpen The number of taps to log. */ public static void logTapsSinceOpenForDecided(int tapsSinceOpen) { RecordHistogram.recordCountHistogram("Search.ContextualSearchTapsSinceOpenDecided", tapsSinceOpen); } /** * Logs whether the Search Term was single or multiword. * @param isSingleWord Whether the resolved search term is a single word or not. */ public static void logSearchTermResolvedWords(boolean isSingleWord) { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchResolvedTermWords", isSingleWord ? RESOLVED_SINGLE_WORD : RESOLVED_MULTI_WORD, RESOLVED_BOUNDARY); } /** * Logs whether the base page was using the HTTP protocol or not. * @param isHttpBasePage Whether the base page was using the HTTP protocol or not (should * be false for HTTPS or other URIs). */ public static void logBasePageProtocol(boolean isHttpBasePage) { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchBasePageProtocol", isHttpBasePage ? PROTOCOL_IS_HTTP : PROTOCOL_NOT_HTTP, PROTOCOL_BOUNDARY); } /** * Logs changes to the Contextual Search preference, aside from those resulting from the first * run flow. * @param enabled Whether the preference is being enabled or disabled. */ public static void logPreferenceChange(boolean enabled) { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchPreferenceStateChange", enabled ? PREFERENCE_ENABLED : PREFERENCE_DISABLED, PREFERENCE_HISTOGRAM_BOUNDARY); } /** * Logs the number of times the Peek Promo was seen. * @param count Number of times the Peek Promo was seen. * @param hasOpenedPanel Whether the Panel was opened. */ public static void logPeekPromoShowCount(int count, boolean hasOpenedPanel) { RecordHistogram.recordCountHistogram("Search.ContextualSearchPeekPromoCount", count); if (hasOpenedPanel) { RecordHistogram.recordCountHistogram( "Search.ContextualSearchPeekPromoCountUntilOpened", count); } } /** * Logs the Peek Promo Outcome. * @param wasPromoSeen Whether the Peek Promo was seen. * @param wouldHaveShownPromo Whether the Promo would have shown. * @param hasOpenedPanel Whether the Panel was opened. */ public static void logPeekPromoOutcome(boolean wasPromoSeen, boolean wouldHaveShownPromo, boolean hasOpenedPanel) { int outcome = -1; if (wasPromoSeen) { outcome = hasOpenedPanel ? PEEK_PROMO_OUTCOME_SEEN_OPENED : PEEK_PROMO_OUTCOME_SEEN_NOT_OPENED; } else if (wouldHaveShownPromo) { outcome = hasOpenedPanel ? PEEK_PROMO_OUTCOME_NOT_SEEN_OPENED : PEEK_PROMO_OUTCOME_NOT_SEEN_NOT_OPENED; } if (outcome != -1) { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchPeekPromoOutcome", outcome, PEEK_PROMO_OUTCOME_BOUNDARY); } } /** * Logs the outcome of the Promo. * Logs multiple histograms; with and without the originating gesture. * @param wasTap Whether the gesture that originally caused the panel to show was a Tap. * @param wasMandatory Whether the Promo was mandatory. */ public static void logPromoOutcome(boolean wasTap, boolean wasMandatory) { int preferenceCode = getPreferenceValue(); RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchFirstRunFlowOutcome", preferenceCode, PREFERENCE_HISTOGRAM_BOUNDARY); int preferenceByGestureCode = getPromoByGestureStateCode(preferenceCode, wasTap); if (wasMandatory) { RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchMandatoryPromoOutcomeByGesture", preferenceByGestureCode, PROMO_BY_GESTURE_BOUNDARY); } else { RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchPromoOutcomeByGesture", preferenceByGestureCode, PROMO_BY_GESTURE_BOUNDARY); } } /** * Logs the duration of a Contextual Search panel being viewed by the user. * @param wereResultsSeen Whether search results were seen. * @param isChained Whether the Contextual Search ended with the start of another. * @param durationMs The duration of the contextual search in milliseconds. */ public static void logDuration(boolean wereResultsSeen, boolean isChained, long durationMs) { if (wereResultsSeen) { RecordHistogram.recordTimesHistogram("Search.ContextualSearchDurationSeen", durationMs, TimeUnit.MILLISECONDS); } else if (isChained) { RecordHistogram.recordTimesHistogram("Search.ContextualSearchDurationUnseenChained", durationMs, TimeUnit.MILLISECONDS); } else { RecordHistogram.recordTimesHistogram("Search.ContextualSearchDurationUnseen", durationMs, TimeUnit.MILLISECONDS); } } /** * Log the duration of finishing loading the SERP after the panel is opened. * @param wasPrefetch Whether the request was prefetch-enabled or not. * @param durationMs The duration of loading the SERP till completely loaded, in milliseconds. * Note that this value will be 0 when the SERP is prefetched and the user waits a * while before opening the panel. */ public static void logSearchPanelLoadDuration(boolean wasPrefetch, long durationMs) { if (wasPrefetch) { RecordHistogram.recordMediumTimesHistogram("Search.ContextualSearchDurationPrefetched", durationMs, TimeUnit.MILLISECONDS); } else { RecordHistogram.recordMediumTimesHistogram( "Search.ContextualSearchDurationNonPrefetched", durationMs, TimeUnit.MILLISECONDS); } // Also record a summary histogram with counts for each possibility. int code = !wasPrefetch ? NOT_PREFETCHED : (durationMs == 0 ? PREFETCHED_FULLY_LOADED : PREFETCHED_PARIALLY_LOADED); RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchPrefetchSummary", code, PREFETCH_BOUNDARY); } /** * Logs the duration from starting a search until the Search Term is resolved. * @param durationMs The duration to record. */ public static void logSearchTermResolutionDuration(long durationMs) { RecordHistogram.recordMediumTimesHistogram( "Search.ContextualSearchResolutionDuration", durationMs, TimeUnit.MILLISECONDS); } /** * Logs the duration from starting a prefetched search until the panel navigates to the results * and they start becoming viewable. Should be called only for searches that are prefetched. * @param durationMs The duration to record. * @param didResolve Whether a Search Term resolution was required as part of the loading. */ public static void logPrefetchedSearchNavigatedDuration(long durationMs, boolean didResolve) { String histogramName = didResolve ? "Search.ContextualSearchResolvedSearchDuration" : "Search.ContextualSearchLiteralSearchDuration"; RecordHistogram.recordMediumTimesHistogram( histogramName, durationMs, TimeUnit.MILLISECONDS); } /** * Logs the duration from opening the panel beyond peek until the panel is closed. * @param durationMs The duration to record. */ public static void logPanelOpenDuration(long durationMs) { RecordHistogram.recordMediumTimesHistogram( "Search.ContextualSearchPanelOpenDuration", durationMs, TimeUnit.MILLISECONDS); } /** * Logs a user action for the duration of viewing the panel that describes the amount of time * the user viewed the bar and panel overall. * @param durationMs The duration to record. */ public static void logPanelViewDurationAction(long durationMs) { if (durationMs < 1000) { RecordUserAction.record("ContextualSearch.ViewLessThanOneSecond"); } else if (durationMs < 3000) { RecordUserAction.record("ContextualSearch.ViewOneToThreeSeconds"); } else if (durationMs < 10000) { RecordUserAction.record("ContextualSearch.ViewThreeToTenSeconds"); } else { RecordUserAction.record("ContextualSearch.ViewMoreThanTenSeconds"); } } /** * Logs whether the promo was seen. * Logs multiple histograms, with and without the original triggering gesture. * @param wasPanelSeen Whether the panel was seen. * @param wasTap Whether the gesture that originally caused the panel to show was a Tap. */ public static void logPromoSeen(boolean wasPanelSeen, boolean wasTap) { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchFirstRunPanelSeen", wasPanelSeen ? RESULTS_SEEN : RESULTS_NOT_SEEN, RESULTS_SEEN_BOUNDARY); logHistogramByGesture(wasPanelSeen, wasTap, "Search.ContextualSearchPromoSeenByGesture"); } /** * Logs whether search results were seen. * Logs multiple histograms; with and without the original triggering gesture. * @param wasPanelSeen Whether the panel was seen. * @param wasTap Whether the gesture that originally caused the panel to show was a Tap. */ public static void logResultsSeen(boolean wasPanelSeen, boolean wasTap) { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchResultsSeen", wasPanelSeen ? RESULTS_SEEN : RESULTS_NOT_SEEN, RESULTS_SEEN_BOUNDARY); logHistogramByGesture(wasPanelSeen, wasTap, "Search.ContextualSearchResultsSeenByGesture"); } /** * Logs whether search results were seen when the selection was part of a URL. * Unlike ContextualSearchResultsSeen, this histogram is logged for both decided and undecided * users. * @param wasPanelSeen Whether the panel was seen. * @param wasTap Whether the gesture that originally caused the panel to show was a Tap. */ public static void logResultsSeenSelectionIsUrl(boolean wasPanelSeen, boolean wasTap) { int result = wasPanelSeen ? (wasTap ? RESULTS_SEEN_FROM_TAP : RESULTS_SEEN_FROM_LONG_PRESS) : (wasTap ? RESULTS_NOT_SEEN_FROM_TAP : RESULTS_NOT_SEEN_FROM_LONG_PRESS); RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchResultsSeenSelectionWasUrl", result, RESULTS_BY_GESTURE_BOUNDARY); } /** * Logs the whether the panel was seen and the type of the trigger and if Bar nearly overlapped. * @param wasPanelSeen Whether the panel was seen. * @param wasTap Whether the gesture was a Tap or not. * @param wasBarOverlap Whether the trigger location overlapped the Bar area. */ public static void logBarOverlapResultsSeen( boolean wasPanelSeen, boolean wasTap, boolean wasBarOverlap) { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchBarOverlapSeen", getBarOverlapEnum(wasBarOverlap, wasPanelSeen, wasTap), BAR_OVERLAP_RESULTS_BOUNDARY); } /** * Log whether the UX was suppressed due to Bar overlap. * @param wasSuppressed Whether showing the UX was suppressed. */ public static void logBarOverlapSuppression(boolean wasSuppressed) { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchBarOverlap", wasSuppressed ? TAP_SUPPRESSED : NOT_TAP_SUPPRESSED, TAP_SUPPRESSED_BOUNDARY); } /** * Logs the location of a Tap and whether the panel was seen and the type of the * trigger. * @param wasPanelSeen Whether the panel was seen. * @param wasTap Whether the gesture was a Tap or not. * @param triggerLocationDps The trigger location from the top of the screen. */ public static void logScreenTopTapLocation( boolean wasPanelSeen, boolean wasTap, int triggerLocationDps) { // We only log Tap locations for the screen top. if (!wasTap) return; String histogram = wasPanelSeen ? "Search.ContextualSearchTopLocationSeen" : "Search.ContextualSearchTopLocationNotSeen"; int min = 1; int max = 250; int numBuckets = 50; RecordHistogram.recordCustomCountHistogram( histogram, triggerLocationDps, min, max, numBuckets); } /** * Log whether the UX was suppressed due to a Tap too close to the screen top. * @param wasSuppressed Whether showing the UX was suppressed. */ public static void logScreenTopTapSuppression(boolean wasSuppressed) { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchScreenTopSuppressed", wasSuppressed ? TAP_SUPPRESSED : NOT_TAP_SUPPRESSED, TAP_SUPPRESSED_BOUNDARY); } /** * Log whether results were seen due to a Tap with broad signals. * @param wasSearchContentViewSeen If the panel was opened. * @param isSecondTap Whether this was the second tap after an initial suppressed tap. */ public static void logTapSuppressionResultsSeen( boolean wasSearchContentViewSeen, boolean isSecondTap) { if (isSecondTap) { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchSecondTapSeen", wasSearchContentViewSeen ? RESULTS_SEEN : RESULTS_NOT_SEEN, RESULTS_SEEN_BOUNDARY); } else { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchTapSuppressionSeen", wasSearchContentViewSeen ? RESULTS_SEEN : RESULTS_NOT_SEEN, RESULTS_SEEN_BOUNDARY); } } /** * Logs whether results were seen when the selected text consisted of all capital letters. * @param wasSearchContentViewSeen If the panel was opened. */ public static void logAllCapsResultsSeen(boolean wasSearchContentViewSeen) { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchAllCapsResultsSeen", wasSearchContentViewSeen ? RESULTS_SEEN : RESULTS_NOT_SEEN, RESULTS_SEEN_BOUNDARY); } /** * Logs whether results were seen when the selected text started with a capital letter but was * not all capital letters. * @param wasSearchContentViewSeen If the panel was opened. */ public static void logStartedWithCapitalResultsSeen(boolean wasSearchContentViewSeen) { RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchStartedWithCapitalResultsSeen", wasSearchContentViewSeen ? RESULTS_SEEN : RESULTS_NOT_SEEN, RESULTS_SEEN_BOUNDARY); } /** * Logs whether results were seen and whether any tap suppression heuristics were satisfied. * @param wasSearchContentViewSeen If the panel was opened. * @param wasAnySuppressionHeuristicSatisfied Whether any of the implemented suppression * heuristics were satisfied. */ public static void logAnyTapSuppressionHeuristicSatisfied(boolean wasSearchContentViewSeen, boolean wasAnySuppressionHeuristicSatisfied) { int code; if (wasAnySuppressionHeuristicSatisfied) { code = wasSearchContentViewSeen ? RESULTS_SEEN_SUPPRESSION_HEURSTIC_SATISFIED : RESULTS_NOT_SEEN_SUPPRESSION_HEURSTIC_SATISFIED; } else { code = wasSearchContentViewSeen ? RESULTS_SEEN_SUPPRESSION_HEURSTIC_NOT_SATISFIED : RESULTS_NOT_SEEN_SUPPRESSION_HEURSTIC_NOT_SATISFIED; } RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchTapSuppressionSeen.AnyHeuristicSatisfied", code, RESULTS_SEEN_SUPPRESSION_BOUNDARY); } /** * Logs whether search results were seen, whether the search provider icon sprite was animated * when the panel first appeared, and the triggering gesture. * @param wasIconSpriteAnimated Whether the search provider icon sprite was animated when the * the panel first appeared. * @param wasPanelSeen Whether the panel was seen. * @param wasTap Whether the gesture that originally caused the panel to show was a Tap. */ public static void logIconSpriteAnimated(boolean wasIconSpriteAnimated, boolean wasPanelSeen, boolean wasTap) { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchIconSpriteAnimated", ICON_SPRITE_ANIMATION_CODES.get(new IconSpriteAnimationKey(wasIconSpriteAnimated, wasPanelSeen, wasTap)), ICON_SPRITE_BOUNDARY); } /** * Logs whether a selection is valid. * @param isSelectionValid Whether the selection is valid. */ public static void logSelectionIsValid(boolean isSelectionValid) { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchSelectionValid", isSelectionValid ? SELECTION_VALID : SELECTION_INVALID, SELECTION_BOUNDARY); } /** * Logs whether a normal priority search request failed. * @param isFailure Whether the request failed. */ public static void logNormalPrioritySearchRequestOutcome(boolean isFailure) { RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchNormalPrioritySearchRequestStatus", isFailure ? REQUEST_FAILED : REQUEST_NOT_FAILED, REQUEST_BOUNDARY); } /** * Logs whether a low priority search request failed. * @param isFailure Whether the request failed. */ public static void logLowPrioritySearchRequestOutcome(boolean isFailure) { RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchLowPrioritySearchRequestStatus", isFailure ? REQUEST_FAILED : REQUEST_NOT_FAILED, REQUEST_BOUNDARY); } /** * Logs whether a fallback search request failed. * @param isFailure Whether the request failed. */ public static void logFallbackSearchRequestOutcome(boolean isFailure) { RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchFallbackSearchRequestStatus", isFailure ? REQUEST_FAILED : REQUEST_NOT_FAILED, REQUEST_BOUNDARY); } /** * Logs whether the SERP was fully loaded when an opened panel was closed. * @param fullyLoaded Whether the SERP had finished loading before the panel was closed. */ public static void logSerpLoadedOnClose(boolean fullyLoaded) { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchSerpLoadedOnClose", fullyLoaded ? FULLY_LOADED : PARTIALLY_LOADED, LOADED_BOUNDARY); } /** * Logs the duration since a recent scroll. * @param durationSinceRecentScrollMs The amount of time since the most recent scroll. * @param wasSearchContentViewSeen If the panel was opened. */ public static void logRecentScrollDuration( int durationSinceRecentScrollMs, boolean wasSearchContentViewSeen) { String histogram = wasSearchContentViewSeen ? "Search.ContextualSearchRecentScrollSeen" : "Search.ContextualSearchRecentScrollNotSeen"; if (durationSinceRecentScrollMs < 1000) { RecordHistogram.recordCount1000Histogram(histogram, durationSinceRecentScrollMs); } } /** * Log whether the UX was suppressed by a recent scroll. * @param wasSuppressed Whether showing the UX was suppressed by a recent scroll. */ public static void logRecentScrollSuppression(boolean wasSuppressed) { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchRecentScrollSuppression", wasSuppressed ? TAP_SUPPRESSED : NOT_TAP_SUPPRESSED, TAP_SUPPRESSED_BOUNDARY); } /** * Logs the duration between the panel being triggered due to a tap or long-press and the * panel being dismissed due to a scroll. * @param durationSincePanelTriggerMs The amount of time between the panel getting triggered and * the panel being dismissed due to a scroll. * @param wasSearchContentViewSeen If the panel was opened. */ public static void logDurationBetweenTriggerAndScroll( long durationSincePanelTriggerMs, boolean wasSearchContentViewSeen) { String histogram = wasSearchContentViewSeen ? "Search.ContextualSearchDurationBetweenTriggerAndScrollSeen" : "Search.ContextualSearchDurationBetweenTriggerAndScrollNotSeen"; if (durationSincePanelTriggerMs < 2000) { RecordHistogram.recordCustomCountHistogram( histogram, (int) durationSincePanelTriggerMs, 1, 2000, 200); } } /** * Logs whether a Quick Answer caption was activated, and whether it was an answer (as opposed * to just being informative), and whether the panel was opened anyway. * Logged only for Tap events. * @param didActivate If the Quick Answer caption was shown. * @param didAnswer If the caption was considered an answer (reducing the need to open the * panel). * @param wasSearchContentViewSeen If the panel was opened. */ static void logQuickAnswerSeen( boolean wasSearchContentViewSeen, boolean didActivate, boolean didAnswer) { RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchQuickAnswerSeen", getQuickAnswerSeenValue(didActivate, didAnswer, wasSearchContentViewSeen), QUICK_ANSWER_SEEN_BOUNDARY); } /** * Logs how a state was entered for the first time within a Contextual Search. * @param fromState The state to transition from. * @param toState The state to transition to. * @param reason The reason for the state transition. */ public static void logFirstStateEntry(PanelState fromState, PanelState toState, StateChangeReason reason) { int code; switch (toState) { case CLOSED: code = getStateChangeCode(fromState, reason, ENTER_CLOSED_STATE_CHANGE_CODES, ENTER_CLOSED_FROM_OTHER); RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchEnterClosed", code, ENTER_CLOSED_FROM_BOUNDARY); break; case PEEKED: code = getStateChangeCode(fromState, reason, ENTER_PEEKED_STATE_CHANGE_CODES, ENTER_PEEKED_FROM_OTHER); RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchEnterPeeked", code, ENTER_PEEKED_FROM_BOUNDARY); break; case EXPANDED: code = getStateChangeCode(fromState, reason, ENTER_EXPANDED_STATE_CHANGE_CODES, ENTER_EXPANDED_FROM_OTHER); RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchEnterExpanded", code, ENTER_EXPANDED_FROM_BOUNDARY); break; case MAXIMIZED: code = getStateChangeCode(fromState, reason, ENTER_MAXIMIZED_STATE_CHANGE_CODES, ENTER_MAXIMIZED_FROM_OTHER); RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchEnterMaximized", code, ENTER_MAXIMIZED_FROM_BOUNDARY); break; default: break; } } /** * Logs a user action for a change to the Panel state, which allows sequencing of actions. * @param toState The state to transition to. * @param reason The reason for the state transition. */ public static void logPanelStateUserAction(PanelState toState, StateChangeReason reason) { switch (toState) { case CLOSED: if (reason == StateChangeReason.BACK_PRESS) { RecordUserAction.record("ContextualSearch.BackPressClose"); } else if (reason == StateChangeReason.CLOSE_BUTTON) { RecordUserAction.record("ContextualSearch.CloseButtonClose"); } else if (reason == StateChangeReason.SWIPE || reason == StateChangeReason.FLING) { RecordUserAction.record("ContextualSearch.SwipeOrFlingClose"); } else if (reason == StateChangeReason.TAB_PROMOTION) { RecordUserAction.record("ContextualSearch.TabPromotionClose"); } else if (reason == StateChangeReason.BASE_PAGE_TAP) { RecordUserAction.record("ContextualSearch.BasePageTapClose"); } else if (reason == StateChangeReason.BASE_PAGE_SCROLL) { RecordUserAction.record("ContextualSearch.BasePageScrollClose"); } else if (reason == StateChangeReason.SEARCH_BAR_TAP) { RecordUserAction.record("ContextualSearch.SearchBarTapClose"); } else if (reason == StateChangeReason.SERP_NAVIGATION) { RecordUserAction.record("ContextualSearch.NavigationClose"); } else { RecordUserAction.record("ContextualSearch.UncommonClose"); } break; case PEEKED: if (reason == StateChangeReason.TEXT_SELECT_TAP) { RecordUserAction.record("ContextualSearch.TapPeek"); } else if (reason == StateChangeReason.SWIPE || reason == StateChangeReason.FLING) { RecordUserAction.record("ContextualSearch.SwipeOrFlingPeek"); } else if (reason == StateChangeReason.TEXT_SELECT_LONG_PRESS) { RecordUserAction.record("ContextualSearch.LongpressPeek"); } break; case EXPANDED: if (reason == StateChangeReason.SWIPE || reason == StateChangeReason.FLING) { RecordUserAction.record("ContextualSearch.SwipeOrFlingExpand"); } else if (reason == StateChangeReason.SEARCH_BAR_TAP) { RecordUserAction.record("ContextualSearch.SearchBarTapExpand"); } break; case MAXIMIZED: if (reason == StateChangeReason.SWIPE || reason == StateChangeReason.FLING) { RecordUserAction.record("ContextualSearch.SwipeOrFlingMaximize"); } else if (reason == StateChangeReason.SERP_NAVIGATION) { RecordUserAction.record("ContextualSearch.NavigationMaximize"); } break; default: break; } } /** * Logs how a state was exited for the first time within a Contextual Search. * @param fromState The state to transition from. * @param toState The state to transition to. * @param reason The reason for the state transition. */ public static void logFirstStateExit(PanelState fromState, PanelState toState, StateChangeReason reason) { int code; switch (fromState) { case UNDEFINED: case CLOSED: code = getStateChangeCode(toState, reason, EXIT_CLOSED_TO_STATE_CHANGE_CODES, EXIT_CLOSED_TO_OTHER); RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchExitClosed", code, EXIT_CLOSED_TO_BOUNDARY); break; case PEEKED: code = getStateChangeCode(toState, reason, EXIT_PEEKED_TO_STATE_CHANGE_CODES, EXIT_PEEKED_TO_OTHER); RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchExitPeeked", code, EXIT_PEEKED_TO_BOUNDARY); break; case EXPANDED: code = getStateChangeCode(toState, reason, EXIT_EXPANDED_TO_STATE_CHANGE_CODES, EXIT_EXPANDED_TO_OTHER); RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchExitExpanded", code, EXIT_EXPANDED_TO_BOUNDARY); break; case MAXIMIZED: code = getStateChangeCode(toState, reason, EXIT_MAXIMIZED_TO_STATE_CHANGE_CODES, EXIT_MAXIMIZED_TO_OTHER); RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchExitMaximized", code, EXIT_MAXIMIZED_TO_BOUNDARY); break; default: break; } } /** * Logs the number of impressions and CTR for the previous week for the current user. * @param previousWeekImpressions The number of times the user saw the Contextual Search Bar. * @param previousWeekCtr The CTR expressed as a percentage. */ public static void logPreviousWeekCtr(int previousWeekImpressions, int previousWeekCtr) { RecordHistogram.recordCountHistogram( "Search.ContextualSearchPreviousWeekImpressions", previousWeekImpressions); RecordHistogram.recordPercentageHistogram( "Search.ContextualSearchPreviousWeekCtr", previousWeekCtr); } /** * Logs the number of impressions and CTR for previous 28-day period for the current user. * @param previous28DayImpressions The number of times the user saw the Contextual Search Bar. * @param previous28DayCtr The CTR expressed as a percentage. */ public static void logPrevious28DayCtr(int previous28DayImpressions, int previous28DayCtr) { RecordHistogram.recordCountHistogram( "Search.ContextualSearchPrevious28DayImpressions", previous28DayImpressions); RecordHistogram.recordPercentageHistogram( "Search.ContextualSearchPrevious28DayCtr", previous28DayCtr); } /** * Get the encoded value to use for the Bar Overlap histogram by encoding all the input * parameters. * @param didBarOverlap Whether the selection overlapped the Bar position. * @param wasPanelSeen Whether the panel content was seen. * @param wasTap Whether the gesture was a Tap. * @return The value for the enum histogram. */ private static int getBarOverlapEnum( boolean didBarOverlap, boolean wasPanelSeen, boolean wasTap) { if (wasTap) { if (didBarOverlap) { if (wasPanelSeen) { return BAR_OVERLAP_RESULTS_SEEN_FROM_TAP; } else { return BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_TAP; } } else { if (wasPanelSeen) { return NO_BAR_OVERLAP_RESULTS_SEEN_FROM_TAP; } else { return NO_BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_TAP; } } } else { if (didBarOverlap) { if (wasPanelSeen) { return BAR_OVERLAP_RESULTS_SEEN_FROM_LONG_PRESS; } else { return BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_LONG_PRESS; } } else { if (wasPanelSeen) { return NO_BAR_OVERLAP_RESULTS_SEEN_FROM_LONG_PRESS; } else { return NO_BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_LONG_PRESS; } } } } /** * Logs that the conditions are right to force the translation one-box, and whether it * was actually forced or not. * @param didForceTranslate Whether the translation onebox was forced. */ public static void logTranslateOnebox(boolean didForceTranslate) { int code = didForceTranslate ? DID_FORCE_TRANSLATE : WOULD_FORCE_TRANSLATE; RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchShouldTranslate", code, FORCE_TRANSLATE_BOUNDARY); } /** * Logs whether a certain category of a blacklisted term resulted in the search results * being seen. * @param reason The given reason. * @param wasSeen Whether the search results were seen. */ public static void logBlacklistSeen(BlacklistReason reason, boolean wasSeen) { if (reason == null) reason = BlacklistReason.NONE; int code = ContextualSearchBlacklist.getBlacklistMetricsCode(reason, wasSeen); RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchBlacklistSeen", code, ContextualSearchBlacklist.BLACKLIST_BOUNDARY); } /** * Logs whether Contextual Cards data was shown. Should be logged on tap if Contextual * Cards integration is enabled. * @param shown Whether Contextual Cards data was shown in the Bar. */ public static void logContextualCardsDataShown(boolean shown) { RecordHistogram.recordBooleanHistogram( "Search.ContextualSearchContextualCardsIntegration.DataShown", shown); } /** * Logs whether results were seen when Contextual Cards data was shown. * @param wasSeen Whether the search results were seen. */ public static void logContextualCardsResultsSeen(boolean wasSeen) { RecordHistogram.recordEnumeratedHistogram( "Search.ContextualSearchContextualCardsIntegration.ResultsSeen", wasSeen ? RESULTS_SEEN : RESULTS_NOT_SEEN, RESULTS_SEEN_BOUNDARY); } /** * Gets the state-change code for the given parameters by doing a lookup in the given map. * @param state The panel state. * @param reason The reason the state changed. * @param stateChangeCodes The map of state and reason to code. * @param defaultCode The code to return if the given values are not found in the map. * @return The code to write into an enum histogram, based on the given map. */ private static int getStateChangeCode(PanelState state, StateChangeReason reason, Map<StateChangeKey, Integer> stateChangeCodes, int defaultCode) { Integer code = stateChangeCodes.get(new StateChangeKey(state, reason)); if (code != null) { return code; } return defaultCode; } /** * Gets the panel-seen code for the given parameters by doing a lookup in the seen-by-gesture * map. * @param wasPanelSeen Whether the panel was seen. * @param wasTap Whether the gesture that originally caused the panel to show was a Tap. * @return The code to write into a panel-seen histogram. */ private static int getPanelSeenByGestureStateCode(boolean wasPanelSeen, boolean wasTap) { return SEEN_BY_GESTURE_CODES.get(new Pair<Boolean, Boolean>(wasPanelSeen, wasTap)); } /** * Gets the promo-outcome code for the given parameter by doing a lookup in the * promo-by-gesture map. * @param preferenceValue The code for the current preference value. * @param wasTap Whether the gesture that originally caused the panel to show was a Tap. * @return The code to write into a promo-outcome histogram. */ private static int getPromoByGestureStateCode(int preferenceValue, boolean wasTap) { return PROMO_BY_GESTURE_CODES.get(new Pair<Integer, Boolean>(preferenceValue, wasTap)); } /** * @return The code for the Contextual Search preference. */ private static int getPreferenceValue() { PrefServiceBridge preferences = PrefServiceBridge.getInstance(); if (preferences.isContextualSearchUninitialized()) { return PREFERENCE_UNINITIALIZED; } else if (preferences.isContextualSearchDisabled()) { return PREFERENCE_DISABLED; } return PREFERENCE_ENABLED; } /** * Gets the encode value for quick answers seen. * @param didActivate Whether the quick answer was shown. * @param didAnswer Whether the caption was a full answer, not just a hint. * @param wasSeen Whether the search panel was opened. * @return The encoded value. */ private static int getQuickAnswerSeenValue( boolean didActivate, boolean didAnswer, boolean wasSeen) { if (wasSeen) { if (didActivate) { if (didAnswer) { return QUICK_ANSWER_ACTIVATED_WAS_AN_ANSWER_SEEN; } else { return QUICK_ANSWER_ACTIVATED_NOT_AN_ANSWER_SEEN; } } else { return QUICK_ANSWER_NOT_ACTIVATED_SEEN; } } else { if (didActivate) { if (didAnswer) { return QUICK_ANSWER_ACTIVATED_WAS_AN_ANSWER_NOT_SEEN; } else { return QUICK_ANSWER_ACTIVATED_NOT_AN_ANSWER_NOT_SEEN; } } else { return QUICK_ANSWER_NOT_ACTIVATED_NOT_SEEN; } } } /** * Logs to a seen-by-gesture histogram of the given name. * @param wasPanelSeen Whether the panel was seen. * @param wasTap Whether the gesture that originally caused the panel to show was a Tap. * @param histogramName The full name of the histogram to log to. */ private static void logHistogramByGesture(boolean wasPanelSeen, boolean wasTap, String histogramName) { RecordHistogram.recordEnumeratedHistogram(histogramName, getPanelSeenByGestureStateCode(wasPanelSeen, wasTap), RESULTS_BY_GESTURE_BOUNDARY); } }