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