Index: chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelStateHandler.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelStateHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelStateHandler.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a775698d7fc9035061834e91867a9ea0d7b373a5 |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelStateHandler.java |
@@ -0,0 +1,263 @@ |
+// 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.compositor.bottombar.contextualsearch; |
+ |
+import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchPanel.PanelState; |
+import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchPanel.StateChangeReason; |
+import org.chromium.chrome.browser.contextualsearch.ContextualSearchUma; |
+ |
+import java.util.ArrayList; |
+import java.util.Collections; |
+import java.util.HashMap; |
+import java.util.Map; |
+ |
+/** |
+ * Holds the state of the Contextual Search Panel. |
+ */ |
+abstract class ContextualSearchPanelStateHandler { |
+ |
+ // Valid previous states for when the promo is active. |
+ private static final Map<PanelState, PanelState> PREVIOUS_STATES_PROMO; |
+ static { |
+ Map<PanelState, PanelState> states = new HashMap<PanelState, PanelState>(); |
+ // Pairs are of the form <Current, Previous>. |
+ states.put(PanelState.PEEKED, PanelState.CLOSED); |
+ states.put(PanelState.PROMO, PanelState.PEEKED); |
+ states.put(PanelState.EXPANDED, PanelState.PROMO); |
+ PREVIOUS_STATES_PROMO = Collections.unmodifiableMap(states); |
+ } |
+ |
+ // Valid previous states for when the promo is not active (normal flow). |
+ private static final Map<PanelState, PanelState> PREVIOUS_STATES_NORMAL; |
+ static { |
+ Map<PanelState, PanelState> states = new HashMap<PanelState, PanelState>(); |
+ // Pairs are of the form <Current, Previous>. |
+ states.put(PanelState.PEEKED, PanelState.CLOSED); |
+ states.put(PanelState.EXPANDED, PanelState.PEEKED); |
+ states.put(PanelState.MAXIMIZED, PanelState.EXPANDED); |
+ PREVIOUS_STATES_NORMAL = Collections.unmodifiableMap(states); |
+ } |
+ |
+ // The current state of the Contextual Search Panel. |
+ private PanelState mPanelState = PanelState.UNDEFINED; |
+ private boolean mDidSearchInvolvePromo; |
+ private boolean mWasSearchContentViewSeen; |
+ private boolean mIsPromoActive; |
+ private boolean mHasExpanded; |
+ private boolean mHasMaximized; |
+ private boolean mHasExitedPeeking; |
+ private boolean mHasExitedExpanded; |
+ private boolean mHasExitedMaximized; |
+ private boolean mIsSerpNavigation; |
+ private long mSearchStartTimeNs; |
+ |
+ // -------------------------------------------------------------------------------------------- |
+ // Contextual Search Panel states |
+ // -------------------------------------------------------------------------------------------- |
+ |
+ /** |
+ * @return The panel's state. |
+ */ |
+ PanelState getPanelState() { |
+ return mPanelState; |
+ } |
+ |
+ /** |
+ * @return The {@code PanelState} that is before the |state| in the order of states. |
+ */ |
+ PanelState getPreviousPanelState(PanelState state) { |
+ PanelState prevState = mIsPromoActive |
+ ? PREVIOUS_STATES_PROMO.get(state) |
+ : PREVIOUS_STATES_NORMAL.get(state); |
+ return prevState != null ? prevState : PanelState.UNDEFINED; |
+ } |
+ |
+ /** |
+ * Return the maximum state that the panel can be in, depending on whether the promo is |
+ * active. |
+ */ |
+ PanelState getMaximumState() { |
+ return mIsPromoActive ? PanelState.PROMO : PanelState.MAXIMIZED; |
+ } |
+ |
+ /** |
+ * Return the intermediary state that the panel can be in, depending on whether the promo is |
+ * active. |
+ */ |
+ PanelState getIntermediaryState() { |
+ return mIsPromoActive ? PanelState.PROMO : PanelState.EXPANDED; |
+ } |
+ |
+ /** |
+ * Sets the panel's state. |
+ * @param toState The panel state to transition to. |
+ * @param reason The reason for a change in the panel's state. |
+ */ |
+ protected void setPanelState(PanelState toState, StateChangeReason reason) { |
+ // Note: the logging within this function includes the promo, unless specifically |
+ // excluded. |
+ PanelState fromState = mPanelState; |
+ boolean isStartingSearch = isStartingNewContextualSearch(toState, reason); |
+ boolean isEndingSearch = isEndingContextualSearch(toState, isStartingSearch); |
+ boolean isChained = isStartingSearch && isOngoingContextualSearch(); |
+ 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) { |
+ if (!mDidSearchInvolvePromo) { |
+ // Measure duration only when the promo is not involved. |
+ long durationMs = (System.nanoTime() - mSearchStartTimeNs) / 1000000; |
+ ContextualSearchUma.logDuration(mWasSearchContentViewSeen, isChained, durationMs); |
+ } |
+ if (mIsPromoActive) { |
+ // The user is exiting still in the promo, without choosing an option. |
+ ContextualSearchUma.logFirstRunPanelSeen(mWasSearchContentViewSeen); |
+ } else { |
+ ContextualSearchUma.logResultsSeen(mWasSearchContentViewSeen); |
+ } |
+ } |
+ if (isStartingSearch) { |
+ mSearchStartTimeNs = System.nanoTime(); |
+ } |
+ |
+ // Log state changes. We only log the first transition to a state within a contextual |
+ // search. Note that when a user clicks on a link on the search content view, this will |
+ // trigger a transition to MAXIMIZED (SERP_NAVIGATION) followed by a transition to |
+ // CLOSED (TAB_PROMOTION). For the purpose of logging, the reason for the second transition |
+ // is reinterpreted to SERP_NAVIGATION, in order to distinguish it from a tab promotion |
+ // caused when tapping on the Search Bar when the Panel is maximized. |
+ StateChangeReason reasonForLogging = |
+ mIsSerpNavigation ? StateChangeReason.SERP_NAVIGATION : reason; |
+ if (isStartingSearch || isEndingSearch |
+ || (!mHasExpanded && toState == PanelState.EXPANDED) |
+ || (!mHasMaximized && toState == PanelState.MAXIMIZED)) { |
+ ContextualSearchUma.logFirstStateEntry(fromState, toState, reasonForLogging); |
+ } |
+ // Note: CLOSED / UNDEFINED state exits are detected when a search that is not chained is |
+ // starting. |
+ if ((isStartingSearch && !isChained) || isFirstExitFromPeeking || isFirstExitFromExpanded |
+ || isFirstExitFromMaximized) { |
+ ContextualSearchUma.logFirstStateExit(fromState, toState, reasonForLogging); |
+ } |
+ |
+ // We can now modify the state. |
+ if (isFirstExitFromPeeking) { |
+ mHasExitedPeeking = true; |
+ } else if (isFirstExitFromExpanded) { |
+ mHasExitedExpanded = true; |
+ } else if (isFirstExitFromMaximized) { |
+ mHasExitedMaximized = true; |
+ } |
+ |
+ mPanelState = toState; |
+ |
+ if (toState == PanelState.EXPANDED) { |
+ mHasExpanded = true; |
+ } else if (toState == PanelState.MAXIMIZED) { |
+ mHasMaximized = true; |
+ } |
+ if (reason == StateChangeReason.SERP_NAVIGATION) { |
+ mIsSerpNavigation = true; |
+ } |
+ |
+ if (isEndingSearch) { |
+ mDidSearchInvolvePromo = false; |
+ mWasSearchContentViewSeen = false; |
+ mHasExpanded = false; |
+ mHasMaximized = false; |
+ mHasExitedPeeking = false; |
+ mHasExitedExpanded = false; |
+ mHasExitedMaximized = false; |
+ mIsSerpNavigation = false; |
+ } |
+ |
+ // TODO(manzagop): When the user opts in, we should replay his actions for the current |
+ // contextual search for the standard (non promo) UMA histograms. |
+ } |
+ |
+ /** |
+ * Determine if a specific {@code PanelState} is a valid state in the current environment. |
+ * @param state The state being evaluated. |
+ * @return whether the state is valid. |
+ */ |
+ boolean isValidState(PanelState state) { |
+ ArrayList<PanelState> validStates; |
+ if (mIsPromoActive) { |
+ validStates = new ArrayList<PanelState>(PREVIOUS_STATES_PROMO.values()); |
+ } else { |
+ validStates = new ArrayList<PanelState>(PREVIOUS_STATES_NORMAL.values()); |
+ // MAXIMIZED is not the previous state of anything, but it's a valid state. |
+ validStates.add(PanelState.MAXIMIZED); |
+ } |
+ |
+ return validStates.contains(state); |
+ } |
+ |
+ /** |
+ * Sets that the contextual search involved the promo. |
+ */ |
+ void setDidSearchInvolvePromo() { |
+ mDidSearchInvolvePromo = true; |
+ } |
+ |
+ /** |
+ * Sets that the Search Content View was seen. |
+ */ |
+ void setWasSearchContentViewSeen() { |
+ mWasSearchContentViewSeen = true; |
+ } |
+ |
+ /** |
+ * Sets whether the promo is active. |
+ */ |
+ void setIsPromoActive(boolean shown) { |
+ mIsPromoActive = shown; |
+ } |
+ |
+ /** |
+ * Gets whether the promo is active. |
+ */ |
+ boolean getIsPromoActive() { |
+ return mIsPromoActive; |
+ } |
+ |
+ // -------------------------------------------------------------------------------------------- |
+ // Helpers |
+ // -------------------------------------------------------------------------------------------- |
+ |
+ /** |
+ * 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 toState, StateChangeReason reason) { |
+ return toState == PanelState.PEEKED |
+ && (reason == StateChangeReason.TEXT_SELECT_TAP |
+ || reason == StateChangeReason.TEXT_SELECT_LONG_PRESS); |
+ } |
+ |
+ /** |
+ * Determine whether a contextual search is ending. |
+ * @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 toState, boolean isStartingSearch) { |
+ return isOngoingContextualSearch() && (toState == PanelState.CLOSED || isStartingSearch); |
+ } |
+ |
+ /** |
+ * @return Whether there is an ongoing contextual search. |
+ */ |
+ private boolean isOngoingContextualSearch() { |
+ return mPanelState != PanelState.UNDEFINED && mPanelState != PanelState.CLOSED; |
+ } |
+} |