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