// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.contextualsearch;
import org.chromium.base.metrics.RecordUserAction;
/**
* Heuristic for general Tap suppression that factors in a variety of signals.
*/
class TapSuppression extends ContextualSearchHeuristic {
private static final int TIME_THRESHOLD_MILLISECONDS = 3000;
private static final int TAP_RADIUS_DPS = 30;
private final boolean mIsTapSuppressionEnabled;
private final int mExperimentThresholdTaps;
private final int mTapsSinceOpen;
private final float mPxToDp;
private final boolean mIsSecondTap;
private final boolean mIsConditionSatisfied; // whether to suppress or not.
/**
* Constructs a heuristic to decide if a Tap should be suppressed or not.
* Combines various signals to determine suppression, including whether the previous
* Tap was suppressed for any reason.
* @param controller The Selection Controller.
* @param previousTapState The specifics regarding the previous Tap.
* @param x The x coordinate of the current tap.
* @param y The y coordinate of the current tap.
* @param tapsSinceOpen the number of Tap gestures since the last open of the panel.
*/
TapSuppression(ContextualSearchSelectionController controller,
ContextualSearchTapState previousTapState, int x, int y, int tapsSinceOpen) {
mIsTapSuppressionEnabled = ContextualSearchFieldTrial.isTapSuppressionEnabled();
mExperimentThresholdTaps = ContextualSearchFieldTrial.getSuppressionTaps();
mPxToDp = controller.getPxToDp();
mTapsSinceOpen = tapsSinceOpen;
mIsSecondTap = previousTapState != null && previousTapState.wasSuppressed()
&& !shouldHandleFirstTap();
if (mIsSecondTap) {
boolean shouldHandle = shouldHandleSecondTap(previousTapState, x, y);
mIsConditionSatisfied = !shouldHandle;
} else {
mIsConditionSatisfied = !shouldHandleFirstTap();
if (mIsConditionSatisfied && mIsTapSuppressionEnabled) {
RecordUserAction.record("ContextualSearch.TapSuppressed.TapThresholdExceeded");
}
}
}
@Override
protected boolean isConditionSatisfiedAndEnabled() {
return mIsTapSuppressionEnabled && mIsConditionSatisfied;
}
@Override
protected void logResultsSeen(boolean wasSearchContentViewSeen, boolean wasActivatedByTap) {
// TODO(donnd): consider logging counter-factual data rather than checking if enabled.
if (wasActivatedByTap && mIsTapSuppressionEnabled) {
ContextualSearchUma.logTapSuppressionResultsSeen(
wasSearchContentViewSeen, mIsSecondTap);
}
}
/**
* @return Whether a first tap should be handled or not.
*/
private boolean shouldHandleFirstTap() {
return mTapsSinceOpen < mExperimentThresholdTaps;
}
/**
* Determines whether a second tap at the given coordinates should be handled.
* @param tapState The specifics regarding the previous Tap.
* @param x The x coordinate of the current tap.
* @param y The y coordinate of the current tap.
* @return whether a second tap at the given coordinates should be handled or not.
*/
private boolean shouldHandleSecondTap(ContextualSearchTapState tapState, int x, int y) {
// The second tap needs to be close to the first tap in both time and space.
// Recent enough?
if (System.nanoTime() - tapState.tapTimeNanoseconds()
> (long) TIME_THRESHOLD_MILLISECONDS * NANOSECONDS_IN_A_MILLISECOND) {
return false;
}
// Within our radius?
float deltaXDp = (tapState.getX() - x) * mPxToDp;
float deltaYDp = (tapState.getY() - y) * mPxToDp;
// Use x^2 * y^2 = r^2
float distanceSquaredDp = deltaXDp * deltaXDp + deltaYDp * deltaYDp;
return distanceSquaredDp <= TAP_RADIUS_DPS * TAP_RADIUS_DPS;
}
}