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; |
+ } |
+ } |
+} |