blob: cc2aa4fe23814217e094b70a1a14478cfdc3fdf2 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.omnibox;
import android.content.Context;
import android.view.ActionMode;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.chromium.base.Callback;
import org.chromium.chrome.browser.omnibox.UrlBar.ScrollType;
import org.chromium.chrome.browser.omnibox.UrlBar.UrlBarDelegate;
import org.chromium.chrome.browser.omnibox.UrlBar.UrlTextChangeListener;
import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
import org.chromium.ui.KeyboardVisibilityDelegate;
import org.chromium.ui.base.WindowDelegate;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Coordinates the interactions with the UrlBar text component.
*/
public class UrlBarCoordinator implements UrlBarEditingTextStateProvider, UrlFocusChangeListener,
KeyboardVisibilityDelegate.KeyboardVisibilityListener {
private static final int KEYBOARD_HIDE_DELAY_MS = 150;
private static final int KEYBOARD_MODE_CHANGE_DELAY_MS = 300;
private static final Runnable NO_OP_RUNNABLE = () -> {};
/** Specified how the text should be selected when focused. */
@IntDef({SelectionState.SELECT_ALL, SelectionState.SELECT_END})
@Retention(RetentionPolicy.SOURCE)
public @interface SelectionState {
/** Select all of the text. */
int SELECT_ALL = 0;
/** Selection (along with the input cursor) will be placed at the end of the text. */
int SELECT_END = 1;
}
private UrlBar mUrlBar;
private UrlBarMediator mMediator;
private KeyboardVisibilityDelegate mKeyboardVisibilityDelegate;
private WindowDelegate mWindowDelegate;
private Runnable mKeyboardResizeModeTask = NO_OP_RUNNABLE;
private Runnable mKeyboardHideTask = NO_OP_RUNNABLE;
private Callback<Boolean> mFocusChangeCallback;
private boolean mShouldShowModernizeVisualUpdate;
/**
* Constructs a coordinator for the given UrlBar view.
*
* @param urlBar The {@link UrlBar} view this coordinator encapsulates.
* @param windowDelegate Delegate for accessing and mutating window properties, e.g. soft input
* mode.
* @param actionModeCallback Callback to handle changes in contextual action Modes.
* @param focusChangeCallback The callback that will be notified when focus changes on the
* UrlBar.
* @param delegate The primary delegate for the UrlBar view.
* @param keyboardVisibilityDelegate Delegate that allows querying and changing the keyboard's
* visibility.
* @param isIncognito Whether incognito mode is initially enabled. This can later be changed
* using {@link #setIncognitoColorsEnabled(boolean)}.
* @param reportExceptionCallback A {@link Callback} to report exceptions.
*/
public UrlBarCoordinator(@NonNull UrlBar urlBar, @Nullable WindowDelegate windowDelegate,
@NonNull ActionMode.Callback actionModeCallback,
@NonNull Callback<Boolean> focusChangeCallback, @NonNull UrlBarDelegate delegate,
@NonNull KeyboardVisibilityDelegate keyboardVisibilityDelegate, boolean isIncognito,
Callback<Throwable> reportExceptionCallback) {
mUrlBar = urlBar;
urlBar.setTag(R.id.report_exception_callback, reportExceptionCallback);
mKeyboardVisibilityDelegate = keyboardVisibilityDelegate;
mWindowDelegate = windowDelegate;
mFocusChangeCallback = focusChangeCallback;
PropertyModel model =
new PropertyModel.Builder(UrlBarProperties.ALL_KEYS)
.with(UrlBarProperties.ACTION_MODE_CALLBACK, actionModeCallback)
.with(UrlBarProperties.WINDOW_DELEGATE, windowDelegate)
.with(UrlBarProperties.DELEGATE, delegate)
.with(UrlBarProperties.INCOGNITO_COLORS_ENABLED, isIncognito)
.build();
PropertyModelChangeProcessor.create(model, urlBar, UrlBarViewBinder::bind);
mMediator = new UrlBarMediator(model, this::onUrlFocusChangeInternal);
mKeyboardVisibilityDelegate.addKeyboardVisibilityListener(this);
}
public void destroy() {
mMediator.destroy();
mMediator = null;
mKeyboardVisibilityDelegate.removeKeyboardVisibilityListener(this);
mUrlBar.removeCallbacks(mKeyboardResizeModeTask);
mUrlBar.removeCallbacks(mKeyboardHideTask);
mUrlBar.destroy();
mUrlBar = null;
mFocusChangeCallback = null;
}
/** @see UrlBarMediator#addUrlTextChangeListener(UrlTextChangeListener) */
public void addUrlTextChangeListener(UrlTextChangeListener listener) {
mMediator.addUrlTextChangeListener(listener);
}
/** @see UrlBarMediator#setUrlBarData(UrlBarData, int, int) */
public boolean setUrlBarData(
UrlBarData data, @ScrollType int scrollType, @SelectionState int state) {
return mMediator.setUrlBarData(data, scrollType, state);
}
/** Returns the UrlBarData representing the current contents of the UrsssdddsssslBar. */
public UrlBarData getUrlBarData() {
return mMediator.getUrlBarData();
}
/** @see UrlBarMediator#setAutocompleteText(String, String) */
public void setAutocompleteText(String userText, String autocompleteText) {
mMediator.setAutocompleteText(userText, autocompleteText);
}
/** @see UrlBarMediator#setBrandedColorScheme(int) */
public boolean setBrandedColorScheme(@BrandedColorScheme int brandedColorScheme) {
return mMediator.setBrandedColorScheme(brandedColorScheme);
}
/** @see UrlBarMediator#setIncognitoColorsEnabled(boolean) */
public void setIncognitoColorsEnabled(boolean incognitoColorsEnabled) {
mMediator.setIncognitoColorsEnabled(incognitoColorsEnabled);
}
/** @see UrlBarMediator#setAllowFocus(boolean) */
public void setAllowFocus(boolean allowFocus) {
mMediator.setAllowFocus(allowFocus);
}
/** @see UrlBarMediator#setUrlDirectionListener(Callback<Integer>) */
public void setUrlDirectionListener(Callback<Integer> listener) {
mMediator.setUrlDirectionListener(listener);
}
/** Selects all of the text of the UrlBar. */
public void selectAll() {
mUrlBar.selectAll();
}
@Override
public int getSelectionStart() {
return mUrlBar.getSelectionStart();
}
@Override
public int getSelectionEnd() {
return mUrlBar.getSelectionEnd();
}
@Override
public boolean shouldAutocomplete() {
return mUrlBar.shouldAutocomplete();
}
@Override
public boolean wasLastEditPaste() {
return mUrlBar.wasLastEditPaste();
}
@Override
public String getTextWithAutocomplete() {
return mUrlBar.getTextWithAutocomplete();
}
@Override
public String getTextWithoutAutocomplete() {
return mUrlBar.getTextWithoutAutocomplete();
}
/** @see UrlBar#getVisibleTextPrefixHint() */
public CharSequence getVisibleTextPrefixHint() {
return mUrlBar.getVisibleTextPrefixHint();
}
// LocationBarLayout.UrlFocusChangeListener implementation.
@Override
public void onUrlFocusChange(boolean hasFocus) {
mUrlBar.removeCallbacks(mKeyboardResizeModeTask);
}
// KeyboardVisibilityDelegate.KeyboardVisibilityListener implementation.
@Override
public void keyboardVisibilityChanged(boolean isKeyboardShowing) {
if (mShouldShowModernizeVisualUpdate) {
// The cursor visibility should follow soft keyboard visibility and should be hidden
// when keyboard is dismissed for any reason (including scroll).
mUrlBar.setCursorVisible(isKeyboardShowing);
}
}
/* package */ boolean hasFocus() {
return mUrlBar.hasFocus();
}
/* package */ void requestFocus() {
mUrlBar.requestFocus();
}
/* package */ void clearFocus() {
mUrlBar.clearFocus();
}
/* package */ void requestAccessibilityFocus() {
mUrlBar.requestAccessibilityFocus();
}
/**
* Controls keyboard visibility.
*
* @param showKeyboard Whether the soft keyboard should be shown.
* @param shouldDelayHiding When true, keyboard hide operation will be delayed slightly to
* improve the animation smoothness.
*/
public void setKeyboardVisibility(boolean showKeyboard, boolean shouldDelayHiding) {
// Cancel pending jobs to prevent any possibility of keyboard flicker.
mUrlBar.removeCallbacks(mKeyboardHideTask);
// Note: due to nature of this mechanism, we may occasionally experience subsequent requests
// to show or hide keyboard anyway. This may happen when we schedule keyboard hide, and
// receive a second request to hide the keyboard instantly.
if (showKeyboard) {
setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN, /* delay */ false);
mKeyboardVisibilityDelegate.showKeyboard(mUrlBar);
} else {
// The animation rendering may not yet be 100% complete and hiding the keyboard makes
// the animation quite choppy.
// clang-format off
mKeyboardHideTask = () -> {
mKeyboardVisibilityDelegate.hideKeyboard(mUrlBar);
mKeyboardHideTask = NO_OP_RUNNABLE;
};
// clang-format on
mUrlBar.postDelayed(mKeyboardHideTask, shouldDelayHiding ? KEYBOARD_HIDE_DELAY_MS : 0);
// Convert the keyboard back to resize mode (delay the change for an arbitrary amount
// of time in hopes the keyboard will be completely hidden before making this change).
setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE, /* delay */ true);
}
}
/**
* @param softInputMode The software input resize mode.
* @param delay Delay the change in input mode.
*/
private void setSoftInputMode(final int softInputMode, boolean delay) {
if (OmniboxFeatures.omniboxConsumesImeInsets()) return;
mUrlBar.removeCallbacks(mKeyboardResizeModeTask);
if (mWindowDelegate == null || mWindowDelegate.getWindowSoftInputMode() == softInputMode) {
return;
}
if (delay) {
mKeyboardResizeModeTask = () -> {
mWindowDelegate.setWindowSoftInputMode(softInputMode);
mKeyboardResizeModeTask = NO_OP_RUNNABLE;
};
mUrlBar.postDelayed(mKeyboardResizeModeTask, KEYBOARD_MODE_CHANGE_DELAY_MS);
} else {
mWindowDelegate.setWindowSoftInputMode(softInputMode);
}
}
private void onUrlFocusChangeInternal(boolean hasFocus) {
InputMethodManager imm = (InputMethodManager) mUrlBar.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
if (hasFocus) {
// Explicitly tell InputMethodManager that the url bar is focused before any callbacks
// so that it updates the active view accordingly. Otherwise, it may fail to update
// the correct active view if ViewGroup.addView() or ViewGroup.removeView() is called
// to update a view that accepts text input.
imm.viewClicked(mUrlBar);
mUrlBar.setCursorVisible(true);
} else {
// Moving focus away from UrlBar(EditText) to a non-editable focus holder, such as
// ToolbarPhone, won't automatically hide keyboard app, but restart it with TYPE_NULL,
// which will result in a visual glitch. Also, currently, we do not allow moving focus
// directly from omnibox to web content's form field. Therefore, we hide keyboard on
// focus blur indiscriminately here. Note that hiding keyboard may lower FPS of other
// animation effects, but we found it tolerable in an experiment.
if (imm.isActive(mUrlBar)) setKeyboardVisibility(false, false);
}
mFocusChangeCallback.onResult(hasFocus);
}
/** Signals that's it safe to call code that requires native to be loaded. */
public void onFinishNativeInitialization() {
mUrlBar.onFinishNativeInitialization();
mShouldShowModernizeVisualUpdate =
OmniboxFeatures.shouldShowModernizeVisualUpdate(mUrlBar.getContext());
}
}