OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.compositor.bottombar.contextualsearch; |
| 6 |
| 7 import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.Context
ualSearchPanel.PanelState; |
| 8 import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.Context
ualSearchPanel.StateChangeReason; |
| 9 import org.chromium.chrome.browser.contextualsearch.ContextualSearchUma; |
| 10 |
| 11 import java.util.ArrayList; |
| 12 import java.util.Collections; |
| 13 import java.util.HashMap; |
| 14 import java.util.Map; |
| 15 |
| 16 /** |
| 17 * Holds the state of the Contextual Search Panel. |
| 18 */ |
| 19 abstract class ContextualSearchPanelStateHandler { |
| 20 |
| 21 // Valid previous states for when the promo is active. |
| 22 private static final Map<PanelState, PanelState> PREVIOUS_STATES_PROMO; |
| 23 static { |
| 24 Map<PanelState, PanelState> states = new HashMap<PanelState, PanelState>
(); |
| 25 // Pairs are of the form <Current, Previous>. |
| 26 states.put(PanelState.PEEKED, PanelState.CLOSED); |
| 27 states.put(PanelState.PROMO, PanelState.PEEKED); |
| 28 states.put(PanelState.EXPANDED, PanelState.PROMO); |
| 29 PREVIOUS_STATES_PROMO = Collections.unmodifiableMap(states); |
| 30 } |
| 31 |
| 32 // Valid previous states for when the promo is not active (normal flow). |
| 33 private static final Map<PanelState, PanelState> PREVIOUS_STATES_NORMAL; |
| 34 static { |
| 35 Map<PanelState, PanelState> states = new HashMap<PanelState, PanelState>
(); |
| 36 // Pairs are of the form <Current, Previous>. |
| 37 states.put(PanelState.PEEKED, PanelState.CLOSED); |
| 38 states.put(PanelState.EXPANDED, PanelState.PEEKED); |
| 39 states.put(PanelState.MAXIMIZED, PanelState.EXPANDED); |
| 40 PREVIOUS_STATES_NORMAL = Collections.unmodifiableMap(states); |
| 41 } |
| 42 |
| 43 // The current state of the Contextual Search Panel. |
| 44 private PanelState mPanelState = PanelState.UNDEFINED; |
| 45 private boolean mDidSearchInvolvePromo; |
| 46 private boolean mWasSearchContentViewSeen; |
| 47 private boolean mIsPromoActive; |
| 48 private boolean mHasExpanded; |
| 49 private boolean mHasMaximized; |
| 50 private boolean mHasExitedPeeking; |
| 51 private boolean mHasExitedExpanded; |
| 52 private boolean mHasExitedMaximized; |
| 53 private boolean mIsSerpNavigation; |
| 54 private long mSearchStartTimeNs; |
| 55 |
| 56 // -------------------------------------------------------------------------
------------------- |
| 57 // Contextual Search Panel states |
| 58 // -------------------------------------------------------------------------
------------------- |
| 59 |
| 60 /** |
| 61 * @return The panel's state. |
| 62 */ |
| 63 PanelState getPanelState() { |
| 64 return mPanelState; |
| 65 } |
| 66 |
| 67 /** |
| 68 * @return The {@code PanelState} that is before the |state| in the order of
states. |
| 69 */ |
| 70 PanelState getPreviousPanelState(PanelState state) { |
| 71 PanelState prevState = mIsPromoActive |
| 72 ? PREVIOUS_STATES_PROMO.get(state) |
| 73 : PREVIOUS_STATES_NORMAL.get(state); |
| 74 return prevState != null ? prevState : PanelState.UNDEFINED; |
| 75 } |
| 76 |
| 77 /** |
| 78 * Return the maximum state that the panel can be in, depending on whether t
he promo is |
| 79 * active. |
| 80 */ |
| 81 PanelState getMaximumState() { |
| 82 return mIsPromoActive ? PanelState.PROMO : PanelState.MAXIMIZED; |
| 83 } |
| 84 |
| 85 /** |
| 86 * Return the intermediary state that the panel can be in, depending on whet
her the promo is |
| 87 * active. |
| 88 */ |
| 89 PanelState getIntermediaryState() { |
| 90 return mIsPromoActive ? PanelState.PROMO : PanelState.EXPANDED; |
| 91 } |
| 92 |
| 93 /** |
| 94 * Sets the panel's state. |
| 95 * @param toState The panel state to transition to. |
| 96 * @param reason The reason for a change in the panel's state. |
| 97 */ |
| 98 protected void setPanelState(PanelState toState, StateChangeReason reason) { |
| 99 // Note: the logging within this function includes the promo, unless spe
cifically |
| 100 // excluded. |
| 101 PanelState fromState = mPanelState; |
| 102 boolean isStartingSearch = isStartingNewContextualSearch(toState, reason
); |
| 103 boolean isEndingSearch = isEndingContextualSearch(toState, isStartingSea
rch); |
| 104 boolean isChained = isStartingSearch && isOngoingContextualSearch(); |
| 105 boolean isSameState = fromState == toState; |
| 106 boolean isFirstExitFromPeeking = fromState == PanelState.PEEKED && !mHas
ExitedPeeking |
| 107 && (!isSameState || isStartingSearch); |
| 108 boolean isFirstExitFromExpanded = fromState == PanelState.EXPANDED && !m
HasExitedExpanded |
| 109 && !isSameState; |
| 110 boolean isFirstExitFromMaximized = fromState == PanelState.MAXIMIZED &&
!mHasExitedMaximized |
| 111 && !isSameState; |
| 112 |
| 113 if (isEndingSearch) { |
| 114 if (!mDidSearchInvolvePromo) { |
| 115 // Measure duration only when the promo is not involved. |
| 116 long durationMs = (System.nanoTime() - mSearchStartTimeNs) / 100
0000; |
| 117 ContextualSearchUma.logDuration(mWasSearchContentViewSeen, isCha
ined, durationMs); |
| 118 } |
| 119 if (mIsPromoActive) { |
| 120 // The user is exiting still in the promo, without choosing an o
ption. |
| 121 ContextualSearchUma.logFirstRunPanelSeen(mWasSearchContentViewSe
en); |
| 122 } else { |
| 123 ContextualSearchUma.logResultsSeen(mWasSearchContentViewSeen); |
| 124 } |
| 125 } |
| 126 if (isStartingSearch) { |
| 127 mSearchStartTimeNs = System.nanoTime(); |
| 128 } |
| 129 |
| 130 // Log state changes. We only log the first transition to a state within
a contextual |
| 131 // search. Note that when a user clicks on a link on the search content
view, this will |
| 132 // trigger a transition to MAXIMIZED (SERP_NAVIGATION) followed by a tra
nsition to |
| 133 // CLOSED (TAB_PROMOTION). For the purpose of logging, the reason for th
e second transition |
| 134 // is reinterpreted to SERP_NAVIGATION, in order to distinguish it from
a tab promotion |
| 135 // caused when tapping on the Search Bar when the Panel is maximized. |
| 136 StateChangeReason reasonForLogging = |
| 137 mIsSerpNavigation ? StateChangeReason.SERP_NAVIGATION : reason; |
| 138 if (isStartingSearch || isEndingSearch |
| 139 || (!mHasExpanded && toState == PanelState.EXPANDED) |
| 140 || (!mHasMaximized && toState == PanelState.MAXIMIZED)) { |
| 141 ContextualSearchUma.logFirstStateEntry(fromState, toState, reasonFor
Logging); |
| 142 } |
| 143 // Note: CLOSED / UNDEFINED state exits are detected when a search that
is not chained is |
| 144 // starting. |
| 145 if ((isStartingSearch && !isChained) || isFirstExitFromPeeking || isFirs
tExitFromExpanded |
| 146 || isFirstExitFromMaximized) { |
| 147 ContextualSearchUma.logFirstStateExit(fromState, toState, reasonForL
ogging); |
| 148 } |
| 149 |
| 150 // We can now modify the state. |
| 151 if (isFirstExitFromPeeking) { |
| 152 mHasExitedPeeking = true; |
| 153 } else if (isFirstExitFromExpanded) { |
| 154 mHasExitedExpanded = true; |
| 155 } else if (isFirstExitFromMaximized) { |
| 156 mHasExitedMaximized = true; |
| 157 } |
| 158 |
| 159 mPanelState = toState; |
| 160 |
| 161 if (toState == PanelState.EXPANDED) { |
| 162 mHasExpanded = true; |
| 163 } else if (toState == PanelState.MAXIMIZED) { |
| 164 mHasMaximized = true; |
| 165 } |
| 166 if (reason == StateChangeReason.SERP_NAVIGATION) { |
| 167 mIsSerpNavigation = true; |
| 168 } |
| 169 |
| 170 if (isEndingSearch) { |
| 171 mDidSearchInvolvePromo = false; |
| 172 mWasSearchContentViewSeen = false; |
| 173 mHasExpanded = false; |
| 174 mHasMaximized = false; |
| 175 mHasExitedPeeking = false; |
| 176 mHasExitedExpanded = false; |
| 177 mHasExitedMaximized = false; |
| 178 mIsSerpNavigation = false; |
| 179 } |
| 180 |
| 181 // TODO(manzagop): When the user opts in, we should replay his actions f
or the current |
| 182 // contextual search for the standard (non promo) UMA histograms. |
| 183 } |
| 184 |
| 185 /** |
| 186 * Determine if a specific {@code PanelState} is a valid state in the curren
t environment. |
| 187 * @param state The state being evaluated. |
| 188 * @return whether the state is valid. |
| 189 */ |
| 190 boolean isValidState(PanelState state) { |
| 191 ArrayList<PanelState> validStates; |
| 192 if (mIsPromoActive) { |
| 193 validStates = new ArrayList<PanelState>(PREVIOUS_STATES_PROMO.values
()); |
| 194 } else { |
| 195 validStates = new ArrayList<PanelState>(PREVIOUS_STATES_NORMAL.value
s()); |
| 196 // MAXIMIZED is not the previous state of anything, but it's a valid
state. |
| 197 validStates.add(PanelState.MAXIMIZED); |
| 198 } |
| 199 |
| 200 return validStates.contains(state); |
| 201 } |
| 202 |
| 203 /** |
| 204 * Sets that the contextual search involved the promo. |
| 205 */ |
| 206 void setDidSearchInvolvePromo() { |
| 207 mDidSearchInvolvePromo = true; |
| 208 } |
| 209 |
| 210 /** |
| 211 * Sets that the Search Content View was seen. |
| 212 */ |
| 213 void setWasSearchContentViewSeen() { |
| 214 mWasSearchContentViewSeen = true; |
| 215 } |
| 216 |
| 217 /** |
| 218 * Sets whether the promo is active. |
| 219 */ |
| 220 void setIsPromoActive(boolean shown) { |
| 221 mIsPromoActive = shown; |
| 222 } |
| 223 |
| 224 /** |
| 225 * Gets whether the promo is active. |
| 226 */ |
| 227 boolean getIsPromoActive() { |
| 228 return mIsPromoActive; |
| 229 } |
| 230 |
| 231 // -------------------------------------------------------------------------
------------------- |
| 232 // Helpers |
| 233 // -------------------------------------------------------------------------
------------------- |
| 234 |
| 235 /** |
| 236 * Determine whether a new contextual search is starting. |
| 237 * @param toState The contextual search state that will be transitioned to. |
| 238 * @param reason The reason for the search state transition. |
| 239 * @return Whether a new contextual search is starting. |
| 240 */ |
| 241 private boolean isStartingNewContextualSearch(PanelState toState, StateChang
eReason reason) { |
| 242 return toState == PanelState.PEEKED |
| 243 && (reason == StateChangeReason.TEXT_SELECT_TAP |
| 244 || reason == StateChangeReason.TEXT_SELECT_LONG_PRESS); |
| 245 } |
| 246 |
| 247 /** |
| 248 * Determine whether a contextual search is ending. |
| 249 * @param toState The contextual search state that will be transitioned to. |
| 250 * @param isStartingSearch Whether a new contextual search is starting. |
| 251 * @return Whether a contextual search is ending. |
| 252 */ |
| 253 private boolean isEndingContextualSearch(PanelState toState, boolean isStart
ingSearch) { |
| 254 return isOngoingContextualSearch() && (toState == PanelState.CLOSED || i
sStartingSearch); |
| 255 } |
| 256 |
| 257 /** |
| 258 * @return Whether there is an ongoing contextual search. |
| 259 */ |
| 260 private boolean isOngoingContextualSearch() { |
| 261 return mPanelState != PanelState.UNDEFINED && mPanelState != PanelState.
CLOSED; |
| 262 } |
| 263 } |
OLD | NEW |