OLD | NEW |
(Empty) | |
| 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.ClipData; |
| 8 import android.content.ClipboardManager; |
| 9 import android.content.Context; |
| 10 import android.content.res.ColorStateList; |
| 11 import android.content.res.Resources; |
| 12 import android.graphics.Canvas; |
| 13 import android.graphics.Rect; |
| 14 import android.os.SystemClock; |
| 15 import android.text.Editable; |
| 16 import android.text.Layout; |
| 17 import android.text.Selection; |
| 18 import android.text.Spanned; |
| 19 import android.text.TextUtils; |
| 20 import android.text.TextWatcher; |
| 21 import android.util.AttributeSet; |
| 22 import android.view.GestureDetector; |
| 23 import android.view.KeyEvent; |
| 24 import android.view.MotionEvent; |
| 25 import android.view.View; |
| 26 import android.view.accessibility.AccessibilityEvent; |
| 27 import android.view.accessibility.AccessibilityManager; |
| 28 import android.view.accessibility.AccessibilityNodeInfo; |
| 29 import android.view.inputmethod.BaseInputConnection; |
| 30 import android.view.inputmethod.EditorInfo; |
| 31 import android.view.inputmethod.InputConnection; |
| 32 import android.view.inputmethod.InputConnectionWrapper; |
| 33 |
| 34 import com.google.android.apps.chrome.R; |
| 35 |
| 36 import org.chromium.base.VisibleForTesting; |
| 37 import org.chromium.chrome.browser.UrlUtilities; |
| 38 import org.chromium.chrome.browser.omnibox.LocationBarLayout.OmniboxLivenessList
ener; |
| 39 import org.chromium.chrome.browser.tab.ChromeTab; |
| 40 import org.chromium.chrome.browser.widget.VerticallyFixedEditText; |
| 41 import org.chromium.content.browser.ContentViewCore; |
| 42 import org.chromium.ui.UiUtils; |
| 43 |
| 44 import java.net.MalformedURLException; |
| 45 import java.net.URI; |
| 46 import java.net.URISyntaxException; |
| 47 import java.net.URL; |
| 48 |
| 49 /** |
| 50 * The URL text entry view for the Omnibox. |
| 51 */ |
| 52 public class UrlBar extends VerticallyFixedEditText { |
| 53 private static final String TAG = "UrlBar"; |
| 54 |
| 55 /** The contents of the URL that precede the path/query after being formatte
d. */ |
| 56 private String mFormattedUrlLocation; |
| 57 |
| 58 /** The contents of the URL that precede the path/query before formatting. *
/ |
| 59 private String mOriginalUrlLocation; |
| 60 |
| 61 /** Overrides the text announced during accessibility events. */ |
| 62 private String mAccessibilityTextOverride; |
| 63 |
| 64 private TextWatcher mLocationBarTextWatcher; |
| 65 |
| 66 private boolean mShowKeyboardOnWindowFocus; |
| 67 |
| 68 private boolean mFirstDrawComplete; |
| 69 |
| 70 /** |
| 71 * The text direction of the URL or query: LAYOUT_DIRECTION_LOCALE, LAYOUT_D
IRECTION_LTR, or |
| 72 * LAYOUT_DIRECTION_RTL. |
| 73 * */ |
| 74 private int mUrlDirection; |
| 75 |
| 76 private UrlBarDelegate mUrlBarDelegate; |
| 77 |
| 78 private UrlDirectionListener mUrlDirectionListener; |
| 79 |
| 80 private final AutocompleteSpan mAutocompleteSpan; |
| 81 |
| 82 /** |
| 83 * The gesture detector is used to detect long presses. Long presses require
special treatment |
| 84 * because the URL bar has custom touch event handling. See: {@link #onTouch
Event}. |
| 85 */ |
| 86 private final GestureDetector mGestureDetector; |
| 87 private boolean mFocused; |
| 88 |
| 89 private final ColorStateList mDarkHintColor; |
| 90 private final int mDarkDefaultTextColor; |
| 91 private final int mDarkHighlightColor; |
| 92 |
| 93 private final int mLightHintColor; |
| 94 private final int mLightDefaultTextColor; |
| 95 private final int mLightHighlightColor; |
| 96 |
| 97 private Boolean mUseDarkColors; |
| 98 |
| 99 private AccessibilityManager mAccessibilityManager; |
| 100 private boolean mDisableTextAccessibilityEvents; |
| 101 |
| 102 /** |
| 103 * Whether default TextView scrolling should be disabled because autocomplet
e has been added. |
| 104 * This allows the user entered text to be shown instead of the end of the a
utocomplete. |
| 105 */ |
| 106 private boolean mDisableTextScrollingFromAutocomplete; |
| 107 |
| 108 private OmniboxLivenessListener mOmniboxLivenessListener; |
| 109 |
| 110 private long mFirstFocusTimeMs; |
| 111 |
| 112 /** |
| 113 * Implement this to get updates when the direction of the text in the URL b
ar changes. |
| 114 * E.g. If the user is typing a URL, then erases it and starts typing a quer
y in Arabic, |
| 115 * the direction will change from left-to-right to right-to-left. |
| 116 */ |
| 117 interface UrlDirectionListener { |
| 118 /** |
| 119 * Called whenever the layout direction of the UrlBar changes. |
| 120 * @param layoutDirection the new direction: android.view.View.LAYOUT_DI
RECTION_LTR or |
| 121 * android.view.View.LAYOUT_DIRECTION_RTL |
| 122 */ |
| 123 public void onUrlDirectionChanged(int layoutDirection); |
| 124 } |
| 125 |
| 126 /** |
| 127 * Delegate used to communicate with the content side and the parent layout. |
| 128 */ |
| 129 public interface UrlBarDelegate { |
| 130 /** |
| 131 * @return The current active {@link ChromeTab}. |
| 132 */ |
| 133 ChromeTab getCurrentTab(); |
| 134 |
| 135 /** |
| 136 * Notify the linked {@link TextWatcher} to ignore any changes made in t
he UrlBar text. |
| 137 * @param ignore Whether the changes should be ignored. |
| 138 */ |
| 139 void setIgnoreURLBarModification(boolean ignore); |
| 140 |
| 141 /** |
| 142 * Called at the beginning of the focus change event before the underlyi
ng TextView |
| 143 * behavior is triggered. |
| 144 * @param gainFocus Whether the URL is gaining focus or not. |
| 145 */ |
| 146 void onUrlPreFocusChanged(boolean gainFocus); |
| 147 |
| 148 /** |
| 149 * Called to notify that back key has been pressed while the focus in on
the url bar. |
| 150 */ |
| 151 void backKeyPressed(); |
| 152 |
| 153 /** |
| 154 * @return Whether original url is shown for preview page. |
| 155 */ |
| 156 boolean showingOriginalUrlForPreview(); |
| 157 |
| 158 /** |
| 159 * @return Whether the light security theme should be used. |
| 160 */ |
| 161 boolean shouldEmphasizeHttpsScheme(); |
| 162 } |
| 163 |
| 164 public UrlBar(Context context, AttributeSet attrs) { |
| 165 super(context, attrs); |
| 166 |
| 167 Resources resources = getResources(); |
| 168 |
| 169 mDarkDefaultTextColor = resources.getColor(R.color.url_emphasis_default_
text); |
| 170 mDarkHintColor = getHintTextColors(); |
| 171 mDarkHighlightColor = getHighlightColor(); |
| 172 |
| 173 mLightDefaultTextColor = resources.getColor(R.color.url_emphasis_light_d
efault_text); |
| 174 mLightHintColor = resources.getColor(R.color.locationbar_light_hint_text
); |
| 175 mLightHighlightColor = resources.getColor(R.color.locationbar_light_sele
ction_color); |
| 176 |
| 177 setUseDarkTextColors(true); |
| 178 |
| 179 mUrlDirection = LAYOUT_DIRECTION_LOCALE; |
| 180 mAutocompleteSpan = new AutocompleteSpan(); |
| 181 |
| 182 // The URL Bar is derived from an text edit class, and as such is focusa
ble by |
| 183 // default. This means that if it is created before the first draw of th
e UI it |
| 184 // will (as the only focusable element of the UI) get focus on the first
draw. |
| 185 // We react to this by greying out the tab area and bringing up the keyb
oard, |
| 186 // which we don't want to do at startup. Prevent this by disabling focus
until |
| 187 // the first draw. |
| 188 setFocusable(false); |
| 189 setFocusableInTouchMode(false); |
| 190 |
| 191 mGestureDetector = new GestureDetector( |
| 192 getContext(), new GestureDetector.SimpleOnGestureListener() { |
| 193 @Override |
| 194 public void onLongPress(MotionEvent e) { |
| 195 performLongClick(); |
| 196 } |
| 197 |
| 198 @Override |
| 199 public boolean onSingleTapUp(MotionEvent e) { |
| 200 requestFocus(); |
| 201 return true; |
| 202 } |
| 203 }); |
| 204 mGestureDetector.setOnDoubleTapListener(null); |
| 205 |
| 206 mAccessibilityManager = |
| 207 (AccessibilityManager) context.getSystemService(Context.ACCESSIB
ILITY_SERVICE); |
| 208 } |
| 209 |
| 210 /** |
| 211 * Specifies whether the URL bar should use dark text colors or light colors
. |
| 212 * @param useDarkColors Whether the text colors should be dark (i.e. appropr
iate for use |
| 213 * on a light background). |
| 214 */ |
| 215 public void setUseDarkTextColors(boolean useDarkColors) { |
| 216 if (mUseDarkColors != null && mUseDarkColors.booleanValue() == useDarkCo
lors) return; |
| 217 |
| 218 mUseDarkColors = useDarkColors; |
| 219 if (mUseDarkColors) { |
| 220 setTextColor(mDarkDefaultTextColor); |
| 221 setHighlightColor(mDarkHighlightColor); |
| 222 } else { |
| 223 setTextColor(mLightDefaultTextColor); |
| 224 setHighlightColor(mLightHighlightColor); |
| 225 } |
| 226 |
| 227 // Note: Setting the hint text color only takes effect if there is not t
ext in the URL bar. |
| 228 // To get around this, set the URL to empty before setting the hin
t color and revert |
| 229 // back to the previous text after. |
| 230 boolean hasNonEmptyText = false; |
| 231 Editable text = getText(); |
| 232 if (!TextUtils.isEmpty(text)) { |
| 233 setText(""); |
| 234 hasNonEmptyText = true; |
| 235 } |
| 236 if (useDarkColors) { |
| 237 setHintTextColor(mDarkHintColor); |
| 238 } else { |
| 239 setHintTextColor(mLightHintColor); |
| 240 } |
| 241 if (hasNonEmptyText) setText(text); |
| 242 |
| 243 if (!hasFocus()) { |
| 244 deEmphasizeUrl(); |
| 245 emphasizeUrl(); |
| 246 } |
| 247 } |
| 248 |
| 249 /** |
| 250 * @return The search query text (non-null). |
| 251 */ |
| 252 public String getQueryText() { |
| 253 return getEditableText() != null ? getEditableText().toString() : ""; |
| 254 } |
| 255 |
| 256 /** |
| 257 * @return Whether the current cursor position is at the end of the user typ
ed text (i.e. |
| 258 * at the beginning of the inline autocomplete text if present other
wise the very |
| 259 * end of the current text). |
| 260 */ |
| 261 public boolean isCursorAtEndOfTypedText() { |
| 262 final int selectionStart = getSelectionStart(); |
| 263 final int selectionEnd = getSelectionEnd(); |
| 264 |
| 265 int expectedSelectionStart = getText().getSpanStart(mAutocompleteSpan); |
| 266 int expectedSelectionEnd = getText().length(); |
| 267 if (expectedSelectionStart < 0) { |
| 268 expectedSelectionStart = expectedSelectionEnd; |
| 269 } |
| 270 |
| 271 return selectionStart == expectedSelectionStart && selectionEnd == expec
tedSelectionEnd; |
| 272 } |
| 273 |
| 274 /** |
| 275 * @return The user text without the autocomplete text. |
| 276 */ |
| 277 public String getTextWithoutAutocomplete() { |
| 278 int autoCompleteIndex = getText().getSpanStart(mAutocompleteSpan); |
| 279 if (autoCompleteIndex < 0) { |
| 280 return getQueryText(); |
| 281 } else { |
| 282 return getQueryText().substring(0, autoCompleteIndex); |
| 283 } |
| 284 } |
| 285 |
| 286 /** @return Whether any autocomplete information is specified on the current
text. */ |
| 287 @VisibleForTesting |
| 288 protected boolean hasAutocomplete() { |
| 289 return getText().getSpanStart(mAutocompleteSpan) >= 0 |
| 290 || mAutocompleteSpan.mAutocompleteText != null |
| 291 || mAutocompleteSpan.mUserText != null; |
| 292 } |
| 293 |
| 294 @Override |
| 295 protected void onSelectionChanged(int selStart, int selEnd) { |
| 296 int spanStart = getText().getSpanStart(mAutocompleteSpan); |
| 297 int spanEnd = getText().getSpanEnd(mAutocompleteSpan); |
| 298 if (spanStart >= 0 && (spanStart != selStart || spanEnd != selEnd)) { |
| 299 // On selection changes, the autocomplete text has been accepted by
the user or needs |
| 300 // to be deleted below. |
| 301 mAutocompleteSpan.clearSpan(); |
| 302 |
| 303 // The autocomplete text will be deleted any time the selection occu
rs entirely before |
| 304 // the start of the autocomplete text. This is required because cer
tain keyboards will |
| 305 // insert characters temporarily when starting a key entry gesture (
whether it be |
| 306 // swyping a word or long pressing to get a special character). Whe
n this temporary |
| 307 // character appears, Chrome may decide to append some autocomplete,
but the keyboard |
| 308 // will then remove this temporary character only while leaving the
autocomplete text |
| 309 // alone. See crbug/273763 for more details. |
| 310 if (selEnd <= spanStart) getText().delete(spanStart, getText().lengt
h()); |
| 311 } |
| 312 super.onSelectionChanged(selStart, selEnd); |
| 313 } |
| 314 |
| 315 @Override |
| 316 protected void onFocusChanged(boolean focused, int direction, Rect previousl
yFocusedRect) { |
| 317 mFocused = focused; |
| 318 mUrlBarDelegate.onUrlPreFocusChanged(focused); |
| 319 if (!focused) mAutocompleteSpan.clearSpan(); |
| 320 super.onFocusChanged(focused, direction, previouslyFocusedRect); |
| 321 |
| 322 if (focused && mFirstFocusTimeMs == 0) { |
| 323 mFirstFocusTimeMs = SystemClock.elapsedRealtime(); |
| 324 if (mOmniboxLivenessListener != null) mOmniboxLivenessListener.onOmn
iboxFocused(); |
| 325 } |
| 326 } |
| 327 |
| 328 /** |
| 329 * @return The elapsed realtime timestamp in ms of the first time the url ba
r was focused, |
| 330 * 0 if never. |
| 331 */ |
| 332 public long getFirstFocusTime() { |
| 333 return mFirstFocusTimeMs; |
| 334 } |
| 335 |
| 336 @Override |
| 337 protected void onWindowVisibilityChanged(int visibility) { |
| 338 super.onWindowVisibilityChanged(visibility); |
| 339 if (visibility == View.GONE && isFocused()) mShowKeyboardOnWindowFocus =
true; |
| 340 } |
| 341 |
| 342 @Override |
| 343 public void onWindowFocusChanged(boolean hasWindowFocus) { |
| 344 super.onWindowFocusChanged(hasWindowFocus); |
| 345 if (hasWindowFocus) { |
| 346 if (mShowKeyboardOnWindowFocus && isFocused()) { |
| 347 // Without the call to post(..), the keyboard was not getting sh
own when the |
| 348 // window regained focus despite this being the final call in th
e view system |
| 349 // flow. |
| 350 post(new Runnable() { |
| 351 @Override |
| 352 public void run() { |
| 353 UiUtils.showKeyboard(UrlBar.this); |
| 354 } |
| 355 }); |
| 356 } |
| 357 mShowKeyboardOnWindowFocus = false; |
| 358 } |
| 359 } |
| 360 |
| 361 @Override |
| 362 public View focusSearch(int direction) { |
| 363 if (direction == View.FOCUS_BACKWARD |
| 364 && mUrlBarDelegate.getCurrentTab().getView() != null) { |
| 365 return mUrlBarDelegate.getCurrentTab().getView(); |
| 366 } else { |
| 367 return super.focusSearch(direction); |
| 368 } |
| 369 } |
| 370 |
| 371 @Override |
| 372 public boolean onKeyPreIme(int keyCode, KeyEvent event) { |
| 373 if (keyCode == KeyEvent.KEYCODE_BACK) { |
| 374 if (event.getAction() == KeyEvent.ACTION_DOWN |
| 375 && event.getRepeatCount() == 0) { |
| 376 // Tell the framework to start tracking this event. |
| 377 getKeyDispatcherState().startTracking(event, this); |
| 378 return true; |
| 379 } else if (event.getAction() == KeyEvent.ACTION_UP) { |
| 380 getKeyDispatcherState().handleUpEvent(event); |
| 381 if (event.isTracking() && !event.isCanceled()) { |
| 382 mUrlBarDelegate.backKeyPressed(); |
| 383 return true; |
| 384 } |
| 385 } |
| 386 } |
| 387 return super.onKeyPreIme(keyCode, event); |
| 388 } |
| 389 |
| 390 @Override |
| 391 public boolean onTouchEvent(MotionEvent event) { |
| 392 ChromeTab currentTab = mUrlBarDelegate.getCurrentTab(); |
| 393 if (currentTab != null |
| 394 && currentTab.getBackgroundContentViewHelper() != null |
| 395 && mUrlBarDelegate.showingOriginalUrlForPreview()) { |
| 396 // When we are showing preview, we treat click on UrlBar as an event
to force swapping |
| 397 // of content views. |
| 398 currentTab.getBackgroundContentViewHelper().forceSwappingContentView
s(); |
| 399 } |
| 400 |
| 401 if (!mFocused) { |
| 402 mGestureDetector.onTouchEvent(event); |
| 403 return true; |
| 404 } |
| 405 |
| 406 if (event.getAction() == MotionEvent.ACTION_DOWN && currentTab != null)
{ |
| 407 // Make sure to hide the current ContentView ActionBar. |
| 408 ContentViewCore viewCore = currentTab.getContentViewCore(); |
| 409 if (viewCore != null) viewCore.hideSelectActionMode(); |
| 410 } |
| 411 |
| 412 return super.onTouchEvent(event); |
| 413 } |
| 414 |
| 415 @Override |
| 416 public boolean bringPointIntoView(int offset) { |
| 417 if (mDisableTextScrollingFromAutocomplete) return false; |
| 418 return super.bringPointIntoView(offset); |
| 419 } |
| 420 |
| 421 @Override |
| 422 public boolean onPreDraw() { |
| 423 boolean retVal = super.onPreDraw(); |
| 424 if (mDisableTextScrollingFromAutocomplete) { |
| 425 // super.onPreDraw will put the selection at the end of the text sel
ection, but |
| 426 // in the case of autocomplete we want the last typed character to b
e shown, which |
| 427 // is the start of selection. |
| 428 mDisableTextScrollingFromAutocomplete = false; |
| 429 bringPointIntoView(getSelectionStart()); |
| 430 retVal = true; |
| 431 } |
| 432 return retVal; |
| 433 } |
| 434 |
| 435 @Override |
| 436 public void onDraw(Canvas canvas) { |
| 437 super.onDraw(canvas); |
| 438 |
| 439 if (!mFirstDrawComplete) { |
| 440 mFirstDrawComplete = true; |
| 441 |
| 442 // We have now avoided the first draw problem (see the comment in |
| 443 // the constructor) so we want to make the URL bar focusable so that |
| 444 // touches etc. activate it. |
| 445 setFocusable(true); |
| 446 setFocusableInTouchMode(true); |
| 447 |
| 448 // The URL bar will now react correctly to a focus change event |
| 449 if (mOmniboxLivenessListener != null) mOmniboxLivenessListener.onOmn
iboxInteractive(); |
| 450 } |
| 451 |
| 452 // Notify listeners if the URL's direction has changed. |
| 453 updateUrlDirection(); |
| 454 } |
| 455 |
| 456 /** |
| 457 * If the direction of the URL has changed, update mUrlDirection and notify
the |
| 458 * UrlDirectionListeners. |
| 459 */ |
| 460 private void updateUrlDirection() { |
| 461 Layout layout = getLayout(); |
| 462 if (layout == null) return; |
| 463 |
| 464 int urlDirection; |
| 465 if (length() == 0) { |
| 466 urlDirection = LAYOUT_DIRECTION_LOCALE; |
| 467 } else if (layout.getParagraphDirection(0) == Layout.DIR_LEFT_TO_RIGHT)
{ |
| 468 urlDirection = LAYOUT_DIRECTION_LTR; |
| 469 } else { |
| 470 urlDirection = LAYOUT_DIRECTION_RTL; |
| 471 } |
| 472 |
| 473 if (urlDirection != mUrlDirection) { |
| 474 mUrlDirection = urlDirection; |
| 475 if (mUrlDirectionListener != null) { |
| 476 mUrlDirectionListener.onUrlDirectionChanged(urlDirection); |
| 477 } |
| 478 } |
| 479 } |
| 480 |
| 481 /** |
| 482 * @return The text direction of the URL, e.g. LAYOUT_DIRECTION_LTR. |
| 483 */ |
| 484 public int getUrlDirection() { |
| 485 return mUrlDirection; |
| 486 } |
| 487 |
| 488 /** |
| 489 * Sets the listener for changes in the url bar's layout direction. Also cal
ls |
| 490 * onUrlDirectionChanged() immediately on the listener. |
| 491 * |
| 492 * @param listener The UrlDirectionListener to receive callbacks when the ur
l direction changes, |
| 493 * or null to unregister any previously registered listener. |
| 494 */ |
| 495 public void setUrlDirectionListener(UrlDirectionListener listener) { |
| 496 mUrlDirectionListener = listener; |
| 497 if (mUrlDirectionListener != null) { |
| 498 mUrlDirectionListener.onUrlDirectionChanged(mUrlDirection); |
| 499 } |
| 500 } |
| 501 |
| 502 void setLocationBarTextWatcher(TextWatcher locationBarTextWatcher) { |
| 503 mLocationBarTextWatcher = locationBarTextWatcher; |
| 504 } |
| 505 |
| 506 /** |
| 507 * Set the url delegate to handle communication from the {@link UrlBar} to t
he rest of the UI. |
| 508 * @param delegate The {@link UrlBarDelegate} to be used. |
| 509 */ |
| 510 public void setDelegate(UrlBarDelegate delegate) { |
| 511 mUrlBarDelegate = delegate; |
| 512 } |
| 513 |
| 514 /** |
| 515 * Set {@link OmniboxLivenessListener} to be used for receiving interaction
related messages |
| 516 * during startup. |
| 517 * @param listener The listener to use for sending the messages. |
| 518 */ |
| 519 @VisibleForTesting |
| 520 public void setOmniboxLivenessListener(OmniboxLivenessListener listener) { |
| 521 mOmniboxLivenessListener = listener; |
| 522 } |
| 523 |
| 524 /** |
| 525 * Signal {@link OmniboxLivenessListener} that the omnibox is completely ope
rational now. |
| 526 */ |
| 527 @VisibleForTesting |
| 528 public void onOmniboxFullyFunctional() { |
| 529 if (mOmniboxLivenessListener != null) mOmniboxLivenessListener.onOmnibox
FullyFunctional(); |
| 530 } |
| 531 |
| 532 @Override |
| 533 public boolean onTextContextMenuItem(int id) { |
| 534 if (id == android.R.id.paste) { |
| 535 ClipboardManager clipboard = (ClipboardManager) getContext() |
| 536 .getSystemService(Context.CLIPBOARD_SERVICE); |
| 537 ClipData clipData = clipboard.getPrimaryClip(); |
| 538 if (clipData != null) { |
| 539 StringBuilder builder = new StringBuilder(); |
| 540 for (int i = 0; i < clipData.getItemCount(); i++) { |
| 541 builder.append(clipData.getItemAt(i).coerceToText(getContext
())); |
| 542 } |
| 543 String pasteString = OmniboxViewUtil.sanitizeTextForPaste(builde
r.toString()); |
| 544 |
| 545 int min = 0; |
| 546 int max = getText().length(); |
| 547 |
| 548 if (isFocused()) { |
| 549 final int selStart = getSelectionStart(); |
| 550 final int selEnd = getSelectionEnd(); |
| 551 |
| 552 min = Math.max(0, Math.min(selStart, selEnd)); |
| 553 max = Math.max(0, Math.max(selStart, selEnd)); |
| 554 } |
| 555 |
| 556 Selection.setSelection(getText(), max); |
| 557 getText().replace(min, max, pasteString); |
| 558 return true; |
| 559 } |
| 560 } |
| 561 |
| 562 if (mOriginalUrlLocation == null || mFormattedUrlLocation == null) { |
| 563 return super.onTextContextMenuItem(id); |
| 564 } |
| 565 |
| 566 int selectedStartIndex = getSelectionStart(); |
| 567 int selectedEndIndex = getSelectionEnd(); |
| 568 |
| 569 // If we are copying/cutting the full previously formatted URL, reset th
e URL |
| 570 // text before initiating the TextViews handling of the context menu. |
| 571 String currentText = getText().toString(); |
| 572 if (selectedStartIndex == 0 |
| 573 && (id == android.R.id.cut || id == android.R.id.copy) |
| 574 && currentText.startsWith(mFormattedUrlLocation) |
| 575 && selectedEndIndex >= mFormattedUrlLocation.length()) { |
| 576 String newText = mOriginalUrlLocation |
| 577 + currentText.substring(mFormattedUrlLocation.length()); |
| 578 selectedEndIndex = selectedEndIndex - mFormattedUrlLocation.length() |
| 579 + mOriginalUrlLocation.length(); |
| 580 mUrlBarDelegate.setIgnoreURLBarModification(true); |
| 581 setText(newText); |
| 582 setSelection(0, selectedEndIndex); |
| 583 boolean retVal = super.onTextContextMenuItem(id); |
| 584 if (getText().toString().equals(newText)) { |
| 585 setText(currentText); |
| 586 setSelection(getText().length()); |
| 587 } |
| 588 mUrlBarDelegate.setIgnoreURLBarModification(false); |
| 589 return retVal; |
| 590 } |
| 591 return super.onTextContextMenuItem(id); |
| 592 } |
| 593 |
| 594 /** |
| 595 * Sets the text content of the URL bar. |
| 596 * |
| 597 * @param url The original URL (or generic text) that can be used for copy/c
ut/paste. |
| 598 * @param formattedUrl Formatted URL for user display. Null if there isn't o
ne. |
| 599 * @return Whether the visible text has changed. |
| 600 */ |
| 601 public boolean setUrl(String url, String formattedUrl) { |
| 602 if (!TextUtils.isEmpty(formattedUrl)) { |
| 603 try { |
| 604 URL javaUrl = new URL(url); |
| 605 mFormattedUrlLocation = |
| 606 getUrlContentsPrePath(formattedUrl, javaUrl.getHost()); |
| 607 mOriginalUrlLocation = |
| 608 getUrlContentsPrePath(url, javaUrl.getHost()); |
| 609 } catch (MalformedURLException mue) { |
| 610 mOriginalUrlLocation = null; |
| 611 mFormattedUrlLocation = null; |
| 612 } |
| 613 } else { |
| 614 mOriginalUrlLocation = null; |
| 615 mFormattedUrlLocation = null; |
| 616 formattedUrl = url; |
| 617 } |
| 618 |
| 619 Editable previousText = getEditableText(); |
| 620 setText(formattedUrl); |
| 621 |
| 622 if (!isFocused()) scrollToTLD(); |
| 623 |
| 624 return !TextUtils.equals(previousText, getEditableText()); |
| 625 } |
| 626 |
| 627 /** |
| 628 * Autocompletes the text on the url bar and selects the text that was not e
ntered by the |
| 629 * user. Using append() instead of setText() to preserve the soft-keyboard l
ayout. |
| 630 * @param userText user The text entered by the user. |
| 631 * @param inlineAutocompleteText The suggested autocompletion for the user's
text. |
| 632 */ |
| 633 public void setAutocompleteText(CharSequence userText, CharSequence inlineAu
tocompleteText) { |
| 634 boolean emptyAutocomplete = TextUtils.isEmpty(inlineAutocompleteText); |
| 635 |
| 636 if (!emptyAutocomplete) mDisableTextScrollingFromAutocomplete = true; |
| 637 |
| 638 int autocompleteIndex = userText.length(); |
| 639 |
| 640 String previousText = getQueryText(); |
| 641 CharSequence newText = TextUtils.concat(userText, inlineAutocompleteText
); |
| 642 |
| 643 mUrlBarDelegate.setIgnoreURLBarModification(true); |
| 644 mDisableTextAccessibilityEvents = true; |
| 645 |
| 646 if (!TextUtils.equals(previousText, newText)) { |
| 647 // The previous text may also have included autocomplete text, so we
only |
| 648 // append the new autocomplete text that has changed. |
| 649 if (TextUtils.indexOf(newText, previousText) == 0) { |
| 650 append(newText.subSequence(previousText.length(), newText.length
())); |
| 651 } else { |
| 652 setUrl(newText.toString(), null); |
| 653 } |
| 654 } |
| 655 |
| 656 if (getSelectionStart() != autocompleteIndex |
| 657 || getSelectionEnd() != getText().length()) { |
| 658 setSelection(autocompleteIndex, getText().length()); |
| 659 |
| 660 if (inlineAutocompleteText.length() != 0) { |
| 661 // Sending a TYPE_VIEW_TEXT_SELECTION_CHANGED accessibility even
t causes the |
| 662 // previous TYPE_VIEW_TEXT_CHANGED event to be swallowed. As a r
esult the user |
| 663 // hears the autocomplete text but *not* the text they typed. In
stead we send a |
| 664 // TYPE_ANNOUNCEMENT event, which doesn't swallow the text-chang
ed event. |
| 665 announceForAccessibility(inlineAutocompleteText); |
| 666 } |
| 667 } |
| 668 |
| 669 if (emptyAutocomplete) { |
| 670 mAutocompleteSpan.clearSpan(); |
| 671 } else { |
| 672 mAutocompleteSpan.setSpan(userText, inlineAutocompleteText); |
| 673 } |
| 674 |
| 675 mUrlBarDelegate.setIgnoreURLBarModification(false); |
| 676 mDisableTextAccessibilityEvents = false; |
| 677 } |
| 678 |
| 679 /** |
| 680 * Overrides the text announced when focusing on the field for accessibility
. This value will |
| 681 * be cleared automatically when the text content changes for this view. |
| 682 * @param accessibilityOverride The text to be announced instead of the curr
ent text value |
| 683 * (or null if the text content should be read)
. |
| 684 */ |
| 685 public void setAccessibilityTextOverride(String accessibilityOverride) { |
| 686 mAccessibilityTextOverride = accessibilityOverride; |
| 687 } |
| 688 |
| 689 private void scrollToTLD() { |
| 690 Editable url = getText(); |
| 691 if (url == null || url.length() < 1) return; |
| 692 String urlString = url.toString(); |
| 693 URL javaUrl; |
| 694 try { |
| 695 javaUrl = new URL(urlString); |
| 696 } catch (MalformedURLException mue) { |
| 697 return; |
| 698 } |
| 699 String host = javaUrl.getHost(); |
| 700 if (host == null || host.isEmpty()) return; |
| 701 int hostStart = urlString.indexOf(host); |
| 702 int hostEnd = hostStart + host.length(); |
| 703 setSelection(hostEnd); |
| 704 } |
| 705 |
| 706 @Override |
| 707 public void setText(CharSequence text, BufferType type) { |
| 708 mDisableTextScrollingFromAutocomplete = false; |
| 709 |
| 710 // Avoid setting the same text to the URL bar as it will mess up the scr
oll/cursor |
| 711 // position. |
| 712 // Setting the text is also quite expensive, so only do it when the text
has changed |
| 713 // (since we apply spans when the URL is not focused, we only optimize t
his when the |
| 714 // URL is being edited). |
| 715 if (!TextUtils.equals(getEditableText(), text)) { |
| 716 super.setText(text, type); |
| 717 mAccessibilityTextOverride = null; |
| 718 } |
| 719 |
| 720 // Verify the autocomplete is still valid after the text change. |
| 721 if (mAutocompleteSpan != null |
| 722 && mAutocompleteSpan.mUserText != null |
| 723 && mAutocompleteSpan.mAutocompleteText != null) { |
| 724 if (getText().getSpanStart(mAutocompleteSpan) < 0) { |
| 725 mAutocompleteSpan.clearSpan(); |
| 726 } else { |
| 727 Editable editableText = getEditableText(); |
| 728 CharSequence previousUserText = mAutocompleteSpan.mUserText; |
| 729 CharSequence previousAutocompleteText = mAutocompleteSpan.mAutoc
ompleteText; |
| 730 if (editableText.length() |
| 731 < (previousUserText.length() + previousAutocompleteText.
length())) { |
| 732 mAutocompleteSpan.clearSpan(); |
| 733 } else if (TextUtils.indexOf(getText(), previousUserText) != 0 |
| 734 || TextUtils.indexOf(getText(), previousAutocompleteText
) |
| 735 != previousUserText.length()) { |
| 736 mAutocompleteSpan.clearSpan(); |
| 737 } |
| 738 } |
| 739 } |
| 740 } |
| 741 |
| 742 /** |
| 743 * Returns the portion of the URL that precedes the path/query section of th
e URL. |
| 744 * |
| 745 * @param url The url to be used to find the preceding portion. |
| 746 * @param host The host to be located in the URL to determine the location o
f the path. |
| 747 * @return The URL contents that precede the path (or the passed in URL if t
he host is |
| 748 * not found). |
| 749 */ |
| 750 private static String getUrlContentsPrePath(String url, String host) { |
| 751 String urlPrePath = url; |
| 752 int hostIndex = url.indexOf(host); |
| 753 if (hostIndex >= 0) { |
| 754 int pathIndex = url.indexOf('/', hostIndex); |
| 755 if (pathIndex > 0) { |
| 756 urlPrePath = url.substring(0, pathIndex); |
| 757 } else { |
| 758 urlPrePath = url; |
| 759 } |
| 760 } |
| 761 return urlPrePath; |
| 762 } |
| 763 |
| 764 @Override |
| 765 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { |
| 766 if (mDisableTextAccessibilityEvents) { |
| 767 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECT
ION_CHANGED |
| 768 || event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT
_CHANGED) { |
| 769 return; |
| 770 } |
| 771 } |
| 772 super.sendAccessibilityEventUnchecked(event); |
| 773 } |
| 774 |
| 775 @Override |
| 776 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { |
| 777 super.onInitializeAccessibilityNodeInfo(info); |
| 778 |
| 779 if (mAccessibilityTextOverride != null) { |
| 780 info.setText(mAccessibilityTextOverride); |
| 781 } |
| 782 } |
| 783 |
| 784 InputConnectionWrapper mInputConnection = new InputConnectionWrapper(null, t
rue) { |
| 785 private final char[] mTempSelectionChar = new char[1]; |
| 786 |
| 787 @Override |
| 788 public boolean commitText(CharSequence text, int newCursorPosition) { |
| 789 Editable currentText = getText(); |
| 790 if (currentText == null) return super.commitText(text, newCursorPosi
tion); |
| 791 |
| 792 int selectionStart = Selection.getSelectionStart(currentText); |
| 793 int selectionEnd = Selection.getSelectionEnd(currentText); |
| 794 int autocompleteIndex = currentText.getSpanStart(mAutocompleteSpan); |
| 795 // If the text being committed is a single character that matches th
e next character |
| 796 // in the selection (assumed to be the autocomplete text), we only m
ove the text |
| 797 // selection instead clearing the autocomplete text causing flickeri
ng as the |
| 798 // autocomplete text will appear once the next suggestions are recei
ved. |
| 799 // |
| 800 // To be confident that the selection is an autocomplete, we ensure
the selection |
| 801 // is at least one character and the end of the selection is the end
of the |
| 802 // currently entered text. |
| 803 if (newCursorPosition == 1 && selectionStart > 0 && selectionStart !
= selectionEnd |
| 804 && selectionEnd >= currentText.length() |
| 805 && autocompleteIndex == selectionStart |
| 806 && text.length() == 1) { |
| 807 currentText.getChars(selectionStart, selectionStart + 1, mTempSe
lectionChar, 0); |
| 808 if (mTempSelectionChar[0] == text.charAt(0)) { |
| 809 |
| 810 // Since the text isn't changing, TalkBack won't read out th
e typed characters. |
| 811 // To work around this, explicitly send an accessibility eve
nt. crbug.com/416595 |
| 812 if (mAccessibilityManager != null && mAccessibilityManager.i
sEnabled()) { |
| 813 AccessibilityEvent event = AccessibilityEvent.obtain( |
| 814 AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); |
| 815 event.setFromIndex(selectionStart); |
| 816 event.setRemovedCount(0); |
| 817 event.setAddedCount(1); |
| 818 event.setBeforeText(currentText.toString().substring(0,
selectionStart)); |
| 819 sendAccessibilityEventUnchecked(event); |
| 820 } |
| 821 |
| 822 if (mLocationBarTextWatcher != null) { |
| 823 mLocationBarTextWatcher.beforeTextChanged(currentText, 0
, 0, 0); |
| 824 } |
| 825 setAutocompleteText( |
| 826 currentText.subSequence(0, selectionStart + 1), |
| 827 currentText.subSequence(selectionStart + 1, selectio
nEnd)); |
| 828 if (mLocationBarTextWatcher != null) { |
| 829 mLocationBarTextWatcher.afterTextChanged(currentText); |
| 830 } |
| 831 return true; |
| 832 } |
| 833 } |
| 834 return super.commitText(text, newCursorPosition); |
| 835 } |
| 836 |
| 837 @Override |
| 838 public boolean setComposingText(CharSequence text, int newCursorPosition
) { |
| 839 Editable currentText = getText(); |
| 840 int autoCompleteSpanStart = currentText.getSpanStart(mAutocompleteSp
an); |
| 841 if (autoCompleteSpanStart >= 0) { |
| 842 int composingEnd = BaseInputConnection.getComposingSpanEnd(curre
ntText); |
| 843 |
| 844 // On certain device/keyboard combinations, the composing region
s are specified |
| 845 // with a noticeable delay after the initial character is typed,
and in certain |
| 846 // circumstances it does not check that the current state of the
text matches the |
| 847 // expectations of it's composing region. |
| 848 // For example, you can be typing: |
| 849 // chrome://f |
| 850 // Chrome will autocomplete to: |
| 851 // chrome://f[lags] |
| 852 // And after the autocomplete has been set, the keyboard will se
t the composing |
| 853 // region to the last character and it assumes it is 'f' as it w
as the last |
| 854 // character the keyboard sent. If we commit this composition,
the text will |
| 855 // look like: |
| 856 // chrome://flag[f] |
| 857 // And if we use the autocomplete clearing logic below, it will
look like: |
| 858 // chrome://f[f] |
| 859 // To work around this, we see if the composition matches all th
e characters prior |
| 860 // to the autocomplete and just readjust the composing region to
be that subset. |
| 861 // |
| 862 // See crbug.com/366732 |
| 863 if (composingEnd == currentText.length() |
| 864 && autoCompleteSpanStart >= text.length() |
| 865 && TextUtils.equals( |
| 866 currentText.subSequence( |
| 867 autoCompleteSpanStart - text.length(), |
| 868 autoCompleteSpanStart), |
| 869 text)) { |
| 870 setComposingRegion( |
| 871 autoCompleteSpanStart - text.length(), autoCompleteS
panStart); |
| 872 } |
| 873 |
| 874 // Once composing text is being modified, the autocomplete text
has been accepted |
| 875 // or has to be deleted. |
| 876 mAutocompleteSpan.clearSpan(); |
| 877 Selection.setSelection(currentText, autoCompleteSpanStart); |
| 878 currentText.delete(autoCompleteSpanStart, currentText.length()); |
| 879 } |
| 880 return super.setComposingText(text, newCursorPosition); |
| 881 } |
| 882 }; |
| 883 |
| 884 @Override |
| 885 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { |
| 886 mInputConnection.setTarget(super.onCreateInputConnection(outAttrs)); |
| 887 return mInputConnection; |
| 888 } |
| 889 |
| 890 /** |
| 891 * Emphasize the TLD and second domain of the URL. |
| 892 */ |
| 893 public void emphasizeUrl() { |
| 894 Editable url = getText(); |
| 895 if (OmniboxUrlEmphasizer.hasEmphasisSpans(url) || hasFocus()) { |
| 896 return; |
| 897 } |
| 898 |
| 899 if (url.length() < 1) { |
| 900 return; |
| 901 } |
| 902 |
| 903 if (mUrlBarDelegate.showingOriginalUrlForPreview()) { |
| 904 // We will make the whole url as greyed out(Tailing url color). This
is the UI |
| 905 // treatment we show to indicate that we are showing original url fo
r preview page. |
| 906 OmniboxUrlEmphasizer.greyOutUrl(url, getResources(), mUseDarkColors)
; |
| 907 return; |
| 908 } |
| 909 |
| 910 // We retrieve the domain and registry from the full URL (the url bar sh
ows a simplified |
| 911 // version of the URL). |
| 912 ChromeTab currentTab = mUrlBarDelegate.getCurrentTab(); |
| 913 if (currentTab == null || currentTab.getProfile() == null) return; |
| 914 |
| 915 boolean isInternalPage = false; |
| 916 try { |
| 917 String tabUrl = currentTab.getUrl(); |
| 918 isInternalPage = UrlUtilities.isInternalScheme(new URI(tabUrl)); |
| 919 } catch (URISyntaxException e) { |
| 920 // Ignore as this only is for applying color |
| 921 } |
| 922 |
| 923 OmniboxUrlEmphasizer.emphasizeUrl(url, getResources(), currentTab.getPro
file(), |
| 924 currentTab.getSecurityLevel(), isInternalPage, |
| 925 mUseDarkColors, mUrlBarDelegate.shouldEmphasizeHttpsScheme()); |
| 926 } |
| 927 |
| 928 /** |
| 929 * Reset the modifications done to emphasize the TLD and second domain of th
e URL. |
| 930 */ |
| 931 public void deEmphasizeUrl() { |
| 932 OmniboxUrlEmphasizer.deEmphasizeUrl(getText()); |
| 933 } |
| 934 |
| 935 /** |
| 936 * Simple span used for tracking the current autocomplete state. |
| 937 */ |
| 938 private class AutocompleteSpan { |
| 939 private CharSequence mUserText; |
| 940 private CharSequence mAutocompleteText; |
| 941 |
| 942 /** |
| 943 * Adds the span to the current text. |
| 944 * @param userText The user entered text. |
| 945 * @param autocompleteText The autocomplete text being appended. |
| 946 */ |
| 947 public void setSpan(CharSequence userText, CharSequence autocompleteText
) { |
| 948 Editable text = getText(); |
| 949 text.removeSpan(this); |
| 950 mAutocompleteText = autocompleteText; |
| 951 mUserText = userText; |
| 952 text.setSpan( |
| 953 this, |
| 954 userText.length(), |
| 955 text.length(), |
| 956 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); |
| 957 } |
| 958 |
| 959 /** Removes this span from the current text and clears the internal stat
e. */ |
| 960 public void clearSpan() { |
| 961 getText().removeSpan(this); |
| 962 mAutocompleteText = null; |
| 963 mUserText = null; |
| 964 } |
| 965 } |
| 966 } |
OLD | NEW |