WebTextView.java revision 72543e1bc386f23f5d8f3f21068887ec16408fef
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.webkit; 18 19import android.content.Context; 20import android.graphics.Canvas; 21import android.graphics.Color; 22import android.graphics.ColorFilter; 23import android.graphics.Paint; 24import android.graphics.PixelFormat; 25import android.graphics.Rect; 26import android.graphics.drawable.Drawable; 27import android.text.Editable; 28import android.text.InputFilter; 29import android.text.Selection; 30import android.text.Spannable; 31import android.text.TextPaint; 32import android.text.TextUtils; 33import android.text.method.MovementMethod; 34import android.text.method.Touch; 35import android.util.Log; 36import android.view.Gravity; 37import android.view.KeyCharacterMap; 38import android.view.KeyEvent; 39import android.view.MotionEvent; 40import android.view.View; 41import android.view.ViewGroup; 42import android.view.inputmethod.EditorInfo; 43import android.view.inputmethod.InputMethodManager; 44import android.view.inputmethod.InputConnection; 45import android.widget.AbsoluteLayout.LayoutParams; 46import android.widget.ArrayAdapter; 47import android.widget.AutoCompleteTextView; 48import android.widget.TextView; 49 50import java.util.ArrayList; 51 52/** 53 * WebTextView is a specialized version of EditText used by WebView 54 * to overlay html textfields (and textareas) to use our standard 55 * text editing. 56 */ 57/* package */ class WebTextView extends AutoCompleteTextView { 58 59 static final String LOGTAG = "webtextview"; 60 61 private WebView mWebView; 62 private boolean mSingle; 63 private int mWidthSpec; 64 private int mHeightSpec; 65 private int mNodePointer; 66 // FIXME: This is a hack for blocking unmatched key ups, in particular 67 // on the enter key. The method for blocking unmatched key ups prevents 68 // the shift key from working properly. 69 private boolean mGotEnterDown; 70 private int mMaxLength; 71 // Keep track of the text before the change so we know whether we actually 72 // need to send down the DOM events. 73 private String mPreChange; 74 private Drawable mBackground; 75 // Variables for keeping track of the touch down, to send to the WebView 76 // when a drag starts 77 private float mDragStartX; 78 private float mDragStartY; 79 private long mDragStartTime; 80 private boolean mDragSent; 81 // True if the most recent drag event has caused either the TextView to 82 // scroll or the web page to scroll. Gets reset after a touch down. 83 private boolean mScrolled; 84 // Array to store the final character added in onTextChanged, so that its 85 // KeyEvents may be determined. 86 private char[] mCharacter = new char[1]; 87 // This is used to reset the length filter when on a textfield 88 // with no max length. 89 // FIXME: This can be replaced with TextView.NO_FILTERS if that 90 // is made public/protected. 91 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 92 93 /** 94 * Create a new WebTextView. 95 * @param context The Context for this WebTextView. 96 * @param webView The WebView that created this. 97 */ 98 /* package */ WebTextView(Context context, WebView webView) { 99 super(context); 100 mWebView = webView; 101 mMaxLength = -1; 102 setImeOptions(EditorInfo.IME_ACTION_NONE); 103 } 104 105 @Override 106 public boolean dispatchKeyEvent(KeyEvent event) { 107 if (event.isSystem()) { 108 return super.dispatchKeyEvent(event); 109 } 110 // Treat ACTION_DOWN and ACTION MULTIPLE the same 111 boolean down = event.getAction() != KeyEvent.ACTION_UP; 112 int keyCode = event.getKeyCode(); 113 114 boolean isArrowKey = false; 115 switch(keyCode) { 116 case KeyEvent.KEYCODE_DPAD_LEFT: 117 case KeyEvent.KEYCODE_DPAD_RIGHT: 118 case KeyEvent.KEYCODE_DPAD_UP: 119 case KeyEvent.KEYCODE_DPAD_DOWN: 120 if (!mWebView.nativeCursorMatchesFocus()) { 121 return down ? mWebView.onKeyDown(keyCode, event) : mWebView 122 .onKeyUp(keyCode, event); 123 124 } 125 isArrowKey = true; 126 break; 127 } 128 129 if (!isArrowKey && mWebView.nativeFocusNodePointer() != mNodePointer) { 130 mWebView.nativeClearCursor(); 131 // Do not call remove() here, which hides the soft keyboard. If 132 // the soft keyboard is being displayed, the user will still want 133 // it there. 134 mWebView.removeView(this); 135 mWebView.requestFocus(); 136 return mWebView.dispatchKeyEvent(event); 137 } 138 139 Spannable text = (Spannable) getText(); 140 int oldLength = text.length(); 141 // Normally the delete key's dom events are sent via onTextChanged. 142 // However, if the length is zero, the text did not change, so we 143 // go ahead and pass the key down immediately. 144 if (KeyEvent.KEYCODE_DEL == keyCode && 0 == oldLength) { 145 sendDomEvent(event); 146 return true; 147 } 148 149 if ((mSingle && KeyEvent.KEYCODE_ENTER == keyCode)) { 150 if (isPopupShowing()) { 151 return super.dispatchKeyEvent(event); 152 } 153 if (!down) { 154 // Hide the keyboard, since the user has just submitted this 155 // form. The submission happens thanks to the two calls 156 // to sendDomEvent. 157 InputMethodManager.getInstance(mContext) 158 .hideSoftInputFromWindow(getWindowToken(), 0); 159 sendDomEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode)); 160 sendDomEvent(event); 161 } 162 return super.dispatchKeyEvent(event); 163 } else if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) { 164 // Note that this handles center key and trackball. 165 if (isPopupShowing()) { 166 return super.dispatchKeyEvent(event); 167 } 168 if (!mWebView.nativeCursorMatchesFocus()) { 169 return down ? mWebView.onKeyDown(keyCode, event) : mWebView 170 .onKeyUp(keyCode, event); 171 } 172 // Center key should be passed to a potential onClick 173 if (!down) { 174 mWebView.shortPressOnTextField(); 175 } 176 // Pass to super to handle longpress. 177 return super.dispatchKeyEvent(event); 178 } 179 180 // Ensure there is a layout so arrow keys are handled properly. 181 if (getLayout() == null) { 182 measure(mWidthSpec, mHeightSpec); 183 } 184 int oldStart = Selection.getSelectionStart(text); 185 int oldEnd = Selection.getSelectionEnd(text); 186 187 boolean maxedOut = mMaxLength != -1 && oldLength == mMaxLength; 188 // If we are at max length, and there is a selection rather than a 189 // cursor, we need to store the text to compare later, since the key 190 // may have changed the string. 191 String oldText; 192 if (maxedOut && oldEnd != oldStart) { 193 oldText = text.toString(); 194 } else { 195 oldText = ""; 196 } 197 if (super.dispatchKeyEvent(event)) { 198 // If the WebTextView handled the key it was either an alphanumeric 199 // key, a delete, or a movement within the text. All of those are 200 // ok to pass to javascript. 201 202 // UNLESS there is a max length determined by the html. In that 203 // case, if the string was already at the max length, an 204 // alphanumeric key will be erased by the LengthFilter, 205 // so do not pass down to javascript, and instead 206 // return true. If it is an arrow key or a delete key, we can go 207 // ahead and pass it down. 208 if (KeyEvent.KEYCODE_ENTER == keyCode) { 209 // For multi-line text boxes, newlines will 210 // trigger onTextChanged for key down (which will send both 211 // key up and key down) but not key up. 212 mGotEnterDown = true; 213 } 214 if (maxedOut && !isArrowKey && keyCode != KeyEvent.KEYCODE_DEL) { 215 if (oldEnd == oldStart) { 216 // Return true so the key gets dropped. 217 return true; 218 } else if (!oldText.equals(getText().toString())) { 219 // FIXME: This makes the text work properly, but it 220 // does not pass down the key event, so it may not 221 // work for a textfield that has the type of 222 // behavior of GoogleSuggest. That said, it is 223 // unlikely that a site would combine the two in 224 // one textfield. 225 Spannable span = (Spannable) getText(); 226 int newStart = Selection.getSelectionStart(span); 227 int newEnd = Selection.getSelectionEnd(span); 228 mWebView.replaceTextfieldText(0, oldLength, span.toString(), 229 newStart, newEnd); 230 return true; 231 } 232 } 233 /* FIXME: 234 * In theory, we would like to send the events for the arrow keys. 235 * However, the TextView can arbitrarily change the selection (i.e. 236 * long press followed by using the trackball). Therefore, we keep 237 * in sync with the TextView via onSelectionChanged. If we also 238 * send the DOM event, we lose the correct selection. 239 if (isArrowKey) { 240 // Arrow key does not change the text, but we still want to send 241 // the DOM events. 242 sendDomEvent(event); 243 } 244 */ 245 return true; 246 } 247 // Ignore the key up event for newlines. This prevents 248 // multiple newlines in the native textarea. 249 if (mGotEnterDown && !down) { 250 return true; 251 } 252 // if it is a navigation key, pass it to WebView 253 if (isArrowKey) { 254 // WebView check the trackballtime in onKeyDown to avoid calling 255 // native from both trackball and key handling. As this is called 256 // from WebTextView, we always want WebView to check with native. 257 // Reset trackballtime to ensure it. 258 mWebView.resetTrackballTime(); 259 return down ? mWebView.onKeyDown(keyCode, event) : mWebView 260 .onKeyUp(keyCode, event); 261 } 262 return false; 263 } 264 265 /** 266 * Create a fake touch up event at (x,y) with respect to this WebTextView. 267 * This is used by WebView to act as though a touch event which happened 268 * before we placed the WebTextView actually hit it, so that it can place 269 * the cursor accordingly. 270 */ 271 /* package */ void fakeTouchEvent(float x, float y) { 272 // We need to ensure that there is a Layout, since the Layout is used 273 // in determining where to place the cursor. 274 if (getLayout() == null) { 275 measure(mWidthSpec, mHeightSpec); 276 } 277 // Create a fake touch up, which is used to place the cursor. 278 MotionEvent ev = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 279 x, y, 0); 280 onTouchEvent(ev); 281 ev.recycle(); 282 } 283 284 /** 285 * Determine whether this WebTextView currently represents the node 286 * represented by ptr. 287 * @param ptr Pointer to a node to compare to. 288 * @return boolean Whether this WebTextView already represents the node 289 * pointed to by ptr. 290 */ 291 /* package */ boolean isSameTextField(int ptr) { 292 return ptr == mNodePointer; 293 } 294 295 @Override public InputConnection onCreateInputConnection( 296 EditorInfo outAttrs) { 297 InputConnection connection = super.onCreateInputConnection(outAttrs); 298 if (mWebView != null) { 299 // Use the name of the textfield + the url. Use backslash as an 300 // arbitrary separator. 301 outAttrs.fieldName = mWebView.nativeFocusCandidateName() + "\\" 302 + mWebView.getUrl(); 303 } 304 return connection; 305 } 306 307 @Override 308 protected void onSelectionChanged(int selStart, int selEnd) { 309 if (mWebView != null) { 310 if (DebugFlags.WEB_TEXT_VIEW) { 311 Log.v(LOGTAG, "onSelectionChanged selStart=" + selStart 312 + " selEnd=" + selEnd); 313 } 314 mWebView.setSelection(selStart, selEnd); 315 } 316 } 317 318 @Override 319 protected void onTextChanged(CharSequence s,int start,int before,int count){ 320 super.onTextChanged(s, start, before, count); 321 String postChange = s.toString(); 322 // Prevent calls to setText from invoking onTextChanged (since this will 323 // mean we are on a different textfield). Also prevent the change when 324 // going from a textfield with a string of text to one with a smaller 325 // limit on text length from registering the onTextChanged event. 326 if (mPreChange == null || mPreChange.equals(postChange) || 327 (mMaxLength > -1 && mPreChange.length() > mMaxLength && 328 mPreChange.substring(0, mMaxLength).equals(postChange))) { 329 return; 330 } 331 mPreChange = postChange; 332 // This was simply a delete or a cut, so just delete the selection. 333 if (before > 0 && 0 == count) { 334 mWebView.deleteSelection(start, start + before); 335 // For this and all changes to the text, update our cache 336 updateCachedTextfield(); 337 return; 338 } 339 // Find the last character being replaced. If it can be represented by 340 // events, we will pass them to native (after replacing the beginning 341 // of the changed text), so we can see javascript events. 342 // Otherwise, replace the text being changed (including the last 343 // character) in the textfield. 344 TextUtils.getChars(s, start + count - 1, start + count, mCharacter, 0); 345 KeyCharacterMap kmap = 346 KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD); 347 KeyEvent[] events = kmap.getEvents(mCharacter); 348 boolean cannotUseKeyEvents = null == events; 349 int charactersFromKeyEvents = cannotUseKeyEvents ? 0 : 1; 350 if (count > 1 || cannotUseKeyEvents) { 351 String replace = s.subSequence(start, 352 start + count - charactersFromKeyEvents).toString(); 353 mWebView.replaceTextfieldText(start, start + before, replace, 354 start + count - charactersFromKeyEvents, 355 start + count - charactersFromKeyEvents); 356 } else { 357 // This corrects the selection which may have been affected by the 358 // trackball or auto-correct. 359 if (DebugFlags.WEB_TEXT_VIEW) { 360 Log.v(LOGTAG, "onTextChanged start=" + start 361 + " start + before=" + (start + before)); 362 } 363 mWebView.setSelection(start, start + before); 364 } 365 if (!cannotUseKeyEvents) { 366 int length = events.length; 367 for (int i = 0; i < length; i++) { 368 // We never send modifier keys to native code so don't send them 369 // here either. 370 if (!KeyEvent.isModifierKey(events[i].getKeyCode())) { 371 sendDomEvent(events[i]); 372 } 373 } 374 } 375 updateCachedTextfield(); 376 } 377 378 @Override 379 public boolean onTouchEvent(MotionEvent event) { 380 switch (event.getAction()) { 381 case MotionEvent.ACTION_DOWN: 382 super.onTouchEvent(event); 383 // This event may be the start of a drag, so store it to pass to the 384 // WebView if it is. 385 mDragStartX = event.getX(); 386 mDragStartY = event.getY(); 387 mDragStartTime = event.getEventTime(); 388 mDragSent = false; 389 mScrolled = false; 390 break; 391 case MotionEvent.ACTION_MOVE: 392 Spannable buffer = getText(); 393 int initialScrollX = Touch.getInitialScrollX(this, buffer); 394 int initialScrollY = Touch.getInitialScrollY(this, buffer); 395 super.onTouchEvent(event); 396 if (mScrollX != initialScrollX 397 || mScrollY != initialScrollY) { 398 if (mWebView != null) { 399 mWebView.scrollFocusedTextInput(mScrollX, mScrollY); 400 } 401 mScrolled = true; 402 return true; 403 } 404 if (mWebView != null) { 405 // Only want to set the initial state once. 406 if (!mDragSent) { 407 mWebView.initiateTextFieldDrag(mDragStartX, mDragStartY, 408 mDragStartTime); 409 mDragSent = true; 410 } 411 boolean scrolled = mWebView.textFieldDrag(event); 412 if (scrolled) { 413 mScrolled = true; 414 cancelLongPress(); 415 return true; 416 } 417 } 418 return false; 419 case MotionEvent.ACTION_UP: 420 case MotionEvent.ACTION_CANCEL: 421 if (!mScrolled) { 422 // If the page scrolled, or the TextView scrolled, we do not 423 // want to change the selection, and the long press has already 424 // been canceled, so there is no need to call into super. 425 super.onTouchEvent(event); 426 } 427 // Necessary for the WebView to reset its state 428 if (mWebView != null && mDragSent) { 429 mWebView.onTouchEvent(event); 430 } 431 break; 432 default: 433 break; 434 } 435 return true; 436 } 437 438 @Override 439 public boolean onTrackballEvent(MotionEvent event) { 440 if (isPopupShowing()) { 441 return super.onTrackballEvent(event); 442 } 443 if (event.getAction() != MotionEvent.ACTION_MOVE) { 444 return false; 445 } 446 // If the Cursor is not on the text input, webview should handle the 447 // trackball 448 if (!mWebView.nativeCursorMatchesFocus()) { 449 return mWebView.onTrackballEvent(event); 450 } 451 Spannable text = (Spannable) getText(); 452 MovementMethod move = getMovementMethod(); 453 if (move != null && getLayout() != null && 454 move.onTrackballEvent(this, text, event)) { 455 // Selection is changed in onSelectionChanged 456 return true; 457 } 458 return false; 459 } 460 461 /** 462 * Remove this WebTextView from its host WebView, and return 463 * focus to the host. 464 */ 465 /* package */ void remove() { 466 // hide the soft keyboard when the edit text is out of focus 467 InputMethodManager.getInstance(mContext).hideSoftInputFromWindow( 468 getWindowToken(), 0); 469 mWebView.removeView(this); 470 mWebView.requestFocus(); 471 } 472 473 /* package */ void bringIntoView() { 474 if (getLayout() != null) { 475 bringPointIntoView(Selection.getSelectionEnd(getText())); 476 } 477 } 478 479 /** 480 * Send the DOM events for the specified event. 481 * @param event KeyEvent to be translated into a DOM event. 482 */ 483 private void sendDomEvent(KeyEvent event) { 484 mWebView.passToJavaScript(getText().toString(), event); 485 } 486 487 /** 488 * Always use this instead of setAdapter, as this has features specific to 489 * the WebTextView. 490 */ 491 public void setAdapterCustom(AutoCompleteAdapter adapter) { 492 if (adapter != null) { 493 setInputType(EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE); 494 adapter.setTextView(this); 495 } 496 super.setAdapter(adapter); 497 } 498 499 /** 500 * This is a special version of ArrayAdapter which changes its text size 501 * to match the text size of its host TextView. 502 */ 503 public static class AutoCompleteAdapter extends ArrayAdapter<String> { 504 private TextView mTextView; 505 506 public AutoCompleteAdapter(Context context, ArrayList<String> entries) { 507 super(context, com.android.internal.R.layout 508 .search_dropdown_item_1line, entries); 509 } 510 511 /** 512 * {@inheritDoc} 513 */ 514 public View getView(int position, View convertView, ViewGroup parent) { 515 TextView tv = 516 (TextView) super.getView(position, convertView, parent); 517 if (tv != null && mTextView != null) { 518 tv.setTextSize(mTextView.getTextSize()); 519 } 520 return tv; 521 } 522 523 /** 524 * Set the TextView so we can match its text size. 525 */ 526 private void setTextView(TextView tv) { 527 mTextView = tv; 528 } 529 } 530 531 /** 532 * Determine whether to use the system-wide password disguising method, 533 * or to use none. 534 * @param inPassword True if the textfield is a password field. 535 */ 536 /* package */ void setInPassword(boolean inPassword) { 537 if (inPassword) { 538 setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo. 539 TYPE_TEXT_VARIATION_PASSWORD); 540 createBackground(); 541 } 542 // For password fields, draw the WebTextView. For others, just show 543 // webkit's drawing. 544 setWillNotDraw(!inPassword); 545 setBackgroundDrawable(inPassword ? mBackground : null); 546 // For non-password fields, avoid the invals from TextView's blinking 547 // cursor 548 setCursorVisible(inPassword); 549 } 550 551 /** 552 * Private class used for the background of a password textfield. 553 */ 554 private static class OutlineDrawable extends Drawable { 555 public void draw(Canvas canvas) { 556 Rect bounds = getBounds(); 557 Paint paint = new Paint(); 558 paint.setAntiAlias(true); 559 // Draw the background. 560 paint.setColor(Color.WHITE); 561 canvas.drawRect(bounds, paint); 562 // Draw the outline. 563 paint.setStyle(Paint.Style.STROKE); 564 paint.setColor(Color.BLACK); 565 canvas.drawRect(bounds, paint); 566 } 567 // Always want it to be opaque. 568 public int getOpacity() { 569 return PixelFormat.OPAQUE; 570 } 571 // These are needed because they are abstract in Drawable. 572 public void setAlpha(int alpha) { } 573 public void setColorFilter(ColorFilter cf) { } 574 } 575 576 /** 577 * Create a background for the WebTextView and set up the paint for drawing 578 * the text. This way, we can see the password transformation of the 579 * system, which (optionally) shows the actual text before changing to dots. 580 * The background is necessary to hide the webkit-drawn text beneath. 581 */ 582 private void createBackground() { 583 if (mBackground != null) { 584 return; 585 } 586 mBackground = new OutlineDrawable(); 587 588 setGravity(Gravity.CENTER_VERTICAL); 589 // Turn on subpixel text, and turn off kerning, so it better matches 590 // the text in webkit. 591 TextPaint paint = getPaint(); 592 int flags = paint.getFlags() | Paint.SUBPIXEL_TEXT_FLAG | 593 Paint.ANTI_ALIAS_FLAG & ~Paint.DEV_KERN_TEXT_FLAG; 594 paint.setFlags(flags); 595 // Set the text color to black, regardless of the theme. This ensures 596 // that other applications that use embedded WebViews will properly 597 // display the text in password textfields. 598 setTextColor(Color.BLACK); 599 } 600 601 /* package */ void setMaxLength(int maxLength) { 602 mMaxLength = maxLength; 603 if (-1 == maxLength) { 604 setFilters(NO_FILTERS); 605 } else { 606 setFilters(new InputFilter[] { 607 new InputFilter.LengthFilter(maxLength) }); 608 } 609 } 610 611 /** 612 * Set the pointer for this node so it can be determined which node this 613 * WebTextView represents. 614 * @param ptr Integer representing the pointer to the node which this 615 * WebTextView represents. 616 */ 617 /* package */ void setNodePointer(int ptr) { 618 mNodePointer = ptr; 619 } 620 621 /** 622 * Determine the position and size of WebTextView, and add it to the 623 * WebView's view heirarchy. All parameters are presumed to be in 624 * view coordinates. Also requests Focus and sets the cursor to not 625 * request to be in view. 626 * @param x x-position of the textfield. 627 * @param y y-position of the textfield. 628 * @param width width of the textfield. 629 * @param height height of the textfield. 630 */ 631 /* package */ void setRect(int x, int y, int width, int height) { 632 LayoutParams lp = (LayoutParams) getLayoutParams(); 633 if (null == lp) { 634 lp = new LayoutParams(width, height, x, y); 635 } else { 636 lp.x = x; 637 lp.y = y; 638 lp.width = width; 639 lp.height = height; 640 } 641 if (getParent() == null) { 642 mWebView.addView(this, lp); 643 } else { 644 setLayoutParams(lp); 645 } 646 // Set up a measure spec so a layout can always be recreated. 647 mWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); 648 mHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); 649 requestFocus(); 650 } 651 652 /** 653 * Set whether this is a single-line textfield or a multi-line textarea. 654 * Textfields scroll horizontally, and do not handle the enter key. 655 * Textareas behave oppositely. 656 * Do NOT call this after calling setInPassword(true). This will result in 657 * removing the password input type. 658 */ 659 public void setSingleLine(boolean single) { 660 int inputType = EditorInfo.TYPE_CLASS_TEXT 661 | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT; 662 if (!single) { 663 inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE 664 | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES 665 | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT; 666 } 667 mSingle = single; 668 setHorizontallyScrolling(single); 669 setInputType(inputType); 670 } 671 672 /** 673 * Set the text for this WebTextView, and set the selection to (start, end) 674 * @param text Text to go into this WebTextView. 675 * @param start Beginning of the selection. 676 * @param end End of the selection. 677 */ 678 /* package */ void setText(CharSequence text, int start, int end) { 679 mPreChange = text.toString(); 680 setText(text); 681 Spannable span = (Spannable) getText(); 682 int length = span.length(); 683 if (end > length) { 684 end = length; 685 } 686 if (start < 0) { 687 start = 0; 688 } else if (start > length) { 689 start = length; 690 } 691 if (DebugFlags.WEB_TEXT_VIEW) { 692 Log.v(LOGTAG, "setText start=" + start 693 + " end=" + end); 694 } 695 Selection.setSelection(span, start, end); 696 } 697 698 /** 699 * Set the text to the new string, but use the old selection, making sure 700 * to keep it within the new string. 701 * @param text The new text to place in the textfield. 702 */ 703 /* package */ void setTextAndKeepSelection(String text) { 704 mPreChange = text.toString(); 705 Editable edit = (Editable) getText(); 706 edit.replace(0, edit.length(), text); 707 updateCachedTextfield(); 708 } 709 710 /** 711 * Update the cache to reflect the current text. 712 */ 713 /* package */ void updateCachedTextfield() { 714 mWebView.updateCachedTextfield(getText().toString()); 715 } 716} 717