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.app.Activity; |
| 8 import android.os.Handler; |
| 9 import android.util.Log; |
| 10 import android.view.View; |
| 11 import android.view.ViewGroup; |
| 12 import android.view.ViewTreeObserver; |
| 13 import android.view.ViewTreeObserver.OnGlobalFocusChangeListener; |
| 14 |
| 15 import com.google.android.apps.chrome.R; |
| 16 |
| 17 import org.chromium.base.ActivityState; |
| 18 import org.chromium.base.ApplicationStatus; |
| 19 import org.chromium.base.ApplicationStatus.ActivityStateListener; |
| 20 import org.chromium.base.CalledByNative; |
| 21 import org.chromium.base.SysUtils; |
| 22 import org.chromium.base.VisibleForTesting; |
| 23 import org.chromium.chrome.browser.ChromeActivity; |
| 24 import org.chromium.chrome.browser.ContentViewUtil; |
| 25 import org.chromium.chrome.browser.Tab; |
| 26 import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.Context
ualSearchControl; |
| 27 import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.Context
ualSearchPanel.PanelState; |
| 28 import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.Context
ualSearchPanel.StateChangeReason; |
| 29 import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.Context
ualSearchPanelDelegate; |
| 30 import org.chromium.chrome.browser.contextualsearch.ContextualSearchSelectionCon
troller.SelectionType; |
| 31 import org.chromium.chrome.browser.device.DeviceClassManager; |
| 32 import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler; |
| 33 import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler.Overrid
eUrlLoadingResult; |
| 34 import org.chromium.chrome.browser.externalnav.ExternalNavigationParams; |
| 35 import org.chromium.chrome.browser.gsa.GSAContextDisplaySelection; |
| 36 import org.chromium.chrome.browser.infobar.InfoBarContainer; |
| 37 import org.chromium.chrome.browser.preferences.PrefServiceBridge; |
| 38 import org.chromium.chrome.browser.tab.TabRedirectHandler; |
| 39 import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver; |
| 40 import org.chromium.chrome.browser.tabmodel.TabModel; |
| 41 import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType; |
| 42 import org.chromium.chrome.browser.tabmodel.TabModelObserver; |
| 43 import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
| 44 import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver; |
| 45 import org.chromium.chrome.browser.widget.findinpage.FindToolbarManager; |
| 46 import org.chromium.chrome.browser.widget.findinpage.FindToolbarObserver; |
| 47 import org.chromium.components.navigation_interception.InterceptNavigationDelega
te; |
| 48 import org.chromium.components.navigation_interception.NavigationParams; |
| 49 import org.chromium.components.web_contents_delegate_android.WebContentsDelegate
Android; |
| 50 import org.chromium.content.browser.ContentView; |
| 51 import org.chromium.content.browser.ContentViewCore; |
| 52 import org.chromium.content.browser.ContextualSearchClient; |
| 53 import org.chromium.content_public.browser.GestureStateListener; |
| 54 import org.chromium.content_public.browser.JavaScriptCallback; |
| 55 import org.chromium.content_public.browser.LoadUrlParams; |
| 56 import org.chromium.content_public.browser.WebContents; |
| 57 import org.chromium.content_public.browser.WebContentsObserver; |
| 58 import org.chromium.content_public.common.TopControlsState; |
| 59 import org.chromium.ui.base.WindowAndroid; |
| 60 |
| 61 import java.net.MalformedURLException; |
| 62 import java.net.URL; |
| 63 import java.util.regex.Pattern; |
| 64 |
| 65 import javax.annotation.Nullable; |
| 66 |
| 67 |
| 68 /** |
| 69 * Manager for the Contextual Search feature. |
| 70 * This class keeps track of the status of Contextual Search and coordinates the
control |
| 71 * with the layout. |
| 72 */ |
| 73 public class ContextualSearchManager extends ContextualSearchObservable |
| 74 implements ContextualSearchManagementDelegate, |
| 75 ContextualSearchNetworkCommunicator, ContextualSearchSelectionHa
ndler, |
| 76 ContextualSearchClient, ActivityStateListener { |
| 77 |
| 78 private static final String TAG = "ContextualSearch"; |
| 79 |
| 80 private static final String CONTAINS_WORD_PATTERN = "(\\w|\\p{L}|\\p{N})+"; |
| 81 |
| 82 // Constants related to the first-run flow. |
| 83 private static final String FIRST_RUN_URL_PREFIX = "chrome://contextual-sear
ch-promo/"; |
| 84 private static final String FIRST_RUN_FLOW_URL = FIRST_RUN_URL_PREFIX + "pro
mo.html"; |
| 85 private static final String FIRST_RUN_OPTIN_URL = FIRST_RUN_FLOW_URL + "#opt
in"; |
| 86 private static final String FIRST_RUN_OPTOUT_URL = FIRST_RUN_FLOW_URL + "#op
tout"; |
| 87 private static final String FIRST_RUN_LEARN_MORE_URL = FIRST_RUN_FLOW_URL +
"#learn-more"; |
| 88 |
| 89 // Max selection length must be limited or the entire request URL can go pas
t the 2K limit. |
| 90 private static final int MAX_SELECTION_LENGTH = 100; |
| 91 |
| 92 private static final boolean ALWAYS_USE_RESOLVED_SEARCH_TERM = true; |
| 93 private static final boolean NEVER_USE_RESOLVED_SEARCH_TERM = false; |
| 94 |
| 95 private static final String INTENT_URL_PREFIX = "intent:"; |
| 96 |
| 97 // The animation duration of a URL being promoted to a tab when triggered by
an |
| 98 // intercept navigation. This is faster than the standard tab promotion anim
ation |
| 99 // so that it completes before the navigation. |
| 100 private static final long INTERCEPT_NAVIGATION_PROMOTION_ANIMATION_DURATION_
MS = 40; |
| 101 |
| 102 // We blacklist this URL because malformed URLs may bring up this page. |
| 103 private static final String BLACKLISTED_URL = "about:blank"; |
| 104 |
| 105 private final ContextualSearchSelectionController mSelectionController; |
| 106 private final Pattern mContainsWordPattern; |
| 107 private final ChromeActivity mActivity; |
| 108 private ViewGroup mParentView; |
| 109 private final ViewTreeObserver.OnGlobalFocusChangeListener mOnFocusChangeLis
tener; |
| 110 |
| 111 private final WindowAndroid mWindowAndroid; |
| 112 private ContentViewCore mSearchContentViewCore; |
| 113 private WebContentsObserver mSearchWebContentsObserver; |
| 114 private final WebContentsDelegateAndroid mWebContentsDelegate; |
| 115 private ContextualSearchContentViewDelegate mSearchContentViewDelegate; |
| 116 private final ContextualSearchTabPromotionDelegate mTabPromotionDelegate; |
| 117 private final FindToolbarObserver mFindToolbarObserver; |
| 118 private FindToolbarManager mFindToolbarManager; |
| 119 private TabModelSelectorTabObserver mTabModelSelectorTabObserver; |
| 120 private TabModelObserver mTabModelObserver; |
| 121 private boolean mIsSearchContentViewShowing; |
| 122 private boolean mDidLoadResolvedSearchRequest; |
| 123 private long mLoadedSearchUrlTimeMs; |
| 124 // TODO(donnd): consider changing this member's name to indicate "opened" in
stead of "seen". |
| 125 private boolean mWereSearchResultsSeen; |
| 126 private boolean mWereInfoBarsHidden; |
| 127 private boolean mDidLoadAnyUrl; |
| 128 private boolean mDidPromoteSearchNavigation; |
| 129 private boolean mDidBasePageLoadJustStart; |
| 130 private boolean mWasActivatedByTap; |
| 131 private boolean mIsInitialized; |
| 132 |
| 133 private boolean mIsShowingPromo; |
| 134 private boolean mDidLogPromoOutcome; |
| 135 |
| 136 private ContextualSearchNetworkCommunicator mNetworkCommunicator; |
| 137 private ContextualSearchPanelDelegate mSearchPanelDelegate; |
| 138 |
| 139 // TODO(pedrosimonetti): also store selected text, surroundings, url, boundi
ng rect of selected |
| 140 // text, and make sure that all states are cleared when starting a new conte
xtual search to |
| 141 // avoid having the values in a funky state. |
| 142 private ContextualSearchRequest mSearchRequest; |
| 143 |
| 144 // The native manager associated with this object. |
| 145 private long mNativeContextualSearchManagerPtr; |
| 146 |
| 147 private TabRedirectHandler mTabRedirectHandler; |
| 148 |
| 149 /** |
| 150 * The delegate that is notified when the Search Panel ContentViewCore is re
ady to be rendered. |
| 151 */ |
| 152 public interface ContextualSearchContentViewDelegate { |
| 153 /** |
| 154 * Sets the {@code ContentViewCore} associated to the Contextual Search
Panel. |
| 155 * @param contentViewCore Reference to the ContentViewCore. |
| 156 */ |
| 157 void setContextualSearchContentViewCore(ContentViewCore contentViewCore)
; |
| 158 |
| 159 /** |
| 160 * Releases the {@code ContentViewCore} associated to the Contextual Sea
rch Panel. |
| 161 */ |
| 162 void releaseContextualSearchContentViewCore(); |
| 163 } |
| 164 |
| 165 /** |
| 166 * The delegate that is responsible for promoting a {@link ContentViewCore}
to a {@link Tab} |
| 167 * when necessary. |
| 168 */ |
| 169 public interface ContextualSearchTabPromotionDelegate { |
| 170 /** |
| 171 * Called when {@code searchContentViewCore} should be promoted to a {@l
ink Tab}. |
| 172 * @param searchContentViewCore The {@link ContentViewCore} to promote. |
| 173 * @return Whether or not {@code searchContentViewC
ore}'s ownership |
| 174 * was transferred to the new {@link Tab} o
r not. If |
| 175 * {@code false}, this {@link ContentViewCo
re} might be |
| 176 * destroyed after this call. |
| 177 */ |
| 178 boolean createContextualSearchTab(ContentViewCore searchContentViewCore)
; |
| 179 } |
| 180 |
| 181 /** |
| 182 * Constructs the manager for the given activity, and will attach views to t
he given parent. |
| 183 * @param activity The {@code ChromeActivity} in use. |
| 184 * @param windowAndroid The {@code WindowAndroid} associated with Chr
ome. |
| 185 * @param tabPromotionDelegate The {@link ContextualSearchTabPromotionDelega
te} that is |
| 186 * responsible for building tabs from contextual
search |
| 187 * {@link ContentViewCore}s. |
| 188 */ |
| 189 public ContextualSearchManager(ChromeActivity activity, WindowAndroid window
Android, |
| 190 ContextualSearchTabPromotionDelegate tabPromotionDelegate) { |
| 191 super(activity); |
| 192 mActivity = activity; |
| 193 mWindowAndroid = windowAndroid; |
| 194 mTabPromotionDelegate = tabPromotionDelegate; |
| 195 mContainsWordPattern = Pattern.compile(CONTAINS_WORD_PATTERN); |
| 196 |
| 197 mSelectionController = new ContextualSearchSelectionController(activity,
this); |
| 198 |
| 199 mWebContentsDelegate = new WebContentsDelegateAndroid() { |
| 200 @Override |
| 201 public void onLoadStarted() { |
| 202 super.onLoadStarted(); |
| 203 mSearchPanelDelegate.onLoadStarted(); |
| 204 } |
| 205 |
| 206 @Override |
| 207 public void onLoadStopped() { |
| 208 super.onLoadStopped(); |
| 209 mSearchPanelDelegate.onLoadStopped(); |
| 210 } |
| 211 |
| 212 @Override |
| 213 public void onLoadProgressChanged(int progress) { |
| 214 super.onLoadProgressChanged(progress); |
| 215 mSearchPanelDelegate.onLoadProgressChanged(progress); |
| 216 } |
| 217 }; |
| 218 |
| 219 mFindToolbarObserver = new FindToolbarObserver() { |
| 220 @Override |
| 221 public void onFindToolbarShown() { |
| 222 hideContextualSearch(StateChangeReason.UNKNOWN); |
| 223 } |
| 224 }; |
| 225 |
| 226 final View controlContainer = mActivity.findViewById(R.id.control_contai
ner); |
| 227 mOnFocusChangeListener = new OnGlobalFocusChangeListener() { |
| 228 @Override |
| 229 public void onGlobalFocusChanged(View oldFocus, View newFocus) { |
| 230 if (controlContainer != null && controlContainer.hasFocus()) { |
| 231 hideContextualSearch(StateChangeReason.UNKNOWN); |
| 232 } |
| 233 } |
| 234 }; |
| 235 |
| 236 mTabModelObserver = new EmptyTabModelObserver() { |
| 237 @Override |
| 238 public void didAddTab(Tab tab, TabLaunchType type) { |
| 239 // If we're in the process of promoting this tab, just return an
d don't mess with |
| 240 // this state. |
| 241 if (tab.getContentViewCore() == mSearchContentViewCore) return; |
| 242 hideContextualSearch(StateChangeReason.UNKNOWN); |
| 243 } |
| 244 }; |
| 245 } |
| 246 |
| 247 /** |
| 248 * Sets the manager in charge of find in page. |
| 249 * @param manager A {@link FindToolbarManager} instance. |
| 250 */ |
| 251 public void setFindToolbarManager(FindToolbarManager manager) { |
| 252 assert manager != null; |
| 253 mFindToolbarManager = manager; |
| 254 mFindToolbarManager.addObserver(mFindToolbarObserver); |
| 255 } |
| 256 |
| 257 /** |
| 258 * Initializes this manager. Must be called before {@link #getContextualSea
rchControl()}. |
| 259 * @param parentView The parent view to attach Contextual Search UX to. |
| 260 */ |
| 261 public void initialize(ViewGroup parentView) { |
| 262 mParentView = parentView; |
| 263 mParentView.getViewTreeObserver().addOnGlobalFocusChangeListener(mOnFocu
sChangeListener); |
| 264 mNativeContextualSearchManagerPtr = nativeInit(); |
| 265 listenForHideNotifications(); |
| 266 mTabRedirectHandler = new TabRedirectHandler(mActivity); |
| 267 |
| 268 mIsShowingPromo = false; |
| 269 mDidLogPromoOutcome = false; |
| 270 mDidLoadResolvedSearchRequest = false; |
| 271 mWereSearchResultsSeen = false; |
| 272 mNetworkCommunicator = this; |
| 273 ApplicationStatus.registerStateListenerForActivity(this, mActivity); |
| 274 mIsInitialized = true; |
| 275 } |
| 276 |
| 277 /** |
| 278 * Destroys the native Contextual Search Manager. |
| 279 * Call this method before orphaning this object to allow it to be garbage c
ollected. |
| 280 */ |
| 281 public void destroy() { |
| 282 if (!mIsInitialized) return; |
| 283 |
| 284 hideContextualSearch(StateChangeReason.UNKNOWN); |
| 285 mParentView.getViewTreeObserver().removeOnGlobalFocusChangeListener(mOnF
ocusChangeListener); |
| 286 nativeDestroy(mNativeContextualSearchManagerPtr); |
| 287 stopListeningForHideNotifications(); |
| 288 mTabRedirectHandler.clear(); |
| 289 ApplicationStatus.unregisterActivityStateListener(this); |
| 290 } |
| 291 |
| 292 @Override |
| 293 public void setContextualSearchPanelDelegate(ContextualSearchPanelDelegate d
elegate) { |
| 294 mSearchPanelDelegate = delegate; |
| 295 |
| 296 boolean shouldHide = nativeShouldHidePromoHeader(mNativeContextualSearch
ManagerPtr); |
| 297 mSearchPanelDelegate.setShouldHidePromoHeader(shouldHide); |
| 298 } |
| 299 |
| 300 /** |
| 301 * @return The {@link ContextualSearchPanelDelegate}, for testing purposes o
nly. |
| 302 */ |
| 303 @VisibleForTesting |
| 304 public ContextualSearchPanelDelegate getContextualSearchPanelDelegate() { |
| 305 return mSearchPanelDelegate; |
| 306 } |
| 307 |
| 308 /** |
| 309 * Sets the selection controller for testing purposes. |
| 310 */ |
| 311 @VisibleForTesting |
| 312 ContextualSearchSelectionController getSelectionController() { |
| 313 return mSelectionController; |
| 314 } |
| 315 |
| 316 @VisibleForTesting |
| 317 boolean isSearchPanelShowing() { |
| 318 return mSearchPanelDelegate.isShowing(); |
| 319 } |
| 320 |
| 321 /** |
| 322 * @return Whether the Search Panel is opened. That is, whether it is EXPAND
ED or MAXIMIZED. |
| 323 */ |
| 324 public boolean isSearchPanelOpened() { |
| 325 PanelState state = mSearchPanelDelegate.getPanelState(); |
| 326 return state == PanelState.EXPANDED || state == PanelState.MAXIMIZED; |
| 327 } |
| 328 |
| 329 /** |
| 330 * @return The Base Page's {@link ContentViewCore}. |
| 331 */ |
| 332 @Nullable private ContentViewCore getBaseContentView() { |
| 333 return mSelectionController.getBaseContentView(); |
| 334 } |
| 335 |
| 336 @Override |
| 337 public void setPreferenceState(boolean enabled) { |
| 338 PrefServiceBridge.getInstance().setContextualSearchState(enabled); |
| 339 } |
| 340 |
| 341 @Override |
| 342 public boolean isOptOutPromoAvailable() { |
| 343 return mPolicy.isOptOutPromoAvailable(); |
| 344 } |
| 345 |
| 346 /** |
| 347 * Hides the Contextual Search UX. |
| 348 * @param reason The {@link StateChangeReason} for hiding Contextual Search. |
| 349 */ |
| 350 public void hideContextualSearch(StateChangeReason reason) { |
| 351 if (mSearchPanelDelegate == null) return; |
| 352 |
| 353 if (mSearchPanelDelegate.isShowing()) { |
| 354 mSearchPanelDelegate.closePanel(reason, false); |
| 355 } |
| 356 } |
| 357 |
| 358 @Override |
| 359 public void onCloseContextualSearch() { |
| 360 if (mSearchPanelDelegate == null) return; |
| 361 |
| 362 // NOTE(pedrosimonetti): hideContextualSearch() will also be called afte
r swiping the |
| 363 // Panel down in order to dismiss it. In this case, hideContextualSearch
() will be called |
| 364 // after completing the hide animation, and at that moment the Panel wil
l not be showing |
| 365 // anymore. Therefore, we need to always clear selection, regardless of
when the Panel |
| 366 // was still visible, in order to make sure the selection will be cleare
d appropriately. |
| 367 if (mSelectionController.getSelectionType() == SelectionType.TAP) { |
| 368 mSelectionController.clearSelection(); |
| 369 } |
| 370 |
| 371 // Show the infobar container if it was visible before Contextual Search
was shown. |
| 372 if (mWereInfoBarsHidden) { |
| 373 mWereInfoBarsHidden = false; |
| 374 InfoBarContainer container = getInfoBarContainer(); |
| 375 if (container != null) { |
| 376 container.setVisibility(View.VISIBLE); |
| 377 container.setDoStayInvisible(false); |
| 378 } |
| 379 } |
| 380 |
| 381 if (!mWereSearchResultsSeen && mLoadedSearchUrlTimeMs != 0L) { |
| 382 removeLastSearchVisit(); |
| 383 } |
| 384 |
| 385 // Clear the timestamp. This is to avoid future calls to hideContextualS
earch clearing |
| 386 // the current URL. |
| 387 mLoadedSearchUrlTimeMs = 0L; |
| 388 mWereSearchResultsSeen = false; |
| 389 |
| 390 mNetworkCommunicator.destroySearchContentView(); |
| 391 mSearchRequest = null; |
| 392 |
| 393 if (mIsShowingPromo && !mDidLogPromoOutcome) { |
| 394 logPromoOutcome(); |
| 395 } |
| 396 |
| 397 mIsShowingPromo = false; |
| 398 mSearchPanelDelegate.setIsPromoActive(false); |
| 399 } |
| 400 |
| 401 /** |
| 402 * Called when the system back button is pressed. Will hide the layout. |
| 403 */ |
| 404 public boolean onBackPressed() { |
| 405 if (!mIsInitialized || !mSearchPanelDelegate.isShowing()) return false; |
| 406 hideContextualSearch(StateChangeReason.BACK_PRESS); |
| 407 return true; |
| 408 } |
| 409 |
| 410 /** |
| 411 * Called when the orientation of the device changes. |
| 412 */ |
| 413 public void onOrientationChange() { |
| 414 if (!mIsInitialized) return; |
| 415 |
| 416 // NOTE(pedrosimonetti): Invalidates the existing height. This is to pre
vent keeping |
| 417 // the wrong height for a short period after rotating the device. |
| 418 mSearchPanelDelegate.setPromoContentHeight(0.f); |
| 419 hideContextualSearch(StateChangeReason.UNKNOWN); |
| 420 } |
| 421 |
| 422 /** |
| 423 * Sets the {@link ContextualSearchNetworkCommunicator} to use for server re
quests. |
| 424 * @param networkCommunicator The communicator for all future requests. |
| 425 */ |
| 426 @VisibleForTesting |
| 427 public void setNetworkCommunicator(ContextualSearchNetworkCommunicator netwo
rkCommunicator) { |
| 428 mNetworkCommunicator = networkCommunicator; |
| 429 } |
| 430 |
| 431 /** |
| 432 * Determines if the given selection is valid or not. |
| 433 * @param selection The selection portion of the context. |
| 434 * @return whether the given selection is considered a valid target for a se
arch. |
| 435 */ |
| 436 private boolean isValidSelection(String selection) { |
| 437 return isValidSelection(selection, getBaseContentView()); |
| 438 } |
| 439 |
| 440 @VisibleForTesting |
| 441 boolean isValidSelection(String selection, ContentViewCore baseContentView)
{ |
| 442 if (selection.length() > MAX_SELECTION_LENGTH || !doesContainAWord(selec
tion)) { |
| 443 return false; |
| 444 } |
| 445 return (baseContentView != null) && !baseContentView.isFocusedNodeEditab
le(); |
| 446 } |
| 447 |
| 448 /** |
| 449 * Shows the Contextual Search UX. |
| 450 * Calls back into onGetContextualSearchQueryResponse. |
| 451 * @param stateChangeReason The reason explaining the change of state. |
| 452 */ |
| 453 private void showContextualSearch(StateChangeReason stateChangeReason) { |
| 454 if (!mSearchPanelDelegate.isShowing()) { |
| 455 // If visible, hide the infobar container before showing the Context
ual Search panel. |
| 456 InfoBarContainer container = getInfoBarContainer(); |
| 457 if (container != null && container.getVisibility() == View.VISIBLE)
{ |
| 458 mWereInfoBarsHidden = true; |
| 459 container.setVisibility(View.INVISIBLE); |
| 460 container.setDoStayInvisible(true); |
| 461 } |
| 462 } |
| 463 |
| 464 // If the user is jumping from one unseen search to another search, remo
ve the last search |
| 465 // from history. |
| 466 PanelState state = mSearchPanelDelegate.getPanelState(); |
| 467 if (!mWereSearchResultsSeen && mLoadedSearchUrlTimeMs != 0L |
| 468 && state != PanelState.UNDEFINED && state != PanelState.CLOSED)
{ |
| 469 removeLastSearchVisit(); |
| 470 } |
| 471 |
| 472 // Make sure we'll create a new Content View when needed. |
| 473 mNetworkCommunicator.destroySearchContentView(); |
| 474 |
| 475 boolean isTap = mSelectionController.getSelectionType() == SelectionType
.TAP; |
| 476 boolean didRequestSurroundings = false; |
| 477 if (mPolicy.isOptInPromoAvailable()) { |
| 478 showFirstRunFlow(); |
| 479 } else if (isTap && mPolicy.shouldPreviousTapResolve( |
| 480 mNetworkCommunicator.getBasePageUrl())) { |
| 481 mNetworkCommunicator.startSearchTermResolutionRequest( |
| 482 mSelectionController.getSelectedText()); |
| 483 didRequestSurroundings = true; |
| 484 } else { |
| 485 boolean shouldPrefetch = mPolicy.shouldPrefetchSearchResult(isTap); |
| 486 mSearchRequest = new ContextualSearchRequest(mSelectionController.ge
tSelectedText(), |
| 487 null, shouldPrefetch); |
| 488 mDidLoadResolvedSearchRequest = false; |
| 489 getContextualSearchControl().setCentralText(mSelectionController.get
SelectedText()); |
| 490 if (shouldPrefetch) loadSearchUrl(); |
| 491 } |
| 492 if (!didRequestSurroundings) { |
| 493 // Gather surrounding text for Icing integration, which will make th
e selection and |
| 494 // a shorter version of the surroundings available for Conversationa
l Search. |
| 495 // Although the surroundings are extracted, they will not be sent to
the server as |
| 496 // part of search term resolution, just sent to Icing which keeps th
em local until |
| 497 // the user activates a Voice Search. |
| 498 nativeGatherSurroundingText(mNativeContextualSearchManagerPtr, |
| 499 mSelectionController.getSelectedText(), NEVER_USE_RESOLVED_S
EARCH_TERM, |
| 500 getBaseContentView()); |
| 501 } |
| 502 |
| 503 mWereSearchResultsSeen = false; |
| 504 |
| 505 // TODO(donnd): although we are showing the bar here, we have not yet se
t the text! |
| 506 // Refactor to show the bar and set the text at the same time! |
| 507 // TODO(donnd): If there was a previously ongoing contextual search, we
should ensure |
| 508 // it's registered as closed. |
| 509 mSearchPanelDelegate.peekPanel(stateChangeReason); |
| 510 |
| 511 // Note: now that the contextual search has properly started, set the fi
rst run involvement. |
| 512 if (mPolicy.isOptOutPromoAvailable()) { |
| 513 mIsShowingPromo = true; |
| 514 mDidLogPromoOutcome = false; |
| 515 mSearchPanelDelegate.setDidSearchInvolvePromo(); |
| 516 } |
| 517 |
| 518 assert mSelectionController.getSelectionType() != SelectionType.UNDETERM
INED; |
| 519 mWasActivatedByTap = mSelectionController.getSelectionType() == Selectio
nType.TAP; |
| 520 } |
| 521 |
| 522 @Override |
| 523 public void startSearchTermResolutionRequest(String selection) { |
| 524 ContentViewCore baseContentView = getBaseContentView(); |
| 525 if (baseContentView != null) { |
| 526 nativeStartSearchTermResolutionRequest(mNativeContextualSearchManage
rPtr, selection, |
| 527 true, getBaseContentView()); |
| 528 } |
| 529 } |
| 530 |
| 531 @Override |
| 532 public void continueSearchTermResolutionRequest() { |
| 533 nativeContinueSearchTermResolutionRequest(mNativeContextualSearchManager
Ptr); |
| 534 } |
| 535 |
| 536 @Override |
| 537 @Nullable public URL getBasePageUrl() { |
| 538 ContentViewCore baseContentViewCore = getBaseContentView(); |
| 539 if (baseContentViewCore == null) return null; |
| 540 |
| 541 try { |
| 542 return new URL(baseContentViewCore.getWebContents().getUrl()); |
| 543 } catch (MalformedURLException e) { |
| 544 return null; |
| 545 } |
| 546 } |
| 547 |
| 548 /** |
| 549 * Loads the first-run flow in the search content view and sets the text on
the contextual |
| 550 * search bar. |
| 551 */ |
| 552 private void showFirstRunFlow() { |
| 553 mIsShowingPromo = true; |
| 554 mDidLogPromoOutcome = false; |
| 555 getContextualSearchControl().setFirstRunText(mSelectionController.getSel
ectedText()); |
| 556 loadUrl(FIRST_RUN_FLOW_URL); |
| 557 mSearchPanelDelegate.setIsPromoActive(true); |
| 558 // Saved in the case a search needs to be made after first run. |
| 559 if (mSelectionController.getSelectionType() == SelectionType.TAP) { |
| 560 nativeGatherSurroundingText(mNativeContextualSearchManagerPtr, |
| 561 mSelectionController.getSelectedText(), |
| 562 ALWAYS_USE_RESOLVED_SEARCH_TERM, getBaseContentView()); |
| 563 } |
| 564 } |
| 565 |
| 566 @Override |
| 567 public void updateTopControlsState(int current, boolean animate) { |
| 568 Tab currentTab = mActivity.getActivityTab(); |
| 569 if (currentTab != null) { |
| 570 currentTab.updateTopControlsState(current, animate); |
| 571 } |
| 572 } |
| 573 |
| 574 /** |
| 575 * Determines if the given selection contains a word or not. |
| 576 * @param selection The the selection to check for a word. |
| 577 * @return Whether the selection contains a word anywhere within it or not. |
| 578 */ |
| 579 @VisibleForTesting |
| 580 public boolean doesContainAWord(String selection) { |
| 581 return mContainsWordPattern.matcher(selection).find(); |
| 582 } |
| 583 |
| 584 /** |
| 585 * Accessor for the {@code InfoBarContainer} currently attached to the {@cod
e Tab}. |
| 586 */ |
| 587 private InfoBarContainer getInfoBarContainer() { |
| 588 Tab tab = mActivity.getActivityTab(); |
| 589 return tab == null ? null : tab.getInfoBarContainer(); |
| 590 } |
| 591 |
| 592 /** |
| 593 * Inflates the Contextual Search control, if needed. |
| 594 */ |
| 595 private ContextualSearchControl getContextualSearchControl() { |
| 596 return mSearchPanelDelegate.getContextualSearchControl(); |
| 597 } |
| 598 |
| 599 /** |
| 600 * Listens for notifications that should hide the Contextual Search bar. |
| 601 */ |
| 602 private void listenForHideNotifications() { |
| 603 TabModelSelector selector = mActivity.getTabModelSelector(); |
| 604 |
| 605 mTabModelSelectorTabObserver = new TabModelSelectorTabObserver(selector)
{ |
| 606 @Override |
| 607 public void onPageLoadStarted(Tab tab) { |
| 608 hideContextualSearch(StateChangeReason.UNKNOWN); |
| 609 mDidBasePageLoadJustStart = true; |
| 610 } |
| 611 |
| 612 @Override |
| 613 public void onCrash(Tab tab, boolean sadTabShown) { |
| 614 if (sadTabShown) { |
| 615 // Hide contextual search if the foreground tab crashed |
| 616 hideContextualSearch(StateChangeReason.UNKNOWN); |
| 617 } |
| 618 } |
| 619 |
| 620 @Override |
| 621 public void onClosingStateChanged(Tab tab, boolean closing) { |
| 622 if (closing) hideContextualSearch(StateChangeReason.UNKNOWN); |
| 623 } |
| 624 }; |
| 625 |
| 626 for (TabModel tabModel : selector.getModels()) { |
| 627 tabModel.addObserver(mTabModelObserver); |
| 628 } |
| 629 } |
| 630 |
| 631 /** |
| 632 * Stops listening for notifications that should hide the Contextual Search
bar. |
| 633 */ |
| 634 private void stopListeningForHideNotifications() { |
| 635 if (mTabModelSelectorTabObserver != null) mTabModelSelectorTabObserver.d
estroy(); |
| 636 if (mFindToolbarManager != null) mFindToolbarManager.removeObserver(mFin
dToolbarObserver); |
| 637 |
| 638 TabModelSelector selector = mActivity.getTabModelSelector(); |
| 639 if (selector != null) { |
| 640 for (TabModel tabModel : selector.getModels()) { |
| 641 tabModel.removeObserver(mTabModelObserver); |
| 642 } |
| 643 } |
| 644 } |
| 645 |
| 646 @Override |
| 647 public void onActivityStateChange(Activity activity, int newState) { |
| 648 if (newState == ActivityState.RESUMED || newState == ActivityState.STOPP
ED |
| 649 || newState == ActivityState.DESTROYED) { |
| 650 hideContextualSearch(StateChangeReason.UNKNOWN); |
| 651 } |
| 652 } |
| 653 |
| 654 /** |
| 655 * Clears our private member referencing the native manager. |
| 656 */ |
| 657 @CalledByNative |
| 658 public void clearNativeManager() { |
| 659 assert mNativeContextualSearchManagerPtr != 0; |
| 660 mNativeContextualSearchManagerPtr = 0; |
| 661 } |
| 662 |
| 663 /** |
| 664 * Sets our private member referencing the native manager. |
| 665 * @param nativeManager The pointer to the native Contextual Search manager. |
| 666 */ |
| 667 @CalledByNative |
| 668 public void setNativeManager(long nativeManager) { |
| 669 assert mNativeContextualSearchManagerPtr == 0; |
| 670 mNativeContextualSearchManagerPtr = nativeManager; |
| 671 } |
| 672 |
| 673 /** |
| 674 * Called when surrounding text is available. |
| 675 * @param beforeText to be shown before the selected word. |
| 676 * @param afterText to be shown after the selected word. |
| 677 */ |
| 678 @CalledByNative |
| 679 private void onSurroundingTextAvailable(final String beforeText, final Strin
g afterText) { |
| 680 if (mSearchPanelDelegate.isShowing()) { |
| 681 getContextualSearchControl().setSearchContext( |
| 682 mSelectionController.getSelectedText(), beforeText, afterTex
t); |
| 683 } |
| 684 } |
| 685 |
| 686 /** |
| 687 * Called by native code when a selection is available to share with Icing (
for Conversational |
| 688 * Search). |
| 689 */ |
| 690 @CalledByNative |
| 691 private void onIcingSelectionAvailable( |
| 692 final String encoding, final String surroundingText, int startOffset
, int endOffset) { |
| 693 GSAContextDisplaySelection selection = |
| 694 new GSAContextDisplaySelection(encoding, surroundingText, startO
ffset, endOffset); |
| 695 notifyShowContextualSearch(selection, mNetworkCommunicator.getBasePageUr
l()); |
| 696 } |
| 697 |
| 698 /** |
| 699 * Called in response to the |
| 700 * {@link ContextualSearchManager#nativeStartSearchTermResolutionRequest} me
thod. |
| 701 * @param isNetworkUnavailable Indicates if the network is unavailable, in w
hich case all other |
| 702 * parameters should be ignored. |
| 703 * @param responseCode The HTTP response code. If the code is not OK, the q
uery |
| 704 * should be ignored. |
| 705 * @param searchTerm The term to use in our subsequent search. |
| 706 * @param displayText The text to display in our UX. |
| 707 * @param alternateTerm The alternate term to display on the results page. |
| 708 */ |
| 709 @CalledByNative |
| 710 public void onSearchTermResolutionResponse(boolean isNetworkUnavailable, int
responseCode, |
| 711 final String searchTerm, final String displayText, final String alte
rnateTerm, |
| 712 boolean doPreventPreload) { |
| 713 mNetworkCommunicator.handleSearchTermResolutionResponse(isNetworkUnavail
able, responseCode, |
| 714 searchTerm, displayText, alternateTerm, doPreventPreload); |
| 715 } |
| 716 |
| 717 @Override |
| 718 public void handleSearchTermResolutionResponse(boolean isNetworkUnavailable,
int responseCode, |
| 719 String searchTerm, String displayText, String alternateTerm, boolean
doPreventPreload) { |
| 720 if (!mSearchPanelDelegate.isShowing()) return; |
| 721 |
| 722 // Show an appropriate message for what to search for. |
| 723 String message; |
| 724 boolean doLiteralSearch = false; |
| 725 if (isNetworkUnavailable) { |
| 726 message = mActivity.getResources().getString( |
| 727 R.string.contextual_search_network_unavailable); |
| 728 } else if (!isHttpFailureCode(responseCode)) { |
| 729 message = displayText; |
| 730 } else if (!mPolicy.shouldShowErrorCodeInBar()) { |
| 731 message = mSelectionController.getSelectedText(); |
| 732 doLiteralSearch = true; |
| 733 } else { |
| 734 message = mActivity.getResources().getString( |
| 735 R.string.contextual_search_error, responseCode); |
| 736 doLiteralSearch = true; |
| 737 } |
| 738 getContextualSearchControl().setCentralText(message); |
| 739 |
| 740 // If there was an error, fall back onto a literal search for the select
ion. |
| 741 // Since we're showing the panel, there must be a selection. |
| 742 if (doLiteralSearch) { |
| 743 searchTerm = mSelectionController.getSelectedText(); |
| 744 alternateTerm = null; |
| 745 doPreventPreload = true; |
| 746 } |
| 747 if (!searchTerm.isEmpty()) { |
| 748 // TODO(donnd): Instead of preloading, we should prefetch (ie the UR
L should not |
| 749 // appear in the user's history until the user views it). See crbug
.com/406446. |
| 750 boolean shouldPreload = !doPreventPreload && mPolicy.shouldPrefetchS
earchResult(true); |
| 751 mSearchRequest = new ContextualSearchRequest(searchTerm, alternateTe
rm, shouldPreload); |
| 752 mDidLoadResolvedSearchRequest = false; |
| 753 if (mIsSearchContentViewShowing) { |
| 754 mSearchRequest.setNormalPriority(); |
| 755 } |
| 756 if (mIsSearchContentViewShowing || shouldPreload) { |
| 757 loadSearchUrl(); |
| 758 } |
| 759 } |
| 760 } |
| 761 |
| 762 /** |
| 763 * Loads a Search Request in the Contextual Search's Content View. |
| 764 */ |
| 765 private void loadSearchUrl() { |
| 766 mLoadedSearchUrlTimeMs = System.currentTimeMillis(); |
| 767 mNetworkCommunicator.loadUrl(mSearchRequest.getSearchUrl()); |
| 768 mSearchPanelDelegate.setIsPromoActive(false); |
| 769 mDidLoadResolvedSearchRequest = true; |
| 770 |
| 771 // TODO(pedrosimonetti): If the user taps on a word and quickly after th
at taps on the |
| 772 // peeking Search Bar, the Search Content View will not be displayed. It
seems that |
| 773 // calling ContentViewCore.onShow() while it's being created has no effe
ct. Need |
| 774 // to coordinate with Chrome-Android folks to come up with a proper fix
for this. |
| 775 // For now, we force the ContentView to be displayed by calling onShow()
again |
| 776 // when a URL is being loaded. See: crbug.com/398206 |
| 777 if (mIsSearchContentViewShowing && mSearchContentViewCore != null) { |
| 778 mSearchContentViewCore.onShow(); |
| 779 } |
| 780 } |
| 781 |
| 782 /** |
| 783 * @return Whether a Tap gesture is currently supported. |
| 784 */ |
| 785 private boolean isTapSupported() { |
| 786 // Base page just started navigating away, so taps should be ignored. |
| 787 if (mDidBasePageLoadJustStart) return false; |
| 788 |
| 789 return mPolicy.isTapSupported(); |
| 790 } |
| 791 |
| 792 // -------------------------------------------------------------------------
------------------- |
| 793 // Search Content View |
| 794 // -------------------------------------------------------------------------
------------------- |
| 795 |
| 796 /** |
| 797 * Gets the {@code ContentViewCore} associated with Contextual Search Panel. |
| 798 * @return Contextual Search Panel's {@code ContentViewCore}. |
| 799 */ |
| 800 @Override |
| 801 public ContentViewCore getSearchContentViewCore() { |
| 802 return mSearchContentViewCore; |
| 803 } |
| 804 |
| 805 /** |
| 806 * Sets the {@code ContextualSearchContentViewDelegate} associated with the
Content View. |
| 807 * @param delegate |
| 808 */ |
| 809 public void setSearchContentViewDelegate(ContextualSearchContentViewDelegate
delegate) { |
| 810 mSearchContentViewDelegate = delegate; |
| 811 } |
| 812 |
| 813 /** |
| 814 * Removes the last resolved search URL from the Chrome history. |
| 815 */ |
| 816 private void removeLastSearchVisit() { |
| 817 if (mSearchRequest != null) { |
| 818 nativeRemoveLastSearchVisit(mNativeContextualSearchManagerPtr, |
| 819 mSearchRequest.getSearchUrl(), mLoadedSearchUrlTimeMs); |
| 820 } |
| 821 } |
| 822 |
| 823 /** |
| 824 * Called when the Search content view navigates to a specific URL related t
o the first run |
| 825 * flow. |
| 826 * @param url The new URL that is being navigated to. |
| 827 */ |
| 828 private void onFirstRunNavigation(String url) { |
| 829 if (FIRST_RUN_OPTIN_URL.equals(url)) { |
| 830 mSearchPanelDelegate.setIsPromoActive(false); |
| 831 mSearchPanelDelegate.animateAfterFirstRunSuccess(); |
| 832 PrefServiceBridge.getInstance().setContextualSearchState(true); |
| 833 logPromoOutcome(); |
| 834 String selection = mSelectionController.getSelectedText(); |
| 835 if (mSelectionController.getSelectionType() == SelectionType.LONG_PR
ESS) { |
| 836 mSearchRequest = new ContextualSearchRequest(selection); |
| 837 loadSearchUrl(); |
| 838 getContextualSearchControl().setCentralText(selection); |
| 839 } else { |
| 840 mNetworkCommunicator.continueSearchTermResolutionRequest(); |
| 841 } |
| 842 } else if (FIRST_RUN_OPTOUT_URL.equals(url)) { |
| 843 PrefServiceBridge.getInstance().setContextualSearchState(false); |
| 844 logPromoOutcome(); |
| 845 mSearchPanelDelegate.closePanel(StateChangeReason.OPTOUT, true); |
| 846 mSearchPanelDelegate.setIsPromoActive(false); |
| 847 } else if (FIRST_RUN_LEARN_MORE_URL.equals(url)) { |
| 848 openContextualSearchLearnMore(); |
| 849 mSearchPanelDelegate.setIsPromoActive(false); |
| 850 } |
| 851 } |
| 852 |
| 853 /** |
| 854 * Called when the Search content view navigates to a contextual search requ
est URL. |
| 855 * This navigation could be for a prefetch when the panel is still closed, o
r |
| 856 * a load of a user-visible search result. |
| 857 * @param isFailure Whether the navigation failed. |
| 858 */ |
| 859 private void onContextualSearchRequestNavigation(boolean isFailure) { |
| 860 if (mSearchRequest == null) return; |
| 861 |
| 862 if (mSearchRequest.isUsingLowPriority()) { |
| 863 ContextualSearchUma.logLowPrioritySearchRequestOutcome(isFailure); |
| 864 } else { |
| 865 ContextualSearchUma.logNormalPrioritySearchRequestOutcome(isFailure)
; |
| 866 if (mSearchRequest.getHasFailed()) { |
| 867 ContextualSearchUma.logFallbackSearchRequestOutcome(isFailure); |
| 868 } |
| 869 } |
| 870 |
| 871 if (isFailure && mSearchRequest.isUsingLowPriority()) { |
| 872 // We're navigating to an error page, so we want to stop and retry. |
| 873 // Stop loading the page that displays the error to the user. |
| 874 if (mSearchContentViewCore != null) { |
| 875 // When running tests the Content View might not exist. |
| 876 mSearchContentViewCore.getWebContents().stop(); |
| 877 } |
| 878 mSearchRequest.setHasFailed(); |
| 879 mSearchRequest.setNormalPriority(); |
| 880 // If the content view is showing, load at normal priority now. |
| 881 if (mIsSearchContentViewShowing) { |
| 882 loadSearchUrl(); |
| 883 } else { |
| 884 mDidLoadResolvedSearchRequest = false; |
| 885 } |
| 886 } |
| 887 } |
| 888 |
| 889 /** |
| 890 * Opens the Contextual Search "Learn More" experience. |
| 891 */ |
| 892 private void openContextualSearchLearnMore() { |
| 893 openUrlInNewTab(mActivity.getString(R.string.contextual_search_learn_mor
e_url)); |
| 894 } |
| 895 |
| 896 @Override |
| 897 public void logPromoOutcome() { |
| 898 ContextualSearchUma.logPromoOutcome(mWasActivatedByTap); |
| 899 mDidLogPromoOutcome = true; |
| 900 } |
| 901 |
| 902 /** |
| 903 * Called when the Search Content view has finished loading to record how lo
ng it takes the SERP |
| 904 * to load after opening the panel. |
| 905 */ |
| 906 private void onSearchResultsLoaded() { |
| 907 if (mSearchRequest == null) return; |
| 908 |
| 909 mSearchPanelDelegate.onSearchResultsLoaded(mSearchRequest.wasPrefetch())
; |
| 910 } |
| 911 |
| 912 /** |
| 913 * Creates a new Content View Core to display search results, if needed. |
| 914 */ |
| 915 private void createNewSearchContentViewCoreIfNeeded() { |
| 916 if (mSearchContentViewCore == null) { |
| 917 mNetworkCommunicator.createNewSearchContentView(); |
| 918 } |
| 919 } |
| 920 |
| 921 @Override |
| 922 public void loadUrl(String url) { |
| 923 createNewSearchContentViewCoreIfNeeded(); |
| 924 if (mSearchContentViewCore != null && mSearchContentViewCore.getWebConte
nts() != null) { |
| 925 mDidLoadAnyUrl = true; |
| 926 mSearchContentViewCore.getWebContents().getNavigationController().lo
adUrl( |
| 927 new LoadUrlParams(url)); |
| 928 } |
| 929 } |
| 930 |
| 931 @Override |
| 932 public void createNewSearchContentView() { |
| 933 if (mSearchContentViewCore != null) { |
| 934 mNetworkCommunicator.destroySearchContentView(); |
| 935 } |
| 936 |
| 937 mSearchContentViewCore = new ContentViewCore(mActivity); |
| 938 ContentView cv = new ContentView(mActivity, mSearchContentViewCore); |
| 939 // Creates an initially hidden WebContents which gets shown when the pan
el is opened. |
| 940 mSearchContentViewCore.initialize(cv, cv, |
| 941 ContentViewUtil.createWebContents(false, true), mWindowAndroid); |
| 942 |
| 943 // Transfers the ownership of the WebContents to the native ContextualSe
archManager. |
| 944 nativeSetWebContents(mNativeContextualSearchManagerPtr, mSearchContentVi
ewCore, |
| 945 mWebContentsDelegate); |
| 946 |
| 947 mSearchWebContentsObserver = |
| 948 new WebContentsObserver(mSearchContentViewCore.getWebContents())
{ |
| 949 @Override |
| 950 public void didStartLoading(String url) { |
| 951 mDidPromoteSearchNavigation = false; |
| 952 } |
| 953 |
| 954 @Override |
| 955 public void didStartProvisionalLoadForFrame(long frameId, lo
ng parentFrameId, |
| 956 boolean isMainFrame, String validatedUrl, boolean is
ErrorPage, |
| 957 boolean isIframeSrcdoc) { |
| 958 if (isMainFrame) onExternalNavigation(validatedUrl); |
| 959 } |
| 960 |
| 961 @Override |
| 962 public void didNavigateMainFrame(String url, String baseUrl, |
| 963 boolean isNavigationToDifferentPage, boolean isNavig
ationInPage, |
| 964 int httpResultCode) { |
| 965 mNetworkCommunicator.handleDidNavigateMainFrame(url, htt
pResultCode); |
| 966 } |
| 967 |
| 968 @Override |
| 969 public void didFinishLoad(long frameId, String validatedUrl, |
| 970 boolean isMainFrame) { |
| 971 onSearchResultsLoaded(); |
| 972 boolean shouldClearHistory = mSearchRequest != null |
| 973 && mSearchRequest.getHasFailed(); |
| 974 if (validatedUrl.equals(FIRST_RUN_FLOW_URL)) { |
| 975 extractFirstRunFlowContentHeight(); |
| 976 } else if (mIsShowingPromo |
| 977 && !validatedUrl.startsWith(FIRST_RUN_URL_PREFIX
)) { |
| 978 shouldClearHistory = true; |
| 979 } |
| 980 // Any time we place a page in a ContentViewCore, clear
history if needed. |
| 981 // This prevents the First Run Flow URL or error URLs fr
om |
| 982 // appearing in the Tab's history stack. |
| 983 // Also please note that clearHistory() will not |
| 984 // clear the current entry (search results page in this
case), |
| 985 // and it will not work properly if there are pending na
vigations. |
| 986 // That's why we need to clear the history here, after t
he navigation |
| 987 // is completed. |
| 988 if (shouldClearHistory && mSearchContentViewCore != null
) { |
| 989 mSearchContentViewCore.getWebContents().getNavigatio
nController() |
| 990 .clearHistory(); |
| 991 } |
| 992 } |
| 993 }; |
| 994 |
| 995 mSearchContentViewDelegate.setContextualSearchContentViewCore(mSearchCon
tentViewCore); |
| 996 nativeSetInterceptNavigationDelegate(mNativeContextualSearchManagerPtr, |
| 997 new InterceptNavigationDelegateImpl(), mSearchContentViewCore.ge
tWebContents()); |
| 998 } |
| 999 |
| 1000 @Override |
| 1001 public void handleDidNavigateMainFrame(String url, int httpResultCode) { |
| 1002 if (url.startsWith(FIRST_RUN_URL_PREFIX)) { |
| 1003 // Navigation should not be done inside this WebContentsObserver, |
| 1004 // and since FirstRun may navigate, we need to delay that navigation
. |
| 1005 final String firstRunUrl = url; |
| 1006 new Handler().post(new Runnable() { |
| 1007 @Override |
| 1008 public void run() { |
| 1009 onFirstRunNavigation(firstRunUrl); |
| 1010 } |
| 1011 }); |
| 1012 } else if (shouldPromoteSearchNavigation()) { |
| 1013 onExternalNavigation(url); |
| 1014 } else { |
| 1015 // Could be just prefetching, check if that failed. |
| 1016 boolean isFailure = isHttpFailureCode(httpResultCode); |
| 1017 onContextualSearchRequestNavigation(isFailure); |
| 1018 } |
| 1019 mDidLoadAnyUrl = false; |
| 1020 } |
| 1021 |
| 1022 /** |
| 1023 * @return Whether the given HTTP result code represents a failure or not. |
| 1024 */ |
| 1025 private boolean isHttpFailureCode(int httpResultCode) { |
| 1026 return httpResultCode <= 0 || httpResultCode >= 400; |
| 1027 } |
| 1028 |
| 1029 /** |
| 1030 * @return whether a navigation in the search content view should promote to
a separate tab. |
| 1031 */ |
| 1032 private boolean shouldPromoteSearchNavigation() { |
| 1033 // A navigation can be due to us loading a URL, or a touch in the search
content view. |
| 1034 // Require a touch, but no recent loading, in order to promote to a sepa
rate tab. |
| 1035 // Note that tapping the opt-in button requires checking for recent load
ing. |
| 1036 return mSearchPanelDelegate.didTouchSearchContentView() && !mDidLoadAnyU
rl; |
| 1037 } |
| 1038 |
| 1039 /** |
| 1040 * Called to check if an external navigation is being done and take the appr
opriate action: |
| 1041 * Auto-promotes the panel into a separate tab if that's not already being d
one. |
| 1042 * @param url The URL we are navigating to. |
| 1043 */ |
| 1044 private void onExternalNavigation(String url) { |
| 1045 if (!mDidPromoteSearchNavigation |
| 1046 && !BLACKLISTED_URL.equals(url) |
| 1047 && !url.startsWith(INTENT_URL_PREFIX) |
| 1048 && shouldPromoteSearchNavigation()) { |
| 1049 // Do not promote to a regular tab if we're loading our Resolved Sea
rch |
| 1050 // URL, otherwise we'll promote it when prefetching the Serp. |
| 1051 // Don't promote URLs when they are navigating to an intent - this i
s |
| 1052 // handled by the InterceptNavigationDelegate which uses a faster |
| 1053 // maximizing animation. |
| 1054 mDidPromoteSearchNavigation = true; |
| 1055 mSearchPanelDelegate.maximizePanelThenPromoteToTab(StateChangeReason
.SERP_NAVIGATION); |
| 1056 } |
| 1057 } |
| 1058 |
| 1059 /** |
| 1060 * Grab the content height from the first run flow and store it. |
| 1061 */ |
| 1062 private void extractFirstRunFlowContentHeight() { |
| 1063 mSearchContentViewCore.getWebContents().evaluateJavaScript("getContentHe
ight()", |
| 1064 new JavaScriptCallback() { |
| 1065 @Override |
| 1066 public void handleJavaScriptResult(String result) { |
| 1067 try { |
| 1068 mSearchPanelDelegate.setPromoContentHeight( |
| 1069 Float.parseFloat(result)); |
| 1070 } catch (NumberFormatException e) { |
| 1071 Log.w(TAG, "Could not extract first-run content heig
ht, using " |
| 1072 + "default value."); |
| 1073 } |
| 1074 } |
| 1075 }); |
| 1076 } |
| 1077 |
| 1078 @Override |
| 1079 public void destroySearchContentView() { |
| 1080 if (mSearchContentViewCore != null && mSearchContentViewDelegate != null
) { |
| 1081 nativeDestroyWebContents(mNativeContextualSearchManagerPtr); |
| 1082 mSearchContentViewDelegate.releaseContextualSearchContentViewCore(); |
| 1083 mSearchContentViewCore.destroy(); |
| 1084 mSearchContentViewCore = null; |
| 1085 if (mSearchWebContentsObserver != null) { |
| 1086 mSearchWebContentsObserver.destroy(); |
| 1087 mSearchWebContentsObserver = null; |
| 1088 } |
| 1089 } |
| 1090 |
| 1091 // This should be called last here. The setSearchContentViewVisibility m
ethod |
| 1092 // will change the visibility the SearchContentView but also set the val
ue of the |
| 1093 // internal property mIsSearchContentViewShowing. If we call this after
deleting |
| 1094 // the SearchContentView, it will be faster, because only the internal p
roperty |
| 1095 // will be changed, since there will be no need to change the visibility
of the |
| 1096 // SearchContentView. |
| 1097 setSearchContentViewVisibility(false); |
| 1098 } |
| 1099 |
| 1100 @Override |
| 1101 public void openResolvedSearchUrlInNewTab() { |
| 1102 if (mSearchRequest != null && mSearchRequest.getSearchUrl() != null) { |
| 1103 openUrlInNewTab(mSearchRequest.getSearchUrl()); |
| 1104 } |
| 1105 } |
| 1106 |
| 1107 /** |
| 1108 * Convenience method for opening a specific |url| in a new Tab. |
| 1109 */ |
| 1110 private void openUrlInNewTab(String url) { |
| 1111 TabModelSelector tabModelSelector = mActivity.getTabModelSelector(); |
| 1112 tabModelSelector.openNewTab( |
| 1113 new LoadUrlParams(url), |
| 1114 TabLaunchType.FROM_MENU_OR_OVERVIEW, |
| 1115 tabModelSelector.getCurrentTab(), |
| 1116 tabModelSelector.isIncognitoSelected()); |
| 1117 } |
| 1118 |
| 1119 @Override |
| 1120 public boolean isRunningInCompatibilityMode() { |
| 1121 return DeviceClassManager.isAccessibilityModeEnabled(mActivity) |
| 1122 || SysUtils.isLowEndDevice(); |
| 1123 } |
| 1124 |
| 1125 @Override |
| 1126 public void promoteToTab(boolean shouldFocusOmnibox) { |
| 1127 // If the request object is null that means that a Contextual Search has
just started |
| 1128 // and the Search Term Resolution response hasn't arrived yet. In this c
ase, promoting |
| 1129 // the Panel to a Tab will result in creating a new tab with URL about:b
lank. To prevent |
| 1130 // this problem, we are ignoring tap gestures in the Search Bar if we do
n't know what |
| 1131 // to search for. |
| 1132 if (mSearchRequest != null |
| 1133 && mSearchContentViewCore != null |
| 1134 && mSearchContentViewCore.getWebContents() != null |
| 1135 && !mSearchContentViewCore.getWebContents().getUrl().equals(FIRS
T_RUN_FLOW_URL)) { |
| 1136 mSelectionController.clearSelection(); |
| 1137 nativeReleaseWebContents(mNativeContextualSearchManagerPtr); |
| 1138 mSearchContentViewDelegate.releaseContextualSearchContentViewCore(); |
| 1139 if (!mTabPromotionDelegate.createContextualSearchTab(mSearchContentV
iewCore)) { |
| 1140 nativeDestroyWebContentsFromContentViewCore(mNativeContextualSea
rchManagerPtr, |
| 1141 mSearchContentViewCore); |
| 1142 mSearchContentViewCore.destroy(); |
| 1143 } |
| 1144 if (mSearchWebContentsObserver != null) { |
| 1145 mSearchWebContentsObserver.destroy(); |
| 1146 mSearchWebContentsObserver = null; |
| 1147 } |
| 1148 mSearchContentViewCore = null; |
| 1149 mIsSearchContentViewShowing = false; |
| 1150 mSearchRequest = null; |
| 1151 |
| 1152 // NOTE(pedrosimonetti): The Panel should be closed after being prom
oted to a Tab |
| 1153 // to prevent Chrome-Android from animating the creation of the new
Tab. |
| 1154 mSearchPanelDelegate.closePanel(StateChangeReason.TAB_PROMOTION, fal
se); |
| 1155 |
| 1156 // Focus the Omnibox. |
| 1157 if (shouldFocusOmnibox) { |
| 1158 new Handler().post(new Runnable() { |
| 1159 @Override |
| 1160 public void run() { |
| 1161 View urlBarView = mActivity.findViewById(R.id.url_bar); |
| 1162 urlBarView.requestFocus(); |
| 1163 } |
| 1164 }); |
| 1165 } |
| 1166 } |
| 1167 } |
| 1168 |
| 1169 @Override |
| 1170 public void resetSearchContentViewScroll() { |
| 1171 if (mSearchContentViewCore != null) { |
| 1172 mSearchContentViewCore.scrollTo(0, 0); |
| 1173 } |
| 1174 } |
| 1175 |
| 1176 @Override |
| 1177 public float getSearchContentViewVerticalScroll() { |
| 1178 return mSearchContentViewCore != null |
| 1179 ? mSearchContentViewCore.computeVerticalScrollOffset() : -1.f; |
| 1180 } |
| 1181 |
| 1182 @Override |
| 1183 public void setSearchContentViewVisibility(boolean isVisible) { |
| 1184 if (mIsSearchContentViewShowing == isVisible) return; |
| 1185 |
| 1186 mIsSearchContentViewShowing = isVisible; |
| 1187 if (isVisible) { |
| 1188 mWereSearchResultsSeen = true; |
| 1189 // If there's no current request, then either a search term resoluti
on |
| 1190 // is in progress or we should do a verbatim search now. |
| 1191 if (mSearchRequest == null |
| 1192 && mPolicy.shouldCreateVerbatimRequest(mSelectionController, |
| 1193 mNetworkCommunicator.getBasePageUrl())) { |
| 1194 mSearchRequest = new ContextualSearchRequest( |
| 1195 mSelectionController.getSelectedText()); |
| 1196 mDidLoadResolvedSearchRequest = false; |
| 1197 } |
| 1198 if (mSearchRequest != null && !mDidLoadResolvedSearchRequest) { |
| 1199 mSearchRequest.setNormalPriority(); |
| 1200 loadSearchUrl(); |
| 1201 } |
| 1202 // The CVC is created with the search request, but if none was made
we'll need |
| 1203 // one in order to display an empty panel. |
| 1204 createNewSearchContentViewCoreIfNeeded(); |
| 1205 if (mSearchContentViewCore != null) mSearchContentViewCore.onShow(); |
| 1206 mSearchPanelDelegate.setWasSearchContentViewSeen(); |
| 1207 mPolicy.resetTapCounters(); |
| 1208 } else { |
| 1209 if (mSearchContentViewCore != null) mSearchContentViewCore.onHide(); |
| 1210 } |
| 1211 } |
| 1212 |
| 1213 @Override |
| 1214 public void preserveBasePageSelectionOnNextLossOfFocus() { |
| 1215 ContentViewCore basePageContentView = getBaseContentView(); |
| 1216 if (basePageContentView != null) { |
| 1217 basePageContentView.preserveSelectionOnNextLossOfFocus(); |
| 1218 } |
| 1219 } |
| 1220 |
| 1221 @Override |
| 1222 public void dismissContextualSearchBar() { |
| 1223 hideContextualSearch(StateChangeReason.UNKNOWN); |
| 1224 } |
| 1225 |
| 1226 // Used to intercept intent navigations. |
| 1227 // TODO(jeremycho): Consider creating a Tab with the Panel's ContentViewCore
, |
| 1228 // which would also handle functionality like long-press-to-paste. |
| 1229 private class InterceptNavigationDelegateImpl implements InterceptNavigation
Delegate { |
| 1230 final ExternalNavigationHandler mExternalNavHandler = new ExternalNaviga
tionHandler( |
| 1231 mActivity); |
| 1232 @Override |
| 1233 public boolean shouldIgnoreNavigation(NavigationParams navigationParams)
{ |
| 1234 mTabRedirectHandler.updateNewUrlLoading(navigationParams.pageTransit
ionType, |
| 1235 navigationParams.isRedirect, |
| 1236 navigationParams.hasUserGesture || navigationParams.hasUserG
estureCarryover, |
| 1237 mActivity.getLastUserInteractionTime(), TabRedirectHandler.I
NVALID_ENTRY_INDEX); |
| 1238 |
| 1239 ExternalNavigationParams params = new ExternalNavigationParams.Build
er( |
| 1240 navigationParams.url, false, getReferrerUrl(), |
| 1241 navigationParams.pageTransitionType, navigationParams.isRedi
rect) |
| 1242 .setApplicationMustBeInForeground(true) |
| 1243 .setRedirectHandler(mTabRedirectHandler) |
| 1244 .setIsMainFrame(navigationParams.isMainFrame) |
| 1245 .build(); |
| 1246 if (mExternalNavHandler.shouldOverrideUrlLoading(params) |
| 1247 != OverrideUrlLoadingResult.NO_OVERRIDE) { |
| 1248 mSearchPanelDelegate.maximizePanelThenPromoteToTab( |
| 1249 StateChangeReason.TAB_PROMOTION, |
| 1250 INTERCEPT_NAVIGATION_PROMOTION_ANIMATION_DURATION_MS); |
| 1251 return true; |
| 1252 } |
| 1253 return false; |
| 1254 } |
| 1255 |
| 1256 private String getReferrerUrl() { |
| 1257 if (mSearchContentViewCore != null && mSearchContentViewCore.getWebC
ontents() != null) { |
| 1258 return mSearchContentViewCore.getWebContents().getNavigationCont
roller() |
| 1259 .getOriginalUrlForVisibleNavigationEntry(); |
| 1260 } else { |
| 1261 return null; |
| 1262 } |
| 1263 } |
| 1264 } |
| 1265 |
| 1266 // -------------------------------------------------------------------------
------------------- |
| 1267 // ContextualSearchClient -- interface used by ContentViewCore. |
| 1268 // -------------------------------------------------------------------------
------------------- |
| 1269 |
| 1270 @Override |
| 1271 public void onSelectionChanged(String selection) { |
| 1272 mSelectionController.handleSelectionChanged(selection); |
| 1273 updateTopControlsState(TopControlsState.BOTH, true); |
| 1274 } |
| 1275 |
| 1276 @Override |
| 1277 public void onSelectionEvent(int eventType, float posXPix, float posYPix) { |
| 1278 mSelectionController.handleSelectionEvent(eventType, posXPix, posYPix); |
| 1279 } |
| 1280 |
| 1281 @Override |
| 1282 public void showUnhandledTapUIIfNeeded(final int x, final int y) { |
| 1283 mDidBasePageLoadJustStart = false; |
| 1284 mSelectionController.handleShowUnhandledTapUIIfNeeded(x, y); |
| 1285 } |
| 1286 |
| 1287 // -------------------------------------------------------------------------
------------------- |
| 1288 // Selection |
| 1289 // -------------------------------------------------------------------------
------------------- |
| 1290 |
| 1291 /** |
| 1292 * Returns a new {@code GestureStateListener} that will listen for events in
the Base Page. |
| 1293 * This listener will handle all Contextual Search-related interactions that
go through the |
| 1294 * listener. |
| 1295 */ |
| 1296 public GestureStateListener getGestureStateListener() { |
| 1297 return mSelectionController.getGestureStateListener(); |
| 1298 } |
| 1299 |
| 1300 @Override |
| 1301 public void handleScroll() { |
| 1302 hideContextualSearch(StateChangeReason.BASE_PAGE_SCROLL); |
| 1303 } |
| 1304 |
| 1305 @Override |
| 1306 public void handleInvalidTap() { |
| 1307 hideContextualSearch(StateChangeReason.BASE_PAGE_TAP); |
| 1308 } |
| 1309 |
| 1310 @Override |
| 1311 public void handleValidTap() { |
| 1312 if (isTapSupported()) { |
| 1313 // Here we are starting a new Contextual Search with a Tap gesture,
therefore |
| 1314 // we need to clear to properly reflect that a search just started a
nd we don't |
| 1315 // have the resolved search term yet. |
| 1316 mSearchRequest = null; |
| 1317 |
| 1318 // Let the policy know that a tap gesture has been received. |
| 1319 mPolicy.registerTap(); |
| 1320 |
| 1321 ContentViewCore baseContentView = getBaseContentView(); |
| 1322 if (baseContentView != null) baseContentView.getWebContents().select
WordAroundCaret(); |
| 1323 } |
| 1324 } |
| 1325 |
| 1326 @Override |
| 1327 public void handleSelection(String selection, SelectionType type, float x, f
loat y) { |
| 1328 if (!selection.isEmpty()) { |
| 1329 boolean isSelectionValid = isValidSelection(selection); |
| 1330 |
| 1331 StateChangeReason stateChangeReason = type == SelectionType.TAP |
| 1332 ? StateChangeReason.TEXT_SELECT_TAP : StateChangeReason.TEXT
_SELECT_LONG_PRESS; |
| 1333 ContextualSearchUma.logSelectionIsValid(isSelectionValid); |
| 1334 |
| 1335 if (isSelectionValid) { |
| 1336 mSearchPanelDelegate.updateBasePageSelectionYPx(y); |
| 1337 showContextualSearch(stateChangeReason); |
| 1338 } else { |
| 1339 hideContextualSearch(stateChangeReason); |
| 1340 } |
| 1341 } |
| 1342 } |
| 1343 |
| 1344 @Override |
| 1345 public void handleSelectionModification(String selection, float x, float y)
{ |
| 1346 if (mSearchPanelDelegate.isShowing()) { |
| 1347 getContextualSearchControl().setCentralText(selection); |
| 1348 } |
| 1349 } |
| 1350 |
| 1351 @Override |
| 1352 public void onClearSelection() { |
| 1353 notifyHideContextualSearch(); |
| 1354 } |
| 1355 |
| 1356 // -------------------------------------------------------------------------
------------------- |
| 1357 // Native calls |
| 1358 // -------------------------------------------------------------------------
------------------- |
| 1359 |
| 1360 private native long nativeInit(); |
| 1361 private native void nativeDestroy(long nativeContextualSearchManager); |
| 1362 private native void nativeStartSearchTermResolutionRequest( |
| 1363 long nativeContextualSearchManager, String selection, boolean useRes
olvedSearchTerm, |
| 1364 ContentViewCore baseContentViewCore); |
| 1365 private native void nativeGatherSurroundingText( |
| 1366 long nativeContextualSearchManager, String selection, boolean useRes
olvedSearchTerm, |
| 1367 ContentViewCore baseContentViewCore); |
| 1368 private native void nativeContinueSearchTermResolutionRequest( |
| 1369 long nativeContextualSearchManager); |
| 1370 private native void nativeRemoveLastSearchVisit( |
| 1371 long nativeContextualSearchManager, String searchUrl, long searchUrl
TimeMs); |
| 1372 private native void nativeSetWebContents(long nativeContextualSearchManager, |
| 1373 ContentViewCore searchContentViewCore, WebContentsDelegateAndroid de
legate); |
| 1374 private native void nativeDestroyWebContents(long nativeContextualSearchMana
ger); |
| 1375 private native void nativeReleaseWebContents(long nativeContextualSearchMana
ger); |
| 1376 private native void nativeDestroyWebContentsFromContentViewCore( |
| 1377 long nativeContextualSearchManager, ContentViewCore contentViewCore)
; |
| 1378 private native boolean nativeShouldHidePromoHeader(long nativeContextualSear
chManager); |
| 1379 private native void nativeSetInterceptNavigationDelegate(long nativeContextu
alSearchManager, |
| 1380 InterceptNavigationDelegate delegate, WebContents webContents); |
| 1381 } |
OLD | NEW |