Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3626)

Unified Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/UrlBar.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/UrlBar.java
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/UrlBar.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/UrlBar.java
new file mode 100644
index 0000000000000000000000000000000000000000..142406f80cb397f5322ca0b5651a5ea350618f86
--- /dev/null
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/UrlBar.java
@@ -0,0 +1,966 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// 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.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.text.Editable;
+import android.text.Layout;
+import android.text.Selection;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputConnectionWrapper;
+
+import com.google.android.apps.chrome.R;
+
+import org.chromium.base.VisibleForTesting;
+import org.chromium.chrome.browser.UrlUtilities;
+import org.chromium.chrome.browser.omnibox.LocationBarLayout.OmniboxLivenessListener;
+import org.chromium.chrome.browser.tab.ChromeTab;
+import org.chromium.chrome.browser.widget.VerticallyFixedEditText;
+import org.chromium.content.browser.ContentViewCore;
+import org.chromium.ui.UiUtils;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+/**
+ * The URL text entry view for the Omnibox.
+ */
+public class UrlBar extends VerticallyFixedEditText {
+ private static final String TAG = "UrlBar";
+
+ /** The contents of the URL that precede the path/query after being formatted. */
+ private String mFormattedUrlLocation;
+
+ /** The contents of the URL that precede the path/query before formatting. */
+ private String mOriginalUrlLocation;
+
+ /** Overrides the text announced during accessibility events. */
+ private String mAccessibilityTextOverride;
+
+ private TextWatcher mLocationBarTextWatcher;
+
+ private boolean mShowKeyboardOnWindowFocus;
+
+ private boolean mFirstDrawComplete;
+
+ /**
+ * The text direction of the URL or query: LAYOUT_DIRECTION_LOCALE, LAYOUT_DIRECTION_LTR, or
+ * LAYOUT_DIRECTION_RTL.
+ * */
+ private int mUrlDirection;
+
+ private UrlBarDelegate mUrlBarDelegate;
+
+ private UrlDirectionListener mUrlDirectionListener;
+
+ private final AutocompleteSpan mAutocompleteSpan;
+
+ /**
+ * The gesture detector is used to detect long presses. Long presses require special treatment
+ * because the URL bar has custom touch event handling. See: {@link #onTouchEvent}.
+ */
+ private final GestureDetector mGestureDetector;
+ private boolean mFocused;
+
+ private final ColorStateList mDarkHintColor;
+ private final int mDarkDefaultTextColor;
+ private final int mDarkHighlightColor;
+
+ private final int mLightHintColor;
+ private final int mLightDefaultTextColor;
+ private final int mLightHighlightColor;
+
+ private Boolean mUseDarkColors;
+
+ private AccessibilityManager mAccessibilityManager;
+ private boolean mDisableTextAccessibilityEvents;
+
+ /**
+ * Whether default TextView scrolling should be disabled because autocomplete has been added.
+ * This allows the user entered text to be shown instead of the end of the autocomplete.
+ */
+ private boolean mDisableTextScrollingFromAutocomplete;
+
+ private OmniboxLivenessListener mOmniboxLivenessListener;
+
+ private long mFirstFocusTimeMs;
+
+ /**
+ * Implement this to get updates when the direction of the text in the URL bar changes.
+ * E.g. If the user is typing a URL, then erases it and starts typing a query in Arabic,
+ * the direction will change from left-to-right to right-to-left.
+ */
+ interface UrlDirectionListener {
+ /**
+ * Called whenever the layout direction of the UrlBar changes.
+ * @param layoutDirection the new direction: android.view.View.LAYOUT_DIRECTION_LTR or
+ * android.view.View.LAYOUT_DIRECTION_RTL
+ */
+ public void onUrlDirectionChanged(int layoutDirection);
+ }
+
+ /**
+ * Delegate used to communicate with the content side and the parent layout.
+ */
+ public interface UrlBarDelegate {
+ /**
+ * @return The current active {@link ChromeTab}.
+ */
+ ChromeTab getCurrentTab();
+
+ /**
+ * Notify the linked {@link TextWatcher} to ignore any changes made in the UrlBar text.
+ * @param ignore Whether the changes should be ignored.
+ */
+ void setIgnoreURLBarModification(boolean ignore);
+
+ /**
+ * Called at the beginning of the focus change event before the underlying TextView
+ * behavior is triggered.
+ * @param gainFocus Whether the URL is gaining focus or not.
+ */
+ void onUrlPreFocusChanged(boolean gainFocus);
+
+ /**
+ * Called to notify that back key has been pressed while the focus in on the url bar.
+ */
+ void backKeyPressed();
+
+ /**
+ * @return Whether original url is shown for preview page.
+ */
+ boolean showingOriginalUrlForPreview();
+
+ /**
+ * @return Whether the light security theme should be used.
+ */
+ boolean shouldEmphasizeHttpsScheme();
+ }
+
+ public UrlBar(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ Resources resources = getResources();
+
+ mDarkDefaultTextColor = resources.getColor(R.color.url_emphasis_default_text);
+ mDarkHintColor = getHintTextColors();
+ mDarkHighlightColor = getHighlightColor();
+
+ mLightDefaultTextColor = resources.getColor(R.color.url_emphasis_light_default_text);
+ mLightHintColor = resources.getColor(R.color.locationbar_light_hint_text);
+ mLightHighlightColor = resources.getColor(R.color.locationbar_light_selection_color);
+
+ setUseDarkTextColors(true);
+
+ mUrlDirection = LAYOUT_DIRECTION_LOCALE;
+ mAutocompleteSpan = new AutocompleteSpan();
+
+ // The URL Bar is derived from an text edit class, and as such is focusable by
+ // default. This means that if it is created before the first draw of the UI it
+ // will (as the only focusable element of the UI) get focus on the first draw.
+ // We react to this by greying out the tab area and bringing up the keyboard,
+ // which we don't want to do at startup. Prevent this by disabling focus until
+ // the first draw.
+ setFocusable(false);
+ setFocusableInTouchMode(false);
+
+ mGestureDetector = new GestureDetector(
+ getContext(), new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public void onLongPress(MotionEvent e) {
+ performLongClick();
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ requestFocus();
+ return true;
+ }
+ });
+ mGestureDetector.setOnDoubleTapListener(null);
+
+ mAccessibilityManager =
+ (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ }
+
+ /**
+ * Specifies whether the URL bar should use dark text colors or light colors.
+ * @param useDarkColors Whether the text colors should be dark (i.e. appropriate for use
+ * on a light background).
+ */
+ public void setUseDarkTextColors(boolean useDarkColors) {
+ if (mUseDarkColors != null && mUseDarkColors.booleanValue() == useDarkColors) return;
+
+ mUseDarkColors = useDarkColors;
+ if (mUseDarkColors) {
+ setTextColor(mDarkDefaultTextColor);
+ setHighlightColor(mDarkHighlightColor);
+ } else {
+ setTextColor(mLightDefaultTextColor);
+ setHighlightColor(mLightHighlightColor);
+ }
+
+ // Note: Setting the hint text color only takes effect if there is not text in the URL bar.
+ // To get around this, set the URL to empty before setting the hint color and revert
+ // back to the previous text after.
+ boolean hasNonEmptyText = false;
+ Editable text = getText();
+ if (!TextUtils.isEmpty(text)) {
+ setText("");
+ hasNonEmptyText = true;
+ }
+ if (useDarkColors) {
+ setHintTextColor(mDarkHintColor);
+ } else {
+ setHintTextColor(mLightHintColor);
+ }
+ if (hasNonEmptyText) setText(text);
+
+ if (!hasFocus()) {
+ deEmphasizeUrl();
+ emphasizeUrl();
+ }
+ }
+
+ /**
+ * @return The search query text (non-null).
+ */
+ public String getQueryText() {
+ return getEditableText() != null ? getEditableText().toString() : "";
+ }
+
+ /**
+ * @return Whether the current cursor position is at the end of the user typed text (i.e.
+ * at the beginning of the inline autocomplete text if present otherwise the very
+ * end of the current text).
+ */
+ public boolean isCursorAtEndOfTypedText() {
+ final int selectionStart = getSelectionStart();
+ final int selectionEnd = getSelectionEnd();
+
+ int expectedSelectionStart = getText().getSpanStart(mAutocompleteSpan);
+ int expectedSelectionEnd = getText().length();
+ if (expectedSelectionStart < 0) {
+ expectedSelectionStart = expectedSelectionEnd;
+ }
+
+ return selectionStart == expectedSelectionStart && selectionEnd == expectedSelectionEnd;
+ }
+
+ /**
+ * @return The user text without the autocomplete text.
+ */
+ public String getTextWithoutAutocomplete() {
+ int autoCompleteIndex = getText().getSpanStart(mAutocompleteSpan);
+ if (autoCompleteIndex < 0) {
+ return getQueryText();
+ } else {
+ return getQueryText().substring(0, autoCompleteIndex);
+ }
+ }
+
+ /** @return Whether any autocomplete information is specified on the current text. */
+ @VisibleForTesting
+ protected boolean hasAutocomplete() {
+ return getText().getSpanStart(mAutocompleteSpan) >= 0
+ || mAutocompleteSpan.mAutocompleteText != null
+ || mAutocompleteSpan.mUserText != null;
+ }
+
+ @Override
+ protected void onSelectionChanged(int selStart, int selEnd) {
+ int spanStart = getText().getSpanStart(mAutocompleteSpan);
+ int spanEnd = getText().getSpanEnd(mAutocompleteSpan);
+ if (spanStart >= 0 && (spanStart != selStart || spanEnd != selEnd)) {
+ // On selection changes, the autocomplete text has been accepted by the user or needs
+ // to be deleted below.
+ mAutocompleteSpan.clearSpan();
+
+ // The autocomplete text will be deleted any time the selection occurs entirely before
+ // the start of the autocomplete text. This is required because certain keyboards will
+ // insert characters temporarily when starting a key entry gesture (whether it be
+ // swyping a word or long pressing to get a special character). When this temporary
+ // character appears, Chrome may decide to append some autocomplete, but the keyboard
+ // will then remove this temporary character only while leaving the autocomplete text
+ // alone. See crbug/273763 for more details.
+ if (selEnd <= spanStart) getText().delete(spanStart, getText().length());
+ }
+ super.onSelectionChanged(selStart, selEnd);
+ }
+
+ @Override
+ protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+ mFocused = focused;
+ mUrlBarDelegate.onUrlPreFocusChanged(focused);
+ if (!focused) mAutocompleteSpan.clearSpan();
+ super.onFocusChanged(focused, direction, previouslyFocusedRect);
+
+ if (focused && mFirstFocusTimeMs == 0) {
+ mFirstFocusTimeMs = SystemClock.elapsedRealtime();
+ if (mOmniboxLivenessListener != null) mOmniboxLivenessListener.onOmniboxFocused();
+ }
+ }
+
+ /**
+ * @return The elapsed realtime timestamp in ms of the first time the url bar was focused,
+ * 0 if never.
+ */
+ public long getFirstFocusTime() {
+ return mFirstFocusTimeMs;
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ if (visibility == View.GONE && isFocused()) mShowKeyboardOnWindowFocus = true;
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+ if (hasWindowFocus) {
+ if (mShowKeyboardOnWindowFocus && isFocused()) {
+ // Without the call to post(..), the keyboard was not getting shown when the
+ // window regained focus despite this being the final call in the view system
+ // flow.
+ post(new Runnable() {
+ @Override
+ public void run() {
+ UiUtils.showKeyboard(UrlBar.this);
+ }
+ });
+ }
+ mShowKeyboardOnWindowFocus = false;
+ }
+ }
+
+ @Override
+ public View focusSearch(int direction) {
+ if (direction == View.FOCUS_BACKWARD
+ && mUrlBarDelegate.getCurrentTab().getView() != null) {
+ return mUrlBarDelegate.getCurrentTab().getView();
+ } else {
+ return super.focusSearch(direction);
+ }
+ }
+
+ @Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getRepeatCount() == 0) {
+ // Tell the framework to start tracking this event.
+ getKeyDispatcherState().startTracking(event, this);
+ return true;
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ getKeyDispatcherState().handleUpEvent(event);
+ if (event.isTracking() && !event.isCanceled()) {
+ mUrlBarDelegate.backKeyPressed();
+ return true;
+ }
+ }
+ }
+ return super.onKeyPreIme(keyCode, event);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ ChromeTab currentTab = mUrlBarDelegate.getCurrentTab();
+ if (currentTab != null
+ && currentTab.getBackgroundContentViewHelper() != null
+ && mUrlBarDelegate.showingOriginalUrlForPreview()) {
+ // When we are showing preview, we treat click on UrlBar as an event to force swapping
+ // of content views.
+ currentTab.getBackgroundContentViewHelper().forceSwappingContentViews();
+ }
+
+ if (!mFocused) {
+ mGestureDetector.onTouchEvent(event);
+ return true;
+ }
+
+ if (event.getAction() == MotionEvent.ACTION_DOWN && currentTab != null) {
+ // Make sure to hide the current ContentView ActionBar.
+ ContentViewCore viewCore = currentTab.getContentViewCore();
+ if (viewCore != null) viewCore.hideSelectActionMode();
+ }
+
+ return super.onTouchEvent(event);
+ }
+
+ @Override
+ public boolean bringPointIntoView(int offset) {
+ if (mDisableTextScrollingFromAutocomplete) return false;
+ return super.bringPointIntoView(offset);
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ boolean retVal = super.onPreDraw();
+ if (mDisableTextScrollingFromAutocomplete) {
+ // super.onPreDraw will put the selection at the end of the text selection, but
+ // in the case of autocomplete we want the last typed character to be shown, which
+ // is the start of selection.
+ mDisableTextScrollingFromAutocomplete = false;
+ bringPointIntoView(getSelectionStart());
+ retVal = true;
+ }
+ return retVal;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (!mFirstDrawComplete) {
+ mFirstDrawComplete = true;
+
+ // We have now avoided the first draw problem (see the comment in
+ // the constructor) so we want to make the URL bar focusable so that
+ // touches etc. activate it.
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+
+ // The URL bar will now react correctly to a focus change event
+ if (mOmniboxLivenessListener != null) mOmniboxLivenessListener.onOmniboxInteractive();
+ }
+
+ // Notify listeners if the URL's direction has changed.
+ updateUrlDirection();
+ }
+
+ /**
+ * If the direction of the URL has changed, update mUrlDirection and notify the
+ * UrlDirectionListeners.
+ */
+ private void updateUrlDirection() {
+ Layout layout = getLayout();
+ if (layout == null) return;
+
+ int urlDirection;
+ if (length() == 0) {
+ urlDirection = LAYOUT_DIRECTION_LOCALE;
+ } else if (layout.getParagraphDirection(0) == Layout.DIR_LEFT_TO_RIGHT) {
+ urlDirection = LAYOUT_DIRECTION_LTR;
+ } else {
+ urlDirection = LAYOUT_DIRECTION_RTL;
+ }
+
+ if (urlDirection != mUrlDirection) {
+ mUrlDirection = urlDirection;
+ if (mUrlDirectionListener != null) {
+ mUrlDirectionListener.onUrlDirectionChanged(urlDirection);
+ }
+ }
+ }
+
+ /**
+ * @return The text direction of the URL, e.g. LAYOUT_DIRECTION_LTR.
+ */
+ public int getUrlDirection() {
+ return mUrlDirection;
+ }
+
+ /**
+ * Sets the listener for changes in the url bar's layout direction. Also calls
+ * onUrlDirectionChanged() immediately on the listener.
+ *
+ * @param listener The UrlDirectionListener to receive callbacks when the url direction changes,
+ * or null to unregister any previously registered listener.
+ */
+ public void setUrlDirectionListener(UrlDirectionListener listener) {
+ mUrlDirectionListener = listener;
+ if (mUrlDirectionListener != null) {
+ mUrlDirectionListener.onUrlDirectionChanged(mUrlDirection);
+ }
+ }
+
+ void setLocationBarTextWatcher(TextWatcher locationBarTextWatcher) {
+ mLocationBarTextWatcher = locationBarTextWatcher;
+ }
+
+ /**
+ * Set the url delegate to handle communication from the {@link UrlBar} to the rest of the UI.
+ * @param delegate The {@link UrlBarDelegate} to be used.
+ */
+ public void setDelegate(UrlBarDelegate delegate) {
+ mUrlBarDelegate = delegate;
+ }
+
+ /**
+ * Set {@link OmniboxLivenessListener} to be used for receiving interaction related messages
+ * during startup.
+ * @param listener The listener to use for sending the messages.
+ */
+ @VisibleForTesting
+ public void setOmniboxLivenessListener(OmniboxLivenessListener listener) {
+ mOmniboxLivenessListener = listener;
+ }
+
+ /**
+ * Signal {@link OmniboxLivenessListener} that the omnibox is completely operational now.
+ */
+ @VisibleForTesting
+ public void onOmniboxFullyFunctional() {
+ if (mOmniboxLivenessListener != null) mOmniboxLivenessListener.onOmniboxFullyFunctional();
+ }
+
+ @Override
+ public boolean onTextContextMenuItem(int id) {
+ if (id == android.R.id.paste) {
+ ClipboardManager clipboard = (ClipboardManager) getContext()
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clipData = clipboard.getPrimaryClip();
+ if (clipData != null) {
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < clipData.getItemCount(); i++) {
+ builder.append(clipData.getItemAt(i).coerceToText(getContext()));
+ }
+ String pasteString = OmniboxViewUtil.sanitizeTextForPaste(builder.toString());
+
+ int min = 0;
+ int max = getText().length();
+
+ if (isFocused()) {
+ final int selStart = getSelectionStart();
+ final int selEnd = getSelectionEnd();
+
+ min = Math.max(0, Math.min(selStart, selEnd));
+ max = Math.max(0, Math.max(selStart, selEnd));
+ }
+
+ Selection.setSelection(getText(), max);
+ getText().replace(min, max, pasteString);
+ return true;
+ }
+ }
+
+ if (mOriginalUrlLocation == null || mFormattedUrlLocation == null) {
+ return super.onTextContextMenuItem(id);
+ }
+
+ int selectedStartIndex = getSelectionStart();
+ int selectedEndIndex = getSelectionEnd();
+
+ // If we are copying/cutting the full previously formatted URL, reset the URL
+ // text before initiating the TextViews handling of the context menu.
+ String currentText = getText().toString();
+ if (selectedStartIndex == 0
+ && (id == android.R.id.cut || id == android.R.id.copy)
+ && currentText.startsWith(mFormattedUrlLocation)
+ && selectedEndIndex >= mFormattedUrlLocation.length()) {
+ String newText = mOriginalUrlLocation
+ + currentText.substring(mFormattedUrlLocation.length());
+ selectedEndIndex = selectedEndIndex - mFormattedUrlLocation.length()
+ + mOriginalUrlLocation.length();
+ mUrlBarDelegate.setIgnoreURLBarModification(true);
+ setText(newText);
+ setSelection(0, selectedEndIndex);
+ boolean retVal = super.onTextContextMenuItem(id);
+ if (getText().toString().equals(newText)) {
+ setText(currentText);
+ setSelection(getText().length());
+ }
+ mUrlBarDelegate.setIgnoreURLBarModification(false);
+ return retVal;
+ }
+ return super.onTextContextMenuItem(id);
+ }
+
+ /**
+ * Sets the text content of the URL bar.
+ *
+ * @param url The original URL (or generic text) that can be used for copy/cut/paste.
+ * @param formattedUrl Formatted URL for user display. Null if there isn't one.
+ * @return Whether the visible text has changed.
+ */
+ public boolean setUrl(String url, String formattedUrl) {
+ if (!TextUtils.isEmpty(formattedUrl)) {
+ try {
+ URL javaUrl = new URL(url);
+ mFormattedUrlLocation =
+ getUrlContentsPrePath(formattedUrl, javaUrl.getHost());
+ mOriginalUrlLocation =
+ getUrlContentsPrePath(url, javaUrl.getHost());
+ } catch (MalformedURLException mue) {
+ mOriginalUrlLocation = null;
+ mFormattedUrlLocation = null;
+ }
+ } else {
+ mOriginalUrlLocation = null;
+ mFormattedUrlLocation = null;
+ formattedUrl = url;
+ }
+
+ Editable previousText = getEditableText();
+ setText(formattedUrl);
+
+ if (!isFocused()) scrollToTLD();
+
+ return !TextUtils.equals(previousText, getEditableText());
+ }
+
+ /**
+ * Autocompletes the text on the url bar and selects the text that was not entered by the
+ * user. Using append() instead of setText() to preserve the soft-keyboard layout.
+ * @param userText user The text entered by the user.
+ * @param inlineAutocompleteText The suggested autocompletion for the user's text.
+ */
+ public void setAutocompleteText(CharSequence userText, CharSequence inlineAutocompleteText) {
+ boolean emptyAutocomplete = TextUtils.isEmpty(inlineAutocompleteText);
+
+ if (!emptyAutocomplete) mDisableTextScrollingFromAutocomplete = true;
+
+ int autocompleteIndex = userText.length();
+
+ String previousText = getQueryText();
+ CharSequence newText = TextUtils.concat(userText, inlineAutocompleteText);
+
+ mUrlBarDelegate.setIgnoreURLBarModification(true);
+ mDisableTextAccessibilityEvents = true;
+
+ if (!TextUtils.equals(previousText, newText)) {
+ // The previous text may also have included autocomplete text, so we only
+ // append the new autocomplete text that has changed.
+ if (TextUtils.indexOf(newText, previousText) == 0) {
+ append(newText.subSequence(previousText.length(), newText.length()));
+ } else {
+ setUrl(newText.toString(), null);
+ }
+ }
+
+ if (getSelectionStart() != autocompleteIndex
+ || getSelectionEnd() != getText().length()) {
+ setSelection(autocompleteIndex, getText().length());
+
+ if (inlineAutocompleteText.length() != 0) {
+ // Sending a TYPE_VIEW_TEXT_SELECTION_CHANGED accessibility event causes the
+ // previous TYPE_VIEW_TEXT_CHANGED event to be swallowed. As a result the user
+ // hears the autocomplete text but *not* the text they typed. Instead we send a
+ // TYPE_ANNOUNCEMENT event, which doesn't swallow the text-changed event.
+ announceForAccessibility(inlineAutocompleteText);
+ }
+ }
+
+ if (emptyAutocomplete) {
+ mAutocompleteSpan.clearSpan();
+ } else {
+ mAutocompleteSpan.setSpan(userText, inlineAutocompleteText);
+ }
+
+ mUrlBarDelegate.setIgnoreURLBarModification(false);
+ mDisableTextAccessibilityEvents = false;
+ }
+
+ /**
+ * Overrides the text announced when focusing on the field for accessibility. This value will
+ * be cleared automatically when the text content changes for this view.
+ * @param accessibilityOverride The text to be announced instead of the current text value
+ * (or null if the text content should be read).
+ */
+ public void setAccessibilityTextOverride(String accessibilityOverride) {
+ mAccessibilityTextOverride = accessibilityOverride;
+ }
+
+ private void scrollToTLD() {
+ Editable url = getText();
+ if (url == null || url.length() < 1) return;
+ String urlString = url.toString();
+ URL javaUrl;
+ try {
+ javaUrl = new URL(urlString);
+ } catch (MalformedURLException mue) {
+ return;
+ }
+ String host = javaUrl.getHost();
+ if (host == null || host.isEmpty()) return;
+ int hostStart = urlString.indexOf(host);
+ int hostEnd = hostStart + host.length();
+ setSelection(hostEnd);
+ }
+
+ @Override
+ public void setText(CharSequence text, BufferType type) {
+ mDisableTextScrollingFromAutocomplete = false;
+
+ // Avoid setting the same text to the URL bar as it will mess up the scroll/cursor
+ // position.
+ // Setting the text is also quite expensive, so only do it when the text has changed
+ // (since we apply spans when the URL is not focused, we only optimize this when the
+ // URL is being edited).
+ if (!TextUtils.equals(getEditableText(), text)) {
+ super.setText(text, type);
+ mAccessibilityTextOverride = null;
+ }
+
+ // Verify the autocomplete is still valid after the text change.
+ if (mAutocompleteSpan != null
+ && mAutocompleteSpan.mUserText != null
+ && mAutocompleteSpan.mAutocompleteText != null) {
+ if (getText().getSpanStart(mAutocompleteSpan) < 0) {
+ mAutocompleteSpan.clearSpan();
+ } else {
+ Editable editableText = getEditableText();
+ CharSequence previousUserText = mAutocompleteSpan.mUserText;
+ CharSequence previousAutocompleteText = mAutocompleteSpan.mAutocompleteText;
+ if (editableText.length()
+ < (previousUserText.length() + previousAutocompleteText.length())) {
+ mAutocompleteSpan.clearSpan();
+ } else if (TextUtils.indexOf(getText(), previousUserText) != 0
+ || TextUtils.indexOf(getText(), previousAutocompleteText)
+ != previousUserText.length()) {
+ mAutocompleteSpan.clearSpan();
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the portion of the URL that precedes the path/query section of the URL.
+ *
+ * @param url The url to be used to find the preceding portion.
+ * @param host The host to be located in the URL to determine the location of the path.
+ * @return The URL contents that precede the path (or the passed in URL if the host is
+ * not found).
+ */
+ private static String getUrlContentsPrePath(String url, String host) {
+ String urlPrePath = url;
+ int hostIndex = url.indexOf(host);
+ if (hostIndex >= 0) {
+ int pathIndex = url.indexOf('/', hostIndex);
+ if (pathIndex > 0) {
+ urlPrePath = url.substring(0, pathIndex);
+ } else {
+ urlPrePath = url;
+ }
+ }
+ return urlPrePath;
+ }
+
+ @Override
+ public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
+ if (mDisableTextAccessibilityEvents) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
+ || event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED) {
+ return;
+ }
+ }
+ super.sendAccessibilityEventUnchecked(event);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+
+ if (mAccessibilityTextOverride != null) {
+ info.setText(mAccessibilityTextOverride);
+ }
+ }
+
+ InputConnectionWrapper mInputConnection = new InputConnectionWrapper(null, true) {
+ private final char[] mTempSelectionChar = new char[1];
+
+ @Override
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ Editable currentText = getText();
+ if (currentText == null) return super.commitText(text, newCursorPosition);
+
+ int selectionStart = Selection.getSelectionStart(currentText);
+ int selectionEnd = Selection.getSelectionEnd(currentText);
+ int autocompleteIndex = currentText.getSpanStart(mAutocompleteSpan);
+ // If the text being committed is a single character that matches the next character
+ // in the selection (assumed to be the autocomplete text), we only move the text
+ // selection instead clearing the autocomplete text causing flickering as the
+ // autocomplete text will appear once the next suggestions are received.
+ //
+ // To be confident that the selection is an autocomplete, we ensure the selection
+ // is at least one character and the end of the selection is the end of the
+ // currently entered text.
+ if (newCursorPosition == 1 && selectionStart > 0 && selectionStart != selectionEnd
+ && selectionEnd >= currentText.length()
+ && autocompleteIndex == selectionStart
+ && text.length() == 1) {
+ currentText.getChars(selectionStart, selectionStart + 1, mTempSelectionChar, 0);
+ if (mTempSelectionChar[0] == text.charAt(0)) {
+
+ // Since the text isn't changing, TalkBack won't read out the typed characters.
+ // To work around this, explicitly send an accessibility event. crbug.com/416595
+ if (mAccessibilityManager != null && mAccessibilityManager.isEnabled()) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
+ event.setFromIndex(selectionStart);
+ event.setRemovedCount(0);
+ event.setAddedCount(1);
+ event.setBeforeText(currentText.toString().substring(0, selectionStart));
+ sendAccessibilityEventUnchecked(event);
+ }
+
+ if (mLocationBarTextWatcher != null) {
+ mLocationBarTextWatcher.beforeTextChanged(currentText, 0, 0, 0);
+ }
+ setAutocompleteText(
+ currentText.subSequence(0, selectionStart + 1),
+ currentText.subSequence(selectionStart + 1, selectionEnd));
+ if (mLocationBarTextWatcher != null) {
+ mLocationBarTextWatcher.afterTextChanged(currentText);
+ }
+ return true;
+ }
+ }
+ return super.commitText(text, newCursorPosition);
+ }
+
+ @Override
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ Editable currentText = getText();
+ int autoCompleteSpanStart = currentText.getSpanStart(mAutocompleteSpan);
+ if (autoCompleteSpanStart >= 0) {
+ int composingEnd = BaseInputConnection.getComposingSpanEnd(currentText);
+
+ // On certain device/keyboard combinations, the composing regions are specified
+ // with a noticeable delay after the initial character is typed, and in certain
+ // circumstances it does not check that the current state of the text matches the
+ // expectations of it's composing region.
+ // For example, you can be typing:
+ // chrome://f
+ // Chrome will autocomplete to:
+ // chrome://f[lags]
+ // And after the autocomplete has been set, the keyboard will set the composing
+ // region to the last character and it assumes it is 'f' as it was the last
+ // character the keyboard sent. If we commit this composition, the text will
+ // look like:
+ // chrome://flag[f]
+ // And if we use the autocomplete clearing logic below, it will look like:
+ // chrome://f[f]
+ // To work around this, we see if the composition matches all the characters prior
+ // to the autocomplete and just readjust the composing region to be that subset.
+ //
+ // See crbug.com/366732
+ if (composingEnd == currentText.length()
+ && autoCompleteSpanStart >= text.length()
+ && TextUtils.equals(
+ currentText.subSequence(
+ autoCompleteSpanStart - text.length(),
+ autoCompleteSpanStart),
+ text)) {
+ setComposingRegion(
+ autoCompleteSpanStart - text.length(), autoCompleteSpanStart);
+ }
+
+ // Once composing text is being modified, the autocomplete text has been accepted
+ // or has to be deleted.
+ mAutocompleteSpan.clearSpan();
+ Selection.setSelection(currentText, autoCompleteSpanStart);
+ currentText.delete(autoCompleteSpanStart, currentText.length());
+ }
+ return super.setComposingText(text, newCursorPosition);
+ }
+ };
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ mInputConnection.setTarget(super.onCreateInputConnection(outAttrs));
+ return mInputConnection;
+ }
+
+ /**
+ * Emphasize the TLD and second domain of the URL.
+ */
+ public void emphasizeUrl() {
+ Editable url = getText();
+ if (OmniboxUrlEmphasizer.hasEmphasisSpans(url) || hasFocus()) {
+ return;
+ }
+
+ if (url.length() < 1) {
+ return;
+ }
+
+ if (mUrlBarDelegate.showingOriginalUrlForPreview()) {
+ // We will make the whole url as greyed out(Tailing url color). This is the UI
+ // treatment we show to indicate that we are showing original url for preview page.
+ OmniboxUrlEmphasizer.greyOutUrl(url, getResources(), mUseDarkColors);
+ return;
+ }
+
+ // We retrieve the domain and registry from the full URL (the url bar shows a simplified
+ // version of the URL).
+ ChromeTab currentTab = mUrlBarDelegate.getCurrentTab();
+ if (currentTab == null || currentTab.getProfile() == null) return;
+
+ boolean isInternalPage = false;
+ try {
+ String tabUrl = currentTab.getUrl();
+ isInternalPage = UrlUtilities.isInternalScheme(new URI(tabUrl));
+ } catch (URISyntaxException e) {
+ // Ignore as this only is for applying color
+ }
+
+ OmniboxUrlEmphasizer.emphasizeUrl(url, getResources(), currentTab.getProfile(),
+ currentTab.getSecurityLevel(), isInternalPage,
+ mUseDarkColors, mUrlBarDelegate.shouldEmphasizeHttpsScheme());
+ }
+
+ /**
+ * Reset the modifications done to emphasize the TLD and second domain of the URL.
+ */
+ public void deEmphasizeUrl() {
+ OmniboxUrlEmphasizer.deEmphasizeUrl(getText());
+ }
+
+ /**
+ * Simple span used for tracking the current autocomplete state.
+ */
+ private class AutocompleteSpan {
+ private CharSequence mUserText;
+ private CharSequence mAutocompleteText;
+
+ /**
+ * Adds the span to the current text.
+ * @param userText The user entered text.
+ * @param autocompleteText The autocomplete text being appended.
+ */
+ public void setSpan(CharSequence userText, CharSequence autocompleteText) {
+ Editable text = getText();
+ text.removeSpan(this);
+ mAutocompleteText = autocompleteText;
+ mUserText = userText;
+ text.setSpan(
+ this,
+ userText.length(),
+ text.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ /** Removes this span from the current text and clears the internal state. */
+ public void clearSpan() {
+ getText().removeSpan(this);
+ mAutocompleteText = null;
+ mUserText = null;
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698