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.omnibox; 6 7 import android.content.Context; 8 import android.content.res.ColorStateList; 9 import android.content.res.Configuration; 10 import android.graphics.drawable.Drawable; 11 import android.os.Parcelable; 12 import android.text.TextUtils; 13 import android.util.AttributeSet; 14 import android.util.SparseArray; 15 import android.view.KeyEvent; 16 import android.view.LayoutInflater; 17 import android.view.View; 18 import android.view.View.OnClickListener; 19 import android.view.ViewGroup; 20 import android.view.WindowManager; 21 import android.view.inputmethod.InputMethodManager; 22 import android.widget.FrameLayout; 23 import android.widget.ImageButton; 24 import android.widget.LinearLayout; 25 import android.widget.TextView; 26 27 import androidx.annotation.CallSuper; 28 import androidx.annotation.Nullable; 29 import androidx.annotation.VisibleForTesting; 30 import androidx.core.view.MarginLayoutParamsCompat; 31 import androidx.core.view.ViewCompat; 32 33 import org.chromium.base.ApiCompatibilityUtils; 34 import org.chromium.base.Callback; 35 import org.chromium.base.CallbackController; 36 import org.chromium.base.CommandLine; 37 import org.chromium.base.ObserverList; 38 import org.chromium.base.metrics.RecordHistogram; 39 import org.chromium.base.metrics.RecordUserAction; 40 import org.chromium.base.supplier.ObservableSupplier; 41 import org.chromium.base.supplier.Supplier; 42 import org.chromium.chrome.R; 43 import org.chromium.chrome.browser.ActivityTabProvider; 44 import org.chromium.chrome.browser.AppHooks; 45 import org.chromium.chrome.browser.WindowDelegate; 46 import org.chromium.chrome.browser.flags.ChromeSwitches; 47 import org.chromium.chrome.browser.gsa.GSAState; 48 import org.chromium.chrome.browser.locale.LocaleManager; 49 import org.chromium.chrome.browser.native_page.NativePageFactory; 50 import org.chromium.chrome.browser.ntp.FakeboxDelegate; 51 import org.chromium.chrome.browser.ntp.NewTabPage; 52 import org.chromium.chrome.browser.ntp.NewTabPageUma; 53 import org.chromium.chrome.browser.omnibox.UrlBar.ScrollType; 54 import org.chromium.chrome.browser.omnibox.UrlBar.UrlBarDelegate; 55 import org.chromium.chrome.browser.omnibox.UrlBarCoordinator.SelectionState; 56 import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader; 57 import org.chromium.chrome.browser.omnibox.status.StatusCoordinator; 58 import org.chromium.chrome.browser.omnibox.status.StatusView; 59 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator; 60 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteDelegate; 61 import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionsDropdownEmbedder; 62 import org.chromium.chrome.browser.omnibox.voice.AssistantVoiceSearchService; 63 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler; 64 import org.chromium.chrome.browser.preferences.SharedPreferencesManager; 65 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManager; 66 import org.chromium.chrome.browser.profiles.Profile; 67 import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory; 68 import org.chromium.chrome.browser.share.ShareDelegate; 69 import org.chromium.chrome.browser.tab.Tab; 70 import org.chromium.chrome.browser.tabmodel.IncognitoStateProvider; 71 import org.chromium.chrome.browser.toolbar.top.ToolbarActionModeCallback; 72 import org.chromium.chrome.browser.util.ChromeAccessibilityUtil; 73 import org.chromium.chrome.browser.util.KeyNavigationUtil; 74 import org.chromium.components.browser_ui.styles.ChromeColors; 75 import org.chromium.components.browser_ui.widget.CompositeTouchDelegate; 76 import org.chromium.components.embedder_support.util.UrlUtilities; 77 import org.chromium.components.search_engines.TemplateUrl; 78 import org.chromium.components.search_engines.TemplateUrlService; 79 import org.chromium.components.search_engines.TemplateUrlService.TemplateUrlServiceObserver; 80 import org.chromium.content_public.browser.LoadUrlParams; 81 import org.chromium.content_public.common.ResourceRequestBody; 82 import org.chromium.ui.KeyboardVisibilityDelegate; 83 import org.chromium.ui.base.DeviceFormFactor; 84 import org.chromium.ui.base.PageTransition; 85 import org.chromium.ui.base.WindowAndroid; 86 import org.chromium.ui.modaldialog.ModalDialogManager; 87 import org.chromium.ui.util.ColorUtils; 88 89 import java.util.ArrayList; 90 import java.util.HashMap; 91 import java.util.List; 92 93 /** 94 * This class represents the location bar where the user types in URLs and 95 * search terms. 96 */ 97 public class LocationBarLayout 98 extends FrameLayout implements OnClickListener, AutocompleteDelegate, FakeboxDelegate, 99 VoiceRecognitionHandler.Delegate, 100 AssistantVoiceSearchService.Observer, UrlBarDelegate { 101 private static final int KEYBOARD_HIDE_DELAY_MS = 150; 102 private static final int KEYBOARD_MODE_CHANGE_DELAY_MS = 300; 103 104 protected ImageButton mDeleteButton; 105 protected ImageButton mMicButton; 106 private boolean mShouldShowMicButtonWhenUnfocused; 107 protected UrlBar mUrlBar; 108 private final boolean mIsTablet; 109 110 protected UrlBarCoordinator mUrlCoordinator; 111 protected AutocompleteCoordinator mAutocompleteCoordinator; 112 113 protected LocationBarDataProvider mLocationBarDataProvider; 114 private final ObserverList<UrlFocusChangeListener> mUrlFocusChangeListeners = 115 new ObserverList<>(); 116 117 private final List<Runnable> mDeferredNativeRunnables = new ArrayList<Runnable>(); 118 119 protected StatusCoordinator mStatusCoordinator; 120 121 private WindowAndroid mWindowAndroid; 122 private WindowDelegate mWindowDelegate; 123 124 private String mOriginalUrl = ""; 125 126 private boolean mUrlFocusChangeInProgress; 127 protected boolean mNativeInitialized; 128 private boolean mUrlHasFocus; 129 private boolean mUrlFocusedFromFakebox; 130 private boolean mUrlFocusedFromQueryTiles; 131 private boolean mUrlFocusedWithoutAnimations; 132 protected boolean mVoiceSearchEnabled; 133 134 private OmniboxPrerender mOmniboxPrerender; 135 136 protected float mUrlFocusChangeFraction; 137 protected LinearLayout mUrlActionContainer; 138 139 private VoiceRecognitionHandler mVoiceRecognitionHandler; 140 141 protected CompositeTouchDelegate mCompositeTouchDelegate; 142 143 private AssistantVoiceSearchService mAssistantVoiceSearchService; 144 private Runnable mKeyboardResizeModeTask; 145 private Runnable mKeyboardHideTask; 146 private ObservableSupplier<Profile> mProfileSupplier; 147 private Callback<Profile> mProfileSupplierObserver; 148 private CallbackController mCallbackController = new CallbackController(); 149 private TemplateUrlServiceObserver mTemplateUrlObserver; 150 private OverrideUrlLoadingDelegate mOverrideUrlLoadingDelegate; 151 152 /** 153 * Class to handle input from a hardware keyboard when the focus is on the URL bar. In 154 * particular, handle navigating the suggestions list from the keyboard. 155 */ 156 private final class UrlBarKeyListener implements OnKeyListener { 157 @Override onKey(View v, int keyCode, KeyEvent event)158 public boolean onKey(View v, int keyCode, KeyEvent event) { 159 boolean isRtl = v.getLayoutDirection() == LAYOUT_DIRECTION_RTL; 160 if (mAutocompleteCoordinator.handleKeyEvent(keyCode, event)) { 161 return true; 162 } else if (keyCode == KeyEvent.KEYCODE_BACK) { 163 if (KeyNavigationUtil.isActionDown(event) && event.getRepeatCount() == 0) { 164 // Tell the framework to start tracking this event. 165 getKeyDispatcherState().startTracking(event, this); 166 return true; 167 } else if (KeyNavigationUtil.isActionUp(event)) { 168 getKeyDispatcherState().handleUpEvent(event); 169 if (event.isTracking() && !event.isCanceled()) { 170 backKeyPressed(); 171 return true; 172 } 173 } 174 } else if (keyCode == KeyEvent.KEYCODE_ESCAPE) { 175 if (KeyNavigationUtil.isActionDown(event) && event.getRepeatCount() == 0) { 176 revertChanges(); 177 return true; 178 } 179 } else if ((!isRtl && KeyNavigationUtil.isGoRight(event)) 180 || (isRtl && KeyNavigationUtil.isGoLeft(event))) { 181 // Ensures URL bar doesn't lose focus, when RIGHT or LEFT (RTL) key is pressed while 182 // the cursor is positioned at the end of the text. 183 TextView tv = (TextView) v; 184 return tv.getSelectionStart() == tv.getSelectionEnd() 185 && tv.getSelectionEnd() == tv.getText().length(); 186 } 187 return false; 188 } 189 } 190 LocationBarLayout(Context context, AttributeSet attrs)191 public LocationBarLayout(Context context, AttributeSet attrs) { 192 this(context, attrs, R.layout.location_bar); 193 194 mCompositeTouchDelegate = new CompositeTouchDelegate(this); 195 setTouchDelegate(mCompositeTouchDelegate); 196 } 197 LocationBarLayout(Context context, AttributeSet attrs, int layoutId)198 public LocationBarLayout(Context context, AttributeSet attrs, int layoutId) { 199 super(context, attrs); 200 201 LayoutInflater.from(context).inflate(layoutId, this, true); 202 203 mIsTablet = DeviceFormFactor.isNonMultiDisplayContextOnTablet(context); 204 205 mDeleteButton = findViewById(R.id.delete_button); 206 207 mUrlBar = findViewById(R.id.url_bar); 208 209 mUrlCoordinator = new UrlBarCoordinator((UrlBar) mUrlBar); 210 mUrlCoordinator.setDelegate(this); 211 212 OmniboxSuggestionsDropdownEmbedder embedder = new OmniboxSuggestionsDropdownEmbedder() { 213 @Override 214 public boolean isTablet() { 215 return mIsTablet; 216 } 217 218 @Override 219 public WindowDelegate getWindowDelegate() { 220 return mWindowDelegate; 221 } 222 223 @Override 224 public View getAnchorView() { 225 return getRootView().findViewById(R.id.toolbar); 226 } 227 228 @Override 229 public View getAlignmentView() { 230 return mIsTablet ? LocationBarLayout.this : null; 231 } 232 }; 233 mAutocompleteCoordinator = 234 new AutocompleteCoordinator(this, this, embedder, mUrlCoordinator); 235 addUrlFocusChangeListener(mAutocompleteCoordinator); 236 mUrlCoordinator.addUrlTextChangeListener(mAutocompleteCoordinator); 237 238 mMicButton = findViewById(R.id.mic_button); 239 240 mUrlActionContainer = (LinearLayout) findViewById(R.id.url_action_container); 241 242 mVoiceRecognitionHandler = new VoiceRecognitionHandler(this); 243 } 244 245 /** 246 * Called when activity is being destroyed. 247 */ destroy()248 void destroy() { 249 mUrlFocusChangeListeners.clear(); 250 251 if (mAutocompleteCoordinator != null) { 252 removeUrlFocusChangeListener(mAutocompleteCoordinator); 253 mAutocompleteCoordinator.destroy(); 254 mAutocompleteCoordinator = null; 255 } 256 257 if (mAssistantVoiceSearchService != null) { 258 mAssistantVoiceSearchService.destroy(); 259 mAssistantVoiceSearchService = null; 260 } 261 262 if (mCallbackController != null) { 263 mCallbackController.destroy(); 264 mCallbackController = null; 265 } 266 267 if (mProfileSupplier != null) { 268 mProfileSupplier.removeObserver(mProfileSupplierObserver); 269 mProfileSupplier = null; 270 mProfileSupplierObserver = null; 271 } 272 273 if (mTemplateUrlObserver != null) { 274 TemplateUrlServiceFactory.get().removeObserver(mTemplateUrlObserver); 275 mTemplateUrlObserver = null; 276 } 277 } 278 279 @Override onFinishInflate()280 protected void onFinishInflate() { 281 super.onFinishInflate(); 282 283 setLayoutTransition(null); 284 285 StatusView statusView = findViewById(R.id.location_bar_status); 286 statusView.setCompositeTouchDelegate(mCompositeTouchDelegate); 287 mStatusCoordinator = new StatusCoordinator(mIsTablet, statusView, mUrlCoordinator); 288 mUrlCoordinator.addUrlTextChangeListener(mStatusCoordinator); 289 290 updateShouldAnimateIconChanges(); 291 mUrlBar.setOnKeyListener(new UrlBarKeyListener()); 292 293 // mLocationBar's direction is tied to this UrlBar's text direction. Icons inside the 294 // location bar, e.g. lock, refresh, X, should be reversed if UrlBar's text is RTL. 295 mUrlCoordinator.setUrlDirectionListener(new UrlBar.UrlDirectionListener() { 296 @Override 297 public void onUrlDirectionChanged(int layoutDirection) { 298 ViewCompat.setLayoutDirection(LocationBarLayout.this, layoutDirection); 299 mAutocompleteCoordinator.updateSuggestionListLayoutDirection(); 300 } 301 }); 302 } 303 304 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)305 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 306 updateLayoutParams(); 307 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 308 } 309 310 @Override dispatchKeyEvent(KeyEvent event)311 public boolean dispatchKeyEvent(KeyEvent event) { 312 boolean retVal = super.dispatchKeyEvent(event); 313 if (retVal && mUrlHasFocus && mUrlFocusedWithoutAnimations 314 && event.getAction() == KeyEvent.ACTION_DOWN && event.isPrintingKey() 315 && event.hasNoModifiers()) { 316 handleUrlFocusAnimation(mUrlHasFocus); 317 } 318 return retVal; 319 } 320 321 @Override onConfigurationChanged(Configuration newConfig)322 protected void onConfigurationChanged(Configuration newConfig) { 323 super.onConfigurationChanged(newConfig); 324 325 if (mUrlHasFocus && mUrlFocusedWithoutAnimations 326 && newConfig.keyboard != Configuration.KEYBOARD_QWERTY) { 327 // If we lose the hardware keyboard and the focus animations were not run, then the 328 // user has not typed any text, so we will just clear the focus instead. 329 setUrlBarFocus(false, null, OmniboxFocusReason.UNFOCUS); 330 } 331 } 332 initializeControls(WindowDelegate windowDelegate, WindowAndroid windowAndroid, ActivityTabProvider activityTabProvider, Supplier<ModalDialogManager> modalDialogManagerSupplier, Supplier<ShareDelegate> shareDelegateSupplier, IncognitoStateProvider incognitoStateProvider, OverrideUrlLoadingDelegate overrideUrlLoadingDelegate)333 public void initializeControls(WindowDelegate windowDelegate, WindowAndroid windowAndroid, 334 ActivityTabProvider activityTabProvider, 335 Supplier<ModalDialogManager> modalDialogManagerSupplier, 336 Supplier<ShareDelegate> shareDelegateSupplier, 337 IncognitoStateProvider incognitoStateProvider, 338 OverrideUrlLoadingDelegate overrideUrlLoadingDelegate) { 339 mWindowDelegate = windowDelegate; 340 mWindowAndroid = windowAndroid; 341 342 mUrlCoordinator.setWindowDelegate(windowDelegate); 343 mAutocompleteCoordinator.setWindowAndroid(windowAndroid); 344 mAutocompleteCoordinator.setActivityTabProvider(activityTabProvider); 345 mAutocompleteCoordinator.setShareDelegateSupplier(shareDelegateSupplier); 346 mStatusCoordinator.setIncognitoStateProvider(incognitoStateProvider); 347 mStatusCoordinator.setModalDialogManagerSupplier(modalDialogManagerSupplier); 348 mOverrideUrlLoadingDelegate = overrideUrlLoadingDelegate; 349 } 350 351 @Override getAutocompleteCoordinator()352 public AutocompleteCoordinator getAutocompleteCoordinator() { 353 return mAutocompleteCoordinator; 354 } 355 356 /** 357 * Runs logic that can't be invoked until after native is initialized but shouldn't be on the 358 * critical path, e.g. pre-fetching autocomplete suggestions. Contrast with 359 * onFinishNativeInitialization, which is for logic that should be on the critical path and need 360 * native to be initialized. This method must be called after onFinishNativeInitialization. 361 */ onDeferredStartup()362 public void onDeferredStartup() { 363 assert mNativeInitialized; 364 startPrefetch(); 365 } 366 onFinishNativeInitialization()367 public void onFinishNativeInitialization() { 368 TemplateUrlServiceFactory.get().runWhenLoaded(this::registerTemplateUrlObserver); 369 mNativeInitialized = true; 370 371 mAutocompleteCoordinator.onNativeInitialized(); 372 mStatusCoordinator.onNativeInitialized(); 373 updateMicButtonState(); 374 mDeleteButton.setOnClickListener(this); 375 mMicButton.setOnClickListener(this); 376 377 mOmniboxPrerender = new OmniboxPrerender(); 378 379 for (Runnable deferredRunnable : mDeferredNativeRunnables) { 380 post(deferredRunnable); 381 } 382 mDeferredNativeRunnables.clear(); 383 384 updateVisualsForState(); 385 386 updateMicButtonVisibility(); 387 388 mAssistantVoiceSearchService = new AssistantVoiceSearchService(getContext(), 389 AppHooks.get().getExternalAuthUtils(), TemplateUrlServiceFactory.get(), 390 GSAState.getInstance(getContext()), this, SharedPreferencesManager.getInstance()); 391 mVoiceRecognitionHandler.setAssistantVoiceSearchService(mAssistantVoiceSearchService); 392 onAssistantVoiceSearchServiceChanged(); 393 setProfile(mProfileSupplier.get()); 394 } 395 396 /** Initiates a prefetch of autocomplete suggestions. */ startPrefetch()397 public void startPrefetch() { 398 if (!mNativeInitialized) return; 399 400 mAutocompleteCoordinator.prefetchZeroSuggestResults(); 401 } 402 setProfileSupplier(ObservableSupplier<Profile> profileSupplier)403 public void setProfileSupplier(ObservableSupplier<Profile> profileSupplier) { 404 assert profileSupplier != null; 405 assert mProfileSupplier == null; 406 mProfileSupplier = profileSupplier; 407 mProfileSupplierObserver = mCallbackController.makeCancelable(this::setProfile); 408 mProfileSupplier.addObserver(mProfileSupplierObserver); 409 } 410 411 @Override clearOmniboxFocus()412 public void clearOmniboxFocus() { 413 setUrlBarFocus(false, null, OmniboxFocusReason.UNFOCUS); 414 } 415 selectAll()416 public void selectAll() { 417 mUrlCoordinator.selectAll(); 418 } 419 revertChanges()420 public void revertChanges() { 421 if (!mUrlHasFocus) { 422 setUrl(mLocationBarDataProvider.getCurrentUrl()); 423 } else { 424 String currentUrl = mLocationBarDataProvider.getCurrentUrl(); 425 if (NativePageFactory.isNativePageUrl( 426 currentUrl, mLocationBarDataProvider.isIncognito())) { 427 setUrlBarTextEmpty(); 428 } else { 429 setUrlBarText(mLocationBarDataProvider.getUrlBarData(), UrlBar.ScrollType.NO_SCROLL, 430 SelectionState.SELECT_ALL); 431 } 432 setKeyboardVisibility(false, false); 433 } 434 } 435 436 @Override onUrlTextChanged()437 public void onUrlTextChanged() { 438 updateButtonVisibility(); 439 } 440 setDefaultTextEditActionModeCallback(ToolbarActionModeCallback callback)441 public void setDefaultTextEditActionModeCallback(ToolbarActionModeCallback callback) { 442 mUrlCoordinator.setActionModeCallback(callback); 443 } 444 445 @Override didFocusUrlFromFakebox()446 public boolean didFocusUrlFromFakebox() { 447 return mUrlFocusedFromFakebox; 448 } 449 450 @Override didFocusUrlFromQueryTiles()451 public boolean didFocusUrlFromQueryTiles() { 452 return mUrlFocusedFromQueryTiles; 453 } 454 showUrlBarCursorWithoutFocusAnimations()455 public void showUrlBarCursorWithoutFocusAnimations() { 456 if (mUrlHasFocus || mUrlFocusedFromFakebox) return; 457 458 mUrlFocusedWithoutAnimations = true; 459 460 // This interface should only be called to devices with a hardware keyboard attached as 461 // described in the LocationBar. 462 setUrlBarFocus(true, null, OmniboxFocusReason.DEFAULT_WITH_HARDWARE_KEYBOARD); 463 } 464 465 /** 466 * Sets the toolbar that owns this LocationBar. 467 */ setLocationBarDataProvider(LocationBarDataProvider locationBarDataProvider)468 public void setLocationBarDataProvider(LocationBarDataProvider locationBarDataProvider) { 469 mLocationBarDataProvider = locationBarDataProvider; 470 471 updateButtonVisibility(); 472 473 mAutocompleteCoordinator.setLocationBarDataProvider(locationBarDataProvider); 474 mStatusCoordinator.setLocationBarDataProvider(locationBarDataProvider); 475 mUrlCoordinator.setOnFocusChangedCallback(this::onUrlFocusChange); 476 } 477 478 @Override getLocationBarDataProvider()479 public final LocationBarDataProvider getLocationBarDataProvider() { 480 return mLocationBarDataProvider; 481 } 482 483 /** 484 * Updates the security icon displayed in the LocationBar. 485 */ updateStatusIcon()486 public void updateStatusIcon() { 487 mStatusCoordinator.updateStatusIcon(); 488 // Update the URL in case the scheme change triggers a URL emphasis change. 489 setUrl(mLocationBarDataProvider.getCurrentUrl()); 490 } 491 492 @Override isKeyboardActive()493 public boolean isKeyboardActive() { 494 return KeyboardVisibilityDelegate.getInstance().isKeyboardShowing(getContext(), this) 495 || (getContext().getResources().getConfiguration().keyboard 496 == Configuration.KEYBOARD_QWERTY); 497 } 498 499 @Override onSuggestionsHidden()500 public void onSuggestionsHidden() {} 501 502 @Override onSuggestionsChanged(String autocompleteText)503 public void onSuggestionsChanged(String autocompleteText) { 504 String userText = mUrlCoordinator.getTextWithoutAutocomplete(); 505 if (mUrlCoordinator.shouldAutocomplete()) { 506 mUrlCoordinator.setAutocompleteText(userText, autocompleteText); 507 } 508 509 // Handle the case where suggestions (in particular zero suggest) are received without the 510 // URL focusing happening. 511 if (mUrlFocusedWithoutAnimations && mUrlHasFocus) { 512 handleUrlFocusAnimation(mUrlHasFocus); 513 } 514 515 if (mNativeInitialized 516 && !CommandLine.getInstance().hasSwitch(ChromeSwitches.DISABLE_INSTANT) 517 && PrivacyPreferencesManager.getInstance().shouldPrerender() 518 && mLocationBarDataProvider.hasTab()) { 519 mOmniboxPrerender.prerenderMaybe(userText, getOriginalUrl(), 520 mAutocompleteCoordinator.getCurrentNativeAutocompleteResult(), 521 mLocationBarDataProvider.getProfile(), mLocationBarDataProvider.getTab()); 522 } 523 } 524 525 @Override dispatchRestoreInstanceState(SparseArray<Parcelable> container)526 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 527 // Don't restore the state of the location bar, it can lead to all kind of bad states with 528 // the popup. 529 // When we restore tabs, we focus the selected tab so the URL of the page shows. 530 } 531 532 @Override performSearchQuery(String query, List<String> searchParams)533 public void performSearchQuery(String query, List<String> searchParams) { 534 if (TextUtils.isEmpty(query)) return; 535 536 String queryUrl = TemplateUrlServiceFactory.get().getUrlForSearchQuery(query, searchParams); 537 538 if (!TextUtils.isEmpty(queryUrl)) { 539 loadUrl(queryUrl, PageTransition.GENERATED, 0); 540 } else { 541 setSearchQuery(query); 542 } 543 } 544 545 /** 546 * Sets the query string in the omnibox (ensuring the URL bar has focus and triggering 547 * autocomplete for the specified query) as if the user typed it. 548 * 549 * @param query The query to be set in the omnibox. 550 */ 551 @Override setSearchQuery(final String query)552 public void setSearchQuery(final String query) { 553 if (TextUtils.isEmpty(query)) return; 554 555 if (!mNativeInitialized) { 556 mDeferredNativeRunnables.add(new Runnable() { 557 @Override 558 public void run() { 559 setSearchQuery(query); 560 } 561 }); 562 return; 563 } 564 565 // Ensure the UrlBar has focus before entering text. If the UrlBar is not focused, 566 // autocomplete text will be updated but the visible text will not. 567 setUrlBarFocus(true, null, OmniboxFocusReason.SEARCH_QUERY); 568 setUrlBarText(UrlBarData.forNonUrlText(query), UrlBar.ScrollType.NO_SCROLL, 569 SelectionState.SELECT_ALL); 570 mAutocompleteCoordinator.startAutocompleteForQuery(query); 571 post(new Runnable() { 572 @Override 573 public void run() { 574 getWindowAndroid().getKeyboardDelegate().showKeyboard(mUrlBar); 575 } 576 }); 577 } 578 579 @Override onClick(View v)580 public void onClick(View v) { 581 if (v == mDeleteButton) { 582 setUrlBarTextEmpty(); 583 updateButtonVisibility(); 584 585 RecordUserAction.record("MobileOmniboxDeleteUrl"); 586 return; 587 } else if (v == mMicButton) { 588 RecordUserAction.record("MobileOmniboxVoiceSearch"); 589 mVoiceRecognitionHandler.startVoiceRecognition( 590 VoiceRecognitionHandler.VoiceInteractionSource.OMNIBOX); 591 } 592 } 593 594 @Override backKeyPressed()595 public void backKeyPressed() { 596 setUrlBarFocus(false, null, OmniboxFocusReason.UNFOCUS); 597 // Revert the URL to match the current page. 598 setUrl(mLocationBarDataProvider.getCurrentUrl()); 599 focusCurrentTab(); 600 } 601 602 @Override gestureDetected(boolean isLongPress)603 public void gestureDetected(boolean isLongPress) { 604 recordOmniboxFocusReason(isLongPress ? OmniboxFocusReason.OMNIBOX_LONG_PRESS 605 : OmniboxFocusReason.OMNIBOX_TAP); 606 } 607 608 /** 609 * Update the location bar visuals based on a loading state change. 610 * @param updateUrl Whether to update the URL as a result of this call. 611 */ updateLoadingState(boolean updateUrl)612 public void updateLoadingState(boolean updateUrl) { 613 if (updateUrl) setUrl(mLocationBarDataProvider.getCurrentUrl()); 614 mStatusCoordinator.updateStatusIcon(); 615 } 616 617 @Override getViewForUrlBackFocus()618 public View getViewForUrlBackFocus() { 619 Tab tab = getCurrentTab(); 620 if (tab == null) return null; 621 return tab.getView(); 622 } 623 624 @Override allowKeyboardLearning()625 public boolean allowKeyboardLearning() { 626 if (mLocationBarDataProvider == null) return false; 627 return !mLocationBarDataProvider.isIncognito(); 628 } 629 630 @Override setUrlBarFocus( boolean shouldBeFocused, @Nullable String pastedText, @OmniboxFocusReason int reason)631 public void setUrlBarFocus( 632 boolean shouldBeFocused, @Nullable String pastedText, @OmniboxFocusReason int reason) { 633 if (shouldBeFocused) { 634 if (!mUrlHasFocus) recordOmniboxFocusReason(reason); 635 if (reason == OmniboxFocusReason.FAKE_BOX_TAP 636 || reason == OmniboxFocusReason.FAKE_BOX_LONG_PRESS 637 || reason == OmniboxFocusReason.TASKS_SURFACE_FAKE_BOX_LONG_PRESS 638 || reason == OmniboxFocusReason.TASKS_SURFACE_FAKE_BOX_TAP) { 639 mUrlFocusedFromFakebox = true; 640 } 641 642 if (reason == OmniboxFocusReason.QUERY_TILES_NTP_TAP) { 643 mUrlFocusedFromFakebox = true; 644 mUrlFocusedFromQueryTiles = true; 645 } 646 647 if (mUrlHasFocus && mUrlFocusedWithoutAnimations) { 648 handleUrlFocusAnimation(mUrlHasFocus); 649 } else { 650 mUrlBar.requestFocus(); 651 } 652 } else { 653 assert pastedText == null; 654 mUrlBar.clearFocus(); 655 } 656 657 if (pastedText != null) { 658 // This must be happen after requestUrlFocus(), which changes the selection. 659 mUrlCoordinator.setUrlBarData(UrlBarData.forNonUrlText(pastedText), 660 UrlBar.ScrollType.NO_SCROLL, UrlBarCoordinator.SelectionState.SELECT_END); 661 forceOnTextChanged(); 662 } 663 } 664 665 @Override isUrlBarFocused()666 public boolean isUrlBarFocused() { 667 return mUrlHasFocus; 668 } 669 670 @Override getVoiceRecognitionHandler()671 public VoiceRecognitionHandler getVoiceRecognitionHandler() { 672 return mVoiceRecognitionHandler; 673 } 674 675 @Override addUrlFocusChangeListener(UrlFocusChangeListener listener)676 public void addUrlFocusChangeListener(UrlFocusChangeListener listener) { 677 mUrlFocusChangeListeners.addObserver(listener); 678 } 679 680 @Override removeUrlFocusChangeListener(UrlFocusChangeListener listener)681 public void removeUrlFocusChangeListener(UrlFocusChangeListener listener) { 682 mUrlFocusChangeListeners.removeObserver(listener); 683 } 684 685 @Override onWindowVisibilityChanged(int visibility)686 protected void onWindowVisibilityChanged(int visibility) { 687 super.onWindowVisibilityChanged(visibility); 688 if (visibility == View.VISIBLE) updateMicButtonState(); 689 } 690 691 /** 692 * Call to force the UI to update the state of various buttons based on whether or not the 693 * current tab is incognito. 694 */ updateVisualsForState()695 public void updateVisualsForState() { 696 // If the location bar is focused, the toolbar background color would be the default color 697 // regardless of whether it is branded or not. 698 final int defaultPrimaryColor = ChromeColors.getDefaultThemeColor( 699 getResources(), mLocationBarDataProvider.isIncognito()); 700 final int primaryColor = 701 mUrlHasFocus ? defaultPrimaryColor : mLocationBarDataProvider.getPrimaryColor(); 702 703 updateAssistantVoiceSearchDrawableAndColors(); 704 705 final boolean useDarkColors = 706 !ColorUtils.shouldUseLightForegroundOnBackground(primaryColor); 707 ColorStateList colorStateList = 708 ChromeColors.getPrimaryIconTint(getContext(), !useDarkColors); 709 ApiCompatibilityUtils.setImageTintList(mDeleteButton, colorStateList); 710 711 // If the URL changed colors and is not focused, update the URL to account for the new 712 // color scheme. 713 if (mUrlCoordinator.setUseDarkTextColors(useDarkColors) && !mUrlBar.hasFocus()) { 714 setUrl(mLocationBarDataProvider.getCurrentUrl()); 715 } 716 717 mStatusCoordinator.setUseDarkColors(useDarkColors); 718 mStatusCoordinator.setIncognitoBadgeVisibility( 719 mLocationBarDataProvider.isIncognito() && !mIsTablet); 720 721 if (mAutocompleteCoordinator != null) { 722 mAutocompleteCoordinator.updateVisualsForState( 723 useDarkColors, mLocationBarDataProvider.isIncognito()); 724 } 725 } 726 onTabLoadingNTP(NewTabPage ntp)727 public void onTabLoadingNTP(NewTabPage ntp) { 728 ntp.setFakeboxDelegate(this); 729 } 730 getContainerView()731 public View getContainerView() { 732 return this; 733 } 734 getSecurityIconView()735 public View getSecurityIconView() { 736 return mStatusCoordinator.getSecurityIconView(); 737 } 738 setShowTitle(boolean showTitle)739 public void setShowTitle(boolean showTitle) {} 740 741 @Override getWindowAndroid()742 public WindowAndroid getWindowAndroid() { 743 return mWindowAndroid; 744 } 745 746 @Override onAssistantVoiceSearchServiceChanged()747 public void onAssistantVoiceSearchServiceChanged() { 748 updateAssistantVoiceSearchDrawableAndColors(); 749 } 750 updateAssistantVoiceSearchDrawableAndColors()751 private void updateAssistantVoiceSearchDrawableAndColors() { 752 if (mAssistantVoiceSearchService == null) return; 753 754 Drawable drawable = mAssistantVoiceSearchService.getCurrentMicDrawable(); 755 mMicButton.setImageDrawable(drawable); 756 757 final int defaultPrimaryColor = ChromeColors.getDefaultThemeColor( 758 getResources(), mLocationBarDataProvider.isIncognito()); 759 final int primaryColor = 760 mUrlHasFocus ? defaultPrimaryColor : mLocationBarDataProvider.getPrimaryColor(); 761 ColorStateList colorStateList = 762 mAssistantVoiceSearchService.getMicButtonColorStateList(primaryColor, getContext()); 763 ApiCompatibilityUtils.setImageTintList(mMicButton, colorStateList); 764 } 765 766 /** 767 * Call to notify the location bar that the state of the voice search microphone button may 768 * need to be updated. 769 */ 770 @Override updateMicButtonState()771 public void updateMicButtonState() { 772 mVoiceSearchEnabled = mVoiceRecognitionHandler.isVoiceSearchEnabled(); 773 updateButtonVisibility(); 774 } 775 776 /** 777 * Sets the displayed URL to be the URL of the page currently showing. 778 * 779 * <p>The URL is converted to the most user friendly format (removing HTTP:// for example). 780 * 781 * <p>If the current tab is null, the URL text will be cleared. 782 */ setUrl(String currentUrl)783 protected void setUrl(String currentUrl) { 784 // If the URL is currently focused, do not replace the text they have entered with the URL. 785 // Once they stop editing the URL, the current tab's URL will automatically be filled in. 786 if (mUrlBar.hasFocus()) { 787 if (mUrlFocusedWithoutAnimations && !UrlUtilities.isNTPUrl(currentUrl)) { 788 // If we did not run the focus animations, then the user has not typed any text. 789 // So, clear the focus and accept whatever URL the page is currently attempting to 790 // display. If the NTP is showing, the current page's URL should not be displayed. 791 setUrlBarFocus(false, null, OmniboxFocusReason.UNFOCUS); 792 } else { 793 return; 794 } 795 } 796 797 mOriginalUrl = currentUrl; 798 setUrlBarText(mLocationBarDataProvider.getUrlBarData(), UrlBar.ScrollType.SCROLL_TO_TLD, 799 SelectionState.SELECT_ALL); 800 if (!mLocationBarDataProvider.hasTab()) return; 801 802 // Profile may be null if switching to a tab that has not yet been initialized. 803 Profile profile = mLocationBarDataProvider.getProfile(); 804 if (profile != null && mOmniboxPrerender != null) mOmniboxPrerender.clear(profile); 805 } 806 807 @Override setOmniboxEditingText(String text)808 public void setOmniboxEditingText(String text) { 809 mUrlCoordinator.setUrlBarData(UrlBarData.forNonUrlText(text), UrlBar.ScrollType.NO_SCROLL, 810 UrlBarCoordinator.SelectionState.SELECT_END); 811 updateButtonVisibility(); 812 } 813 814 @Override loadUrlFromVoice(String url)815 public void loadUrlFromVoice(String url) { 816 loadUrl(url, PageTransition.TYPED, 0); 817 } 818 819 /** 820 * Load the url given with the given transition. Exposed for child classes to overwrite as 821 * necessary. 822 */ 823 @Override loadUrl(String url, @PageTransition int transition, long inputStart)824 public void loadUrl(String url, @PageTransition int transition, long inputStart) { 825 loadUrlWithPostData(url, transition, inputStart, null, null); 826 } 827 828 @Override loadUrlWithPostData(String url, @PageTransition int transition, long inputStart, @Nullable String postDataType, @Nullable byte[] postData)829 public void loadUrlWithPostData(String url, @PageTransition int transition, long inputStart, 830 @Nullable String postDataType, @Nullable byte[] postData) { 831 Tab currentTab = getCurrentTab(); 832 833 // The code of the rest of this class ensures that this can't be called until the native 834 // side is initialized 835 assert mNativeInitialized : "Loading URL before native side initialized"; 836 837 // TODO(crbug.com/1085812): Should be taking a full loaded LoadUrlParams. 838 if (mOverrideUrlLoadingDelegate.willHandleLoadUrlWithPostData(url, transition, postDataType, 839 postData, mLocationBarDataProvider.isIncognito())) { 840 return; 841 } 842 843 if (currentTab != null 844 && (currentTab.isNativePage() 845 || UrlUtilities.isNTPUrl(currentTab.getUrlString()))) { 846 NewTabPageUma.recordOmniboxNavigation(url, transition); 847 // Passing in an empty string should not do anything unless the user is at the NTP. 848 // Since the NTP has no url, pressing enter while clicking on the URL bar should refresh 849 // the page as it does when you click and press enter on any other site. 850 if (url.isEmpty()) url = currentTab.getUrlString(); 851 } 852 853 // Loads the |url| in a new tab or the current ContentView and gives focus to the 854 // ContentView. 855 if (currentTab != null && !url.isEmpty()) { 856 LoadUrlParams loadUrlParams = new LoadUrlParams(url); 857 loadUrlParams.setVerbatimHeaders(GeolocationHeader.getGeoHeader(url, currentTab)); 858 loadUrlParams.setTransitionType(transition | PageTransition.FROM_ADDRESS_BAR); 859 if (inputStart != 0) { 860 loadUrlParams.setInputStartTimestamp(inputStart); 861 } 862 863 if (!TextUtils.isEmpty(postDataType)) { 864 StringBuilder headers = new StringBuilder(); 865 String prevHeader = loadUrlParams.getVerbatimHeaders(); 866 if (prevHeader != null && !prevHeader.isEmpty()) { 867 headers.append(prevHeader); 868 headers.append("\r\n"); 869 } 870 loadUrlParams.setExtraHeaders(new HashMap<String, String>() { 871 { put("Content-Type", postDataType); } 872 }); 873 headers.append(loadUrlParams.getExtraHttpRequestHeadersString()); 874 loadUrlParams.setVerbatimHeaders(headers.toString()); 875 } 876 877 if (postData != null && postData.length != 0) { 878 loadUrlParams.setPostData(ResourceRequestBody.createFromBytes(postData)); 879 } 880 881 currentTab.loadUrl(loadUrlParams); 882 RecordUserAction.record("MobileOmniboxUse"); 883 } 884 LocaleManager.getInstance().recordLocaleBasedSearchMetrics(false, url, transition); 885 886 focusCurrentTab(); 887 } 888 889 /** 890 * @param focusable Whether the url bar should be focusable. 891 */ setUrlBarFocusable(boolean focusable)892 public void setUrlBarFocusable(boolean focusable) { 893 if (mUrlCoordinator == null) return; 894 mUrlCoordinator.setAllowFocus(focusable); 895 } 896 897 @CallSuper setUrlFocusChangeFraction(float fraction)898 public void setUrlFocusChangeFraction(float fraction) { 899 mUrlFocusChangeFraction = fraction; 900 } 901 registerTemplateUrlObserver()902 private void registerTemplateUrlObserver() { 903 final TemplateUrlService templateUrlService = TemplateUrlServiceFactory.get(); 904 assert mTemplateUrlObserver == null; 905 mTemplateUrlObserver = new TemplateUrlServiceObserver() { 906 private TemplateUrl mSearchEngine = 907 templateUrlService.getDefaultSearchEngineTemplateUrl(); 908 909 @Override 910 public void onTemplateURLServiceChanged() { 911 TemplateUrl searchEngine = templateUrlService.getDefaultSearchEngineTemplateUrl(); 912 if ((mSearchEngine == null && searchEngine == null) 913 || (mSearchEngine != null && mSearchEngine.equals(searchEngine))) { 914 return; 915 } 916 917 mSearchEngine = searchEngine; 918 updateSearchEngineStatusIcon(SearchEngineLogoUtils.shouldShowSearchEngineLogo( 919 mLocationBarDataProvider.isIncognito()), 920 TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle(), 921 SearchEngineLogoUtils.getSearchLogoUrl()); 922 } 923 }; 924 templateUrlService.addObserver(mTemplateUrlObserver); 925 926 // Force an update once to populate initial data. 927 updateSearchEngineStatusIcon(SearchEngineLogoUtils.shouldShowSearchEngineLogo( 928 mLocationBarDataProvider.isIncognito()), 929 TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle(), 930 SearchEngineLogoUtils.getSearchLogoUrl()); 931 } 932 933 /** 934 * Evaluate state and update child components' animations. 935 * 936 * This call and all overrides should invoke `notifyShouldAnimateIconChanges(boolean)` with a 937 * computed boolean value toggling animation support in child components. 938 */ updateShouldAnimateIconChanges()939 protected void updateShouldAnimateIconChanges() { 940 notifyShouldAnimateIconChanges(mUrlHasFocus); 941 } 942 943 /** 944 * Toggle child components animations. 945 * @param shouldAnimate Boolean flag indicating whether animations should be enabled. 946 */ notifyShouldAnimateIconChanges(boolean shouldAnimate)947 protected void notifyShouldAnimateIconChanges(boolean shouldAnimate) { 948 mStatusCoordinator.setShouldAnimateIconChanges(shouldAnimate); 949 } 950 951 /** 952 * Updates the profile used by this LocationBar, for, e.g. determining incognito status or 953 * generating autocomplete suggestions.. 954 * @param profile The profile to be used. 955 */ setProfile(Profile profile)956 private void setProfile(Profile profile) { 957 if (profile == null || !mNativeInitialized) return; 958 mAutocompleteCoordinator.setAutocompleteProfile(profile); 959 mOmniboxPrerender.initializeForProfile(profile); 960 961 setShowIconsWhenUrlFocused( 962 SearchEngineLogoUtils.shouldShowSearchEngineLogo(profile.isOffTheRecord())); 963 } 964 965 /** Focuses the current page. */ focusCurrentTab()966 private void focusCurrentTab() { 967 if (mLocationBarDataProvider.hasTab()) { 968 View view = getCurrentTab().getView(); 969 if (view != null) view.requestFocus(); 970 } 971 } 972 973 /** 974 * @return Whether the URL focus change is taking place, e.g. a focus animation is running on 975 * a phone device. 976 */ isUrlFocusChangeInProgress()977 public boolean isUrlFocusChangeInProgress() { 978 return mUrlFocusChangeInProgress; 979 } 980 981 /** 982 * Specify whether location bar should present icons when focused. 983 * @param showIcon True if we should show the icons when the url is focused. 984 */ setShowIconsWhenUrlFocused(boolean showIcon)985 protected void setShowIconsWhenUrlFocused(boolean showIcon) {} 986 987 /** 988 * @param inProgress Whether a URL focus change is taking place. 989 */ setUrlFocusChangeInProgress(boolean inProgress)990 protected void setUrlFocusChangeInProgress(boolean inProgress) { 991 mUrlFocusChangeInProgress = inProgress; 992 if (!inProgress) { 993 updateButtonVisibility(); 994 995 // The accessibility bounding box is not properly updated when focusing the Omnibox 996 // from the NTP fakebox. Clearing/re-requesting focus triggers the bounding box to 997 // be recalculated. 998 if (didFocusUrlFromFakebox() && mUrlHasFocus 999 && ChromeAccessibilityUtil.get().isAccessibilityEnabled()) { 1000 String existingText = mUrlCoordinator.getTextWithoutAutocomplete(); 1001 mUrlBar.clearFocus(); 1002 mUrlBar.requestFocus(); 1003 // Existing text (e.g. if the user pasted via the fakebox) from the fake box 1004 // should be restored after toggling the focus. 1005 if (!TextUtils.isEmpty(existingText)) { 1006 mUrlCoordinator.setUrlBarData(UrlBarData.forNonUrlText(existingText), 1007 UrlBar.ScrollType.NO_SCROLL, 1008 UrlBarCoordinator.SelectionState.SELECT_END); 1009 forceOnTextChanged(); 1010 } 1011 } 1012 1013 for (UrlFocusChangeListener listener : mUrlFocusChangeListeners) { 1014 listener.onUrlAnimationFinished(mUrlHasFocus); 1015 } 1016 } 1017 } 1018 1019 /** 1020 * Triggered when the URL input field has gained or lost focus. 1021 * @param hasFocus Whether the URL field has gained focus. 1022 */ onUrlFocusChange(boolean hasFocus)1023 protected void onUrlFocusChange(boolean hasFocus) { 1024 mUrlHasFocus = hasFocus; 1025 updateButtonVisibility(); 1026 updateShouldAnimateIconChanges(); 1027 1028 if (mUrlHasFocus) { 1029 if (mNativeInitialized) RecordUserAction.record("FocusLocation"); 1030 UrlBarData urlBarData = mLocationBarDataProvider.getUrlBarData(); 1031 if (urlBarData.editingText != null) { 1032 setUrlBarText(urlBarData, UrlBar.ScrollType.NO_SCROLL, SelectionState.SELECT_ALL); 1033 } 1034 1035 // Explicitly tell InputMethodManager that the url bar is focused before any callbacks 1036 // so that it updates the active view accordingly. Otherwise, it may fail to update 1037 // the correct active view if ViewGroup.addView() or ViewGroup.removeView() is called 1038 // to update a view that accepts text input. 1039 InputMethodManager imm = (InputMethodManager) mUrlBar.getContext().getSystemService( 1040 Context.INPUT_METHOD_SERVICE); 1041 imm.viewClicked(mUrlBar); 1042 } else { 1043 mUrlFocusedFromFakebox = false; 1044 mUrlFocusedFromQueryTiles = false; 1045 mUrlFocusedWithoutAnimations = false; 1046 1047 // Focus change caused by a close-tab may result in an invalid current tab. 1048 if (mLocationBarDataProvider.hasTab()) { 1049 setUrl(mLocationBarDataProvider.getCurrentUrl()); 1050 } 1051 1052 // Moving focus away from UrlBar(EditText) to a non-editable focus holder, such as 1053 // ToolbarPhone, won't automatically hide keyboard app, but restart it with TYPE_NULL, 1054 // which will result in a visual glitch. Also, currently, we do not allow moving focus 1055 // directly from omnibox to web content's form field. Therefore, we hide keyboard on 1056 // focus blur indiscriminately here. Note that hiding keyboard may lower FPS of other 1057 // animation effects, but we found it tolerable in an experiment. 1058 InputMethodManager imm = (InputMethodManager) getContext().getSystemService( 1059 Context.INPUT_METHOD_SERVICE); 1060 if (imm.isActive(mUrlBar)) imm.hideSoftInputFromWindow(getWindowToken(), 0, null); 1061 } 1062 1063 if (mLocationBarDataProvider.isUsingBrandColor()) updateVisualsForState(); 1064 1065 mStatusCoordinator.onUrlFocusChange(mUrlHasFocus); 1066 1067 if (!mUrlFocusedWithoutAnimations) handleUrlFocusAnimation(mUrlHasFocus); 1068 1069 if (mUrlHasFocus && mLocationBarDataProvider.hasTab() 1070 && !mLocationBarDataProvider.isIncognito()) { 1071 if (mNativeInitialized 1072 && TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle()) { 1073 GeolocationHeader.primeLocationForGeoHeader(); 1074 } else { 1075 mDeferredNativeRunnables.add(new Runnable() { 1076 @Override 1077 public void run() { 1078 if (TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle()) { 1079 GeolocationHeader.primeLocationForGeoHeader(); 1080 } 1081 } 1082 }); 1083 } 1084 } 1085 } 1086 1087 /** 1088 * Handle and run any necessary animations that are triggered off focusing the UrlBar. 1089 * @param hasFocus Whether focus was gained. 1090 */ handleUrlFocusAnimation(boolean hasFocus)1091 protected void handleUrlFocusAnimation(boolean hasFocus) { 1092 removeCallbacks(mKeyboardResizeModeTask); 1093 if (hasFocus) mUrlFocusedWithoutAnimations = false; 1094 for (UrlFocusChangeListener listener : mUrlFocusChangeListeners) { 1095 listener.onUrlFocusChange(hasFocus); 1096 } 1097 } 1098 1099 /** 1100 * @return The margin to be applied to the URL bar based on the buttons currently visible next 1101 * to it, used to avoid text overlapping the buttons and vice versa. 1102 */ getUrlContainerMarginEnd()1103 private int getUrlContainerMarginEnd() { 1104 int urlContainerMarginEnd = 0; 1105 for (View childView : getUrlContainerViewsForMargin()) { 1106 ViewGroup.MarginLayoutParams childLayoutParams = 1107 (ViewGroup.MarginLayoutParams) childView.getLayoutParams(); 1108 urlContainerMarginEnd += childLayoutParams.width 1109 + MarginLayoutParamsCompat.getMarginStart(childLayoutParams) 1110 + MarginLayoutParamsCompat.getMarginEnd(childLayoutParams); 1111 } 1112 if (mUrlActionContainer != null && mUrlActionContainer.getVisibility() == View.VISIBLE) { 1113 ViewGroup.MarginLayoutParams urlActionContainerLayoutParams = 1114 (ViewGroup.MarginLayoutParams) mUrlActionContainer.getLayoutParams(); 1115 urlContainerMarginEnd += 1116 MarginLayoutParamsCompat.getMarginStart(urlActionContainerLayoutParams) 1117 + MarginLayoutParamsCompat.getMarginEnd(urlActionContainerLayoutParams); 1118 } 1119 // Include the space which the URL bar will be translated post-layout into the end 1120 // margin so the URL bar doesn't overlap with the URL actions container when focused. 1121 if (mStatusCoordinator.isSearchEngineStatusIconVisible() && hasFocus()) { 1122 urlContainerMarginEnd += mStatusCoordinator.getEndPaddingPixelSizeOnFocusDelta(); 1123 } 1124 return urlContainerMarginEnd; 1125 } 1126 1127 /** 1128 * Updates the layout params for the location bar start aligned views. 1129 */ 1130 @VisibleForTesting updateLayoutParams()1131 void updateLayoutParams() { 1132 int startMargin = 0; 1133 for (int i = 0; i < getChildCount(); i++) { 1134 View childView = getChildAt(i); 1135 if (childView.getVisibility() != GONE) { 1136 LayoutParams childLayoutParams = (LayoutParams) childView.getLayoutParams(); 1137 if (MarginLayoutParamsCompat.getMarginStart(childLayoutParams) != startMargin) { 1138 MarginLayoutParamsCompat.setMarginStart(childLayoutParams, startMargin); 1139 childView.setLayoutParams(childLayoutParams); 1140 } 1141 if (childView == mUrlBar) break; 1142 1143 int widthMeasureSpec; 1144 int heightMeasureSpec; 1145 if (childLayoutParams.width == LayoutParams.WRAP_CONTENT) { 1146 widthMeasureSpec = 1147 MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); 1148 } else if (childLayoutParams.width == LayoutParams.MATCH_PARENT) { 1149 widthMeasureSpec = 1150 MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY); 1151 } else { 1152 widthMeasureSpec = MeasureSpec.makeMeasureSpec( 1153 childLayoutParams.width, MeasureSpec.EXACTLY); 1154 } 1155 if (childLayoutParams.height == LayoutParams.WRAP_CONTENT) { 1156 heightMeasureSpec = 1157 MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); 1158 } else if (childLayoutParams.height == LayoutParams.MATCH_PARENT) { 1159 heightMeasureSpec = 1160 MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY); 1161 } else { 1162 heightMeasureSpec = MeasureSpec.makeMeasureSpec( 1163 childLayoutParams.height, MeasureSpec.EXACTLY); 1164 } 1165 childView.measure(widthMeasureSpec, heightMeasureSpec); 1166 startMargin += childView.getMeasuredWidth(); 1167 } 1168 } 1169 1170 int urlContainerMarginEnd = getUrlContainerMarginEnd(); 1171 LayoutParams urlLayoutParams = (LayoutParams) mUrlBar.getLayoutParams(); 1172 if (MarginLayoutParamsCompat.getMarginEnd(urlLayoutParams) != urlContainerMarginEnd) { 1173 MarginLayoutParamsCompat.setMarginEnd(urlLayoutParams, urlContainerMarginEnd); 1174 mUrlBar.setLayoutParams(urlLayoutParams); 1175 } 1176 } 1177 1178 /** 1179 * Gets the list of views that need to be taken into account for adding margin to the end of the 1180 * URL bar. 1181 * 1182 * @return A {@link List} of the views to be taken into account for URL bar margin to avoid 1183 * overlapping text and buttons. 1184 */ getUrlContainerViewsForMargin()1185 protected List<View> getUrlContainerViewsForMargin() { 1186 List<View> outList = new ArrayList<View>(); 1187 if (mUrlActionContainer == null) return outList; 1188 1189 for (int i = 0; i < mUrlActionContainer.getChildCount(); i++) { 1190 View childView = mUrlActionContainer.getChildAt(i); 1191 if (childView.getVisibility() != GONE) outList.add(childView); 1192 } 1193 return outList; 1194 } 1195 1196 /** 1197 * @return Whether the delete button should be shown. 1198 */ shouldShowDeleteButton()1199 protected boolean shouldShowDeleteButton() { 1200 // Show the delete button at the end when the bar has focus and has some text. 1201 boolean hasText = !TextUtils.isEmpty(mUrlCoordinator.getTextWithAutocomplete()); 1202 return hasText && (mUrlBar.hasFocus() || mUrlFocusChangeInProgress); 1203 } 1204 1205 /** 1206 * Updates the display of the delete URL content button. 1207 */ updateDeleteButtonVisibility()1208 protected void updateDeleteButtonVisibility() { 1209 mDeleteButton.setVisibility(shouldShowDeleteButton() ? VISIBLE : GONE); 1210 } 1211 1212 /** 1213 * @return Returns the original url of the page. 1214 */ getOriginalUrl()1215 public String getOriginalUrl() { 1216 return mOriginalUrl; 1217 } 1218 1219 /** 1220 * Changes the text on the url bar. The text update will be applied regardless of the current 1221 * focus state (comparing to {@link #setUrlToPageUrl(mLocationBarDataProvider.getCurrentUrl())} 1222 * which only applies text updates when not focused). 1223 * 1224 * @param urlBarData The contents of the URL bar, both for editing and displaying. 1225 * @param scrollType Specifies how the text should be scrolled in the unfocused state. 1226 * @param selectionState Specifies how the text should be selected in the focused state. 1227 * @return Whether the URL was changed as a result of this call. 1228 */ setUrlBarText( UrlBarData urlBarData, @ScrollType int scrollType, @SelectionState int selectionState)1229 private boolean setUrlBarText( 1230 UrlBarData urlBarData, @ScrollType int scrollType, @SelectionState int selectionState) { 1231 return mUrlCoordinator.setUrlBarData(urlBarData, scrollType, selectionState); 1232 } 1233 1234 /** 1235 * Clear any text in the URL bar. 1236 * @return Whether this changed the existing text. 1237 */ setUrlBarTextEmpty()1238 private boolean setUrlBarTextEmpty() { 1239 boolean textChanged = mUrlCoordinator.setUrlBarData( 1240 UrlBarData.EMPTY, UrlBar.ScrollType.SCROLL_TO_BEGINNING, SelectionState.SELECT_ALL); 1241 forceOnTextChanged(); 1242 return textChanged; 1243 } 1244 1245 /** @return The current active {@link Tab}. */ 1246 @Nullable getCurrentTab()1247 private Tab getCurrentTab() { 1248 if (mLocationBarDataProvider == null) return null; 1249 return mLocationBarDataProvider.getTab(); 1250 } 1251 setUnfocusedWidth(int unfocusedWidth)1252 public void setUnfocusedWidth(int unfocusedWidth) { 1253 mStatusCoordinator.setUnfocusedLocationBarWidth(unfocusedWidth); 1254 } 1255 updateSearchEngineStatusIcon(boolean shouldShowSearchEngineLogo, boolean isSearchEngineGoogle, String searchEngineUrl)1256 protected void updateSearchEngineStatusIcon(boolean shouldShowSearchEngineLogo, 1257 boolean isSearchEngineGoogle, String searchEngineUrl) { 1258 mStatusCoordinator.updateSearchEngineStatusIcon( 1259 shouldShowSearchEngineLogo, isSearchEngineGoogle, searchEngineUrl); 1260 } 1261 1262 /** 1263 * Call to update the visibility of the buttons inside the location bar. 1264 */ updateButtonVisibility()1265 protected void updateButtonVisibility() { 1266 updateDeleteButtonVisibility(); 1267 } 1268 1269 /** 1270 * Updates the display of the mic button. 1271 */ updateMicButtonVisibility()1272 protected void updateMicButtonVisibility() { 1273 boolean visible = !shouldShowDeleteButton(); 1274 boolean showMicButton = mVoiceSearchEnabled && visible 1275 && (mUrlBar.hasFocus() || mUrlFocusChangeInProgress || mUrlFocusChangeFraction > 0f 1276 || mShouldShowMicButtonWhenUnfocused); 1277 mMicButton.setVisibility(showMicButton ? VISIBLE : GONE); 1278 } 1279 1280 /** 1281 * Value determines if mic button should be shown when location bar is not focused. By default 1282 * mic button is not shown. It is only shown for SearchActivityLocationBarLayout. 1283 */ setShouldShowMicButtonWhenUnfocused(boolean shouldShowMicButtonWhenUnfocused)1284 protected void setShouldShowMicButtonWhenUnfocused(boolean shouldShowMicButtonWhenUnfocused) { 1285 mShouldShowMicButtonWhenUnfocused = shouldShowMicButtonWhenUnfocused; 1286 } 1287 1288 @VisibleForTesting getStatusCoordinatorForTesting()1289 public StatusCoordinator getStatusCoordinatorForTesting() { 1290 return mStatusCoordinator; 1291 } 1292 forceOnTextChanged()1293 private void forceOnTextChanged() { 1294 String textWithoutAutocomplete = mUrlCoordinator.getTextWithoutAutocomplete(); 1295 String textWithAutocomplete = mUrlCoordinator.getTextWithAutocomplete(); 1296 mAutocompleteCoordinator.onTextChanged(textWithoutAutocomplete, textWithAutocomplete); 1297 } 1298 recordOmniboxFocusReason(@mniboxFocusReason int reason)1299 private void recordOmniboxFocusReason(@OmniboxFocusReason int reason) { 1300 RecordHistogram.recordEnumeratedHistogram( 1301 "Android.OmniboxFocusReason", reason, OmniboxFocusReason.NUM_ENTRIES); 1302 } 1303 1304 /** 1305 * Handles any actions to be performed after all other actions triggered by the URL focus 1306 * change. This will be called after any animations are performed to transition from one 1307 * focus state to the other. 1308 * @param hasFocus Whether the URL field has gained focus. 1309 * @param shouldShowKeyboard Whether the keyboard should be shown. This value should be the same 1310 * as hasFocus by default. 1311 */ finishUrlFocusChange(boolean hasFocus, boolean shouldShowKeyboard)1312 protected void finishUrlFocusChange(boolean hasFocus, boolean shouldShowKeyboard) { 1313 setKeyboardVisibility(hasFocus && shouldShowKeyboard, true); 1314 setUrlFocusChangeInProgress(false); 1315 updateShouldAnimateIconChanges(); 1316 } 1317 1318 /** 1319 * Controls keyboard visibility. 1320 * TODO(https://crbug.com/1060729): This should be relocated to UrlBar component. 1321 * 1322 * @param shouldShow Whether the soft keyboard should be shown. 1323 * @param shouldDelayHiding When true, keyboard hide operation will be delayed slightly to 1324 * improve the animation smoothness. 1325 */ 1326 @Override setKeyboardVisibility(boolean showKeyboard, boolean shouldDelayHiding)1327 public void setKeyboardVisibility(boolean showKeyboard, boolean shouldDelayHiding) { 1328 // Cancel pending jobs to prevent any possibility of keyboard flicker. 1329 if (mKeyboardHideTask != null) { 1330 removeCallbacks(mKeyboardHideTask); 1331 } 1332 1333 // Note: due to nature of this mechanism, we may occasionally experience subsequent requests 1334 // to show or hide keyboard anyway. This may happen when we schedule keyboard hide, and 1335 // receive a second request to hide the keyboard instantly. 1336 if (showKeyboard) { 1337 setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN, /* delay */ false); 1338 getWindowAndroid().getKeyboardDelegate().showKeyboard(mUrlBar); 1339 } else { 1340 // The animation rendering may not yet be 100% complete and hiding the keyboard makes 1341 // the animation quite choppy. 1342 // clang-format off 1343 mKeyboardHideTask = () -> { 1344 getWindowAndroid().getKeyboardDelegate().hideKeyboard(mUrlBar); 1345 mKeyboardHideTask = null; 1346 }; 1347 // clang-format on 1348 postDelayed(mKeyboardHideTask, shouldDelayHiding ? KEYBOARD_HIDE_DELAY_MS : 0); 1349 // Convert the keyboard back to resize mode (delay the change for an arbitrary amount 1350 // of time in hopes the keyboard will be completely hidden before making this change). 1351 setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE, /* delay */ true); 1352 } 1353 } 1354 1355 /** 1356 * @param softInputMode The software input resize mode. 1357 * @param delay Delay the change in input mode. 1358 */ setSoftInputMode(final int softInputMode, boolean delay)1359 private void setSoftInputMode(final int softInputMode, boolean delay) { 1360 if (mKeyboardResizeModeTask != null) { 1361 removeCallbacks(mKeyboardResizeModeTask); 1362 mKeyboardResizeModeTask = null; 1363 } 1364 1365 if (mWindowDelegate == null || mWindowDelegate.getWindowSoftInputMode() == softInputMode) { 1366 return; 1367 } 1368 1369 if (delay) { 1370 mKeyboardResizeModeTask = () -> { 1371 mWindowDelegate.setWindowSoftInputMode(softInputMode); 1372 mKeyboardResizeModeTask = null; 1373 }; 1374 postDelayed(mKeyboardResizeModeTask, KEYBOARD_MODE_CHANGE_DELAY_MS); 1375 } else { 1376 mWindowDelegate.setWindowSoftInputMode(softInputMode); 1377 } 1378 } 1379 setVoiceRecognitionHandlerForTesting( VoiceRecognitionHandler voiceRecognitionHandler)1380 public void setVoiceRecognitionHandlerForTesting( 1381 VoiceRecognitionHandler voiceRecognitionHandler) { 1382 mVoiceRecognitionHandler = voiceRecognitionHandler; 1383 } 1384 } 1385