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.contextualsearch;
6 
7 import android.text.TextUtils;
8 import android.text.format.DateUtils;
9 import android.util.Pair;
10 
11 import androidx.annotation.IntDef;
12 import androidx.annotation.Nullable;
13 
14 import org.chromium.base.metrics.RecordHistogram;
15 import org.chromium.base.metrics.RecordUserAction;
16 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.PanelState;
17 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChangeReason;
18 import org.chromium.chrome.browser.contextualsearch.ResolvedSearchTerm.CardTag;
19 import org.chromium.chrome.browser.sync.AndroidSyncSettings;
20 
21 import java.lang.annotation.Retention;
22 import java.lang.annotation.RetentionPolicy;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.regex.Pattern;
27 
28 /**
29  * Centralizes UMA data collection for Contextual Search. All calls must be made from the UI thread.
30  */
31 public class ContextualSearchUma {
32     // Constants to use for the original selection gesture
33     private static final boolean LONG_PRESS = false;
34     private static final boolean TAP = true;
35 
36     /** A pattern to determine if text contains any whitespace. */
37     private static final Pattern CONTAINS_WHITESPACE_PATTERN = Pattern.compile("\\s");
38 
39     // Constants used to log UMA "enum" histograms about the Contextual Search's preference state.
40     @IntDef({Preference.UNINITIALIZED, Preference.ENABLED, Preference.DISABLED})
41     @Retention(RetentionPolicy.SOURCE)
42     private @interface Preference {
43         int UNINITIALIZED = 0;
44         int ENABLED = 1;
45         int DISABLED = 2;
46         int NUM_ENTRIES = 3;
47     }
48 
49     // Constants used to log UMA "enum" histograms about whether search results were seen.
50     @IntDef({Results.SEEN, Results.NOT_SEEN})
51     @Retention(RetentionPolicy.SOURCE)
52     private @interface Results {
53         int SEEN = 0;
54         int NOT_SEEN = 1;
55         int NUM_ENTRIES = 2;
56     }
57 
58     // Constants used to log UMA "enum" histograms about whether the selection is valid.
59     @IntDef({Selection.VALID, Selection.INVALID})
60     @Retention(RetentionPolicy.SOURCE)
61     private @interface Selection {
62         int VALID = 0;
63         int INVALID = 1;
64         int NUM_ENTRIES = 2;
65     }
66 
67     // Constants used to log UMA "enum" histograms about a request's outcome.
68     @IntDef({Request.NOT_FAILED, Request.FAILED})
69     @Retention(RetentionPolicy.SOURCE)
70     private @interface Request {
71         int NOT_FAILED = 0;
72         int FAILED = 1;
73         int NUM_ENTRIES = 2;
74     }
75 
76     // Constants used to log UMA "enum" histograms about the panel's state transitions.
77     // Entry code: first entry into CLOSED.
78     @IntDef({EnterClosedFrom.OTHER, EnterClosedFrom.PEEKED_BACK_PRESS,
79             EnterClosedFrom.PEEKED_BASE_PAGE_SCROLL, EnterClosedFrom.PEEKED_TEXT_SELECT_TAP,
80             EnterClosedFrom.EXPANDED_BACK_PRESS, EnterClosedFrom.EXPANDED_BASE_PAGE_TAP,
81             EnterClosedFrom.EXPANDED_FLING, EnterClosedFrom.MAXIMIZED_BACK_PRESS,
82             EnterClosedFrom.MAXIMIZED_FLING, EnterClosedFrom.MAXIMIZED_TAB_PROMOTION,
83             EnterClosedFrom.MAXIMIZED_SERP_NAVIGATION})
84     @Retention(RetentionPolicy.SOURCE)
85     private @interface EnterClosedFrom {
86         int OTHER = 0;
87         int PEEKED_BACK_PRESS = 1;
88         int PEEKED_BASE_PAGE_SCROLL = 2;
89         int PEEKED_TEXT_SELECT_TAP = 3;
90         int EXPANDED_BACK_PRESS = 4;
91         int EXPANDED_BASE_PAGE_TAP = 5;
92         int EXPANDED_FLING = 6;
93         int MAXIMIZED_BACK_PRESS = 7;
94         int MAXIMIZED_FLING = 8;
95         int MAXIMIZED_TAB_PROMOTION = 9;
96         int MAXIMIZED_SERP_NAVIGATION = 10;
97         int NUM_ENTRIES = 11;
98     }
99 
100     // Entry code: first entry into PEEKED.
101     @IntDef({EnterPeekedFrom.OTHER, EnterPeekedFrom.CLOSED_TEXT_SELECT_TAP,
102             EnterPeekedFrom.CLOSED_EXT_SELECT_LONG_PRESS, EnterPeekedFrom.PEEKED_TEXT_SELECT_TAP,
103             EnterPeekedFrom.PEEKED_TEXT_SELECT_LONG_PRESS, EnterPeekedFrom.EXPANDED_SEARCH_BAR_TAP,
104             EnterPeekedFrom.EXPANDED_SWIPE, EnterPeekedFrom.EXPANDED_FLING,
105             EnterPeekedFrom.MAXIMIZED_SWIPE, EnterPeekedFrom.MAXIMIZED_FLING})
106     @Retention(RetentionPolicy.SOURCE)
107     private @interface EnterPeekedFrom {
108         int OTHER = 0;
109         int CLOSED_TEXT_SELECT_TAP = 1;
110         int CLOSED_EXT_SELECT_LONG_PRESS = 2;
111         int PEEKED_TEXT_SELECT_TAP = 3;
112         int PEEKED_TEXT_SELECT_LONG_PRESS = 4;
113         int EXPANDED_SEARCH_BAR_TAP = 5;
114         int EXPANDED_SWIPE = 6;
115         int EXPANDED_FLING = 7;
116         int MAXIMIZED_SWIPE = 8;
117         int MAXIMIZED_FLING = 9;
118         int NUM_ENTRIES = 10;
119     }
120 
121     // Entry code: first entry into EXPANDED.
122     @IntDef({EnterExpandedFrom.OTHER, EnterExpandedFrom.PEEKED_SEARCH_BAR_TAP,
123             EnterExpandedFrom.PEEKED_SWIPE, EnterExpandedFrom.PEEKED_FLING,
124             EnterExpandedFrom.MAXIMIZED_SWIPE, EnterExpandedFrom.MAXIMIZED_FLING})
125     @Retention(RetentionPolicy.SOURCE)
126     private @interface EnterExpandedFrom {
127         int OTHER = 0;
128         int PEEKED_SEARCH_BAR_TAP = 1;
129         int PEEKED_SWIPE = 2;
130         int PEEKED_FLING = 3;
131         int MAXIMIZED_SWIPE = 4;
132         int MAXIMIZED_FLING = 5;
133         int NUM_ENTRIES = 6;
134     }
135 
136     // Entry code: first entry into MAXIMIZED.
137     @IntDef({EnterMaximizedFrom.OTHER, EnterMaximizedFrom.PEEKED_SWIPE,
138             EnterMaximizedFrom.PEEKED_FLING, EnterMaximizedFrom.EXPANDED_SWIPE,
139             EnterMaximizedFrom.EXPANDED_FLING, EnterMaximizedFrom.EXPANDED_SERP_NAVIGATION})
140     @Retention(RetentionPolicy.SOURCE)
141     private @interface EnterMaximizedFrom {
142         int OTHER = 0;
143         int PEEKED_SWIPE = 1;
144         int PEEKED_FLING = 2;
145         int EXPANDED_SWIPE = 3;
146         int EXPANDED_FLING = 4;
147         int EXPANDED_SERP_NAVIGATION = 5;
148         int NUM_ENTRIES = 6;
149     }
150 
151     // Exit code: first exit from CLOSED (or UNDEFINED).
152     @IntDef({ExitClosedTo.OTHER, ExitClosedTo.PEEKED_TEXT_SELECT_TAP,
153             ExitClosedTo.PEEKED_TEXT_SELECT_LONG_PRESS})
154     @Retention(RetentionPolicy.SOURCE)
155     private @interface ExitClosedTo {
156         int OTHER = 0;
157         int PEEKED_TEXT_SELECT_TAP = 1;
158         int PEEKED_TEXT_SELECT_LONG_PRESS = 2;
159         int NUM_ENTRIES = 3;
160     }
161 
162     // Exit code: first exit from PEEKED.
163     @IntDef({ExitPeekedTo.OTHER, ExitPeekedTo.CLOSED_BACK_PRESS,
164             ExitPeekedTo.CLOSED_BASE_PAGE_SCROLL, ExitPeekedTo.CLOSED_TEXT_SELECT_TAP,
165             ExitPeekedTo.PEEKED_TEXT_SELECT_TAP, ExitPeekedTo.PEEKED_TEXT_SELECT_LONG_PRESS,
166             ExitPeekedTo.EXPANDED_SEARCH_BAR_TAP, ExitPeekedTo.EXPANDED_SWIPE,
167             ExitPeekedTo.EXPANDED_FLING, ExitPeekedTo.MAXIMIZED_SWIPE,
168             ExitPeekedTo.MAXIMIZED_FLING})
169     @Retention(RetentionPolicy.SOURCE)
170     private @interface ExitPeekedTo {
171         int OTHER = 0;
172         int CLOSED_BACK_PRESS = 1;
173         int CLOSED_BASE_PAGE_SCROLL = 2;
174         int CLOSED_TEXT_SELECT_TAP = 3;
175         int PEEKED_TEXT_SELECT_TAP = 4;
176         int PEEKED_TEXT_SELECT_LONG_PRESS = 5;
177         int EXPANDED_SEARCH_BAR_TAP = 6;
178         int EXPANDED_SWIPE = 7;
179         int EXPANDED_FLING = 8;
180         int MAXIMIZED_SWIPE = 9;
181         int MAXIMIZED_FLING = 10;
182         int NUM_ENTRIES = 11;
183     }
184 
185     // Exit code: first exit from EXPANDED.
186     @IntDef({ExitExpandedTo.OTHER, ExitExpandedTo.CLOSED_BACK_PRESS,
187             ExitExpandedTo.CLOSED_BASE_PAGE_TAP, ExitExpandedTo.CLOSED_FLING,
188             ExitExpandedTo.PEEKED_SEARCH_BAR_TAP, ExitExpandedTo.PEEKED_SWIPE,
189             ExitExpandedTo.PEEKED_FLING, ExitExpandedTo.MAXIMIZED_SWIPE,
190             ExitExpandedTo.MAXIMIZED_FLING, ExitExpandedTo.MAXIMIZED_SERP_NAVIGATION})
191     @Retention(RetentionPolicy.SOURCE)
192     private @interface ExitExpandedTo {
193         int OTHER = 0;
194         int CLOSED_BACK_PRESS = 1;
195         int CLOSED_BASE_PAGE_TAP = 2;
196         int CLOSED_FLING = 3;
197         int PEEKED_SEARCH_BAR_TAP = 4;
198         int PEEKED_SWIPE = 5;
199         int PEEKED_FLING = 6;
200         int MAXIMIZED_SWIPE = 7;
201         int MAXIMIZED_FLING = 8;
202         int MAXIMIZED_SERP_NAVIGATION = 9;
203         int NUM_ENTRIES = 10;
204     }
205 
206     // Exit code: first exit from MAXIMIZED.
207     @IntDef({ExitMaximizedTo.OTHER, ExitMaximizedTo.CLOSED_BACK_PRESS, ExitMaximizedTo.CLOSED_FLING,
208             ExitMaximizedTo.CLOSED_TAB_PROMOTION, ExitMaximizedTo.CLOSED_SERP_NAVIGATION,
209             ExitMaximizedTo.PEEKED_SWIPE, ExitMaximizedTo.PEEKED_FLING,
210             ExitMaximizedTo.EXPANDED_SWIPE, ExitMaximizedTo.EXPANDED_FLING})
211     @Retention(RetentionPolicy.SOURCE)
212     private @interface ExitMaximizedTo {
213         int OTHER = 0;
214         int CLOSED_BACK_PRESS = 1;
215         int CLOSED_FLING = 2;
216         int CLOSED_TAB_PROMOTION = 3;
217         int CLOSED_SERP_NAVIGATION = 4;
218         int PEEKED_SWIPE = 5;
219         int PEEKED_FLING = 6;
220         int EXPANDED_SWIPE = 7;
221         int EXPANDED_FLING = 8;
222         int NUM_ENTRIES = 9;
223     }
224 
225     // Constants used to log UMA "enum" histograms with details about whether search results
226     // were seen, and what the original triggering gesture was.
227     @IntDef({ResultsByGesture.SEEN_FROM_TAP, ResultsByGesture.NOT_SEEN_FROM_TAP,
228             ResultsByGesture.SEEN_FROM_LONG_PRESS, ResultsByGesture.NOT_SEEN_FROM_LONG_PRESS})
229     @Retention(RetentionPolicy.SOURCE)
230     private @interface ResultsByGesture {
231         int SEEN_FROM_TAP = 0;
232         int NOT_SEEN_FROM_TAP = 1;
233         int SEEN_FROM_LONG_PRESS = 2;
234         int NOT_SEEN_FROM_LONG_PRESS = 3;
235         int NUM_ENTRIES = 4;
236     }
237 
238     // Constants used to log UMA "enum" histograms with details about whether search results
239     // were seen, and whether any existing tap suppression heuristics were satisfied.
240     @IntDef({ResultsBySuppression.SEEN_SUPPRESSION_HEURSTIC_SATISFIED,
241             ResultsBySuppression.NOT_SEEN_SUPPRESSION_HEURSTIC_SATISFIED,
242             ResultsBySuppression.SEEN_SUPPRESSION_HEURSTIC_NOT_SATISFIED,
243             ResultsBySuppression.NOT_SEEN_SUPPRESSION_HEURSTIC_NOT_SATISFIED})
244     @Retention(RetentionPolicy.SOURCE)
245     private @interface ResultsBySuppression {
246         int SEEN_SUPPRESSION_HEURSTIC_SATISFIED = 0;
247         int NOT_SEEN_SUPPRESSION_HEURSTIC_SATISFIED = 1;
248         int SEEN_SUPPRESSION_HEURSTIC_NOT_SATISFIED = 2;
249         int NOT_SEEN_SUPPRESSION_HEURSTIC_NOT_SATISFIED = 3;
250         int NUM_ENTRIES = 4;
251     }
252 
253     // Constants used to log UMA "enum" histograms with details about whether search results
254     // were seen, and what the original triggering gesture was.
255     @IntDef({Promo.ENABLED_FROM_TAP, Promo.DISABLED_FROM_TAP, Promo.UNDECIDED_FROM_TAP,
256             Promo.ENABLED_FROM_LONG_PRESS, Promo.DISABLED_FROM_LONG_PRESS,
257             Promo.UNDECIDED_FROM_LONG_PRESS})
258     @Retention(RetentionPolicy.SOURCE)
259     private @interface Promo {
260         int ENABLED_FROM_TAP = 0;
261         int DISABLED_FROM_TAP = 1;
262         int UNDECIDED_FROM_TAP = 2;
263         int ENABLED_FROM_LONG_PRESS = 3;
264         int DISABLED_FROM_LONG_PRESS = 4;
265         int UNDECIDED_FROM_LONG_PRESS = 5;
266         int NUM_ENTRIES = 6;
267     }
268 
269     // Constants used to log UMA "enum" histograms for HTTP / HTTPS.
270     @IntDef({Protocol.IS_HTTP, Protocol.NOT_HTTP})
271     @Retention(RetentionPolicy.SOURCE)
272     private @interface Protocol {
273         int IS_HTTP = 0;
274         int NOT_HTTP = 1;
275         int NUM_ENTRIES = 2;
276     }
277 
278     // Constants used to log UMA "enum" histograms for single / multi-word.
279     @IntDef({ResolvedGranularity.SINGLE_WORD, ResolvedGranularity.MULTI_WORD})
280     @Retention(RetentionPolicy.SOURCE)
281     private @interface ResolvedGranularity {
282         int SINGLE_WORD = 0;
283         int MULTI_WORD = 1;
284         int NUM_ENTRIES = 2;
285     }
286 
287     // Constants used to log UMA "enum" histograms for Quick Answers.
288     @IntDef({QuickAnswerSeen.ACTIVATED_WAS_AN_ANSWER_SEEN,
289             QuickAnswerSeen.ACTIVATED_WAS_AN_ANSWER_NOT_SEEN,
290             QuickAnswerSeen.ACTIVATED_NOT_AN_ANSWER_SEEN,
291             QuickAnswerSeen.ACTIVATED_NOT_AN_ANSWER_NOT_SEEN, QuickAnswerSeen.NOT_ACTIVATED_SEEN,
292             QuickAnswerSeen.NOT_ACTIVATED_NOT_SEEN})
293     @Retention(RetentionPolicy.SOURCE)
294     private @interface QuickAnswerSeen {
295         int ACTIVATED_WAS_AN_ANSWER_SEEN = 0;
296         int ACTIVATED_WAS_AN_ANSWER_NOT_SEEN = 1;
297         int ACTIVATED_NOT_AN_ANSWER_SEEN = 2;
298         int ACTIVATED_NOT_AN_ANSWER_NOT_SEEN = 3;
299         int NOT_ACTIVATED_SEEN = 4;
300         int NOT_ACTIVATED_NOT_SEEN = 5;
301         int NUM_ENTRIES = 6;
302     }
303 
304     // Constants for "Bar Overlap" with triggering gesture, and whether the results were seen.
305     @IntDef({BarOverlapResults.BAR_OVERLAP_RESULTS_SEEN_FROM_TAP,
306             BarOverlapResults.BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_TAP,
307             BarOverlapResults.NO_BAR_OVERLAP_RESULTS_SEEN_FROM_TAP,
308             BarOverlapResults.NO_BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_TAP,
309             BarOverlapResults.BAR_OVERLAP_RESULTS_SEEN_FROM_LONG_PRESS,
310             BarOverlapResults.BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_LONG_PRESS,
311             BarOverlapResults.NO_BAR_OVERLAP_RESULTS_SEEN_FROM_LONG_PRESS,
312             BarOverlapResults.NO_BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_LONG_PRESS})
313     @Retention(RetentionPolicy.SOURCE)
314     private @interface BarOverlapResults {
315         int BAR_OVERLAP_RESULTS_SEEN_FROM_TAP = 0;
316         int BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_TAP = 1;
317         int NO_BAR_OVERLAP_RESULTS_SEEN_FROM_TAP = 2;
318         int NO_BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_TAP = 3;
319         int BAR_OVERLAP_RESULTS_SEEN_FROM_LONG_PRESS = 4;
320         int BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_LONG_PRESS = 5;
321         int NO_BAR_OVERLAP_RESULTS_SEEN_FROM_LONG_PRESS = 6;
322         int NO_BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_LONG_PRESS = 7;
323         int NUM_ENTRIES = 8;
324     }
325 
326     // Constants for quick action intent resolution histogram.
327     @IntDef({QuickActionResolve.FAILED, QuickActionResolve.SINGLE, QuickActionResolve.MULTIPLE})
328     @Retention(RetentionPolicy.SOURCE)
329     private @interface QuickActionResolve {
330         int FAILED = 0;
331         int SINGLE = 1;
332         int MULTIPLE = 2;
333         int NUM_ENTRIES = 3;
334     }
335 
336     // Constants for user permissions histogram.
337     @IntDef({
338             Permissions.SEND_NOTHING,
339             Permissions.SEND_URL,
340             Permissions.SEND_CONTENT,
341             Permissions.SEND_URL_AND_CONTENT,
342     })
343     @Retention(RetentionPolicy.SOURCE)
344     private @interface Permissions {
345         int SEND_NOTHING = 0;
346         int SEND_URL = 1;
347         int SEND_CONTENT = 2;
348         int SEND_URL_AND_CONTENT = 3;
349         int NUM_ENTRIES = 4;
350     }
351 
352     /**
353      * Key used in maps from {state, reason} to state entry (exit) logging code.
354      */
355     static class StateChangeKey {
356         final @PanelState int mState;
357         final @StateChangeReason int mReason;
358         final int mHashCode;
359 
StateChangeKey(@anelState int state, @StateChangeReason int reason)360         StateChangeKey(@PanelState int state, @StateChangeReason int reason) {
361             mState = state;
362             mReason = reason;
363             mHashCode = 31 * state + reason;
364         }
365 
366         @Override
equals(Object obj)367         public boolean equals(Object obj) {
368             if (!(obj instanceof StateChangeKey)) return false;
369             if (obj == this) return true;
370             StateChangeKey other = (StateChangeKey) obj;
371             return mState == other.mState && mReason == other.mReason;
372         }
373 
374         @Override
hashCode()375         public int hashCode() {
376             return mHashCode;
377         }
378     }
379 
380     // TODO(donnd): switch from using Maps to some method that does not require creation of a key.
381 
382     // Entry code map: first entry into CLOSED.
383     private static final Map<StateChangeKey, Integer> ENTER_CLOSED_STATE_CHANGE_CODES;
384     static {
385         Map<StateChangeKey, Integer> codes = new HashMap<StateChangeKey, Integer>();
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.BACK_PRESS), EnterClosedFrom.PEEKED_BACK_PRESS)386         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.BACK_PRESS),
387                 EnterClosedFrom.PEEKED_BACK_PRESS);
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.BASE_PAGE_SCROLL), EnterClosedFrom.PEEKED_BASE_PAGE_SCROLL)388         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.BASE_PAGE_SCROLL),
389                 EnterClosedFrom.PEEKED_BASE_PAGE_SCROLL);
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_TAP), EnterClosedFrom.PEEKED_TEXT_SELECT_TAP)390         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_TAP),
391                 EnterClosedFrom.PEEKED_TEXT_SELECT_TAP);
codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.BACK_PRESS), EnterClosedFrom.EXPANDED_BACK_PRESS)392         codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.BACK_PRESS),
393                 EnterClosedFrom.EXPANDED_BACK_PRESS);
codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.BASE_PAGE_TAP), EnterClosedFrom.EXPANDED_BASE_PAGE_TAP)394         codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.BASE_PAGE_TAP),
395                 EnterClosedFrom.EXPANDED_BASE_PAGE_TAP);
codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.FLING), EnterClosedFrom.EXPANDED_FLING)396         codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.FLING),
397                 EnterClosedFrom.EXPANDED_FLING);
codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.BACK_PRESS), EnterClosedFrom.MAXIMIZED_BACK_PRESS)398         codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.BACK_PRESS),
399                 EnterClosedFrom.MAXIMIZED_BACK_PRESS);
codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.FLING), EnterClosedFrom.MAXIMIZED_FLING)400         codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.FLING),
401                 EnterClosedFrom.MAXIMIZED_FLING);
codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.TAB_PROMOTION), EnterClosedFrom.MAXIMIZED_TAB_PROMOTION)402         codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.TAB_PROMOTION),
403                 EnterClosedFrom.MAXIMIZED_TAB_PROMOTION);
codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SERP_NAVIGATION), EnterClosedFrom.MAXIMIZED_SERP_NAVIGATION)404         codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SERP_NAVIGATION),
405                 EnterClosedFrom.MAXIMIZED_SERP_NAVIGATION);
406         ENTER_CLOSED_STATE_CHANGE_CODES = Collections.unmodifiableMap(codes);
407     }
408 
409     // Entry code map: first entry into PEEKED.
410     private static final Map<StateChangeKey, Integer> ENTER_PEEKED_STATE_CHANGE_CODES;
411     static {
412         Map<StateChangeKey, Integer> codes = new HashMap<StateChangeKey, Integer>();
413         // Note: we don't distinguish entering PEEKED from UNDEFINED / CLOSED.
codes.put(new StateChangeKey(PanelState.UNDEFINED, StateChangeReason.TEXT_SELECT_TAP), EnterPeekedFrom.CLOSED_TEXT_SELECT_TAP)414         codes.put(new StateChangeKey(PanelState.UNDEFINED, StateChangeReason.TEXT_SELECT_TAP),
415                 EnterPeekedFrom.CLOSED_TEXT_SELECT_TAP);
codes.put( new StateChangeKey(PanelState.UNDEFINED, StateChangeReason.TEXT_SELECT_LONG_PRESS), EnterPeekedFrom.CLOSED_EXT_SELECT_LONG_PRESS)416         codes.put(
417                 new StateChangeKey(PanelState.UNDEFINED, StateChangeReason.TEXT_SELECT_LONG_PRESS),
418                 EnterPeekedFrom.CLOSED_EXT_SELECT_LONG_PRESS);
codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.TEXT_SELECT_TAP), EnterPeekedFrom.CLOSED_TEXT_SELECT_TAP)419         codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.TEXT_SELECT_TAP),
420                 EnterPeekedFrom.CLOSED_TEXT_SELECT_TAP);
codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.TEXT_SELECT_LONG_PRESS), EnterPeekedFrom.CLOSED_EXT_SELECT_LONG_PRESS)421         codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.TEXT_SELECT_LONG_PRESS),
422                 EnterPeekedFrom.CLOSED_EXT_SELECT_LONG_PRESS);
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_TAP), EnterPeekedFrom.PEEKED_TEXT_SELECT_TAP)423         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_TAP),
424                 EnterPeekedFrom.PEEKED_TEXT_SELECT_TAP);
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_LONG_PRESS), EnterPeekedFrom.PEEKED_TEXT_SELECT_LONG_PRESS)425         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_LONG_PRESS),
426                 EnterPeekedFrom.PEEKED_TEXT_SELECT_LONG_PRESS);
codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SEARCH_BAR_TAP), EnterPeekedFrom.EXPANDED_SEARCH_BAR_TAP)427         codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SEARCH_BAR_TAP),
428                 EnterPeekedFrom.EXPANDED_SEARCH_BAR_TAP);
codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SWIPE), EnterPeekedFrom.EXPANDED_SWIPE)429         codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SWIPE),
430                 EnterPeekedFrom.EXPANDED_SWIPE);
codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.FLING), EnterPeekedFrom.EXPANDED_FLING)431         codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.FLING),
432                 EnterPeekedFrom.EXPANDED_FLING);
codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SWIPE), EnterPeekedFrom.MAXIMIZED_SWIPE)433         codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SWIPE),
434                 EnterPeekedFrom.MAXIMIZED_SWIPE);
codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.FLING), EnterPeekedFrom.MAXIMIZED_FLING)435         codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.FLING),
436                 EnterPeekedFrom.MAXIMIZED_FLING);
437         ENTER_PEEKED_STATE_CHANGE_CODES = Collections.unmodifiableMap(codes);
438     }
439 
440     // Entry code map: first entry into EXPANDED.
441     private static final Map<StateChangeKey, Integer> ENTER_EXPANDED_STATE_CHANGE_CODES;
442     static {
443         Map<StateChangeKey, Integer> codes = new HashMap<StateChangeKey, Integer>();
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SEARCH_BAR_TAP), EnterExpandedFrom.PEEKED_SEARCH_BAR_TAP)444         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SEARCH_BAR_TAP),
445                 EnterExpandedFrom.PEEKED_SEARCH_BAR_TAP);
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SWIPE), EnterExpandedFrom.PEEKED_SWIPE)446         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SWIPE),
447                 EnterExpandedFrom.PEEKED_SWIPE);
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.FLING), EnterExpandedFrom.PEEKED_FLING)448         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.FLING),
449                 EnterExpandedFrom.PEEKED_FLING);
codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SWIPE), EnterExpandedFrom.MAXIMIZED_SWIPE)450         codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SWIPE),
451                 EnterExpandedFrom.MAXIMIZED_SWIPE);
codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.FLING), EnterExpandedFrom.MAXIMIZED_FLING)452         codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.FLING),
453                 EnterExpandedFrom.MAXIMIZED_FLING);
454         ENTER_EXPANDED_STATE_CHANGE_CODES = Collections.unmodifiableMap(codes);
455     }
456 
457     // Entry code map: first entry into MAXIMIZED.
458     private static final Map<StateChangeKey, Integer> ENTER_MAXIMIZED_STATE_CHANGE_CODES;
459     static {
460         Map<StateChangeKey, Integer> codes = new HashMap<StateChangeKey, Integer>();
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SWIPE), EnterMaximizedFrom.PEEKED_SWIPE)461         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SWIPE),
462                 EnterMaximizedFrom.PEEKED_SWIPE);
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.FLING), EnterMaximizedFrom.PEEKED_FLING)463         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.FLING),
464                 EnterMaximizedFrom.PEEKED_FLING);
codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SWIPE), EnterMaximizedFrom.EXPANDED_SWIPE)465         codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SWIPE),
466                 EnterMaximizedFrom.EXPANDED_SWIPE);
codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.FLING), EnterMaximizedFrom.EXPANDED_FLING)467         codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.FLING),
468                 EnterMaximizedFrom.EXPANDED_FLING);
codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SERP_NAVIGATION), EnterMaximizedFrom.EXPANDED_SERP_NAVIGATION)469         codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SERP_NAVIGATION),
470                 EnterMaximizedFrom.EXPANDED_SERP_NAVIGATION);
471         ENTER_MAXIMIZED_STATE_CHANGE_CODES = Collections.unmodifiableMap(codes);
472     }
473 
474     // Exit code map: first exit from CLOSED.
475     private static final Map<StateChangeKey, Integer> EXIT_CLOSED_TO_STATE_CHANGE_CODES;
476     static {
477         Map<StateChangeKey, Integer> codes = new HashMap<StateChangeKey, Integer>();
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_TAP), ExitClosedTo.PEEKED_TEXT_SELECT_TAP)478         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_TAP),
479                 ExitClosedTo.PEEKED_TEXT_SELECT_TAP);
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_LONG_PRESS), ExitClosedTo.PEEKED_TEXT_SELECT_LONG_PRESS)480         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_LONG_PRESS),
481                 ExitClosedTo.PEEKED_TEXT_SELECT_LONG_PRESS);
482         EXIT_CLOSED_TO_STATE_CHANGE_CODES = Collections.unmodifiableMap(codes);
483     }
484 
485     // Exit code map: first exit from PEEKED.
486     private static final Map<StateChangeKey, Integer> EXIT_PEEKED_TO_STATE_CHANGE_CODES;
487     static {
488         Map<StateChangeKey, Integer> codes = new HashMap<StateChangeKey, Integer>();
codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BACK_PRESS), ExitPeekedTo.CLOSED_BACK_PRESS)489         codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BACK_PRESS),
490                 ExitPeekedTo.CLOSED_BACK_PRESS);
codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BASE_PAGE_SCROLL), ExitPeekedTo.CLOSED_BASE_PAGE_SCROLL)491         codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BASE_PAGE_SCROLL),
492                 ExitPeekedTo.CLOSED_BASE_PAGE_SCROLL);
codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BASE_PAGE_TAP), ExitPeekedTo.CLOSED_TEXT_SELECT_TAP)493         codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BASE_PAGE_TAP),
494                 ExitPeekedTo.CLOSED_TEXT_SELECT_TAP);
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_TAP), ExitPeekedTo.PEEKED_TEXT_SELECT_TAP)495         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_TAP),
496                 ExitPeekedTo.PEEKED_TEXT_SELECT_TAP);
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_LONG_PRESS), ExitPeekedTo.PEEKED_TEXT_SELECT_LONG_PRESS)497         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.TEXT_SELECT_LONG_PRESS),
498                 ExitPeekedTo.PEEKED_TEXT_SELECT_LONG_PRESS);
codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SEARCH_BAR_TAP), ExitPeekedTo.EXPANDED_SEARCH_BAR_TAP)499         codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SEARCH_BAR_TAP),
500                 ExitPeekedTo.EXPANDED_SEARCH_BAR_TAP);
codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SWIPE), ExitPeekedTo.EXPANDED_SWIPE)501         codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SWIPE),
502                 ExitPeekedTo.EXPANDED_SWIPE);
codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.FLING), ExitPeekedTo.EXPANDED_FLING)503         codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.FLING),
504                 ExitPeekedTo.EXPANDED_FLING);
codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SWIPE), ExitPeekedTo.MAXIMIZED_SWIPE)505         codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SWIPE),
506                 ExitPeekedTo.MAXIMIZED_SWIPE);
codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.FLING), ExitPeekedTo.MAXIMIZED_FLING)507         codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.FLING),
508                 ExitPeekedTo.MAXIMIZED_FLING);
509         EXIT_PEEKED_TO_STATE_CHANGE_CODES = Collections.unmodifiableMap(codes);
510     }
511 
512     // Exit code map: first exit from EXPANDED.
513     private static final Map<StateChangeKey, Integer> EXIT_EXPANDED_TO_STATE_CHANGE_CODES;
514     static {
515         Map<StateChangeKey, Integer> codes = new HashMap<StateChangeKey, Integer>();
codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BACK_PRESS), ExitExpandedTo.CLOSED_BACK_PRESS)516         codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BACK_PRESS),
517                 ExitExpandedTo.CLOSED_BACK_PRESS);
codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BASE_PAGE_TAP), ExitExpandedTo.CLOSED_BASE_PAGE_TAP)518         codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BASE_PAGE_TAP),
519                 ExitExpandedTo.CLOSED_BASE_PAGE_TAP);
codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.FLING), ExitExpandedTo.CLOSED_FLING)520         codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.FLING),
521                 ExitExpandedTo.CLOSED_FLING);
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SEARCH_BAR_TAP), ExitExpandedTo.PEEKED_SEARCH_BAR_TAP)522         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SEARCH_BAR_TAP),
523                 ExitExpandedTo.PEEKED_SEARCH_BAR_TAP);
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SWIPE), ExitExpandedTo.PEEKED_SWIPE)524         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SWIPE),
525                 ExitExpandedTo.PEEKED_SWIPE);
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.FLING), ExitExpandedTo.PEEKED_FLING)526         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.FLING),
527                 ExitExpandedTo.PEEKED_FLING);
codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SWIPE), ExitExpandedTo.MAXIMIZED_SWIPE)528         codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SWIPE),
529                 ExitExpandedTo.MAXIMIZED_SWIPE);
codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.FLING), ExitExpandedTo.MAXIMIZED_FLING)530         codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.FLING),
531                 ExitExpandedTo.MAXIMIZED_FLING);
codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SERP_NAVIGATION), ExitExpandedTo.MAXIMIZED_SERP_NAVIGATION)532         codes.put(new StateChangeKey(PanelState.MAXIMIZED, StateChangeReason.SERP_NAVIGATION),
533                 ExitExpandedTo.MAXIMIZED_SERP_NAVIGATION);
534         EXIT_EXPANDED_TO_STATE_CHANGE_CODES = Collections.unmodifiableMap(codes);
535     }
536 
537     // Exit code map: first exit from MAXIMIZED.
538     private static final Map<StateChangeKey, Integer> EXIT_MAXIMIZED_TO_STATE_CHANGE_CODES;
539     static {
540         Map<StateChangeKey, Integer> codes = new HashMap<StateChangeKey, Integer>();
codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BACK_PRESS), ExitMaximizedTo.CLOSED_BACK_PRESS)541         codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.BACK_PRESS),
542                 ExitMaximizedTo.CLOSED_BACK_PRESS);
codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.FLING), ExitMaximizedTo.CLOSED_FLING)543         codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.FLING),
544                 ExitMaximizedTo.CLOSED_FLING);
codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.TAB_PROMOTION), ExitMaximizedTo.CLOSED_TAB_PROMOTION)545         codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.TAB_PROMOTION),
546                 ExitMaximizedTo.CLOSED_TAB_PROMOTION);
codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.SERP_NAVIGATION), ExitMaximizedTo.CLOSED_SERP_NAVIGATION)547         codes.put(new StateChangeKey(PanelState.CLOSED, StateChangeReason.SERP_NAVIGATION),
548                 ExitMaximizedTo.CLOSED_SERP_NAVIGATION);
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SWIPE), ExitMaximizedTo.PEEKED_SWIPE)549         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.SWIPE),
550                 ExitMaximizedTo.PEEKED_SWIPE);
codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.FLING), ExitMaximizedTo.PEEKED_FLING)551         codes.put(new StateChangeKey(PanelState.PEEKED, StateChangeReason.FLING),
552                 ExitMaximizedTo.PEEKED_FLING);
codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SWIPE), ExitMaximizedTo.EXPANDED_SWIPE)553         codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.SWIPE),
554                 ExitMaximizedTo.EXPANDED_SWIPE);
codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.FLING), ExitMaximizedTo.EXPANDED_FLING)555         codes.put(new StateChangeKey(PanelState.EXPANDED, StateChangeReason.FLING),
556                 ExitMaximizedTo.EXPANDED_FLING);
557         EXIT_MAXIMIZED_TO_STATE_CHANGE_CODES = Collections.unmodifiableMap(codes);
558     }
559 
560     // "Seen by gesture" code map: logged on first exit from expanded panel, or promo,
561     // broken down by gesture.
562     private static final Map<Pair<Boolean, Boolean>, Integer> SEEN_BY_GESTURE_CODES;
563     static {
564         final boolean unseen = false;
565         final boolean seen = true;
566         Map<Pair<Boolean, Boolean>, Integer> codes = new HashMap<Pair<Boolean, Boolean>, Integer>();
codes.put(new Pair<Boolean, Boolean>(seen, TAP), ResultsByGesture.SEEN_FROM_TAP)567         codes.put(new Pair<Boolean, Boolean>(seen, TAP), ResultsByGesture.SEEN_FROM_TAP);
codes.put(new Pair<Boolean, Boolean>(unseen, TAP), ResultsByGesture.NOT_SEEN_FROM_TAP)568         codes.put(new Pair<Boolean, Boolean>(unseen, TAP), ResultsByGesture.NOT_SEEN_FROM_TAP);
codes.put(new Pair<Boolean, Boolean>(seen, LONG_PRESS), ResultsByGesture.SEEN_FROM_LONG_PRESS)569         codes.put(new Pair<Boolean, Boolean>(seen, LONG_PRESS),
570                 ResultsByGesture.SEEN_FROM_LONG_PRESS);
codes.put(new Pair<Boolean, Boolean>(unseen, LONG_PRESS), ResultsByGesture.NOT_SEEN_FROM_LONG_PRESS)571         codes.put(new Pair<Boolean, Boolean>(unseen, LONG_PRESS),
572                 ResultsByGesture.NOT_SEEN_FROM_LONG_PRESS);
573         SEEN_BY_GESTURE_CODES = Collections.unmodifiableMap(codes);
574     }
575 
576     // "Promo outcome by gesture" code map: logged on exit from promo, broken down by gesture.
577     private static final Map<Pair<Integer, Boolean>, Integer> PROMO_BY_GESTURE_CODES;
578     static {
579         Map<Pair<Integer, Boolean>, Integer> codes =
580                 new HashMap<Pair<Integer, Boolean>, Integer>();
codes.put(new Pair<Integer, Boolean>(Preference.ENABLED, TAP), Promo.ENABLED_FROM_TAP)581         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)582         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)583         codes.put(new Pair<Integer, Boolean>(Preference.UNINITIALIZED, TAP),
584                 Promo.UNDECIDED_FROM_TAP);
codes.put(new Pair<Integer, Boolean>(Preference.ENABLED, LONG_PRESS), Promo.ENABLED_FROM_LONG_PRESS)585         codes.put(new Pair<Integer, Boolean>(Preference.ENABLED, LONG_PRESS),
586                 Promo.ENABLED_FROM_LONG_PRESS);
codes.put(new Pair<Integer, Boolean>(Preference.DISABLED, LONG_PRESS), Promo.DISABLED_FROM_LONG_PRESS)587         codes.put(new Pair<Integer, Boolean>(Preference.DISABLED, LONG_PRESS),
588                 Promo.DISABLED_FROM_LONG_PRESS);
codes.put(new Pair<Integer, Boolean>(Preference.UNINITIALIZED, LONG_PRESS), Promo.UNDECIDED_FROM_LONG_PRESS)589         codes.put(new Pair<Integer, Boolean>(Preference.UNINITIALIZED, LONG_PRESS),
590                 Promo.UNDECIDED_FROM_LONG_PRESS);
591         PROMO_BY_GESTURE_CODES = Collections.unmodifiableMap(codes);
592     }
593 
594     /**
595      * Logs the state of the Contextual Search preference. This function should be called if the
596      * Contextual Search feature is active, and will track the different preference settings
597      * (disabled, enabled or uninitialized). Calling more than once is fine.
598      */
logPreferenceState()599     public static void logPreferenceState() {
600         RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchPreferenceState",
601                 getPreferenceValue(), Preference.NUM_ENTRIES);
602     }
603 
604     /**
605      * Logs the given number of promo taps remaining.  Should be called only for users that
606      * are still undecided.
607      * @param promoTapsRemaining The number of taps remaining (should not be negative).
608      */
logPromoTapsRemaining(int promoTapsRemaining)609     public static void logPromoTapsRemaining(int promoTapsRemaining) {
610         if (promoTapsRemaining >= 0) {
611             RecordHistogram.recordCountHistogram("Search.ContextualSearchPromoTapsRemaining",
612                     promoTapsRemaining);
613         }
614     }
615 
616     /**
617      * Logs the historic number of times that a Tap gesture triggered the peeking promo
618      * for users that have never opened the panel.  This should be called periodically for
619      * undecided users only.
620      * @param promoTaps The historic number of taps that have caused the peeking bar for the promo,
621      *        for users that have never opened the panel.
622      */
logPromoTapsForNeverOpened(int promoTaps)623     public static void logPromoTapsForNeverOpened(int promoTaps) {
624         RecordHistogram.recordCountHistogram("Search.ContextualSearchPromoTapsForNeverOpened",
625                 promoTaps);
626     }
627 
628     /**
629      * Logs the historic number of times that a Tap gesture triggered the peeking promo before
630      * the user ever opened the panel.  This should be called periodically for all users.
631      * @param promoTaps The historic number of taps that have caused the peeking bar for the promo
632      *        before the first open of the panel, for all users that have ever opened the panel.
633      */
logPromoTapsBeforeFirstOpen(int promoTaps)634     public static void logPromoTapsBeforeFirstOpen(int promoTaps) {
635         RecordHistogram.recordCountHistogram("Search.ContextualSearchPromoTapsBeforeFirstOpen",
636                 promoTaps);
637     }
638 
639     /**
640      * Records the total count of times the promo panel has *ever* been opened.  This should only
641      * be called when the user is still undecided.
642      * @param count The total historic count of times the panel has ever been opened for the
643      *        current user.
644      */
logPromoOpenCount(int count)645     public static void logPromoOpenCount(int count) {
646         RecordHistogram.recordCountHistogram("Search.ContextualSearchPromoOpenCount", count);
647     }
648 
649     /**
650      * Logs the number of taps that have been counted since the user last opened the panel, for
651      * undecided users.
652      * @param tapsSinceOpen The number of taps to log.
653      */
logTapsSinceOpenForUndecided(int tapsSinceOpen)654     public static void logTapsSinceOpenForUndecided(int tapsSinceOpen) {
655         RecordHistogram.recordCountHistogram("Search.ContextualSearchTapsSinceOpenUndecided",
656                 tapsSinceOpen);
657     }
658 
659     /**
660      * Logs the number of taps that have been counted since the user last opened the panel, for
661      * decided users.
662      * @param tapsSinceOpen The number of taps to log.
663      */
logTapsSinceOpenForDecided(int tapsSinceOpen)664     public static void logTapsSinceOpenForDecided(int tapsSinceOpen) {
665         RecordHistogram.recordCountHistogram("Search.ContextualSearchTapsSinceOpenDecided",
666                 tapsSinceOpen);
667     }
668 
669     /**
670      * Logs whether the Search Term was single or multiword.
671      * @param isSingleWord Whether the resolved search term is a single word or not.
672      */
logSearchTermResolvedWords(boolean isSingleWord)673     public static void logSearchTermResolvedWords(boolean isSingleWord) {
674         RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchResolvedTermWords",
675                 isSingleWord ? ResolvedGranularity.SINGLE_WORD : ResolvedGranularity.MULTI_WORD,
676                 ResolvedGranularity.NUM_ENTRIES);
677     }
678 
679     /**
680      * Logs whether the base page was using the HTTP protocol or not.
681      * @param isHttpBasePage Whether the base page was using the HTTP protocol or not (should
682      *        be false for HTTPS or other URIs).
683      */
logBasePageProtocol(boolean isHttpBasePage)684     public static void logBasePageProtocol(boolean isHttpBasePage) {
685         RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchBasePageProtocol",
686                 isHttpBasePage ? Protocol.IS_HTTP : Protocol.NOT_HTTP, Protocol.NUM_ENTRIES);
687     }
688 
689     /**
690      * Logs changes to the Contextual Search preference, aside from those resulting from the first
691      * run flow.
692      * @param enabled Whether the preference is being enabled or disabled.
693      */
logPreferenceChange(boolean enabled)694     public static void logPreferenceChange(boolean enabled) {
695         RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchPreferenceStateChange",
696                 enabled ? Preference.ENABLED : Preference.DISABLED, Preference.NUM_ENTRIES);
697     }
698 
699     /**
700      * Logs the outcome of the Promo.
701      * Logs multiple histograms; with and without the originating gesture.
702      * @param wasTap Whether the gesture that originally caused the panel to show was a Tap.
703      * @param wasMandatory Whether the Promo was mandatory.
704      */
logPromoOutcome(boolean wasTap, boolean wasMandatory)705     public static void logPromoOutcome(boolean wasTap, boolean wasMandatory) {
706         int preferenceCode = getPreferenceValue();
707         RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchFirstRunFlowOutcome",
708                 preferenceCode, Preference.NUM_ENTRIES);
709 
710         int preferenceByGestureCode = getPromoByGestureStateCode(preferenceCode, wasTap);
711         if (wasMandatory) {
712             RecordHistogram.recordEnumeratedHistogram(
713                     "Search.ContextualSearchMandatoryPromoOutcomeByGesture",
714                     preferenceByGestureCode, Promo.NUM_ENTRIES);
715         } else {
716             RecordHistogram.recordEnumeratedHistogram(
717                     "Search.ContextualSearchPromoOutcomeByGesture", preferenceByGestureCode,
718                     Promo.NUM_ENTRIES);
719         }
720     }
721 
722     /**
723      * Logs the duration of a Contextual Search panel being viewed by the user.
724      * @param wereResultsSeen Whether search results were seen.
725      * @param isChained Whether the Contextual Search ended with the start of another.
726      * @param durationMs The duration of the contextual search in milliseconds.
727      */
logDuration(boolean wereResultsSeen, boolean isChained, long durationMs)728     public static void logDuration(boolean wereResultsSeen, boolean isChained, long durationMs) {
729         if (wereResultsSeen) {
730             RecordHistogram.recordTimesHistogram("Search.ContextualSearchDurationSeen", durationMs);
731         } else if (isChained) {
732             RecordHistogram.recordTimesHistogram(
733                     "Search.ContextualSearchDurationUnseenChained", durationMs);
734         } else {
735             RecordHistogram.recordTimesHistogram(
736                     "Search.ContextualSearchDurationUnseen", durationMs);
737         }
738     }
739 
740     /**
741      * Logs the duration from starting a search until the Search Term is resolved.
742      * @param durationMs The duration to record.
743      */
logSearchTermResolutionDuration(long durationMs)744     public static void logSearchTermResolutionDuration(long durationMs) {
745         RecordHistogram.recordMediumTimesHistogram(
746                 "Search.ContextualSearchResolutionDuration", durationMs);
747     }
748 
749     /**
750      * Logs the duration from starting a prefetched search until the panel navigates to the results
751      * and they start becoming viewable. Should be called only for searches that are prefetched.
752      * @param durationMs The duration to record.
753      * @param didResolve Whether a Search Term resolution was required as part of the loading.
754      */
logPrefetchedSearchNavigatedDuration(long durationMs, boolean didResolve)755     public static void logPrefetchedSearchNavigatedDuration(long durationMs, boolean didResolve) {
756         String histogramName = didResolve ? "Search.ContextualSearchResolvedSearchDuration"
757                                           : "Search.ContextualSearchLiteralSearchDuration";
758         RecordHistogram.recordMediumTimesHistogram(histogramName, durationMs);
759     }
760 
761     /**
762      * Logs the duration from opening the panel beyond peek until the panel is closed.
763      * @param durationMs The duration to record.
764      */
logPanelOpenDuration(long durationMs)765     public static void logPanelOpenDuration(long durationMs) {
766         RecordHistogram.recordMediumTimesHistogram(
767                 "Search.ContextualSearchPanelOpenDuration", durationMs);
768     }
769 
770     /**
771      * When Contextual Search panel is opened, logs whether In-Product Help for opening the panel
772      * was ever shown.
773      * @param wasIPHShown Whether In-Product help was shown.
774      */
logPanelOpenedIPH(boolean wasIPHShown)775     public static void logPanelOpenedIPH(boolean wasIPHShown) {
776         RecordHistogram.recordBooleanHistogram(
777                 "Search.ContextualSearchPanelOpenedIPHShown", wasIPHShown);
778     }
779 
780     /**
781      * When Contextual Search panel is opened, logs whether In-Product Help for Contextual Search
782      * was ever shown.
783      * @param wasIPHShown Whether In-Product help was shown.
784      */
logContextualSearchIPH(boolean wasIPHShown)785     public static void logContextualSearchIPH(boolean wasIPHShown) {
786         RecordHistogram.recordBooleanHistogram("Search.ContextualSearchIPHShown", wasIPHShown);
787     }
788 
789     /**
790      * When Contextual Search is triggered by tapping, logs whether In-Product Help for tapping was
791      * ever shown.
792      * @param wasIPHShown Whether In-Product help was shown.
793      */
logTapIPH(boolean wasIPHShown)794     public static void logTapIPH(boolean wasIPHShown) {
795         RecordHistogram.recordBooleanHistogram("Search.ContextualSearchTapIPHShown", wasIPHShown);
796     }
797 
798     /**
799      * Logs whether we have ever shown an In-Product Help for Translations suggesting that the user
800      * Opt-in.
801      * @param wasIPHShown Whether In-Product help was shown.
802      */
logTranslationsOptInIPHShown(boolean wasIPHShown)803     public static void logTranslationsOptInIPHShown(boolean wasIPHShown) {
804         RecordHistogram.recordBooleanHistogram(
805                 "Search.ContextualSearch.TranslationsOptInIPHShown", wasIPHShown);
806     }
807 
808     /**
809      * Logs whether the user actually did opt-in after seeing the In-Product Help for Translations
810      * suggesting that the user should Opt-in.
811      * @param didOptIn Whether the user did opt-in.
812      */
logTranslationsOptInIPHWorked(boolean didOptIn)813     public static void logTranslationsOptInIPHWorked(boolean didOptIn) {
814         RecordHistogram.recordBooleanHistogram(
815                 "Search.ContextualSearch.TranslationsOptInIPHWorked", didOptIn);
816     }
817 
818     /**
819      * Logs a user action for the duration of viewing the panel that describes the amount of time
820      * the user viewed the bar and panel overall.
821      * @param durationMs The duration to record.
822      */
logPanelViewDurationAction(long durationMs)823     public static void logPanelViewDurationAction(long durationMs) {
824         if (durationMs < DateUtils.SECOND_IN_MILLIS) {
825             RecordUserAction.record("ContextualSearch.ViewLessThanOneSecond");
826         } else if (durationMs < DateUtils.SECOND_IN_MILLIS * 3) {
827             RecordUserAction.record("ContextualSearch.ViewOneToThreeSeconds");
828         } else if (durationMs < DateUtils.SECOND_IN_MILLIS * 10) {
829             RecordUserAction.record("ContextualSearch.ViewThreeToTenSeconds");
830         } else {
831             RecordUserAction.record("ContextualSearch.ViewMoreThanTenSeconds");
832         }
833     }
834 
835     /**
836      * Logs whether the promo was seen.
837      * Logs multiple histograms, with and without the original triggering gesture.
838      * @param wasPanelSeen Whether the panel was seen.
839      * @param wasTap Whether the gesture that originally caused the panel to show was a Tap.
840      */
logPromoSeen(boolean wasPanelSeen, boolean wasTap)841     public static void logPromoSeen(boolean wasPanelSeen, boolean wasTap) {
842         RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchFirstRunPanelSeen",
843                 wasPanelSeen ? Results.SEEN : Results.NOT_SEEN, Results.NUM_ENTRIES);
844         logHistogramByGesture(wasPanelSeen, wasTap, "Search.ContextualSearchPromoSeenByGesture");
845     }
846 
847     /**
848      * Logs whether search results were seen.
849      * Logs multiple histograms; with and without the original triggering gesture.
850      * @param wasPanelSeen Whether the panel was seen.
851      * @param wasTap Whether the gesture that originally caused the panel to show was a Tap.
852      */
logResultsSeen(boolean wasPanelSeen, boolean wasTap)853     public static void logResultsSeen(boolean wasPanelSeen, boolean wasTap) {
854         RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchResultsSeen",
855                 wasPanelSeen ? Results.SEEN : Results.NOT_SEEN, Results.NUM_ENTRIES);
856         logHistogramByGesture(wasPanelSeen, wasTap, "Search.ContextualSearchResultsSeenByGesture");
857     }
858 
859     /**
860      * Logs whether search results were seen for a Tap gesture, for all users and sync-enabled
861      * users. For sync-enabled users we log to a separate histogram for that sub-population in order
862      * to help validate the Ranker Tap Suppression model results (since they are trained on UKM data
863      * which approximately reflects this sync-enabled population).
864      * @param wasPanelSeen Whether the panel was seen.
865      */
logTapResultsSeen(boolean wasPanelSeen)866     public static void logTapResultsSeen(boolean wasPanelSeen) {
867         RecordHistogram.recordBooleanHistogram(
868                 "Search.ContextualSearch.Tap.ResultsSeen", wasPanelSeen);
869         if (AndroidSyncSettings.get().isSyncEnabled()) {
870             RecordHistogram.recordBooleanHistogram(
871                     "Search.ContextualSearch.Tap.SyncEnabled.ResultsSeen", wasPanelSeen);
872         }
873     }
874 
875     /**
876      * Logs whether search results were seen for all gestures.  Recorded for all users.
877      * @param wasPanelSeen Whether the panel was seen.
878      */
logAllResultsSeen(boolean wasPanelSeen)879     public static void logAllResultsSeen(boolean wasPanelSeen) {
880         RecordHistogram.recordBooleanHistogram(
881                 "Search.ContextualSearch.All.ResultsSeen", wasPanelSeen);
882     }
883 
884     /**
885      * Logs the whether the panel was seen and the type of the trigger and if Bar nearly overlapped.
886      * If the panel was seen, logs the duration of the panel view into a BarOverlap or BarNoOverlap
887      * duration histogram.
888      * @param wasPanelSeen Whether the panel was seen.
889      * @param wasTap Whether the gesture was a Tap or not.
890      * @param wasBarOverlap Whether the trigger location overlapped the Bar area.
891      */
logBarOverlapResultsSeen( boolean wasPanelSeen, boolean wasTap, boolean wasBarOverlap)892     public static void logBarOverlapResultsSeen(
893             boolean wasPanelSeen, boolean wasTap, boolean wasBarOverlap) {
894         RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchBarOverlapSeen",
895                 getBarOverlapEnum(wasBarOverlap, wasPanelSeen, wasTap),
896                 BarOverlapResults.NUM_ENTRIES);
897     }
898 
899     /**
900      * Logs the duration of the panel viewed in its Peeked state before being opened.
901      * @param wasBarOverlap Whether the trigger location overlapped the Bar area.
902      * @param panelPeekDurationMs The duration that the panel was peeking before being opened
903      *        by the user.
904      */
logBarOverlapPeekDuration(boolean wasBarOverlap, long panelPeekDurationMs)905     public static void logBarOverlapPeekDuration(boolean wasBarOverlap, long panelPeekDurationMs) {
906         String histogram = wasBarOverlap ? "Search.ContextualSearchBarOverlap.PeekDuration"
907                                          : "Search.ContextualSearchBarNoOverlap.PeekDuration";
908         RecordHistogram.recordMediumTimesHistogram(histogram, panelPeekDurationMs);
909     }
910 
911     /**
912      * Log whether the UX was suppressed due to Bar overlap.
913      * @param wasSuppressed Whether showing the UX was suppressed.
914      */
logBarOverlapSuppression(boolean wasSuppressed)915     public static void logBarOverlapSuppression(boolean wasSuppressed) {
916         RecordHistogram.recordBooleanHistogram("Search.ContextualSearchBarOverlap", wasSuppressed);
917     }
918 
919     /**
920      * Logs the length of the selection in two histograms, one when results were seen and one when
921      * results were not seen.
922      * @param wasPanelSeen Whether the panel was seen.
923      * @param selectionLength The length of the triggering selection.
924      */
logSelectionLengthResultsSeen(boolean wasPanelSeen, int selectionLength)925     public static void logSelectionLengthResultsSeen(boolean wasPanelSeen, int selectionLength) {
926         if (wasPanelSeen) {
927             RecordHistogram.recordSparseHistogram(
928                     "Search.ContextualSearchSelectionLengthSeen", selectionLength);
929         } else {
930             RecordHistogram.recordSparseHistogram(
931                     "Search.ContextualSearchSelectionLengthNotSeen", selectionLength);
932         }
933     }
934 
935     /**
936      * Log whether the UX was suppressed due to the selection length.
937      * @param wasSuppressed Whether showing the UX was suppressed due to selection length.
938      */
logSelectionLengthSuppression(boolean wasSuppressed)939     public static void logSelectionLengthSuppression(boolean wasSuppressed) {
940         RecordHistogram.recordBooleanHistogram(
941                 "Search.ContextualSearchSelectionLengthSuppression", wasSuppressed);
942     }
943 
944     /**
945      * Logs the location of a Tap and whether the panel was seen and the type of the
946      * trigger.
947      * @param wasPanelSeen Whether the panel was seen.
948      * @param wasTap Whether the gesture was a Tap or not.
949      * @param triggerLocationDps The trigger location from the top of the screen.
950      */
logScreenTopTapLocation( boolean wasPanelSeen, boolean wasTap, int triggerLocationDps)951     public static void logScreenTopTapLocation(
952             boolean wasPanelSeen, boolean wasTap, int triggerLocationDps) {
953         // We only log Tap locations for the screen top.
954         if (!wasTap) return;
955         String histogram = wasPanelSeen ? "Search.ContextualSearchTopLocationSeen"
956                                         : "Search.ContextualSearchTopLocationNotSeen";
957         int min = 1;
958         int max = 250;
959         int numBuckets = 50;
960         RecordHistogram.recordCustomCountHistogram(
961                 histogram, triggerLocationDps, min, max, numBuckets);
962     }
963 
964     /**
965      * Log whether the UX was suppressed due to a Tap too close to the screen top.
966      * @param wasSuppressed Whether showing the UX was suppressed.
967      */
logScreenTopTapSuppression(boolean wasSuppressed)968     public static void logScreenTopTapSuppression(boolean wasSuppressed) {
969         RecordHistogram.recordBooleanHistogram(
970                 "Search.ContextualSearchScreenTopSuppressed", wasSuppressed);
971     }
972 
973     /**
974      * Log whether results were seen due to a Tap that was allowed to override an ML suppression.
975      * @param wasSearchContentViewSeen If the panel was opened.
976      */
logSecondTapMlOverrideResultsSeen(boolean wasSearchContentViewSeen)977     static void logSecondTapMlOverrideResultsSeen(boolean wasSearchContentViewSeen) {
978         RecordHistogram.recordBooleanHistogram(
979                 "Search.ContextualSearchSecondTapMlOverrideSeen", wasSearchContentViewSeen);
980     }
981 
982     /**
983      * Logs whether results were seen based on the duration of the Tap, for both short and long
984      * durations.
985      * @param wasSearchContentViewSeen If the panel was opened.
986      * @param isTapShort Whether this tap was "short" in duration.
987      */
logTapDurationSeen(boolean wasSearchContentViewSeen, boolean isTapShort)988     public static void logTapDurationSeen(boolean wasSearchContentViewSeen, boolean isTapShort) {
989         if (isTapShort) {
990             RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchTapShortDurationSeen",
991                     wasSearchContentViewSeen ? Results.SEEN : Results.NOT_SEEN,
992                     Results.NUM_ENTRIES);
993         } else {
994             RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchTapLongDurationSeen",
995                     wasSearchContentViewSeen ? Results.SEEN : Results.NOT_SEEN,
996                     Results.NUM_ENTRIES);
997         }
998     }
999 
1000     /**
1001      * Logs the duration of a Tap in ms into custom histograms to profile the duration of seen
1002      * and not seen taps.
1003      * @param wasPanelSeen Whether the panel was seen.
1004      * @param durationMs The duration of the tap gesture.
1005      */
logTapDuration(boolean wasPanelSeen, int durationMs)1006     public static void logTapDuration(boolean wasPanelSeen, int durationMs) {
1007         int min = 1;
1008         int max = 1000;
1009         int numBuckets = 100;
1010 
1011         if (wasPanelSeen) {
1012             RecordHistogram.recordCustomCountHistogram(
1013                     "Search.ContextualSearchTapDurationSeen", durationMs, min, max, numBuckets);
1014         } else {
1015             RecordHistogram.recordCustomCountHistogram(
1016                     "Search.ContextualSearchTapDurationNotSeen", durationMs, min, max, numBuckets);
1017         }
1018     }
1019 
1020     /**
1021      * Log whether results were seen due to a Tap on a short word.
1022      * @param wasSearchContentViewSeen If the panel was opened.
1023      * @param isTapOnShortWord Whether this tap was on a "short" word.
1024      */
logTapShortWordSeen( boolean wasSearchContentViewSeen, boolean isTapOnShortWord)1025     public static void logTapShortWordSeen(
1026             boolean wasSearchContentViewSeen, boolean isTapOnShortWord) {
1027         if (!isTapOnShortWord) return;
1028 
1029         // We just record CTR of short words.
1030         RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchTapShortWordSeen",
1031                 wasSearchContentViewSeen ? Results.SEEN : Results.NOT_SEEN, Results.NUM_ENTRIES);
1032     }
1033 
1034     /**
1035      * Log whether results were seen due to a Tap on a long word.
1036      * @param wasSearchContentViewSeen If the panel was opened.
1037      * @param isTapOnLongWord Whether this tap was on a long word.
1038      */
logTapLongWordSeen( boolean wasSearchContentViewSeen, boolean isTapOnLongWord)1039     public static void logTapLongWordSeen(
1040             boolean wasSearchContentViewSeen, boolean isTapOnLongWord) {
1041         if (!isTapOnLongWord) return;
1042 
1043         RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchTapLongWordSeen",
1044                 wasSearchContentViewSeen ? Results.SEEN : Results.NOT_SEEN, Results.NUM_ENTRIES);
1045     }
1046 
1047     /**
1048      * Log whether results were seen due to a Tap that was on the middle of a word.
1049      * @param wasSearchContentViewSeen If the panel was opened.
1050      * @param isTapOnWordMiddle Whether this tap was on the middle of a word.
1051      */
logTapOnWordMiddleSeen( boolean wasSearchContentViewSeen, boolean isTapOnWordMiddle)1052     public static void logTapOnWordMiddleSeen(
1053             boolean wasSearchContentViewSeen, boolean isTapOnWordMiddle) {
1054         if (!isTapOnWordMiddle) return;
1055 
1056         // We just record CTR of words tapped in the "middle".
1057         RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchTapOnWordMiddleSeen",
1058                 wasSearchContentViewSeen ? Results.SEEN : Results.NOT_SEEN, Results.NUM_ENTRIES);
1059     }
1060 
1061     /**
1062      * Log whether results were seen due to a Tap on what we've recognized as a probable entity.
1063      * @param wasSearchContentViewSeen If the panel was opened.
1064      * @param isWordAnEntity Whether this tap was on a word that's an entity.
1065      */
logTapOnEntitySeen( boolean wasSearchContentViewSeen, boolean isWordAnEntity)1066     public static void logTapOnEntitySeen(
1067             boolean wasSearchContentViewSeen, boolean isWordAnEntity) {
1068         if (isWordAnEntity) {
1069             // We just record CTR of probable entity words.
1070             RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchEntitySeen",
1071                     wasSearchContentViewSeen ? Results.SEEN : Results.NOT_SEEN,
1072                     Results.NUM_ENTRIES);
1073         }
1074     }
1075 
1076     /**
1077      * Logs whether results were seen and whether any tap suppression heuristics were satisfied.
1078      * @param wasSearchContentViewSeen If the panel was opened.
1079      * @param wasAnySuppressionHeuristicSatisfied Whether any of the implemented suppression
1080      *                                            heuristics were satisfied.
1081      */
logAnyTapSuppressionHeuristicSatisfied(boolean wasSearchContentViewSeen, boolean wasAnySuppressionHeuristicSatisfied)1082     public static void logAnyTapSuppressionHeuristicSatisfied(boolean wasSearchContentViewSeen,
1083             boolean wasAnySuppressionHeuristicSatisfied) {
1084         int code;
1085         if (wasAnySuppressionHeuristicSatisfied) {
1086             code = wasSearchContentViewSeen
1087                     ? ResultsBySuppression.SEEN_SUPPRESSION_HEURSTIC_SATISFIED
1088                     : ResultsBySuppression.NOT_SEEN_SUPPRESSION_HEURSTIC_SATISFIED;
1089         } else {
1090             code = wasSearchContentViewSeen
1091                     ? ResultsBySuppression.SEEN_SUPPRESSION_HEURSTIC_NOT_SATISFIED
1092                     : ResultsBySuppression.NOT_SEEN_SUPPRESSION_HEURSTIC_NOT_SATISFIED;
1093         }
1094 
1095         RecordHistogram.recordEnumeratedHistogram(
1096                 "Search.ContextualSearchTapSuppressionSeen.AnyHeuristicSatisfied", code,
1097                 ResultsBySuppression.NUM_ENTRIES);
1098     }
1099 
1100     /**
1101      * Logs whether a selection is valid.
1102      * @param isSelectionValid Whether the selection is valid.
1103      */
logSelectionIsValid(boolean isSelectionValid)1104     public static void logSelectionIsValid(boolean isSelectionValid) {
1105         RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchSelectionValid",
1106                 isSelectionValid ? Selection.VALID : Selection.INVALID, Selection.NUM_ENTRIES);
1107     }
1108 
1109     /**
1110      * Logs whether a normal priority search request failed.
1111      * @param isFailure Whether the request failed.
1112      */
logNormalPrioritySearchRequestOutcome(boolean isFailure)1113     public static void logNormalPrioritySearchRequestOutcome(boolean isFailure) {
1114         RecordHistogram.recordEnumeratedHistogram(
1115                 "Search.ContextualSearchNormalPrioritySearchRequestStatus",
1116                 isFailure ? Request.FAILED : Request.NOT_FAILED, Request.NUM_ENTRIES);
1117     }
1118 
1119     /**
1120      * Logs whether a low priority search request failed.
1121      * @param isFailure Whether the request failed.
1122      */
logLowPrioritySearchRequestOutcome(boolean isFailure)1123     public static void logLowPrioritySearchRequestOutcome(boolean isFailure) {
1124         RecordHistogram.recordEnumeratedHistogram(
1125                 "Search.ContextualSearchLowPrioritySearchRequestStatus",
1126                 isFailure ? Request.FAILED : Request.NOT_FAILED, Request.NUM_ENTRIES);
1127     }
1128 
1129     /**
1130      * Logs whether a fallback search request failed.
1131      * @param isFailure Whether the request failed.
1132      */
logFallbackSearchRequestOutcome(boolean isFailure)1133     public static void logFallbackSearchRequestOutcome(boolean isFailure) {
1134         RecordHistogram.recordEnumeratedHistogram(
1135                 "Search.ContextualSearchFallbackSearchRequestStatus",
1136                 isFailure ? Request.FAILED : Request.NOT_FAILED, Request.NUM_ENTRIES);
1137     }
1138 
1139     /**
1140      * Log whether the UX was suppressed by a recent scroll.
1141      * @param wasSuppressed Whether showing the UX was suppressed by a recent scroll.
1142      */
logRecentScrollSuppression(boolean wasSuppressed)1143     public static void logRecentScrollSuppression(boolean wasSuppressed) {
1144         RecordHistogram.recordBooleanHistogram(
1145                 "Search.ContextualSearchRecentScrollSuppression", wasSuppressed);
1146     }
1147 
1148     /**
1149      * Logs the duration between the panel being triggered due to a tap and the panel being
1150      * dismissed due to a scroll.
1151      * @param durationSincePanelTriggerMs The amount of time between the panel getting triggered and
1152      *                                    the panel being dismissed due to a scroll.
1153      * @param wasSearchContentViewSeen If the panel was opened.
1154      */
logDurationBetweenTriggerAndScroll( long durationSincePanelTriggerMs, boolean wasSearchContentViewSeen)1155     public static void logDurationBetweenTriggerAndScroll(
1156             long durationSincePanelTriggerMs, boolean wasSearchContentViewSeen) {
1157         String histogram = wasSearchContentViewSeen
1158                 ? "Search.ContextualSearchDurationBetweenTriggerAndScrollSeen"
1159                 : "Search.ContextualSearchDurationBetweenTriggerAndScrollNotSeen";
1160         if (durationSincePanelTriggerMs < 2000) {
1161             RecordHistogram.recordCustomCountHistogram(
1162                     histogram, (int) durationSincePanelTriggerMs, 1, 2000, 200);
1163         }
1164     }
1165 
1166     /**
1167      * Logs whether a Quick Answer caption was activated, and whether it was an answer (as opposed
1168      * to just being informative), and whether the panel was opened anyway.
1169      * Logged only for Tap events.
1170      * @param didActivate If the Quick Answer caption was shown.
1171      * @param didAnswer If the caption was considered an answer (reducing the need to open the
1172      *        panel).
1173      * @param wasSearchContentViewSeen If the panel was opened.
1174      */
logQuickAnswerSeen( boolean wasSearchContentViewSeen, boolean didActivate, boolean didAnswer)1175     static void logQuickAnswerSeen(
1176             boolean wasSearchContentViewSeen, boolean didActivate, boolean didAnswer) {
1177         RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchQuickAnswerSeen",
1178                 getQuickAnswerSeenValue(didActivate, didAnswer, wasSearchContentViewSeen),
1179                 QuickAnswerSeen.NUM_ENTRIES);
1180     }
1181 
1182     /**
1183      * Logs how a state was entered for the first time within a Contextual Search.
1184      * @param fromState The state to transition from.
1185      * @param toState The state to transition to.
1186      * @param reason The reason for the state transition.
1187      */
logFirstStateEntry( @anelState int fromState, @PanelState int toState, @StateChangeReason int reason)1188     public static void logFirstStateEntry(
1189             @PanelState int fromState, @PanelState int toState, @StateChangeReason int reason) {
1190         int code;
1191         switch (toState) {
1192             case PanelState.CLOSED:
1193                 code = getStateChangeCode(
1194                         fromState, reason, ENTER_CLOSED_STATE_CHANGE_CODES, EnterClosedFrom.OTHER);
1195                 RecordHistogram.recordEnumeratedHistogram(
1196                         "Search.ContextualSearchEnterClosed", code, EnterClosedFrom.NUM_ENTRIES);
1197                 break;
1198             case PanelState.PEEKED:
1199                 code = getStateChangeCode(
1200                         fromState, reason, ENTER_PEEKED_STATE_CHANGE_CODES, EnterPeekedFrom.OTHER);
1201                 RecordHistogram.recordEnumeratedHistogram(
1202                         "Search.ContextualSearchEnterPeeked", code, EnterPeekedFrom.NUM_ENTRIES);
1203                 break;
1204             case PanelState.EXPANDED:
1205                 code = getStateChangeCode(fromState, reason, ENTER_EXPANDED_STATE_CHANGE_CODES,
1206                         EnterExpandedFrom.OTHER);
1207                 RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchEnterExpanded",
1208                         code, EnterExpandedFrom.NUM_ENTRIES);
1209                 break;
1210             case PanelState.MAXIMIZED:
1211                 code = getStateChangeCode(fromState, reason, ENTER_MAXIMIZED_STATE_CHANGE_CODES,
1212                         EnterMaximizedFrom.OTHER);
1213                 RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchEnterMaximized",
1214                         code, EnterMaximizedFrom.NUM_ENTRIES);
1215                 break;
1216             default:
1217                 break;
1218         }
1219     }
1220 
1221     /**
1222      * Logs a user action for a change to the Panel state, which allows sequencing of actions.
1223      * @param toState The state to transition to.
1224      * @param reason The reason for the state transition.
1225      */
logPanelStateUserAction( @anelState int toState, @StateChangeReason int reason)1226     public static void logPanelStateUserAction(
1227             @PanelState int toState, @StateChangeReason int reason) {
1228         switch (toState) {
1229             case PanelState.CLOSED:
1230                 if (reason == StateChangeReason.BACK_PRESS) {
1231                     RecordUserAction.record("ContextualSearch.BackPressClose");
1232                 } else if (reason == StateChangeReason.CLOSE_BUTTON) {
1233                     RecordUserAction.record("ContextualSearch.CloseButtonClose");
1234                 } else if (reason == StateChangeReason.SWIPE || reason == StateChangeReason.FLING) {
1235                     RecordUserAction.record("ContextualSearch.SwipeOrFlingClose");
1236                 } else if (reason == StateChangeReason.TAB_PROMOTION) {
1237                     RecordUserAction.record("ContextualSearch.TabPromotionClose");
1238                 } else if (reason == StateChangeReason.BASE_PAGE_TAP) {
1239                     RecordUserAction.record("ContextualSearch.BasePageTapClose");
1240                 } else if (reason == StateChangeReason.BASE_PAGE_SCROLL) {
1241                     RecordUserAction.record("ContextualSearch.BasePageScrollClose");
1242                 } else if (reason == StateChangeReason.SEARCH_BAR_TAP) {
1243                     RecordUserAction.record("ContextualSearch.SearchBarTapClose");
1244                 } else if (reason == StateChangeReason.SERP_NAVIGATION) {
1245                     RecordUserAction.record("ContextualSearch.NavigationClose");
1246                 } else {
1247                     RecordUserAction.record("ContextualSearch.UncommonClose");
1248                 }
1249                 break;
1250             case PanelState.PEEKED:
1251                 if (reason == StateChangeReason.TEXT_SELECT_TAP) {
1252                     RecordUserAction.record("ContextualSearch.TapPeek");
1253                 } else if (reason == StateChangeReason.SWIPE || reason == StateChangeReason.FLING) {
1254                     RecordUserAction.record("ContextualSearch.SwipeOrFlingPeek");
1255                 } else if (reason == StateChangeReason.TEXT_SELECT_LONG_PRESS) {
1256                     RecordUserAction.record("ContextualSearch.LongpressPeek");
1257                 }
1258                 break;
1259             case PanelState.EXPANDED:
1260                 if (reason == StateChangeReason.SWIPE || reason == StateChangeReason.FLING) {
1261                     RecordUserAction.record("ContextualSearch.SwipeOrFlingExpand");
1262                 } else if (reason == StateChangeReason.SEARCH_BAR_TAP) {
1263                     RecordUserAction.record("ContextualSearch.SearchBarTapExpand");
1264                 }
1265                 break;
1266             case PanelState.MAXIMIZED:
1267                 if (reason == StateChangeReason.SWIPE || reason == StateChangeReason.FLING) {
1268                     RecordUserAction.record("ContextualSearch.SwipeOrFlingMaximize");
1269                 } else if (reason == StateChangeReason.SERP_NAVIGATION) {
1270                     RecordUserAction.record("ContextualSearch.NavigationMaximize");
1271                 }
1272                 break;
1273             default:
1274                 break;
1275         }
1276     }
1277 
1278     /**
1279      * Logs that the user established a new selection when Contextual Search is active.
1280      */
logSelectionEstablished()1281     public static void logSelectionEstablished() {
1282         RecordUserAction.record("ContextualSearch.SelectionEstablished");
1283     }
1284 
1285     /**
1286      * Logs that the user manually adjusted a selection when Contextual Search is active.
1287      * @param selection The new selection.
1288      */
logSelectionAdjusted(@ullable String selection)1289     public static void logSelectionAdjusted(@Nullable String selection) {
1290         if (TextUtils.isEmpty(selection)) return;
1291 
1292         boolean isSingleWord = !CONTAINS_WHITESPACE_PATTERN.matcher(selection.trim()).find();
1293         if (isSingleWord) {
1294             RecordUserAction.record("ContextualSearch.ManualRefineSingleWord");
1295         } else {
1296             RecordUserAction.record("ContextualSearch.ManualRefineMultiWord");
1297         }
1298     }
1299 
1300     /**
1301      * Logs that the system automatically expanded the selection when a user triggered
1302      * Contextual Search on a multiword phrase that could be identified by the server.
1303      * @param fromTapGesture Whether the gesture that originally established the selection
1304      *        was Tap.
1305      */
logSelectionExpanded(boolean fromTapGesture)1306     public static void logSelectionExpanded(boolean fromTapGesture) {
1307         RecordHistogram.recordBooleanHistogram(
1308                 "Search.ContextualSearch.SelectionExpanded", fromTapGesture);
1309     }
1310 
1311     /**
1312      * Logs that the system sent a server request to resolve the search term.
1313      * @param fromTapGesture Whether the gesture that originally established the selection
1314      *        was Tap.
1315      */
logResolveRequested(boolean fromTapGesture)1316     public static void logResolveRequested(boolean fromTapGesture) {
1317         RecordHistogram.recordBooleanHistogram(
1318                 "Search.ContextualSearch.ResolveRequested", fromTapGesture);
1319     }
1320 
1321     /**
1322      * Logs that the system received a server response from a resolve request.
1323      * @param fromTapGesture Whether the gesture that originally established the selection
1324      *        was Tap.
1325      */
logResolveReceived(boolean fromTapGesture)1326     public static void logResolveReceived(boolean fromTapGesture) {
1327         RecordHistogram.recordBooleanHistogram(
1328                 "Search.ContextualSearch.ResolveReceived", fromTapGesture);
1329     }
1330 
1331     /**
1332      * Logs that the user needs a translation of the selection. The user may or may not actually
1333      * see a translation - this only logs that it's needed.
1334      * @param fromTapGesture Whether the gesture that originally established the selection
1335      *        was Tap.
1336      */
logTranslationNeeded(boolean fromTapGesture)1337     public static void logTranslationNeeded(boolean fromTapGesture) {
1338         RecordHistogram.recordBooleanHistogram(
1339                 "Search.ContextualSearch.TranslationNeeded", fromTapGesture);
1340     }
1341 
1342     /**
1343      * Logs how a state was exited for the first time within a Contextual Search.
1344      * @param fromState The state to transition from.
1345      * @param toState The state to transition to.
1346      * @param reason The reason for the state transition.
1347      */
logFirstStateExit( @anelState int fromState, @PanelState int toState, @StateChangeReason int reason)1348     public static void logFirstStateExit(
1349             @PanelState int fromState, @PanelState int toState, @StateChangeReason int reason) {
1350         int code;
1351         switch (fromState) {
1352             case PanelState.UNDEFINED:
1353             case PanelState.CLOSED:
1354                 code = getStateChangeCode(
1355                         toState, reason, EXIT_CLOSED_TO_STATE_CHANGE_CODES, ExitClosedTo.OTHER);
1356                 RecordHistogram.recordEnumeratedHistogram(
1357                         "Search.ContextualSearchExitClosed", code, ExitClosedTo.NUM_ENTRIES);
1358                 break;
1359             case PanelState.PEEKED:
1360                 code = getStateChangeCode(
1361                         toState, reason, EXIT_PEEKED_TO_STATE_CHANGE_CODES, ExitPeekedTo.OTHER);
1362                 RecordHistogram.recordEnumeratedHistogram(
1363                         "Search.ContextualSearchExitPeeked", code, ExitPeekedTo.NUM_ENTRIES);
1364                 break;
1365             case PanelState.EXPANDED:
1366                 code = getStateChangeCode(
1367                         toState, reason, EXIT_EXPANDED_TO_STATE_CHANGE_CODES, ExitExpandedTo.OTHER);
1368                 RecordHistogram.recordEnumeratedHistogram(
1369                         "Search.ContextualSearchExitExpanded", code, ExitExpandedTo.NUM_ENTRIES);
1370                 break;
1371             case PanelState.MAXIMIZED:
1372                 code = getStateChangeCode(toState, reason, EXIT_MAXIMIZED_TO_STATE_CHANGE_CODES,
1373                         ExitMaximizedTo.OTHER);
1374                 RecordHistogram.recordEnumeratedHistogram(
1375                         "Search.ContextualSearchExitMaximized", code, ExitMaximizedTo.NUM_ENTRIES);
1376                 break;
1377             default:
1378                 break;
1379         }
1380     }
1381 
1382     /**
1383      * Logs the number of impressions and CTR for the previous week for the current user.
1384      * @param previousWeekImpressions The number of times the user saw the Contextual Search Bar.
1385      * @param previousWeekCtr The CTR expressed as a percentage.
1386      */
logPreviousWeekCtr(int previousWeekImpressions, int previousWeekCtr)1387     public static void logPreviousWeekCtr(int previousWeekImpressions, int previousWeekCtr) {
1388         RecordHistogram.recordCountHistogram(
1389                 "Search.ContextualSearchPreviousWeekImpressions", previousWeekImpressions);
1390         RecordHistogram.recordPercentageHistogram(
1391                 "Search.ContextualSearchPreviousWeekCtr", previousWeekCtr);
1392     }
1393 
1394     /**
1395      * Logs the number of impressions and CTR for previous 28-day period for the current user.
1396      * @param previous28DayImpressions The number of times the user saw the Contextual Search Bar.
1397      * @param previous28DayCtr The CTR expressed as a percentage.
1398      */
logPrevious28DayCtr(int previous28DayImpressions, int previous28DayCtr)1399     public static void logPrevious28DayCtr(int previous28DayImpressions, int previous28DayCtr) {
1400         RecordHistogram.recordCountHistogram(
1401                 "Search.ContextualSearchPrevious28DayImpressions", previous28DayImpressions);
1402         RecordHistogram.recordPercentageHistogram(
1403                 "Search.ContextualSearchPrevious28DayCtr", previous28DayCtr);
1404     }
1405 
1406     /**
1407      * Logs a duration since the outcomes (and associated timestamp) were saved in persistent
1408      * storage.
1409      * @param durationMs The duration to log, in milliseconds.
1410      */
logOutcomesTimestamp(long durationMs)1411     public static void logOutcomesTimestamp(long durationMs) {
1412         int durationInDays = (int) (durationMs / DateUtils.DAY_IN_MILLIS);
1413         RecordHistogram.recordCount100Histogram(
1414                 "Search.ContextualSearch.OutcomesDuration", durationInDays);
1415     }
1416 
1417     /**
1418      * Get the encoded value to use for the Bar Overlap histogram by encoding all the input
1419      * parameters.
1420      * @param didBarOverlap Whether the selection overlapped the Bar position.
1421      * @param wasPanelSeen Whether the panel content was seen.
1422      * @param wasTap Whether the gesture was a Tap.
1423      * @return The value for the enum histogram.
1424      */
getBarOverlapEnum( boolean didBarOverlap, boolean wasPanelSeen, boolean wasTap)1425     private static @BarOverlapResults int getBarOverlapEnum(
1426             boolean didBarOverlap, boolean wasPanelSeen, boolean wasTap) {
1427         if (wasTap) {
1428             if (didBarOverlap) {
1429                 return wasPanelSeen ? BarOverlapResults.BAR_OVERLAP_RESULTS_SEEN_FROM_TAP
1430                                     : BarOverlapResults.BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_TAP;
1431             } else {
1432                 return wasPanelSeen ? BarOverlapResults.NO_BAR_OVERLAP_RESULTS_SEEN_FROM_TAP
1433                                     : BarOverlapResults.NO_BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_TAP;
1434             }
1435         } else {
1436             if (didBarOverlap) {
1437                 return wasPanelSeen
1438                         ? BarOverlapResults.BAR_OVERLAP_RESULTS_SEEN_FROM_LONG_PRESS
1439                         : BarOverlapResults.BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_LONG_PRESS;
1440             } else {
1441                 return wasPanelSeen
1442                         ? BarOverlapResults.NO_BAR_OVERLAP_RESULTS_SEEN_FROM_LONG_PRESS
1443                         : BarOverlapResults.NO_BAR_OVERLAP_RESULTS_NOT_SEEN_FROM_LONG_PRESS;
1444             }
1445         }
1446     }
1447 
1448     /**
1449      * Logs whether Contextual Cards data was shown. Should be logged on tap if Contextual
1450      * Cards integration is enabled.
1451      * @param shown Whether Contextual Cards data was shown in the Bar.
1452      */
logContextualCardsDataShown(boolean shown)1453     public static void logContextualCardsDataShown(boolean shown) {
1454         RecordHistogram.recordBooleanHistogram(
1455                 "Search.ContextualSearchContextualCardsIntegration.DataShown", shown);
1456     }
1457 
1458     /**
1459      * Logs whether results were seen when Contextual Cards data was shown.
1460      * @param wasSeen Whether the search results were seen.
1461      */
logContextualCardsResultsSeen(boolean wasSeen)1462     public static void logContextualCardsResultsSeen(boolean wasSeen) {
1463         RecordHistogram.recordEnumeratedHistogram(
1464                 "Search.ContextualSearchContextualCardsIntegration.ResultsSeen",
1465                 wasSeen ? Results.SEEN : Results.NOT_SEEN, Results.NUM_ENTRIES);
1466     }
1467 
1468     /**
1469      * Logs whether a quick action intent resolved to zero, one, or many apps.
1470      * @param quickActionCategory The {@link QuickActionCategory} for the quick action.
1471      * @param numMatchingAppsApps The number of apps that the resolved intent matched.
1472      */
logQuickActionIntentResolution(int quickActionCategory, int numMatchingAppsApps)1473     public static void logQuickActionIntentResolution(int quickActionCategory,
1474             int numMatchingAppsApps) {
1475         int code = numMatchingAppsApps == 0
1476                 ? QuickActionResolve.FAILED
1477                 : numMatchingAppsApps == 1 ? QuickActionResolve.SINGLE
1478                                            : QuickActionResolve.MULTIPLE;
1479         RecordHistogram.recordEnumeratedHistogram(
1480                 "Search.ContextualSearchQuickActions.IntentResolution."
1481                         + getLabelForQuickActionCategory(quickActionCategory),
1482                 code, QuickActionResolve.NUM_ENTRIES);
1483     }
1484 
1485     /**
1486      * Logs whether a quick action was shown, and the quick aciton category if a quick action was
1487      * shown. Should be logged on tap if Contextual Search single actions are enabled.
1488      * @param quickActionShown Whether a quick action was shown.
1489      * @param quickActionCategory The {@link QuickActionCategory} for the quick action.
1490      */
logQuickActionShown(boolean quickActionShown, int quickActionCategory)1491     public static void logQuickActionShown(boolean quickActionShown, int quickActionCategory) {
1492         RecordHistogram.recordBooleanHistogram(
1493                 "Search.ContextualSearchQuickActions.Shown", quickActionShown);
1494         if (quickActionShown) {
1495             RecordHistogram.recordEnumeratedHistogram(
1496                     "Search.ContextualSearchQuickActions.Category", quickActionCategory,
1497                     QuickActionCategory.BOUNDARY);
1498         }
1499     }
1500 
1501     /**
1502      * Logs whether results were seen when a quick action was present.
1503      * @param wasSeen Whether the search results were seen.
1504      * @param quickActionCategory The {@link QuickActionCategory} for the quick action.
1505      */
logQuickActionResultsSeen(boolean wasSeen, int quickActionCategory)1506     public static void logQuickActionResultsSeen(boolean wasSeen, int quickActionCategory) {
1507         RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchQuickActions.ResultsSeen."
1508                         + getLabelForQuickActionCategory(quickActionCategory),
1509                 wasSeen ? Results.SEEN : Results.NOT_SEEN, Results.NUM_ENTRIES);
1510     }
1511 
1512     /**
1513      * Logs whether a quick action was clicked.
1514      * @param wasClicked Whether the quick action was clicked
1515      * @param quickActionCategory The {@link QuickActionCategory} for the quick action.
1516      */
logQuickActionClicked(boolean wasClicked, int quickActionCategory)1517     public static void logQuickActionClicked(boolean wasClicked, int quickActionCategory) {
1518         RecordHistogram.recordBooleanHistogram(
1519                 "Search.ContextualSearchQuickActions.Clicked."
1520                         + getLabelForQuickActionCategory(quickActionCategory),
1521                  wasClicked);
1522     }
1523 
1524     /**
1525      * Logs the primary CoCa {@link CardTag} for searches where the panel contents was seen,
1526      * including {@codeCardTag.CT_NONE} when no card or tag, and {@codeCardTag.CT_OTHER} when it's
1527      * one we do not recognize.
1528      * @param wasSearchContentViewSeen Whether the panel was seen.
1529      * @param cardTagEnum The primary CoCa card Tag for the result seen.
1530      */
logCardTagSeen(boolean wasSearchContentViewSeen, @CardTag int cardTagEnum)1531     public static void logCardTagSeen(boolean wasSearchContentViewSeen, @CardTag int cardTagEnum) {
1532         if (wasSearchContentViewSeen) {
1533             RecordHistogram.recordEnumeratedHistogram(
1534                     "Search.ContextualSearch.CardTagSeen", cardTagEnum, CardTag.NUM_ENTRIES);
1535         }
1536         RecordHistogram.recordEnumeratedHistogram(
1537                 "Search.ContextualSearch.CardTag", cardTagEnum, CardTag.NUM_ENTRIES);
1538     }
1539 
1540     /**
1541      * Logs results-seen when we have a useful Ranker prediction inference.
1542      * @param wasPanelSeen Whether the panel was seen.
1543      * @param predictionKind An integer reflecting the Ranker prediction, e.g. that this is a good
1544      *        time to suppress triggering because the likelihood of opening the panel is relatively
1545      *        low.
1546      */
logRankerInference( boolean wasPanelSeen, @AssistRankerPrediction int predictionKind)1547     public static void logRankerInference(
1548             boolean wasPanelSeen, @AssistRankerPrediction int predictionKind) {
1549         if (predictionKind == AssistRankerPrediction.SHOW) {
1550             RecordHistogram.recordEnumeratedHistogram(
1551                     "Search.ContextualSearch.Ranker.NotSuppressed.ResultsSeen",
1552                     wasPanelSeen ? Results.SEEN : Results.NOT_SEEN, Results.NUM_ENTRIES);
1553         } else if (predictionKind == AssistRankerPrediction.SUPPRESS) {
1554             RecordHistogram.recordEnumeratedHistogram(
1555                     "Search.ContextualSearch.Ranker.WouldSuppress.ResultsSeen",
1556                     wasPanelSeen ? Results.SEEN : Results.NOT_SEEN, Results.NUM_ENTRIES);
1557         }
1558     }
1559 
1560     /**
1561      * Logs Ranker's prediction of whether or not to suppress.
1562      * @param predictionKind An integer reflecting the Ranker prediction, e.g. that this is a good
1563      *        time to suppress triggering because the likelihood of opening the panel is relatively
1564      *        low.
1565      */
logRankerPrediction(@ssistRankerPrediction int predictionKind)1566     public static void logRankerPrediction(@AssistRankerPrediction int predictionKind) {
1567         // For now we just log whether or not suppression is predicted.
1568         RecordHistogram.recordBooleanHistogram("Search.ContextualSearch.Ranker.Suppressed",
1569                 predictionKind == AssistRankerPrediction.SUPPRESS);
1570     }
1571 
1572     /** Logs that Ranker recorded a set of features for training or inference. */
logRecordedFeaturesToRanker()1573     public static void logRecordedFeaturesToRanker() {
1574         logRecordedToRanker(false);
1575     }
1576 
1577     /** Logs that Ranker recorded a set of outcomes for training or inference. */
logRecordedOutcomesToRanker()1578     public static void logRecordedOutcomesToRanker() {
1579         logRecordedToRanker(true);
1580     }
1581 
1582     /**
1583      * Logs that Ranker recorded some data for training or inference.
1584      * @param areOutcomes Whether the data are outcomes.
1585      */
logRecordedToRanker(boolean areOutcomes)1586     private static void logRecordedToRanker(boolean areOutcomes) {
1587         RecordHistogram.recordBooleanHistogram(
1588                 "Search.ContextualSearch.Ranker.Recorded", areOutcomes);
1589     }
1590 
1591     /**
1592      * Logs that features or outcomes are available to record to Ranker.
1593      * This data can be used to correlate with #logRecordedToRanker to validate that everything that
1594      * should be recorded is actually being recorded.
1595      * @param areOutcomes Whether the features available are outcomes.
1596      */
logRankerFeaturesAvailable(boolean areOutcomes)1597     static void logRankerFeaturesAvailable(boolean areOutcomes) {
1598         RecordHistogram.recordBooleanHistogram(
1599                 "Search.ContextualSearch.Ranker.FeaturesAvailable", areOutcomes);
1600     }
1601 
1602     /**
1603      * Logs the previous enabled-state of this user before the feature was turned full-on for
1604      * Unified Consent (when integration is enabled).
1605      * @param wasPreviouslyUndecided Whether the user was previously undecided.
1606      */
logUnifiedConsentPreviousEnabledState(boolean wasPreviouslyUndecided)1607     static void logUnifiedConsentPreviousEnabledState(boolean wasPreviouslyUndecided) {
1608         RecordHistogram.recordBooleanHistogram(
1609                 "Search.ContextualSearch.UnifiedConsent.PreviouslyUndecided",
1610                 wasPreviouslyUndecided);
1611     }
1612 
1613     /**
1614      * Logs whether a request will be throttled for Unified Consent integration, for all requests
1615      * regardless of whether the integration feature is enabled.  Logged multiple times per request.
1616      * @param isRequestThrottled Whether the current request is being throttled.
1617      */
logUnifiedConsentThrottledRequests(boolean isRequestThrottled)1618     static void logUnifiedConsentThrottledRequests(boolean isRequestThrottled) {
1619         RecordHistogram.recordBooleanHistogram(
1620                 "Search.ContextualSearch.UnifiedConsent.ThrottledRequests", isRequestThrottled);
1621     }
1622 
1623     /**
1624      * Logs whether this user was eligible for throttling of requests when Unified Consent
1625      * integration is enabled and throttling is in effect.
1626      * @param isThrottleEligible Whether this user is eligible to be throttled.
1627      */
logUnifiedConsentThrottleEligible(boolean isThrottleEligible)1628     static void logUnifiedConsentThrottleEligible(boolean isThrottleEligible) {
1629         RecordHistogram.recordBooleanHistogram(
1630                 "Search.ContextualSearch.UnifiedConsent.ThrottleEligible", isThrottleEligible);
1631     }
1632 
1633     /**
1634      * Logs a histogram indicating which privacy permissions are available that Related Searches
1635      * cares about. This ignores any language constraint.
1636      * <p>This can be called multiple times for each user from any part of the code that's freqently
1637      * executed.
1638      * @param canSendUrl Whether this user has allowed sending page URL info to Google.
1639      * @param canSendContent Whether the user can send page content to Google (has accepted the
1640      *        Contextual Search opt-in).
1641      */
logRelatedSearchesPermissionsForAllUsers( boolean canSendUrl, boolean canSendContent)1642     static void logRelatedSearchesPermissionsForAllUsers(
1643             boolean canSendUrl, boolean canSendContent) {
1644         @Permissions
1645         int permissionsEnum;
1646         if (canSendUrl) {
1647             permissionsEnum =
1648                     canSendContent ? Permissions.SEND_URL_AND_CONTENT : Permissions.SEND_URL;
1649         } else {
1650             permissionsEnum = canSendContent ? Permissions.SEND_CONTENT : Permissions.SEND_NOTHING;
1651         }
1652         RecordHistogram.recordEnumeratedHistogram("Search.RelatedSearches.AllUserPermissions",
1653                 permissionsEnum, Permissions.NUM_ENTRIES);
1654     }
1655 
1656     /**
1657      * Logs a histogram indicating that a user is qualified for the Related Searches experiment
1658      * regardless of whether that feature is enabled. This uses a boolean histogram but always
1659      * logs true in order to get a raw bucket count (without using a user action, as suggested
1660      * in the User Action Guidelines doc).
1661      * <p>We use this to gauge whether each group has a balanced number of qualified users.
1662      * Can be logged multiple times since we'll just look at the user-count of this histogram.
1663      * This should be called any time a gesture is detected that could trigger a Related Search
1664      * if the feature were enabled.
1665      */
logRelatedSearchesQualifiedUsers()1666     static void logRelatedSearchesQualifiedUsers() {
1667         RecordHistogram.recordBooleanHistogram("Search.RelatedSearches.QualifiedUsers", true);
1668     }
1669 
1670     /**
1671      * Gets the state-change code for the given parameters by doing a lookup in the given map.
1672      * @param state The panel state.
1673      * @param reason The reason the state changed.
1674      * @param stateChangeCodes The map of state and reason to code.
1675      * @param defaultCode The code to return if the given values are not found in the map.
1676      * @return The code to write into an enum histogram, based on the given map.
1677      */
getStateChangeCode(@anelState int state, @StateChangeReason int reason, Map<StateChangeKey, Integer> stateChangeCodes, int defaultCode)1678     private static int getStateChangeCode(@PanelState int state, @StateChangeReason int reason,
1679             Map<StateChangeKey, Integer> stateChangeCodes, int defaultCode) {
1680         Integer code = stateChangeCodes.get(new StateChangeKey(state, reason));
1681         return code != null ? code : defaultCode;
1682     }
1683 
1684     /**
1685      * Gets the panel-seen code for the given parameters by doing a lookup in the seen-by-gesture
1686      * map.
1687      * @param wasPanelSeen Whether the panel was seen.
1688      * @param wasTap Whether the gesture that originally caused the panel to show was a Tap.
1689      * @return The code to write into a panel-seen histogram.
1690      */
getPanelSeenByGestureStateCode(boolean wasPanelSeen, boolean wasTap)1691     private static int getPanelSeenByGestureStateCode(boolean wasPanelSeen, boolean wasTap) {
1692         return SEEN_BY_GESTURE_CODES.get(new Pair<Boolean, Boolean>(wasPanelSeen, wasTap));
1693     }
1694 
1695     /**
1696      * Gets the promo-outcome code for the given parameter by doing a lookup in the
1697      * promo-by-gesture map.
1698      * @param preferenceValue The code for the current preference value.
1699      * @param wasTap Whether the gesture that originally caused the panel to show was a Tap.
1700      * @return The code to write into a promo-outcome histogram.
1701      */
getPromoByGestureStateCode(int preferenceValue, boolean wasTap)1702     private static int getPromoByGestureStateCode(int preferenceValue, boolean wasTap) {
1703         return PROMO_BY_GESTURE_CODES.get(new Pair<Integer, Boolean>(preferenceValue, wasTap));
1704     }
1705 
1706     /**
1707      * @return The code for the Contextual Search preference.
1708      */
getPreferenceValue()1709     private static int getPreferenceValue() {
1710         if (ContextualSearchManager.isContextualSearchUninitialized()) {
1711             return Preference.UNINITIALIZED;
1712         } else if (ContextualSearchManager.isContextualSearchDisabled()) {
1713             return Preference.DISABLED;
1714         }
1715         return Preference.ENABLED;
1716     }
1717 
1718     /**
1719      * Gets the encode value for quick answers seen.
1720      * @param didActivate Whether the quick answer was shown.
1721      * @param didAnswer Whether the caption was a full answer, not just a hint.
1722      * @param wasSeen Whether the search panel was opened.
1723      * @return The encoded value.
1724      */
getQuickAnswerSeenValue( boolean didActivate, boolean didAnswer, boolean wasSeen)1725     private static @QuickAnswerSeen int getQuickAnswerSeenValue(
1726             boolean didActivate, boolean didAnswer, boolean wasSeen) {
1727         if (wasSeen) {
1728             if (didActivate) {
1729                 return didAnswer ? QuickAnswerSeen.ACTIVATED_WAS_AN_ANSWER_SEEN
1730                                  : QuickAnswerSeen.ACTIVATED_NOT_AN_ANSWER_SEEN;
1731             } else {
1732                 return QuickAnswerSeen.NOT_ACTIVATED_SEEN;
1733             }
1734         } else {
1735             if (didActivate) {
1736                 return didAnswer ? QuickAnswerSeen.ACTIVATED_WAS_AN_ANSWER_NOT_SEEN
1737                                  : QuickAnswerSeen.ACTIVATED_NOT_AN_ANSWER_NOT_SEEN;
1738             } else {
1739                 return QuickAnswerSeen.NOT_ACTIVATED_NOT_SEEN;
1740             }
1741         }
1742     }
1743 
1744     /**
1745      * Logs to a seen-by-gesture histogram of the given name.
1746      * @param wasPanelSeen Whether the panel was seen.
1747      * @param wasTap Whether the gesture that originally caused the panel to show was a Tap.
1748      * @param histogramName The full name of the histogram to log to.
1749      */
logHistogramByGesture(boolean wasPanelSeen, boolean wasTap, String histogramName)1750     private static void logHistogramByGesture(boolean wasPanelSeen, boolean wasTap,
1751             String histogramName) {
1752         RecordHistogram.recordEnumeratedHistogram(histogramName,
1753                 getPanelSeenByGestureStateCode(wasPanelSeen, wasTap), ResultsByGesture.NUM_ENTRIES);
1754     }
1755 
getLabelForQuickActionCategory(int quickActionCategory)1756     private static String getLabelForQuickActionCategory(int quickActionCategory) {
1757         switch(quickActionCategory) {
1758             case QuickActionCategory.ADDRESS:
1759                 return "Address";
1760             case QuickActionCategory.EMAIL:
1761                 return "Email";
1762             case QuickActionCategory.EVENT:
1763                 return "Event";
1764             case QuickActionCategory.PHONE:
1765                 return "Phone";
1766             case QuickActionCategory.WEBSITE:
1767                 return "Website";
1768             default:
1769                 return "None";
1770         }
1771     }
1772 }
1773