| Index: chrome/android/java_staging/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchPolicy.java
|
| diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchPolicy.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchPolicy.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a29b7d1657d6c26840d0d28b5121f8f54a557c22
|
| --- /dev/null
|
| +++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchPolicy.java
|
| @@ -0,0 +1,372 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.chrome.browser.contextualsearch;
|
| +
|
| +import android.content.Context;
|
| +
|
| +import org.chromium.base.VisibleForTesting;
|
| +import org.chromium.chrome.browser.ChromeVersionInfo;
|
| +import org.chromium.chrome.browser.contextualsearch.ContextualSearchSelectionController.SelectionType;
|
| +import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
|
| +import org.chromium.chrome.browser.preferences.NetworkPredictionOptions;
|
| +import org.chromium.chrome.browser.preferences.PrefServiceBridge;
|
| +
|
| +import java.net.URL;
|
| +
|
| +import javax.annotation.Nullable;
|
| +
|
| +
|
| +/**
|
| + * Handles policy decisions for the {@code ContextualSearchManager}.
|
| + */
|
| +class ContextualSearchPolicy {
|
| + private static final int PROMO_TAPS_NOT_LIMITED = -1;
|
| + private static final int PROMO_TAPS_DISABLED_BIAS = -2;
|
| +
|
| + private static ContextualSearchPolicy sInstance;
|
| +
|
| + private final ChromePreferenceManager mPreferenceManager;
|
| +
|
| + // Members used only for testing purposes.
|
| + private boolean mDidOverrideDecidedStateForTesting;
|
| + private boolean mDecidedStateForTesting;
|
| + private boolean mDidResetTapCounters;
|
| +
|
| + static ContextualSearchPolicy getInstance(Context context) {
|
| + if (sInstance == null) {
|
| + sInstance = new ContextualSearchPolicy(context);
|
| + }
|
| + return sInstance;
|
| + }
|
| +
|
| + /**
|
| + * @param context The Android Context.
|
| + */
|
| + ContextualSearchPolicy(Context context) {
|
| + mPreferenceManager = ChromePreferenceManager.getInstance(context);
|
| + }
|
| +
|
| + // TODO(donnd): Consider adding a test-only constructor that uses dependency injection of a
|
| + // preference manager and PrefServiceBridge. Currently this is not possible because the
|
| + // PrefServiceBridge is final.
|
| +
|
| + /**
|
| + * @return The number of additional times to show the promo on tap, 0 if it should not be shown,
|
| + * or a negative value if the counter has been disabled.
|
| + */
|
| + int getPromoTapsRemaining() {
|
| + if (ContextualSearchFieldTrial.isPromoLimitedByTapCounts()) {
|
| + int count = mPreferenceManager.getContextualSearchTapTriggeredPromoCount();
|
| +
|
| + // Return a negative value if opt-out promo counter has been disabled.
|
| + if (isOptOutPromoAvailable() && !isOptOutPromoCounterEnabled(count)) return count;
|
| +
|
| + int limit = ContextualSearchFieldTrial.getPromoTapTriggeredLimit();
|
| + if (limit >= 0) return Math.max(0, limit - count);
|
| + }
|
| +
|
| + return PROMO_TAPS_NOT_LIMITED;
|
| + }
|
| +
|
| + /**
|
| + * @return Whether a Tap gesture is currently supported as a trigger for the feature.
|
| + */
|
| + boolean isTapSupported() {
|
| + if (!isUserUndecided()) return true;
|
| +
|
| + if (ContextualSearchFieldTrial.isPromoLongpressTriggeredOnly()) return false;
|
| +
|
| + return getPromoTapsRemaining() != 0;
|
| + }
|
| +
|
| + /**
|
| + * @return whether or not the Contextual Search Result should be preloaded before the user
|
| + * explicitly interacts with the feature.
|
| + */
|
| + boolean shouldPrefetchSearchResult(boolean isTapTriggered) {
|
| + if (PrefServiceBridge.getInstance().getNetworkPredictionOptions()
|
| + == NetworkPredictionOptions.NETWORK_PREDICTION_NEVER) {
|
| + return false;
|
| + }
|
| +
|
| + if (isTapPrefetchBeyondTheLimit()) return false;
|
| +
|
| + // If we're not resolving the tap due to the tap limit, we should not preload either.
|
| + if (isTapResolveBeyondTheLimit()) return false;
|
| +
|
| + // We never preload on long-press so users can cut & paste without hitting the servers.
|
| + return isTapTriggered;
|
| + }
|
| +
|
| + /**
|
| + * Returns whether the previous tap (the tap last counted) should resolve.
|
| + * @return Whether the previous tap should resolve.
|
| + */
|
| + boolean shouldPreviousTapResolve(@Nullable URL url) {
|
| + if (isTapResolveBeyondTheLimit()) {
|
| + return false;
|
| + }
|
| +
|
| + if (isOptOutPromoAvailable()) {
|
| + return isBasePageHTTP(url);
|
| + }
|
| +
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * Returns whether surrounding context can be accessed by other systems or not.
|
| + * @baseContentViewUrl The URL of the base page.
|
| + * @return Whether surroundings are available.
|
| + */
|
| + boolean canSendSurroundings(@Nullable URL baseContentViewUrl) {
|
| + if (isUserUndecided()) return false;
|
| +
|
| + if (isOptOutPromoAvailable()) {
|
| + return isBasePageHTTP(baseContentViewUrl);
|
| + }
|
| +
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * @return Whether the Opt-out promo is available to be shown in any panel.
|
| + */
|
| + boolean isOptOutPromoAvailable() {
|
| + return ContextualSearchFieldTrial.isPromoOptOut() && isUserUndecided();
|
| + }
|
| +
|
| + /**
|
| + * @return Whether the classic opt-in promo is available.
|
| + */
|
| + protected boolean isOptInPromoAvailable() {
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * Registers that a tap has taken place by incrementing tap-tracking counters.
|
| + */
|
| + void registerTap() {
|
| + if (isOptInPromoAvailable() || isOptOutPromoAvailable()) {
|
| + int count = mPreferenceManager.getContextualSearchTapTriggeredPromoCount();
|
| + // Bump the counter only when it is still enabled.
|
| + if (isOptInPromoAvailable() || isOptOutPromoCounterEnabled(count)) {
|
| + mPreferenceManager.setContextualSearchTapTriggeredPromoCount(++count);
|
| + }
|
| + }
|
| + if (isTapLimited()) {
|
| + int count = mPreferenceManager.getContextualSearchTapCount();
|
| + mPreferenceManager.setContextualSearchTapCount(++count);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Resets all the "tap" counters.
|
| + */
|
| + void resetTapCounters() {
|
| + // Always completely reset the tap counters, since tests push beyond limits: this
|
| + // would affect subsequent tests unless they can reset without having a limit.
|
| + mPreferenceManager.setContextualSearchTapCount(0);
|
| +
|
| + // Disable the "promo tap" counter, but only if we're using the Opt-out onboarding.
|
| + // For Opt-in, we never disable the promo tap counter.
|
| + if (isOptOutPromoAvailable()) disableOptOutPromoCounter();
|
| + mDidResetTapCounters = true;
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + void overrideDecidedStateForTesting(boolean decidedState) {
|
| + mDidOverrideDecidedStateForTesting = true;
|
| + mDecidedStateForTesting = decidedState;
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + boolean didResetTapCounters() {
|
| + return mDidResetTapCounters;
|
| + }
|
| +
|
| + /**
|
| + * @return Whether a verbatim request should be made for the given base page, assuming there
|
| + * is no exiting request.
|
| + */
|
| + boolean shouldCreateVerbatimRequest(ContextualSearchSelectionController controller,
|
| + @Nullable URL basePageUrl) {
|
| + // TODO(donnd): refactor to make the controller a member of this class?
|
| + return (controller.getSelectedText() != null
|
| + && (controller.getSelectionType() == SelectionType.LONG_PRESS
|
| + || (controller.getSelectionType() == SelectionType.TAP
|
| + && !shouldPreviousTapResolve(basePageUrl))));
|
| + }
|
| +
|
| + /**
|
| + * Determines whether an error from a search term resolution request should
|
| + * be shown to the user, or not.
|
| + */
|
| + boolean shouldShowErrorCodeInBar() {
|
| + // Builds with lots of real users should not see raw error codes.
|
| + return !(ChromeVersionInfo.isStableBuild() || ChromeVersionInfo.isBetaBuild());
|
| + }
|
| +
|
| + // --------------------------------------------------------------------------------------------
|
| + // Opt-out style Promo counter
|
| + //
|
| + // The Opt-out style promo tap counter needs to do two things:
|
| + // 1) Count Taps that trigger the promo, so they can be limited.
|
| + // 2) Support a "disabled" state; when the user opens the panel then Taps trigger from then on.
|
| + // We use a single persistent setting to record both meanings by using a negative value to
|
| + // indicate disabled.
|
| + //
|
| + // TODO(donnd): make a separate class for this kind of counter.
|
| + // --------------------------------------------------------------------------------------------
|
| +
|
| + /**
|
| + * Determines if the given Opt-out style promo counter represents a count of promo taps in
|
| + * the enabled state.
|
| + * @param counter The persistent counter value to consider.
|
| + * @return Whether the given counter is enabled.
|
| + */
|
| + private boolean isOptOutPromoCounterEnabled(int counter) {
|
| + return counter >= 0;
|
| + }
|
| +
|
| + /**
|
| + * Generates an equivalent counter value with the enabled state opposite of the given value.
|
| + * @param count The current value of the counter.
|
| + * @return The equivalent value in with its enabled/disabled state toggled.
|
| + */
|
| + private int toggleOptOutPromoCounterEnabled(int count) {
|
| + return PROMO_TAPS_DISABLED_BIAS - count;
|
| + }
|
| +
|
| + /**
|
| + * Disables the Opt-out promo counter, unless it is already disabled.
|
| + */
|
| + private void disableOptOutPromoCounter() {
|
| + int count = mPreferenceManager.getContextualSearchTapTriggeredPromoCount();
|
| + if (isOptOutPromoCounterEnabled(count)) {
|
| + count = toggleOptOutPromoCounterEnabled(count);
|
| + mPreferenceManager.setContextualSearchTapTriggeredPromoCount(count);
|
| + }
|
| + }
|
| +
|
| + // --------------------------------------------------------------------------------------------
|
| + // Private helpers.
|
| + // --------------------------------------------------------------------------------------------
|
| +
|
| + /**
|
| + * @return Whether a promo is needed because the user is still undecided
|
| + * on enabling or disabling the feature.
|
| + */
|
| + private boolean isUserUndecided() {
|
| + // TODO(donnd) use dependency injection for the PrefServiceBridge instead!
|
| + if (mDidOverrideDecidedStateForTesting) return !mDecidedStateForTesting;
|
| +
|
| + return PrefServiceBridge.getInstance().isContextualSearchUninitialized();
|
| + }
|
| +
|
| + /**
|
| + * @param url The URL of the base page.
|
| + * @return Whether the given content view is for an HTTP page.
|
| + */
|
| + private boolean isBasePageHTTP(@Nullable URL url) {
|
| + // We shouldn't be checking HTTP unless we're in the opt-out promo which
|
| + // treats HTTP differently.
|
| + assert ContextualSearchFieldTrial.isPromoOptOut();
|
| + return url != null && "http".equals(url.getProtocol());
|
| + }
|
| +
|
| + /**
|
| + * @return Whether the tap resolve limit has been exceeded.
|
| + */
|
| + private boolean isTapResolveBeyondTheLimit() {
|
| + return isTapResolveLimited()
|
| + && mPreferenceManager.getContextualSearchTapCount() > getTapResolveLimit();
|
| + }
|
| +
|
| + /**
|
| + * @return Whether the tap resolve limit has been exceeded.
|
| + */
|
| + private boolean isTapPrefetchBeyondTheLimit() {
|
| + return isTapPrefetchLimited()
|
| + && mPreferenceManager.getContextualSearchTapCount() > getTapPrefetchLimit();
|
| + }
|
| +
|
| + /**
|
| + * Whether taps for decided users are limited, either for prefetch or resolve.
|
| + */
|
| + private boolean isTapLimited() {
|
| + return isTapPrefetchLimited() || isTapResolveLimited();
|
| + }
|
| +
|
| + /**
|
| + * @return Whether a tap gesture is resolve-limited.
|
| + */
|
| + private boolean isTapResolveLimited() {
|
| + return isUserUndecided()
|
| + ? isTapResolveLimitedForUndecided()
|
| + : isTapResolveLimitedForDecided();
|
| + }
|
| +
|
| + /**
|
| + * @return Whether a tap gesture is resolve-limited.
|
| + */
|
| + private boolean isTapPrefetchLimited() {
|
| + return isUserUndecided()
|
| + ? isTapPrefetchLimitedForUndecided()
|
| + : isTapPrefetchLimitedForDecided();
|
| + }
|
| +
|
| + /**
|
| + * @return The limit of the number of taps to prefetch.
|
| + */
|
| + private int getTapPrefetchLimit() {
|
| + return isUserUndecided()
|
| + ? ContextualSearchFieldTrial.getTapPrefetchLimitForUndecided()
|
| + : ContextualSearchFieldTrial.getTapPrefetchLimitForDecided();
|
| + }
|
| +
|
| + /**
|
| + * @return The limit of the number of taps to resolve using search term resolution.
|
| + */
|
| + private int getTapResolveLimit() {
|
| + return isUserUndecided()
|
| + ? ContextualSearchFieldTrial.getTapResolveLimitForUndecided()
|
| + : ContextualSearchFieldTrial.getTapResolveLimitForDecided();
|
| + }
|
| +
|
| + /**
|
| + * @return Whether Search Term Resolution in response to a Tap gesture is limited for decided
|
| + * users.
|
| + */
|
| + private boolean isTapResolveLimitedForDecided() {
|
| + return ContextualSearchFieldTrial.getTapResolveLimitForDecided()
|
| + != ContextualSearchFieldTrial.UNLIMITED_TAPS;
|
| + }
|
| +
|
| + /**
|
| + * @return Whether prefetch in response to a Tap gesture is limited for decided users.
|
| + */
|
| + private boolean isTapPrefetchLimitedForDecided() {
|
| + return ContextualSearchFieldTrial.getTapPrefetchLimitForDecided()
|
| + != ContextualSearchFieldTrial.UNLIMITED_TAPS;
|
| + }
|
| +
|
| + /**
|
| + * @return Whether Search Term Resolution in response to a Tap gesture is limited for undecided
|
| + * users.
|
| + */
|
| + private boolean isTapResolveLimitedForUndecided() {
|
| + return ContextualSearchFieldTrial.getTapResolveLimitForUndecided()
|
| + != ContextualSearchFieldTrial.UNLIMITED_TAPS;
|
| + }
|
| +
|
| + /**
|
| + * @return Whether prefetch in response to a Tap gesture is limited for undecided users.
|
| + */
|
| + private boolean isTapPrefetchLimitedForUndecided() {
|
| + return ContextualSearchFieldTrial.getTapPrefetchLimitForUndecided()
|
| + != ContextualSearchFieldTrial.UNLIMITED_TAPS;
|
| + }
|
| +}
|
|
|