OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.contextualsearch; |
| 6 |
| 7 import android.content.Context; |
| 8 |
| 9 import org.chromium.base.VisibleForTesting; |
| 10 import org.chromium.chrome.browser.ChromeVersionInfo; |
| 11 import org.chromium.chrome.browser.contextualsearch.ContextualSearchSelectionCon
troller.SelectionType; |
| 12 import org.chromium.chrome.browser.preferences.ChromePreferenceManager; |
| 13 import org.chromium.chrome.browser.preferences.NetworkPredictionOptions; |
| 14 import org.chromium.chrome.browser.preferences.PrefServiceBridge; |
| 15 |
| 16 import java.net.URL; |
| 17 |
| 18 import javax.annotation.Nullable; |
| 19 |
| 20 |
| 21 /** |
| 22 * Handles policy decisions for the {@code ContextualSearchManager}. |
| 23 */ |
| 24 class ContextualSearchPolicy { |
| 25 private static final int PROMO_TAPS_NOT_LIMITED = -1; |
| 26 private static final int PROMO_TAPS_DISABLED_BIAS = -2; |
| 27 |
| 28 private static ContextualSearchPolicy sInstance; |
| 29 |
| 30 private final ChromePreferenceManager mPreferenceManager; |
| 31 |
| 32 // Members used only for testing purposes. |
| 33 private boolean mDidOverrideDecidedStateForTesting; |
| 34 private boolean mDecidedStateForTesting; |
| 35 private boolean mDidResetTapCounters; |
| 36 |
| 37 static ContextualSearchPolicy getInstance(Context context) { |
| 38 if (sInstance == null) { |
| 39 sInstance = new ContextualSearchPolicy(context); |
| 40 } |
| 41 return sInstance; |
| 42 } |
| 43 |
| 44 /** |
| 45 * @param context The Android Context. |
| 46 */ |
| 47 ContextualSearchPolicy(Context context) { |
| 48 mPreferenceManager = ChromePreferenceManager.getInstance(context); |
| 49 } |
| 50 |
| 51 // TODO(donnd): Consider adding a test-only constructor that uses dependency
injection of a |
| 52 // preference manager and PrefServiceBridge. Currently this is not possible
because the |
| 53 // PrefServiceBridge is final. |
| 54 |
| 55 /** |
| 56 * @return The number of additional times to show the promo on tap, 0 if it
should not be shown, |
| 57 * or a negative value if the counter has been disabled. |
| 58 */ |
| 59 int getPromoTapsRemaining() { |
| 60 if (ContextualSearchFieldTrial.isPromoLimitedByTapCounts()) { |
| 61 int count = mPreferenceManager.getContextualSearchTapTriggeredPromoC
ount(); |
| 62 |
| 63 // Return a negative value if opt-out promo counter has been disable
d. |
| 64 if (isOptOutPromoAvailable() && !isOptOutPromoCounterEnabled(count))
return count; |
| 65 |
| 66 int limit = ContextualSearchFieldTrial.getPromoTapTriggeredLimit(); |
| 67 if (limit >= 0) return Math.max(0, limit - count); |
| 68 } |
| 69 |
| 70 return PROMO_TAPS_NOT_LIMITED; |
| 71 } |
| 72 |
| 73 /** |
| 74 * @return Whether a Tap gesture is currently supported as a trigger for the
feature. |
| 75 */ |
| 76 boolean isTapSupported() { |
| 77 if (!isUserUndecided()) return true; |
| 78 |
| 79 if (ContextualSearchFieldTrial.isPromoLongpressTriggeredOnly()) return f
alse; |
| 80 |
| 81 return getPromoTapsRemaining() != 0; |
| 82 } |
| 83 |
| 84 /** |
| 85 * @return whether or not the Contextual Search Result should be preloaded b
efore the user |
| 86 * explicitly interacts with the feature. |
| 87 */ |
| 88 boolean shouldPrefetchSearchResult(boolean isTapTriggered) { |
| 89 if (PrefServiceBridge.getInstance().getNetworkPredictionOptions() |
| 90 == NetworkPredictionOptions.NETWORK_PREDICTION_NEVER) { |
| 91 return false; |
| 92 } |
| 93 |
| 94 if (isTapPrefetchBeyondTheLimit()) return false; |
| 95 |
| 96 // If we're not resolving the tap due to the tap limit, we should not pr
eload either. |
| 97 if (isTapResolveBeyondTheLimit()) return false; |
| 98 |
| 99 // We never preload on long-press so users can cut & paste without hitti
ng the servers. |
| 100 return isTapTriggered; |
| 101 } |
| 102 |
| 103 /** |
| 104 * Returns whether the previous tap (the tap last counted) should resolve. |
| 105 * @return Whether the previous tap should resolve. |
| 106 */ |
| 107 boolean shouldPreviousTapResolve(@Nullable URL url) { |
| 108 if (isTapResolveBeyondTheLimit()) { |
| 109 return false; |
| 110 } |
| 111 |
| 112 if (isOptOutPromoAvailable()) { |
| 113 return isBasePageHTTP(url); |
| 114 } |
| 115 |
| 116 return true; |
| 117 } |
| 118 |
| 119 /** |
| 120 * Returns whether surrounding context can be accessed by other systems or n
ot. |
| 121 * @baseContentViewUrl The URL of the base page. |
| 122 * @return Whether surroundings are available. |
| 123 */ |
| 124 boolean canSendSurroundings(@Nullable URL baseContentViewUrl) { |
| 125 if (isUserUndecided()) return false; |
| 126 |
| 127 if (isOptOutPromoAvailable()) { |
| 128 return isBasePageHTTP(baseContentViewUrl); |
| 129 } |
| 130 |
| 131 return true; |
| 132 } |
| 133 |
| 134 /** |
| 135 * @return Whether the Opt-out promo is available to be shown in any panel. |
| 136 */ |
| 137 boolean isOptOutPromoAvailable() { |
| 138 return ContextualSearchFieldTrial.isPromoOptOut() && isUserUndecided(); |
| 139 } |
| 140 |
| 141 /** |
| 142 * @return Whether the classic opt-in promo is available. |
| 143 */ |
| 144 protected boolean isOptInPromoAvailable() { |
| 145 return false; |
| 146 } |
| 147 |
| 148 /** |
| 149 * Registers that a tap has taken place by incrementing tap-tracking counter
s. |
| 150 */ |
| 151 void registerTap() { |
| 152 if (isOptInPromoAvailable() || isOptOutPromoAvailable()) { |
| 153 int count = mPreferenceManager.getContextualSearchTapTriggeredPromoC
ount(); |
| 154 // Bump the counter only when it is still enabled. |
| 155 if (isOptInPromoAvailable() || isOptOutPromoCounterEnabled(count)) { |
| 156 mPreferenceManager.setContextualSearchTapTriggeredPromoCount(++c
ount); |
| 157 } |
| 158 } |
| 159 if (isTapLimited()) { |
| 160 int count = mPreferenceManager.getContextualSearchTapCount(); |
| 161 mPreferenceManager.setContextualSearchTapCount(++count); |
| 162 } |
| 163 } |
| 164 |
| 165 /** |
| 166 * Resets all the "tap" counters. |
| 167 */ |
| 168 void resetTapCounters() { |
| 169 // Always completely reset the tap counters, since tests push beyond lim
its: this |
| 170 // would affect subsequent tests unless they can reset without having a
limit. |
| 171 mPreferenceManager.setContextualSearchTapCount(0); |
| 172 |
| 173 // Disable the "promo tap" counter, but only if we're using the Opt-out
onboarding. |
| 174 // For Opt-in, we never disable the promo tap counter. |
| 175 if (isOptOutPromoAvailable()) disableOptOutPromoCounter(); |
| 176 mDidResetTapCounters = true; |
| 177 } |
| 178 |
| 179 @VisibleForTesting |
| 180 void overrideDecidedStateForTesting(boolean decidedState) { |
| 181 mDidOverrideDecidedStateForTesting = true; |
| 182 mDecidedStateForTesting = decidedState; |
| 183 } |
| 184 |
| 185 @VisibleForTesting |
| 186 boolean didResetTapCounters() { |
| 187 return mDidResetTapCounters; |
| 188 } |
| 189 |
| 190 /** |
| 191 * @return Whether a verbatim request should be made for the given base page
, assuming there |
| 192 * is no exiting request. |
| 193 */ |
| 194 boolean shouldCreateVerbatimRequest(ContextualSearchSelectionController cont
roller, |
| 195 @Nullable URL basePageUrl) { |
| 196 // TODO(donnd): refactor to make the controller a member of this class? |
| 197 return (controller.getSelectedText() != null |
| 198 && (controller.getSelectionType() == SelectionType.LONG_PRESS |
| 199 || (controller.getSelectionType() == SelectionType.TAP |
| 200 && !shouldPreviousTapResolve(basePageUrl)))); |
| 201 } |
| 202 |
| 203 /** |
| 204 * Determines whether an error from a search term resolution request should |
| 205 * be shown to the user, or not. |
| 206 */ |
| 207 boolean shouldShowErrorCodeInBar() { |
| 208 // Builds with lots of real users should not see raw error codes. |
| 209 return !(ChromeVersionInfo.isStableBuild() || ChromeVersionInfo.isBetaBu
ild()); |
| 210 } |
| 211 |
| 212 // -------------------------------------------------------------------------
------------------- |
| 213 // Opt-out style Promo counter |
| 214 // |
| 215 // The Opt-out style promo tap counter needs to do two things: |
| 216 // 1) Count Taps that trigger the promo, so they can be limited. |
| 217 // 2) Support a "disabled" state; when the user opens the panel then Taps tr
igger from then on. |
| 218 // We use a single persistent setting to record both meanings by using a neg
ative value to |
| 219 // indicate disabled. |
| 220 // |
| 221 // TODO(donnd): make a separate class for this kind of counter. |
| 222 // -------------------------------------------------------------------------
------------------- |
| 223 |
| 224 /** |
| 225 * Determines if the given Opt-out style promo counter represents a count of
promo taps in |
| 226 * the enabled state. |
| 227 * @param counter The persistent counter value to consider. |
| 228 * @return Whether the given counter is enabled. |
| 229 */ |
| 230 private boolean isOptOutPromoCounterEnabled(int counter) { |
| 231 return counter >= 0; |
| 232 } |
| 233 |
| 234 /** |
| 235 * Generates an equivalent counter value with the enabled state opposite of
the given value. |
| 236 * @param count The current value of the counter. |
| 237 * @return The equivalent value in with its enabled/disabled state toggled. |
| 238 */ |
| 239 private int toggleOptOutPromoCounterEnabled(int count) { |
| 240 return PROMO_TAPS_DISABLED_BIAS - count; |
| 241 } |
| 242 |
| 243 /** |
| 244 * Disables the Opt-out promo counter, unless it is already disabled. |
| 245 */ |
| 246 private void disableOptOutPromoCounter() { |
| 247 int count = mPreferenceManager.getContextualSearchTapTriggeredPromoCount
(); |
| 248 if (isOptOutPromoCounterEnabled(count)) { |
| 249 count = toggleOptOutPromoCounterEnabled(count); |
| 250 mPreferenceManager.setContextualSearchTapTriggeredPromoCount(count); |
| 251 } |
| 252 } |
| 253 |
| 254 // -------------------------------------------------------------------------
------------------- |
| 255 // Private helpers. |
| 256 // -------------------------------------------------------------------------
------------------- |
| 257 |
| 258 /** |
| 259 * @return Whether a promo is needed because the user is still undecided |
| 260 * on enabling or disabling the feature. |
| 261 */ |
| 262 private boolean isUserUndecided() { |
| 263 // TODO(donnd) use dependency injection for the PrefServiceBridge instea
d! |
| 264 if (mDidOverrideDecidedStateForTesting) return !mDecidedStateForTesting; |
| 265 |
| 266 return PrefServiceBridge.getInstance().isContextualSearchUninitialized()
; |
| 267 } |
| 268 |
| 269 /** |
| 270 * @param url The URL of the base page. |
| 271 * @return Whether the given content view is for an HTTP page. |
| 272 */ |
| 273 private boolean isBasePageHTTP(@Nullable URL url) { |
| 274 // We shouldn't be checking HTTP unless we're in the opt-out promo which |
| 275 // treats HTTP differently. |
| 276 assert ContextualSearchFieldTrial.isPromoOptOut(); |
| 277 return url != null && "http".equals(url.getProtocol()); |
| 278 } |
| 279 |
| 280 /** |
| 281 * @return Whether the tap resolve limit has been exceeded. |
| 282 */ |
| 283 private boolean isTapResolveBeyondTheLimit() { |
| 284 return isTapResolveLimited() |
| 285 && mPreferenceManager.getContextualSearchTapCount() > getTapReso
lveLimit(); |
| 286 } |
| 287 |
| 288 /** |
| 289 * @return Whether the tap resolve limit has been exceeded. |
| 290 */ |
| 291 private boolean isTapPrefetchBeyondTheLimit() { |
| 292 return isTapPrefetchLimited() |
| 293 && mPreferenceManager.getContextualSearchTapCount() > getTapPref
etchLimit(); |
| 294 } |
| 295 |
| 296 /** |
| 297 * Whether taps for decided users are limited, either for prefetch or resolv
e. |
| 298 */ |
| 299 private boolean isTapLimited() { |
| 300 return isTapPrefetchLimited() || isTapResolveLimited(); |
| 301 } |
| 302 |
| 303 /** |
| 304 * @return Whether a tap gesture is resolve-limited. |
| 305 */ |
| 306 private boolean isTapResolveLimited() { |
| 307 return isUserUndecided() |
| 308 ? isTapResolveLimitedForUndecided() |
| 309 : isTapResolveLimitedForDecided(); |
| 310 } |
| 311 |
| 312 /** |
| 313 * @return Whether a tap gesture is resolve-limited. |
| 314 */ |
| 315 private boolean isTapPrefetchLimited() { |
| 316 return isUserUndecided() |
| 317 ? isTapPrefetchLimitedForUndecided() |
| 318 : isTapPrefetchLimitedForDecided(); |
| 319 } |
| 320 |
| 321 /** |
| 322 * @return The limit of the number of taps to prefetch. |
| 323 */ |
| 324 private int getTapPrefetchLimit() { |
| 325 return isUserUndecided() |
| 326 ? ContextualSearchFieldTrial.getTapPrefetchLimitForUndecided() |
| 327 : ContextualSearchFieldTrial.getTapPrefetchLimitForDecided(); |
| 328 } |
| 329 |
| 330 /** |
| 331 * @return The limit of the number of taps to resolve using search term reso
lution. |
| 332 */ |
| 333 private int getTapResolveLimit() { |
| 334 return isUserUndecided() |
| 335 ? ContextualSearchFieldTrial.getTapResolveLimitForUndecided() |
| 336 : ContextualSearchFieldTrial.getTapResolveLimitForDecided(); |
| 337 } |
| 338 |
| 339 /** |
| 340 * @return Whether Search Term Resolution in response to a Tap gesture is li
mited for decided |
| 341 * users. |
| 342 */ |
| 343 private boolean isTapResolveLimitedForDecided() { |
| 344 return ContextualSearchFieldTrial.getTapResolveLimitForDecided() |
| 345 != ContextualSearchFieldTrial.UNLIMITED_TAPS; |
| 346 } |
| 347 |
| 348 /** |
| 349 * @return Whether prefetch in response to a Tap gesture is limited for deci
ded users. |
| 350 */ |
| 351 private boolean isTapPrefetchLimitedForDecided() { |
| 352 return ContextualSearchFieldTrial.getTapPrefetchLimitForDecided() |
| 353 != ContextualSearchFieldTrial.UNLIMITED_TAPS; |
| 354 } |
| 355 |
| 356 /** |
| 357 * @return Whether Search Term Resolution in response to a Tap gesture is li
mited for undecided |
| 358 * users. |
| 359 */ |
| 360 private boolean isTapResolveLimitedForUndecided() { |
| 361 return ContextualSearchFieldTrial.getTapResolveLimitForUndecided() |
| 362 != ContextualSearchFieldTrial.UNLIMITED_TAPS; |
| 363 } |
| 364 |
| 365 /** |
| 366 * @return Whether prefetch in response to a Tap gesture is limited for unde
cided users. |
| 367 */ |
| 368 private boolean isTapPrefetchLimitedForUndecided() { |
| 369 return ContextualSearchFieldTrial.getTapPrefetchLimitForUndecided() |
| 370 != ContextualSearchFieldTrial.UNLIMITED_TAPS; |
| 371 } |
| 372 } |
OLD | NEW |