TextView.java revision 48540eb8537b501b70ff96858b77b37c3f930603
1/* 2 * Copyright (C) 2006 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.widget; 18 19import com.android.internal.util.FastMath; 20import com.android.internal.widget.EditableInputConnection; 21 22import org.xmlpull.v1.XmlPullParserException; 23 24import android.content.Context; 25import android.content.Intent; 26import android.content.res.ColorStateList; 27import android.content.res.Resources; 28import android.content.res.TypedArray; 29import android.content.res.XmlResourceParser; 30import android.graphics.Canvas; 31import android.graphics.Paint; 32import android.graphics.Path; 33import android.graphics.Rect; 34import android.graphics.RectF; 35import android.graphics.Typeface; 36import android.graphics.drawable.Drawable; 37import android.os.Bundle; 38import android.os.Handler; 39import android.os.Message; 40import android.os.Parcel; 41import android.os.Parcelable; 42import android.os.ResultReceiver; 43import android.os.SystemClock; 44import android.text.BoringLayout; 45import android.text.ClipboardManager; 46import android.text.DynamicLayout; 47import android.text.Editable; 48import android.text.GetChars; 49import android.text.GraphicsOperations; 50import android.text.InputFilter; 51import android.text.InputType; 52import android.text.Layout; 53import android.text.ParcelableSpan; 54import android.text.Selection; 55import android.text.SpanWatcher; 56import android.text.Spannable; 57import android.text.SpannableString; 58import android.text.Spanned; 59import android.text.SpannedString; 60import android.text.StaticLayout; 61import android.text.TextPaint; 62import android.text.TextUtils; 63import android.text.TextWatcher; 64import android.text.method.DateKeyListener; 65import android.text.method.DateTimeKeyListener; 66import android.text.method.DialerKeyListener; 67import android.text.method.DigitsKeyListener; 68import android.text.method.KeyListener; 69import android.text.method.LinkMovementMethod; 70import android.text.method.MetaKeyKeyListener; 71import android.text.method.MovementMethod; 72import android.text.method.PasswordTransformationMethod; 73import android.text.method.SingleLineTransformationMethod; 74import android.text.method.TextKeyListener; 75import android.text.method.TimeKeyListener; 76import android.text.method.TransformationMethod; 77import android.text.style.ParagraphStyle; 78import android.text.style.URLSpan; 79import android.text.style.UpdateAppearance; 80import android.text.util.Linkify; 81import android.util.AttributeSet; 82import android.util.FloatMath; 83import android.util.Log; 84import android.util.TypedValue; 85import android.view.ContextMenu; 86import android.view.Gravity; 87import android.view.KeyEvent; 88import android.view.LayoutInflater; 89import android.view.MenuItem; 90import android.view.MotionEvent; 91import android.view.View; 92import android.view.ViewDebug; 93import android.view.ViewRoot; 94import android.view.ViewTreeObserver; 95import android.view.ViewGroup.LayoutParams; 96import android.view.accessibility.AccessibilityEvent; 97import android.view.accessibility.AccessibilityManager; 98import android.view.animation.AnimationUtils; 99import android.view.inputmethod.BaseInputConnection; 100import android.view.inputmethod.CompletionInfo; 101import android.view.inputmethod.EditorInfo; 102import android.view.inputmethod.ExtractedText; 103import android.view.inputmethod.ExtractedTextRequest; 104import android.view.inputmethod.InputConnection; 105import android.view.inputmethod.InputMethodManager; 106import android.widget.RemoteViews.RemoteView; 107 108import java.io.IOException; 109import java.lang.ref.WeakReference; 110import java.util.ArrayList; 111 112/** 113 * Displays text to the user and optionally allows them to edit it. A TextView 114 * is a complete text editor, however the basic class is configured to not 115 * allow editing; see {@link EditText} for a subclass that configures the text 116 * view for editing. 117 * 118 * <p> 119 * <b>XML attributes</b> 120 * <p> 121 * See {@link android.R.styleable#TextView TextView Attributes}, 122 * {@link android.R.styleable#View View Attributes} 123 * 124 * @attr ref android.R.styleable#TextView_text 125 * @attr ref android.R.styleable#TextView_bufferType 126 * @attr ref android.R.styleable#TextView_hint 127 * @attr ref android.R.styleable#TextView_textColor 128 * @attr ref android.R.styleable#TextView_textColorHighlight 129 * @attr ref android.R.styleable#TextView_textColorHint 130 * @attr ref android.R.styleable#TextView_textSize 131 * @attr ref android.R.styleable#TextView_textScaleX 132 * @attr ref android.R.styleable#TextView_typeface 133 * @attr ref android.R.styleable#TextView_textStyle 134 * @attr ref android.R.styleable#TextView_cursorVisible 135 * @attr ref android.R.styleable#TextView_maxLines 136 * @attr ref android.R.styleable#TextView_maxHeight 137 * @attr ref android.R.styleable#TextView_lines 138 * @attr ref android.R.styleable#TextView_height 139 * @attr ref android.R.styleable#TextView_minLines 140 * @attr ref android.R.styleable#TextView_minHeight 141 * @attr ref android.R.styleable#TextView_maxEms 142 * @attr ref android.R.styleable#TextView_maxWidth 143 * @attr ref android.R.styleable#TextView_ems 144 * @attr ref android.R.styleable#TextView_width 145 * @attr ref android.R.styleable#TextView_minEms 146 * @attr ref android.R.styleable#TextView_minWidth 147 * @attr ref android.R.styleable#TextView_gravity 148 * @attr ref android.R.styleable#TextView_scrollHorizontally 149 * @attr ref android.R.styleable#TextView_password 150 * @attr ref android.R.styleable#TextView_singleLine 151 * @attr ref android.R.styleable#TextView_selectAllOnFocus 152 * @attr ref android.R.styleable#TextView_includeFontPadding 153 * @attr ref android.R.styleable#TextView_maxLength 154 * @attr ref android.R.styleable#TextView_shadowColor 155 * @attr ref android.R.styleable#TextView_shadowDx 156 * @attr ref android.R.styleable#TextView_shadowDy 157 * @attr ref android.R.styleable#TextView_shadowRadius 158 * @attr ref android.R.styleable#TextView_autoLink 159 * @attr ref android.R.styleable#TextView_linksClickable 160 * @attr ref android.R.styleable#TextView_numeric 161 * @attr ref android.R.styleable#TextView_digits 162 * @attr ref android.R.styleable#TextView_phoneNumber 163 * @attr ref android.R.styleable#TextView_inputMethod 164 * @attr ref android.R.styleable#TextView_capitalize 165 * @attr ref android.R.styleable#TextView_autoText 166 * @attr ref android.R.styleable#TextView_editable 167 * @attr ref android.R.styleable#TextView_drawableTop 168 * @attr ref android.R.styleable#TextView_drawableBottom 169 * @attr ref android.R.styleable#TextView_drawableRight 170 * @attr ref android.R.styleable#TextView_drawableLeft 171 * @attr ref android.R.styleable#TextView_lineSpacingExtra 172 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 173 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 174 */ 175@RemoteView 176public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { 177 static final String TAG = "TextView"; 178 static final boolean DEBUG_EXTRACT = false; 179 180 private static int PRIORITY = 100; 181 182 private ColorStateList mTextColor; 183 private int mCurTextColor; 184 private ColorStateList mHintTextColor; 185 private ColorStateList mLinkTextColor; 186 private int mCurHintTextColor; 187 private boolean mFreezesText; 188 private boolean mFrozenWithFocus; 189 private boolean mTemporaryDetach; 190 191 private boolean mEatTouchRelease = false; 192 private boolean mScrolled = false; 193 194 private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); 195 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); 196 197 private float mShadowRadius, mShadowDx, mShadowDy; 198 199 private static final int PREDRAW_NOT_REGISTERED = 0; 200 private static final int PREDRAW_PENDING = 1; 201 private static final int PREDRAW_DONE = 2; 202 private int mPreDrawState = PREDRAW_NOT_REGISTERED; 203 204 private TextUtils.TruncateAt mEllipsize = null; 205 206 // Enum for the "typeface" XML parameter. 207 // TODO: How can we get this from the XML instead of hardcoding it here? 208 private static final int SANS = 1; 209 private static final int SERIF = 2; 210 private static final int MONOSPACE = 3; 211 212 // Bitfield for the "numeric" XML parameter. 213 // TODO: How can we get this from the XML instead of hardcoding it here? 214 private static final int SIGNED = 2; 215 private static final int DECIMAL = 4; 216 217 class Drawables { 218 final Rect mCompoundRect = new Rect(); 219 Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight; 220 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight; 221 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight; 222 int mDrawablePadding; 223 } 224 private Drawables mDrawables; 225 226 private CharSequence mError; 227 private boolean mErrorWasChanged; 228 private ErrorPopup mPopup; 229 /** 230 * This flag is set if the TextView tries to display an error before it 231 * is attached to the window (so its position is still unknown). 232 * It causes the error to be shown later, when onAttachedToWindow() 233 * is called. 234 */ 235 private boolean mShowErrorAfterAttach; 236 237 private CharWrapper mCharWrapper = null; 238 239 private boolean mSelectionMoved = false; 240 private boolean mTouchFocusSelected = false; 241 242 private Marquee mMarquee; 243 private boolean mRestartMarquee; 244 245 private int mMarqueeRepeatLimit = 3; 246 247 class InputContentType { 248 int imeOptions = EditorInfo.IME_NULL; 249 String privateImeOptions; 250 CharSequence imeActionLabel; 251 int imeActionId; 252 Bundle extras; 253 OnEditorActionListener onEditorActionListener; 254 boolean enterDown; 255 } 256 InputContentType mInputContentType; 257 258 class InputMethodState { 259 Rect mCursorRectInWindow = new Rect(); 260 RectF mTmpRectF = new RectF(); 261 float[] mTmpOffset = new float[2]; 262 ExtractedTextRequest mExtracting; 263 final ExtractedText mTmpExtracted = new ExtractedText(); 264 int mBatchEditNesting; 265 boolean mCursorChanged; 266 boolean mSelectionModeChanged; 267 boolean mContentChanged; 268 int mChangedStart, mChangedEnd, mChangedDelta; 269 } 270 InputMethodState mInputMethodState; 271 272 /* 273 * Kick-start the font cache for the zygote process (to pay the cost of 274 * initializing freetype for our default font only once). 275 */ 276 static { 277 Paint p = new Paint(); 278 p.setAntiAlias(true); 279 // We don't care about the result, just the side-effect of measuring. 280 p.measureText("H"); 281 } 282 283 /** 284 * Interface definition for a callback to be invoked when an action is 285 * performed on the editor. 286 */ 287 public interface OnEditorActionListener { 288 /** 289 * Called when an action is being performed. 290 * 291 * @param v The view that was clicked. 292 * @param actionId Identifier of the action. This will be either the 293 * identifier you supplied, or {@link EditorInfo#IME_NULL 294 * EditorInfo.IME_NULL} if being called due to the enter key 295 * being pressed. 296 * @param event If triggered by an enter key, this is the event; 297 * otherwise, this is null. 298 * @return Return true if you have consumed the action, else false. 299 */ 300 boolean onEditorAction(TextView v, int actionId, KeyEvent event); 301 } 302 303 public TextView(Context context) { 304 this(context, null); 305 } 306 307 public TextView(Context context, 308 AttributeSet attrs) { 309 this(context, attrs, com.android.internal.R.attr.textViewStyle); 310 } 311 312 public TextView(Context context, 313 AttributeSet attrs, 314 int defStyle) { 315 super(context, attrs, defStyle); 316 mText = ""; 317 318 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 319 // If we get the paint from the skin, we should set it to left, since 320 // the layout always wants it to be left. 321 // mTextPaint.setTextAlign(Paint.Align.LEFT); 322 323 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 324 325 mMovement = getDefaultMovementMethod(); 326 mTransformation = null; 327 328 TypedArray a = 329 context.obtainStyledAttributes( 330 attrs, com.android.internal.R.styleable.TextView, defStyle, 0); 331 332 int textColorHighlight = 0; 333 ColorStateList textColor = null; 334 ColorStateList textColorHint = null; 335 ColorStateList textColorLink = null; 336 int textSize = 15; 337 int typefaceIndex = -1; 338 int styleIndex = -1; 339 340 /* 341 * Look the appearance up without checking first if it exists because 342 * almost every TextView has one and it greatly simplifies the logic 343 * to be able to parse the appearance first and then let specific tags 344 * for this View override it. 345 */ 346 TypedArray appearance = null; 347 int ap = a.getResourceId(com.android.internal.R.styleable.TextView_textAppearance, -1); 348 if (ap != -1) { 349 appearance = context.obtainStyledAttributes(ap, 350 com.android.internal.R.styleable. 351 TextAppearance); 352 } 353 if (appearance != null) { 354 int n = appearance.getIndexCount(); 355 for (int i = 0; i < n; i++) { 356 int attr = appearance.getIndex(i); 357 358 switch (attr) { 359 case com.android.internal.R.styleable.TextAppearance_textColorHighlight: 360 textColorHighlight = appearance.getColor(attr, textColorHighlight); 361 break; 362 363 case com.android.internal.R.styleable.TextAppearance_textColor: 364 textColor = appearance.getColorStateList(attr); 365 break; 366 367 case com.android.internal.R.styleable.TextAppearance_textColorHint: 368 textColorHint = appearance.getColorStateList(attr); 369 break; 370 371 case com.android.internal.R.styleable.TextAppearance_textColorLink: 372 textColorLink = appearance.getColorStateList(attr); 373 break; 374 375 case com.android.internal.R.styleable.TextAppearance_textSize: 376 textSize = appearance.getDimensionPixelSize(attr, textSize); 377 break; 378 379 case com.android.internal.R.styleable.TextAppearance_typeface: 380 typefaceIndex = appearance.getInt(attr, -1); 381 break; 382 383 case com.android.internal.R.styleable.TextAppearance_textStyle: 384 styleIndex = appearance.getInt(attr, -1); 385 break; 386 } 387 } 388 389 appearance.recycle(); 390 } 391 392 boolean editable = getDefaultEditable(); 393 CharSequence inputMethod = null; 394 int numeric = 0; 395 CharSequence digits = null; 396 boolean phone = false; 397 boolean autotext = false; 398 int autocap = -1; 399 int buffertype = 0; 400 boolean selectallonfocus = false; 401 Drawable drawableLeft = null, drawableTop = null, drawableRight = null, 402 drawableBottom = null; 403 int drawablePadding = 0; 404 int ellipsize = -1; 405 boolean singleLine = false; 406 int maxlength = -1; 407 CharSequence text = ""; 408 int shadowcolor = 0; 409 float dx = 0, dy = 0, r = 0; 410 boolean password = false; 411 int inputType = EditorInfo.TYPE_NULL; 412 413 int n = a.getIndexCount(); 414 for (int i = 0; i < n; i++) { 415 int attr = a.getIndex(i); 416 417 switch (attr) { 418 case com.android.internal.R.styleable.TextView_editable: 419 editable = a.getBoolean(attr, editable); 420 break; 421 422 case com.android.internal.R.styleable.TextView_inputMethod: 423 inputMethod = a.getText(attr); 424 break; 425 426 case com.android.internal.R.styleable.TextView_numeric: 427 numeric = a.getInt(attr, numeric); 428 break; 429 430 case com.android.internal.R.styleable.TextView_digits: 431 digits = a.getText(attr); 432 break; 433 434 case com.android.internal.R.styleable.TextView_phoneNumber: 435 phone = a.getBoolean(attr, phone); 436 break; 437 438 case com.android.internal.R.styleable.TextView_autoText: 439 autotext = a.getBoolean(attr, autotext); 440 break; 441 442 case com.android.internal.R.styleable.TextView_capitalize: 443 autocap = a.getInt(attr, autocap); 444 break; 445 446 case com.android.internal.R.styleable.TextView_bufferType: 447 buffertype = a.getInt(attr, buffertype); 448 break; 449 450 case com.android.internal.R.styleable.TextView_selectAllOnFocus: 451 selectallonfocus = a.getBoolean(attr, selectallonfocus); 452 break; 453 454 case com.android.internal.R.styleable.TextView_autoLink: 455 mAutoLinkMask = a.getInt(attr, 0); 456 break; 457 458 case com.android.internal.R.styleable.TextView_linksClickable: 459 mLinksClickable = a.getBoolean(attr, true); 460 break; 461 462 case com.android.internal.R.styleable.TextView_drawableLeft: 463 drawableLeft = a.getDrawable(attr); 464 break; 465 466 case com.android.internal.R.styleable.TextView_drawableTop: 467 drawableTop = a.getDrawable(attr); 468 break; 469 470 case com.android.internal.R.styleable.TextView_drawableRight: 471 drawableRight = a.getDrawable(attr); 472 break; 473 474 case com.android.internal.R.styleable.TextView_drawableBottom: 475 drawableBottom = a.getDrawable(attr); 476 break; 477 478 case com.android.internal.R.styleable.TextView_drawablePadding: 479 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); 480 break; 481 482 case com.android.internal.R.styleable.TextView_maxLines: 483 setMaxLines(a.getInt(attr, -1)); 484 break; 485 486 case com.android.internal.R.styleable.TextView_maxHeight: 487 setMaxHeight(a.getDimensionPixelSize(attr, -1)); 488 break; 489 490 case com.android.internal.R.styleable.TextView_lines: 491 setLines(a.getInt(attr, -1)); 492 break; 493 494 case com.android.internal.R.styleable.TextView_height: 495 setHeight(a.getDimensionPixelSize(attr, -1)); 496 break; 497 498 case com.android.internal.R.styleable.TextView_minLines: 499 setMinLines(a.getInt(attr, -1)); 500 break; 501 502 case com.android.internal.R.styleable.TextView_minHeight: 503 setMinHeight(a.getDimensionPixelSize(attr, -1)); 504 break; 505 506 case com.android.internal.R.styleable.TextView_maxEms: 507 setMaxEms(a.getInt(attr, -1)); 508 break; 509 510 case com.android.internal.R.styleable.TextView_maxWidth: 511 setMaxWidth(a.getDimensionPixelSize(attr, -1)); 512 break; 513 514 case com.android.internal.R.styleable.TextView_ems: 515 setEms(a.getInt(attr, -1)); 516 break; 517 518 case com.android.internal.R.styleable.TextView_width: 519 setWidth(a.getDimensionPixelSize(attr, -1)); 520 break; 521 522 case com.android.internal.R.styleable.TextView_minEms: 523 setMinEms(a.getInt(attr, -1)); 524 break; 525 526 case com.android.internal.R.styleable.TextView_minWidth: 527 setMinWidth(a.getDimensionPixelSize(attr, -1)); 528 break; 529 530 case com.android.internal.R.styleable.TextView_gravity: 531 setGravity(a.getInt(attr, -1)); 532 break; 533 534 case com.android.internal.R.styleable.TextView_hint: 535 setHint(a.getText(attr)); 536 break; 537 538 case com.android.internal.R.styleable.TextView_text: 539 text = a.getText(attr); 540 break; 541 542 case com.android.internal.R.styleable.TextView_scrollHorizontally: 543 if (a.getBoolean(attr, false)) { 544 setHorizontallyScrolling(true); 545 } 546 break; 547 548 case com.android.internal.R.styleable.TextView_singleLine: 549 singleLine = a.getBoolean(attr, singleLine); 550 break; 551 552 case com.android.internal.R.styleable.TextView_ellipsize: 553 ellipsize = a.getInt(attr, ellipsize); 554 break; 555 556 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: 557 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); 558 break; 559 560 case com.android.internal.R.styleable.TextView_includeFontPadding: 561 if (!a.getBoolean(attr, true)) { 562 setIncludeFontPadding(false); 563 } 564 break; 565 566 case com.android.internal.R.styleable.TextView_cursorVisible: 567 if (!a.getBoolean(attr, true)) { 568 setCursorVisible(false); 569 } 570 break; 571 572 case com.android.internal.R.styleable.TextView_maxLength: 573 maxlength = a.getInt(attr, -1); 574 break; 575 576 case com.android.internal.R.styleable.TextView_textScaleX: 577 setTextScaleX(a.getFloat(attr, 1.0f)); 578 break; 579 580 case com.android.internal.R.styleable.TextView_freezesText: 581 mFreezesText = a.getBoolean(attr, false); 582 break; 583 584 case com.android.internal.R.styleable.TextView_shadowColor: 585 shadowcolor = a.getInt(attr, 0); 586 break; 587 588 case com.android.internal.R.styleable.TextView_shadowDx: 589 dx = a.getFloat(attr, 0); 590 break; 591 592 case com.android.internal.R.styleable.TextView_shadowDy: 593 dy = a.getFloat(attr, 0); 594 break; 595 596 case com.android.internal.R.styleable.TextView_shadowRadius: 597 r = a.getFloat(attr, 0); 598 break; 599 600 case com.android.internal.R.styleable.TextView_enabled: 601 setEnabled(a.getBoolean(attr, isEnabled())); 602 break; 603 604 case com.android.internal.R.styleable.TextView_textColorHighlight: 605 textColorHighlight = a.getColor(attr, textColorHighlight); 606 break; 607 608 case com.android.internal.R.styleable.TextView_textColor: 609 textColor = a.getColorStateList(attr); 610 break; 611 612 case com.android.internal.R.styleable.TextView_textColorHint: 613 textColorHint = a.getColorStateList(attr); 614 break; 615 616 case com.android.internal.R.styleable.TextView_textColorLink: 617 textColorLink = a.getColorStateList(attr); 618 break; 619 620 case com.android.internal.R.styleable.TextView_textSize: 621 textSize = a.getDimensionPixelSize(attr, textSize); 622 break; 623 624 case com.android.internal.R.styleable.TextView_typeface: 625 typefaceIndex = a.getInt(attr, typefaceIndex); 626 break; 627 628 case com.android.internal.R.styleable.TextView_textStyle: 629 styleIndex = a.getInt(attr, styleIndex); 630 break; 631 632 case com.android.internal.R.styleable.TextView_password: 633 password = a.getBoolean(attr, password); 634 break; 635 636 case com.android.internal.R.styleable.TextView_lineSpacingExtra: 637 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); 638 break; 639 640 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: 641 mSpacingMult = a.getFloat(attr, mSpacingMult); 642 break; 643 644 case com.android.internal.R.styleable.TextView_inputType: 645 inputType = a.getInt(attr, mInputType); 646 break; 647 648 case com.android.internal.R.styleable.TextView_imeOptions: 649 if (mInputContentType == null) { 650 mInputContentType = new InputContentType(); 651 } 652 mInputContentType.imeOptions = a.getInt(attr, 653 mInputContentType.imeOptions); 654 break; 655 656 case com.android.internal.R.styleable.TextView_imeActionLabel: 657 if (mInputContentType == null) { 658 mInputContentType = new InputContentType(); 659 } 660 mInputContentType.imeActionLabel = a.getText(attr); 661 break; 662 663 case com.android.internal.R.styleable.TextView_imeActionId: 664 if (mInputContentType == null) { 665 mInputContentType = new InputContentType(); 666 } 667 mInputContentType.imeActionId = a.getInt(attr, 668 mInputContentType.imeActionId); 669 break; 670 671 case com.android.internal.R.styleable.TextView_privateImeOptions: 672 setPrivateImeOptions(a.getString(attr)); 673 break; 674 675 case com.android.internal.R.styleable.TextView_editorExtras: 676 try { 677 setInputExtras(a.getResourceId(attr, 0)); 678 } catch (XmlPullParserException e) { 679 Log.w("TextView", "Failure reading input extras", e); 680 } catch (IOException e) { 681 Log.w("TextView", "Failure reading input extras", e); 682 } 683 break; 684 } 685 } 686 a.recycle(); 687 688 BufferType bufferType = BufferType.EDITABLE; 689 690 if ((inputType&(EditorInfo.TYPE_MASK_CLASS 691 |EditorInfo.TYPE_MASK_VARIATION)) 692 == (EditorInfo.TYPE_CLASS_TEXT 693 |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) { 694 password = true; 695 } 696 697 if (inputMethod != null) { 698 Class c; 699 700 try { 701 c = Class.forName(inputMethod.toString()); 702 } catch (ClassNotFoundException ex) { 703 throw new RuntimeException(ex); 704 } 705 706 try { 707 mInput = (KeyListener) c.newInstance(); 708 } catch (InstantiationException ex) { 709 throw new RuntimeException(ex); 710 } catch (IllegalAccessException ex) { 711 throw new RuntimeException(ex); 712 } 713 try { 714 mInputType = inputType != EditorInfo.TYPE_NULL 715 ? inputType 716 : mInput.getInputType(); 717 } catch (IncompatibleClassChangeError e) { 718 mInputType = EditorInfo.TYPE_CLASS_TEXT; 719 } 720 } else if (digits != null) { 721 mInput = DigitsKeyListener.getInstance(digits.toString()); 722 mInputType = inputType; 723 } else if (inputType != EditorInfo.TYPE_NULL) { 724 setInputType(inputType, true); 725 singleLine = (inputType&(EditorInfo.TYPE_MASK_CLASS 726 | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) != 727 (EditorInfo.TYPE_CLASS_TEXT 728 | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 729 } else if (phone) { 730 mInput = DialerKeyListener.getInstance(); 731 mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE; 732 } else if (numeric != 0) { 733 mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0, 734 (numeric & DECIMAL) != 0); 735 inputType = EditorInfo.TYPE_CLASS_NUMBER; 736 if ((numeric & SIGNED) != 0) { 737 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED; 738 } 739 if ((numeric & DECIMAL) != 0) { 740 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL; 741 } 742 mInputType = inputType; 743 } else if (autotext || autocap != -1) { 744 TextKeyListener.Capitalize cap; 745 746 inputType = EditorInfo.TYPE_CLASS_TEXT; 747 if (!singleLine) { 748 inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 749 } 750 751 switch (autocap) { 752 case 1: 753 cap = TextKeyListener.Capitalize.SENTENCES; 754 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; 755 break; 756 757 case 2: 758 cap = TextKeyListener.Capitalize.WORDS; 759 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; 760 break; 761 762 case 3: 763 cap = TextKeyListener.Capitalize.CHARACTERS; 764 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; 765 break; 766 767 default: 768 cap = TextKeyListener.Capitalize.NONE; 769 break; 770 } 771 772 mInput = TextKeyListener.getInstance(autotext, cap); 773 mInputType = inputType; 774 } else if (editable) { 775 mInput = TextKeyListener.getInstance(); 776 mInputType = EditorInfo.TYPE_CLASS_TEXT; 777 } else { 778 mInput = null; 779 780 switch (buffertype) { 781 case 0: 782 bufferType = BufferType.NORMAL; 783 break; 784 case 1: 785 bufferType = BufferType.SPANNABLE; 786 break; 787 case 2: 788 bufferType = BufferType.EDITABLE; 789 break; 790 } 791 } 792 793 if (password && (mInputType&EditorInfo.TYPE_MASK_CLASS) 794 == EditorInfo.TYPE_CLASS_TEXT) { 795 mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION)) 796 | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 797 } 798 799 if (selectallonfocus) { 800 mSelectAllOnFocus = true; 801 802 if (bufferType == BufferType.NORMAL) 803 bufferType = BufferType.SPANNABLE; 804 } 805 806 setCompoundDrawablesWithIntrinsicBounds( 807 drawableLeft, drawableTop, drawableRight, drawableBottom); 808 setCompoundDrawablePadding(drawablePadding); 809 810 if (singleLine) { 811 setSingleLine(); 812 813 if (mInput == null && ellipsize < 0) { 814 ellipsize = 3; // END 815 } 816 } 817 818 switch (ellipsize) { 819 case 1: 820 setEllipsize(TextUtils.TruncateAt.START); 821 break; 822 case 2: 823 setEllipsize(TextUtils.TruncateAt.MIDDLE); 824 break; 825 case 3: 826 setEllipsize(TextUtils.TruncateAt.END); 827 break; 828 case 4: 829 setHorizontalFadingEdgeEnabled(true); 830 setEllipsize(TextUtils.TruncateAt.MARQUEE); 831 break; 832 } 833 834 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000)); 835 setHintTextColor(textColorHint); 836 setLinkTextColor(textColorLink); 837 if (textColorHighlight != 0) { 838 setHighlightColor(textColorHighlight); 839 } 840 setRawTextSize(textSize); 841 842 if (password) { 843 setTransformationMethod(PasswordTransformationMethod.getInstance()); 844 typefaceIndex = MONOSPACE; 845 } else if ((mInputType&(EditorInfo.TYPE_MASK_CLASS 846 |EditorInfo.TYPE_MASK_VARIATION)) 847 == (EditorInfo.TYPE_CLASS_TEXT 848 |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) { 849 typefaceIndex = MONOSPACE; 850 } 851 852 setTypefaceByIndex(typefaceIndex, styleIndex); 853 854 if (shadowcolor != 0) { 855 setShadowLayer(r, dx, dy, shadowcolor); 856 } 857 858 if (maxlength >= 0) { 859 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); 860 } else { 861 setFilters(NO_FILTERS); 862 } 863 864 setText(text, bufferType); 865 866 /* 867 * Views are not normally focusable unless specified to be. 868 * However, TextViews that have input or movement methods *are* 869 * focusable by default. 870 */ 871 a = context.obtainStyledAttributes(attrs, 872 com.android.internal.R.styleable.View, 873 defStyle, 0); 874 875 boolean focusable = mMovement != null || mInput != null; 876 boolean clickable = focusable; 877 boolean longClickable = focusable; 878 879 n = a.getIndexCount(); 880 for (int i = 0; i < n; i++) { 881 int attr = a.getIndex(i); 882 883 switch (attr) { 884 case com.android.internal.R.styleable.View_focusable: 885 focusable = a.getBoolean(attr, focusable); 886 break; 887 888 case com.android.internal.R.styleable.View_clickable: 889 clickable = a.getBoolean(attr, clickable); 890 break; 891 892 case com.android.internal.R.styleable.View_longClickable: 893 longClickable = a.getBoolean(attr, longClickable); 894 break; 895 } 896 } 897 a.recycle(); 898 899 setFocusable(focusable); 900 setClickable(clickable); 901 setLongClickable(longClickable); 902 } 903 904 private void setTypefaceByIndex(int typefaceIndex, int styleIndex) { 905 Typeface tf = null; 906 switch (typefaceIndex) { 907 case SANS: 908 tf = Typeface.SANS_SERIF; 909 break; 910 911 case SERIF: 912 tf = Typeface.SERIF; 913 break; 914 915 case MONOSPACE: 916 tf = Typeface.MONOSPACE; 917 break; 918 } 919 920 setTypeface(tf, styleIndex); 921 } 922 923 /** 924 * Sets the typeface and style in which the text should be displayed, 925 * and turns on the fake bold and italic bits in the Paint if the 926 * Typeface that you provided does not have all the bits in the 927 * style that you specified. 928 * 929 * @attr ref android.R.styleable#TextView_typeface 930 * @attr ref android.R.styleable#TextView_textStyle 931 */ 932 public void setTypeface(Typeface tf, int style) { 933 if (style > 0) { 934 if (tf == null) { 935 tf = Typeface.defaultFromStyle(style); 936 } else { 937 tf = Typeface.create(tf, style); 938 } 939 940 setTypeface(tf); 941 // now compute what (if any) algorithmic styling is needed 942 int typefaceStyle = tf != null ? tf.getStyle() : 0; 943 int need = style & ~typefaceStyle; 944 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 945 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 946 } else { 947 mTextPaint.setFakeBoldText(false); 948 mTextPaint.setTextSkewX(0); 949 setTypeface(tf); 950 } 951 } 952 953 /** 954 * Subclasses override this to specify that they have a KeyListener 955 * by default even if not specifically called for in the XML options. 956 */ 957 protected boolean getDefaultEditable() { 958 return false; 959 } 960 961 /** 962 * Subclasses override this to specify a default movement method. 963 */ 964 protected MovementMethod getDefaultMovementMethod() { 965 return null; 966 } 967 968 /** 969 * Return the text the TextView is displaying. If setText() was called with 970 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast 971 * the return value from this method to Spannable or Editable, respectively. 972 * 973 * Note: The content of the return value should not be modified. If you want 974 * a modifiable one, you should make your own copy first. 975 */ 976 @ViewDebug.CapturedViewProperty 977 public CharSequence getText() { 978 return mText; 979 } 980 981 /** 982 * Returns the length, in characters, of the text managed by this TextView 983 */ 984 public int length() { 985 return mText.length(); 986 } 987 988 /** 989 * Return the text the TextView is displaying as an Editable object. If 990 * the text is not editable, null is returned. 991 * 992 * @see #getText 993 */ 994 public Editable getEditableText() { 995 return (mText instanceof Editable) ? (Editable)mText : null; 996 } 997 998 /** 999 * @return the height of one standard line in pixels. Note that markup 1000 * within the text can cause individual lines to be taller or shorter 1001 * than this height, and the layout may contain additional first- 1002 * or last-line padding. 1003 */ 1004 public int getLineHeight() { 1005 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult 1006 + mSpacingAdd); 1007 } 1008 1009 /** 1010 * @return the Layout that is currently being used to display the text. 1011 * This can be null if the text or width has recently changes. 1012 */ 1013 public final Layout getLayout() { 1014 return mLayout; 1015 } 1016 1017 /** 1018 * @return the current key listener for this TextView. 1019 * This will frequently be null for non-EditText TextViews. 1020 */ 1021 public final KeyListener getKeyListener() { 1022 return mInput; 1023 } 1024 1025 /** 1026 * Sets the key listener to be used with this TextView. This can be null 1027 * to disallow user input. Note that this method has significant and 1028 * subtle interactions with soft keyboards and other input method: 1029 * see {@link KeyListener#getInputType() KeyListener.getContentType()} 1030 * for important details. Calling this method will replace the current 1031 * content type of the text view with the content type returned by the 1032 * key listener. 1033 * <p> 1034 * Be warned that if you want a TextView with a key listener or movement 1035 * method not to be focusable, or if you want a TextView without a 1036 * key listener or movement method to be focusable, you must call 1037 * {@link #setFocusable} again after calling this to get the focusability 1038 * back the way you want it. 1039 * 1040 * @attr ref android.R.styleable#TextView_numeric 1041 * @attr ref android.R.styleable#TextView_digits 1042 * @attr ref android.R.styleable#TextView_phoneNumber 1043 * @attr ref android.R.styleable#TextView_inputMethod 1044 * @attr ref android.R.styleable#TextView_capitalize 1045 * @attr ref android.R.styleable#TextView_autoText 1046 */ 1047 public void setKeyListener(KeyListener input) { 1048 setKeyListenerOnly(input); 1049 fixFocusableAndClickableSettings(); 1050 1051 if (input != null) { 1052 try { 1053 mInputType = mInput.getInputType(); 1054 } catch (IncompatibleClassChangeError e) { 1055 mInputType = EditorInfo.TYPE_CLASS_TEXT; 1056 } 1057 if ((mInputType&EditorInfo.TYPE_MASK_CLASS) 1058 == EditorInfo.TYPE_CLASS_TEXT) { 1059 if (mSingleLine) { 1060 mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 1061 } else { 1062 mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 1063 } 1064 } 1065 } else { 1066 mInputType = EditorInfo.TYPE_NULL; 1067 } 1068 1069 InputMethodManager imm = InputMethodManager.peekInstance(); 1070 if (imm != null) imm.restartInput(this); 1071 } 1072 1073 private void setKeyListenerOnly(KeyListener input) { 1074 mInput = input; 1075 if (mInput != null && !(mText instanceof Editable)) 1076 setText(mText); 1077 1078 setFilters((Editable) mText, mFilters); 1079 } 1080 1081 /** 1082 * @return the movement method being used for this TextView. 1083 * This will frequently be null for non-EditText TextViews. 1084 */ 1085 public final MovementMethod getMovementMethod() { 1086 return mMovement; 1087 } 1088 1089 /** 1090 * Sets the movement method (arrow key handler) to be used for 1091 * this TextView. This can be null to disallow using the arrow keys 1092 * to move the cursor or scroll the view. 1093 * <p> 1094 * Be warned that if you want a TextView with a key listener or movement 1095 * method not to be focusable, or if you want a TextView without a 1096 * key listener or movement method to be focusable, you must call 1097 * {@link #setFocusable} again after calling this to get the focusability 1098 * back the way you want it. 1099 */ 1100 public final void setMovementMethod(MovementMethod movement) { 1101 mMovement = movement; 1102 1103 if (mMovement != null && !(mText instanceof Spannable)) 1104 setText(mText); 1105 1106 fixFocusableAndClickableSettings(); 1107 } 1108 1109 private void fixFocusableAndClickableSettings() { 1110 if ((mMovement != null) || mInput != null) { 1111 setFocusable(true); 1112 setClickable(true); 1113 setLongClickable(true); 1114 } else { 1115 setFocusable(false); 1116 setClickable(false); 1117 setLongClickable(false); 1118 } 1119 } 1120 1121 /** 1122 * @return the current transformation method for this TextView. 1123 * This will frequently be null except for single-line and password 1124 * fields. 1125 */ 1126 public final TransformationMethod getTransformationMethod() { 1127 return mTransformation; 1128 } 1129 1130 /** 1131 * Sets the transformation that is applied to the text that this 1132 * TextView is displaying. 1133 * 1134 * @attr ref android.R.styleable#TextView_password 1135 * @attr ref android.R.styleable#TextView_singleLine 1136 */ 1137 public final void setTransformationMethod(TransformationMethod method) { 1138 if (method == mTransformation) { 1139 // Avoid the setText() below if the transformation is 1140 // the same. 1141 return; 1142 } 1143 if (mTransformation != null) { 1144 if (mText instanceof Spannable) { 1145 ((Spannable) mText).removeSpan(mTransformation); 1146 } 1147 } 1148 1149 mTransformation = method; 1150 1151 setText(mText); 1152 } 1153 1154 /** 1155 * Returns the top padding of the view, plus space for the top 1156 * Drawable if any. 1157 */ 1158 public int getCompoundPaddingTop() { 1159 final Drawables dr = mDrawables; 1160 if (dr == null || dr.mDrawableTop == null) { 1161 return mPaddingTop; 1162 } else { 1163 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; 1164 } 1165 } 1166 1167 /** 1168 * Returns the bottom padding of the view, plus space for the bottom 1169 * Drawable if any. 1170 */ 1171 public int getCompoundPaddingBottom() { 1172 final Drawables dr = mDrawables; 1173 if (dr == null || dr.mDrawableBottom == null) { 1174 return mPaddingBottom; 1175 } else { 1176 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; 1177 } 1178 } 1179 1180 /** 1181 * Returns the left padding of the view, plus space for the left 1182 * Drawable if any. 1183 */ 1184 public int getCompoundPaddingLeft() { 1185 final Drawables dr = mDrawables; 1186 if (dr == null || dr.mDrawableLeft == null) { 1187 return mPaddingLeft; 1188 } else { 1189 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; 1190 } 1191 } 1192 1193 /** 1194 * Returns the right padding of the view, plus space for the right 1195 * Drawable if any. 1196 */ 1197 public int getCompoundPaddingRight() { 1198 final Drawables dr = mDrawables; 1199 if (dr == null || dr.mDrawableRight == null) { 1200 return mPaddingRight; 1201 } else { 1202 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; 1203 } 1204 } 1205 1206 /** 1207 * Returns the extended top padding of the view, including both the 1208 * top Drawable if any and any extra space to keep more than maxLines 1209 * of text from showing. It is only valid to call this after measuring. 1210 */ 1211 public int getExtendedPaddingTop() { 1212 if (mMaxMode != LINES) { 1213 return getCompoundPaddingTop(); 1214 } 1215 1216 if (mLayout.getLineCount() <= mMaximum) { 1217 return getCompoundPaddingTop(); 1218 } 1219 1220 int top = getCompoundPaddingTop(); 1221 int bottom = getCompoundPaddingBottom(); 1222 int viewht = getHeight() - top - bottom; 1223 int layoutht = mLayout.getLineTop(mMaximum); 1224 1225 if (layoutht >= viewht) { 1226 return top; 1227 } 1228 1229 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 1230 if (gravity == Gravity.TOP) { 1231 return top; 1232 } else if (gravity == Gravity.BOTTOM) { 1233 return top + viewht - layoutht; 1234 } else { // (gravity == Gravity.CENTER_VERTICAL) 1235 return top + (viewht - layoutht) / 2; 1236 } 1237 } 1238 1239 /** 1240 * Returns the extended bottom padding of the view, including both the 1241 * bottom Drawable if any and any extra space to keep more than maxLines 1242 * of text from showing. It is only valid to call this after measuring. 1243 */ 1244 public int getExtendedPaddingBottom() { 1245 if (mMaxMode != LINES) { 1246 return getCompoundPaddingBottom(); 1247 } 1248 1249 if (mLayout.getLineCount() <= mMaximum) { 1250 return getCompoundPaddingBottom(); 1251 } 1252 1253 int top = getCompoundPaddingTop(); 1254 int bottom = getCompoundPaddingBottom(); 1255 int viewht = getHeight() - top - bottom; 1256 int layoutht = mLayout.getLineTop(mMaximum); 1257 1258 if (layoutht >= viewht) { 1259 return bottom; 1260 } 1261 1262 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 1263 if (gravity == Gravity.TOP) { 1264 return bottom + viewht - layoutht; 1265 } else if (gravity == Gravity.BOTTOM) { 1266 return bottom; 1267 } else { // (gravity == Gravity.CENTER_VERTICAL) 1268 return bottom + (viewht - layoutht) / 2; 1269 } 1270 } 1271 1272 /** 1273 * Returns the total left padding of the view, including the left 1274 * Drawable if any. 1275 */ 1276 public int getTotalPaddingLeft() { 1277 return getCompoundPaddingLeft(); 1278 } 1279 1280 /** 1281 * Returns the total right padding of the view, including the right 1282 * Drawable if any. 1283 */ 1284 public int getTotalPaddingRight() { 1285 return getCompoundPaddingRight(); 1286 } 1287 1288 /** 1289 * Returns the total top padding of the view, including the top 1290 * Drawable if any, the extra space to keep more than maxLines 1291 * from showing, and the vertical offset for gravity, if any. 1292 */ 1293 public int getTotalPaddingTop() { 1294 return getExtendedPaddingTop() + getVerticalOffset(true); 1295 } 1296 1297 /** 1298 * Returns the total bottom padding of the view, including the bottom 1299 * Drawable if any, the extra space to keep more than maxLines 1300 * from showing, and the vertical offset for gravity, if any. 1301 */ 1302 public int getTotalPaddingBottom() { 1303 return getExtendedPaddingBottom() + getBottomVerticalOffset(true); 1304 } 1305 1306 /** 1307 * Sets the Drawables (if any) to appear to the left of, above, 1308 * to the right of, and below the text. Use null if you do not 1309 * want a Drawable there. The Drawables must already have had 1310 * {@link Drawable#setBounds} called. 1311 * 1312 * @attr ref android.R.styleable#TextView_drawableLeft 1313 * @attr ref android.R.styleable#TextView_drawableTop 1314 * @attr ref android.R.styleable#TextView_drawableRight 1315 * @attr ref android.R.styleable#TextView_drawableBottom 1316 */ 1317 public void setCompoundDrawables(Drawable left, Drawable top, 1318 Drawable right, Drawable bottom) { 1319 Drawables dr = mDrawables; 1320 1321 final boolean drawables = left != null || top != null 1322 || right != null || bottom != null; 1323 1324 if (!drawables) { 1325 // Clearing drawables... can we free the data structure? 1326 if (dr != null) { 1327 if (dr.mDrawablePadding == 0) { 1328 mDrawables = null; 1329 } else { 1330 // We need to retain the last set padding, so just clear 1331 // out all of the fields in the existing structure. 1332 if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null); 1333 dr.mDrawableLeft = null; 1334 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null); 1335 dr.mDrawableTop = null; 1336 if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null); 1337 dr.mDrawableRight = null; 1338 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null); 1339 dr.mDrawableBottom = null; 1340 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 1341 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 1342 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 1343 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 1344 } 1345 } 1346 } else { 1347 if (dr == null) { 1348 mDrawables = dr = new Drawables(); 1349 } 1350 1351 if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) { 1352 dr.mDrawableLeft.setCallback(null); 1353 } 1354 dr.mDrawableLeft = left; 1355 if (dr.mDrawableTop != left && dr.mDrawableTop != null) { 1356 dr.mDrawableTop.setCallback(null); 1357 } 1358 dr.mDrawableTop = top; 1359 if (dr.mDrawableRight != left && dr.mDrawableRight != null) { 1360 dr.mDrawableRight.setCallback(null); 1361 } 1362 dr.mDrawableRight = right; 1363 if (dr.mDrawableBottom != left && dr.mDrawableBottom != null) { 1364 dr.mDrawableBottom.setCallback(null); 1365 } 1366 dr.mDrawableBottom = bottom; 1367 1368 final Rect compoundRect = dr.mCompoundRect; 1369 int[] state; 1370 1371 state = getDrawableState(); 1372 1373 if (left != null) { 1374 left.setState(state); 1375 left.copyBounds(compoundRect); 1376 left.setCallback(this); 1377 dr.mDrawableSizeLeft = compoundRect.width(); 1378 dr.mDrawableHeightLeft = compoundRect.height(); 1379 } else { 1380 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 1381 } 1382 1383 if (right != null) { 1384 right.setState(state); 1385 right.copyBounds(compoundRect); 1386 right.setCallback(this); 1387 dr.mDrawableSizeRight = compoundRect.width(); 1388 dr.mDrawableHeightRight = compoundRect.height(); 1389 } else { 1390 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 1391 } 1392 1393 if (top != null) { 1394 top.setState(state); 1395 top.copyBounds(compoundRect); 1396 top.setCallback(this); 1397 dr.mDrawableSizeTop = compoundRect.height(); 1398 dr.mDrawableWidthTop = compoundRect.width(); 1399 } else { 1400 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 1401 } 1402 1403 if (bottom != null) { 1404 bottom.setState(state); 1405 bottom.copyBounds(compoundRect); 1406 bottom.setCallback(this); 1407 dr.mDrawableSizeBottom = compoundRect.height(); 1408 dr.mDrawableWidthBottom = compoundRect.width(); 1409 } else { 1410 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 1411 } 1412 } 1413 1414 invalidate(); 1415 requestLayout(); 1416 } 1417 1418 /** 1419 * Sets the Drawables (if any) to appear to the left of, above, 1420 * to the right of, and below the text. Use 0 if you do not 1421 * want a Drawable there. The Drawables' bounds will be set to 1422 * their intrinsic bounds. 1423 * 1424 * @param left Resource identifier of the left Drawable. 1425 * @param top Resource identifier of the top Drawable. 1426 * @param right Resource identifier of the right Drawable. 1427 * @param bottom Resource identifier of the bottom Drawable. 1428 * 1429 * @attr ref android.R.styleable#TextView_drawableLeft 1430 * @attr ref android.R.styleable#TextView_drawableTop 1431 * @attr ref android.R.styleable#TextView_drawableRight 1432 * @attr ref android.R.styleable#TextView_drawableBottom 1433 */ 1434 public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) { 1435 final Resources resources = getContext().getResources(); 1436 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null, 1437 top != 0 ? resources.getDrawable(top) : null, 1438 right != 0 ? resources.getDrawable(right) : null, 1439 bottom != 0 ? resources.getDrawable(bottom) : null); 1440 } 1441 1442 /** 1443 * Sets the Drawables (if any) to appear to the left of, above, 1444 * to the right of, and below the text. Use null if you do not 1445 * want a Drawable there. The Drawables' bounds will be set to 1446 * their intrinsic bounds. 1447 * 1448 * @attr ref android.R.styleable#TextView_drawableLeft 1449 * @attr ref android.R.styleable#TextView_drawableTop 1450 * @attr ref android.R.styleable#TextView_drawableRight 1451 * @attr ref android.R.styleable#TextView_drawableBottom 1452 */ 1453 public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, 1454 Drawable right, Drawable bottom) { 1455 1456 if (left != null) { 1457 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); 1458 } 1459 if (right != null) { 1460 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); 1461 } 1462 if (top != null) { 1463 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 1464 } 1465 if (bottom != null) { 1466 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 1467 } 1468 setCompoundDrawables(left, top, right, bottom); 1469 } 1470 1471 /** 1472 * Returns drawables for the left, top, right, and bottom borders. 1473 */ 1474 public Drawable[] getCompoundDrawables() { 1475 final Drawables dr = mDrawables; 1476 if (dr != null) { 1477 return new Drawable[] { 1478 dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom 1479 }; 1480 } else { 1481 return new Drawable[] { null, null, null, null }; 1482 } 1483 } 1484 1485 /** 1486 * Sets the size of the padding between the compound drawables and 1487 * the text. 1488 * 1489 * @attr ref android.R.styleable#TextView_drawablePadding 1490 */ 1491 public void setCompoundDrawablePadding(int pad) { 1492 Drawables dr = mDrawables; 1493 if (pad == 0) { 1494 if (dr != null) { 1495 dr.mDrawablePadding = pad; 1496 } 1497 } else { 1498 if (dr == null) { 1499 mDrawables = dr = new Drawables(); 1500 } 1501 dr.mDrawablePadding = pad; 1502 } 1503 1504 invalidate(); 1505 requestLayout(); 1506 } 1507 1508 /** 1509 * Returns the padding between the compound drawables and the text. 1510 */ 1511 public int getCompoundDrawablePadding() { 1512 final Drawables dr = mDrawables; 1513 return dr != null ? dr.mDrawablePadding : 0; 1514 } 1515 1516 @Override 1517 public void setPadding(int left, int top, int right, int bottom) { 1518 if (left != mPaddingLeft || 1519 right != mPaddingRight || 1520 top != mPaddingTop || 1521 bottom != mPaddingBottom) { 1522 nullLayouts(); 1523 } 1524 1525 // the super call will requestLayout() 1526 super.setPadding(left, top, right, bottom); 1527 invalidate(); 1528 } 1529 1530 /** 1531 * Gets the autolink mask of the text. See {@link 1532 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 1533 * possible values. 1534 * 1535 * @attr ref android.R.styleable#TextView_autoLink 1536 */ 1537 public final int getAutoLinkMask() { 1538 return mAutoLinkMask; 1539 } 1540 1541 /** 1542 * Sets the text color, size, style, hint color, and highlight color 1543 * from the specified TextAppearance resource. 1544 */ 1545 public void setTextAppearance(Context context, int resid) { 1546 TypedArray appearance = 1547 context.obtainStyledAttributes(resid, 1548 com.android.internal.R.styleable.TextAppearance); 1549 1550 int color; 1551 ColorStateList colors; 1552 int ts; 1553 1554 color = appearance.getColor(com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0); 1555 if (color != 0) { 1556 setHighlightColor(color); 1557 } 1558 1559 colors = appearance.getColorStateList(com.android.internal.R.styleable. 1560 TextAppearance_textColor); 1561 if (colors != null) { 1562 setTextColor(colors); 1563 } 1564 1565 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable. 1566 TextAppearance_textSize, 0); 1567 if (ts != 0) { 1568 setRawTextSize(ts); 1569 } 1570 1571 colors = appearance.getColorStateList(com.android.internal.R.styleable. 1572 TextAppearance_textColorHint); 1573 if (colors != null) { 1574 setHintTextColor(colors); 1575 } 1576 1577 colors = appearance.getColorStateList(com.android.internal.R.styleable. 1578 TextAppearance_textColorLink); 1579 if (colors != null) { 1580 setLinkTextColor(colors); 1581 } 1582 1583 int typefaceIndex, styleIndex; 1584 1585 typefaceIndex = appearance.getInt(com.android.internal.R.styleable. 1586 TextAppearance_typeface, -1); 1587 styleIndex = appearance.getInt(com.android.internal.R.styleable. 1588 TextAppearance_textStyle, -1); 1589 1590 setTypefaceByIndex(typefaceIndex, styleIndex); 1591 appearance.recycle(); 1592 } 1593 1594 /** 1595 * @return the size (in pixels) of the default text size in this TextView. 1596 */ 1597 public float getTextSize() { 1598 return mTextPaint.getTextSize(); 1599 } 1600 1601 /** 1602 * Set the default text size to the given value, interpreted as "scaled 1603 * pixel" units. This size is adjusted based on the current density and 1604 * user font size preference. 1605 * 1606 * @param size The scaled pixel size. 1607 * 1608 * @attr ref android.R.styleable#TextView_textSize 1609 */ 1610 @android.view.RemotableViewMethod 1611 public void setTextSize(float size) { 1612 setTextSize(TypedValue.COMPLEX_UNIT_SP, size); 1613 } 1614 1615 /** 1616 * Set the default text size to a given unit and value. See {@link 1617 * TypedValue} for the possible dimension units. 1618 * 1619 * @param unit The desired dimension unit. 1620 * @param size The desired size in the given units. 1621 * 1622 * @attr ref android.R.styleable#TextView_textSize 1623 */ 1624 public void setTextSize(int unit, float size) { 1625 Context c = getContext(); 1626 Resources r; 1627 1628 if (c == null) 1629 r = Resources.getSystem(); 1630 else 1631 r = c.getResources(); 1632 1633 setRawTextSize(TypedValue.applyDimension( 1634 unit, size, r.getDisplayMetrics())); 1635 } 1636 1637 private void setRawTextSize(float size) { 1638 if (size != mTextPaint.getTextSize()) { 1639 mTextPaint.setTextSize(size); 1640 1641 if (mLayout != null) { 1642 nullLayouts(); 1643 requestLayout(); 1644 invalidate(); 1645 } 1646 } 1647 } 1648 1649 /** 1650 * @return the extent by which text is currently being stretched 1651 * horizontally. This will usually be 1. 1652 */ 1653 public float getTextScaleX() { 1654 return mTextPaint.getTextScaleX(); 1655 } 1656 1657 /** 1658 * Sets the extent by which text should be stretched horizontally. 1659 * 1660 * @attr ref android.R.styleable#TextView_textScaleX 1661 */ 1662 @android.view.RemotableViewMethod 1663 public void setTextScaleX(float size) { 1664 if (size != mTextPaint.getTextScaleX()) { 1665 mUserSetTextScaleX = true; 1666 mTextPaint.setTextScaleX(size); 1667 1668 if (mLayout != null) { 1669 nullLayouts(); 1670 requestLayout(); 1671 invalidate(); 1672 } 1673 } 1674 } 1675 1676 /** 1677 * Sets the typeface and style in which the text should be displayed. 1678 * Note that not all Typeface families actually have bold and italic 1679 * variants, so you may need to use 1680 * {@link #setTypeface(Typeface, int)} to get the appearance 1681 * that you actually want. 1682 * 1683 * @attr ref android.R.styleable#TextView_typeface 1684 * @attr ref android.R.styleable#TextView_textStyle 1685 */ 1686 public void setTypeface(Typeface tf) { 1687 if (mTextPaint.getTypeface() != tf) { 1688 mTextPaint.setTypeface(tf); 1689 1690 if (mLayout != null) { 1691 nullLayouts(); 1692 requestLayout(); 1693 invalidate(); 1694 } 1695 } 1696 } 1697 1698 /** 1699 * @return the current typeface and style in which the text is being 1700 * displayed. 1701 */ 1702 public Typeface getTypeface() { 1703 return mTextPaint.getTypeface(); 1704 } 1705 1706 /** 1707 * Sets the text color for all the states (normal, selected, 1708 * focused) to be this color. 1709 * 1710 * @attr ref android.R.styleable#TextView_textColor 1711 */ 1712 @android.view.RemotableViewMethod 1713 public void setTextColor(int color) { 1714 mTextColor = ColorStateList.valueOf(color); 1715 updateTextColors(); 1716 } 1717 1718 /** 1719 * Sets the text color. 1720 * 1721 * @attr ref android.R.styleable#TextView_textColor 1722 */ 1723 public void setTextColor(ColorStateList colors) { 1724 if (colors == null) { 1725 throw new NullPointerException(); 1726 } 1727 1728 mTextColor = colors; 1729 updateTextColors(); 1730 } 1731 1732 /** 1733 * Return the set of text colors. 1734 * 1735 * @return Returns the set of text colors. 1736 */ 1737 public final ColorStateList getTextColors() { 1738 return mTextColor; 1739 } 1740 1741 /** 1742 * <p>Return the current color selected for normal text.</p> 1743 * 1744 * @return Returns the current text color. 1745 */ 1746 public final int getCurrentTextColor() { 1747 return mCurTextColor; 1748 } 1749 1750 /** 1751 * Sets the color used to display the selection highlight. 1752 * 1753 * @attr ref android.R.styleable#TextView_textColorHighlight 1754 */ 1755 @android.view.RemotableViewMethod 1756 public void setHighlightColor(int color) { 1757 if (mHighlightColor != color) { 1758 mHighlightColor = color; 1759 invalidate(); 1760 } 1761 } 1762 1763 /** 1764 * Gives the text a shadow of the specified radius and color, the specified 1765 * distance from its normal position. 1766 * 1767 * @attr ref android.R.styleable#TextView_shadowColor 1768 * @attr ref android.R.styleable#TextView_shadowDx 1769 * @attr ref android.R.styleable#TextView_shadowDy 1770 * @attr ref android.R.styleable#TextView_shadowRadius 1771 */ 1772 public void setShadowLayer(float radius, float dx, float dy, int color) { 1773 mTextPaint.setShadowLayer(radius, dx, dy, color); 1774 1775 mShadowRadius = radius; 1776 mShadowDx = dx; 1777 mShadowDy = dy; 1778 1779 invalidate(); 1780 } 1781 1782 /** 1783 * @return the base paint used for the text. Please use this only to 1784 * consult the Paint's properties and not to change them. 1785 */ 1786 public TextPaint getPaint() { 1787 return mTextPaint; 1788 } 1789 1790 /** 1791 * Sets the autolink mask of the text. See {@link 1792 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 1793 * possible values. 1794 * 1795 * @attr ref android.R.styleable#TextView_autoLink 1796 */ 1797 @android.view.RemotableViewMethod 1798 public final void setAutoLinkMask(int mask) { 1799 mAutoLinkMask = mask; 1800 } 1801 1802 /** 1803 * Sets whether the movement method will automatically be set to 1804 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 1805 * set to nonzero and links are detected in {@link #setText}. 1806 * The default is true. 1807 * 1808 * @attr ref android.R.styleable#TextView_linksClickable 1809 */ 1810 @android.view.RemotableViewMethod 1811 public final void setLinksClickable(boolean whether) { 1812 mLinksClickable = whether; 1813 } 1814 1815 /** 1816 * Returns whether the movement method will automatically be set to 1817 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 1818 * set to nonzero and links are detected in {@link #setText}. 1819 * The default is true. 1820 * 1821 * @attr ref android.R.styleable#TextView_linksClickable 1822 */ 1823 public final boolean getLinksClickable() { 1824 return mLinksClickable; 1825 } 1826 1827 /** 1828 * Returns the list of URLSpans attached to the text 1829 * (by {@link Linkify} or otherwise) if any. You can call 1830 * {@link URLSpan#getURL} on them to find where they link to 1831 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} 1832 * to find the region of the text they are attached to. 1833 */ 1834 public URLSpan[] getUrls() { 1835 if (mText instanceof Spanned) { 1836 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); 1837 } else { 1838 return new URLSpan[0]; 1839 } 1840 } 1841 1842 /** 1843 * Sets the color of the hint text. 1844 * 1845 * @attr ref android.R.styleable#TextView_textColorHint 1846 */ 1847 @android.view.RemotableViewMethod 1848 public final void setHintTextColor(int color) { 1849 mHintTextColor = ColorStateList.valueOf(color); 1850 updateTextColors(); 1851 } 1852 1853 /** 1854 * Sets the color of the hint text. 1855 * 1856 * @attr ref android.R.styleable#TextView_textColorHint 1857 */ 1858 public final void setHintTextColor(ColorStateList colors) { 1859 mHintTextColor = colors; 1860 updateTextColors(); 1861 } 1862 1863 /** 1864 * <p>Return the color used to paint the hint text.</p> 1865 * 1866 * @return Returns the list of hint text colors. 1867 */ 1868 public final ColorStateList getHintTextColors() { 1869 return mHintTextColor; 1870 } 1871 1872 /** 1873 * <p>Return the current color selected to paint the hint text.</p> 1874 * 1875 * @return Returns the current hint text color. 1876 */ 1877 public final int getCurrentHintTextColor() { 1878 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; 1879 } 1880 1881 /** 1882 * Sets the color of links in the text. 1883 * 1884 * @attr ref android.R.styleable#TextView_textColorLink 1885 */ 1886 @android.view.RemotableViewMethod 1887 public final void setLinkTextColor(int color) { 1888 mLinkTextColor = ColorStateList.valueOf(color); 1889 updateTextColors(); 1890 } 1891 1892 /** 1893 * Sets the color of links in the text. 1894 * 1895 * @attr ref android.R.styleable#TextView_textColorLink 1896 */ 1897 public final void setLinkTextColor(ColorStateList colors) { 1898 mLinkTextColor = colors; 1899 updateTextColors(); 1900 } 1901 1902 /** 1903 * <p>Returns the color used to paint links in the text.</p> 1904 * 1905 * @return Returns the list of link text colors. 1906 */ 1907 public final ColorStateList getLinkTextColors() { 1908 return mLinkTextColor; 1909 } 1910 1911 /** 1912 * Sets the horizontal alignment of the text and the 1913 * vertical gravity that will be used when there is extra space 1914 * in the TextView beyond what is required for the text itself. 1915 * 1916 * @see android.view.Gravity 1917 * @attr ref android.R.styleable#TextView_gravity 1918 */ 1919 public void setGravity(int gravity) { 1920 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) { 1921 gravity |= Gravity.LEFT; 1922 } 1923 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 1924 gravity |= Gravity.TOP; 1925 } 1926 1927 boolean newLayout = false; 1928 1929 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) != 1930 (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK)) { 1931 newLayout = true; 1932 } 1933 1934 if (gravity != mGravity) { 1935 invalidate(); 1936 } 1937 1938 mGravity = gravity; 1939 1940 if (mLayout != null && newLayout) { 1941 // XXX this is heavy-handed because no actual content changes. 1942 int want = mLayout.getWidth(); 1943 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 1944 1945 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 1946 mRight - mLeft - getCompoundPaddingLeft() - 1947 getCompoundPaddingRight(), true); 1948 } 1949 } 1950 1951 /** 1952 * Returns the horizontal and vertical alignment of this TextView. 1953 * 1954 * @see android.view.Gravity 1955 * @attr ref android.R.styleable#TextView_gravity 1956 */ 1957 public int getGravity() { 1958 return mGravity; 1959 } 1960 1961 /** 1962 * @return the flags on the Paint being used to display the text. 1963 * @see Paint#getFlags 1964 */ 1965 public int getPaintFlags() { 1966 return mTextPaint.getFlags(); 1967 } 1968 1969 /** 1970 * Sets flags on the Paint being used to display the text and 1971 * reflows the text if they are different from the old flags. 1972 * @see Paint#setFlags 1973 */ 1974 @android.view.RemotableViewMethod 1975 public void setPaintFlags(int flags) { 1976 if (mTextPaint.getFlags() != flags) { 1977 mTextPaint.setFlags(flags); 1978 1979 if (mLayout != null) { 1980 nullLayouts(); 1981 requestLayout(); 1982 invalidate(); 1983 } 1984 } 1985 } 1986 1987 /** 1988 * Sets whether the text should be allowed to be wider than the 1989 * View is. If false, it will be wrapped to the width of the View. 1990 * 1991 * @attr ref android.R.styleable#TextView_scrollHorizontally 1992 */ 1993 public void setHorizontallyScrolling(boolean whether) { 1994 mHorizontallyScrolling = whether; 1995 1996 if (mLayout != null) { 1997 nullLayouts(); 1998 requestLayout(); 1999 invalidate(); 2000 } 2001 } 2002 2003 /** 2004 * Makes the TextView at least this many lines tall 2005 * 2006 * @attr ref android.R.styleable#TextView_minLines 2007 */ 2008 @android.view.RemotableViewMethod 2009 public void setMinLines(int minlines) { 2010 mMinimum = minlines; 2011 mMinMode = LINES; 2012 2013 requestLayout(); 2014 invalidate(); 2015 } 2016 2017 /** 2018 * Makes the TextView at least this many pixels tall 2019 * 2020 * @attr ref android.R.styleable#TextView_minHeight 2021 */ 2022 @android.view.RemotableViewMethod 2023 public void setMinHeight(int minHeight) { 2024 mMinimum = minHeight; 2025 mMinMode = PIXELS; 2026 2027 requestLayout(); 2028 invalidate(); 2029 } 2030 2031 /** 2032 * Makes the TextView at most this many lines tall 2033 * 2034 * @attr ref android.R.styleable#TextView_maxLines 2035 */ 2036 @android.view.RemotableViewMethod 2037 public void setMaxLines(int maxlines) { 2038 mMaximum = maxlines; 2039 mMaxMode = LINES; 2040 2041 requestLayout(); 2042 invalidate(); 2043 } 2044 2045 /** 2046 * Makes the TextView at most this many pixels tall 2047 * 2048 * @attr ref android.R.styleable#TextView_maxHeight 2049 */ 2050 @android.view.RemotableViewMethod 2051 public void setMaxHeight(int maxHeight) { 2052 mMaximum = maxHeight; 2053 mMaxMode = PIXELS; 2054 2055 requestLayout(); 2056 invalidate(); 2057 } 2058 2059 /** 2060 * Makes the TextView exactly this many lines tall 2061 * 2062 * @attr ref android.R.styleable#TextView_lines 2063 */ 2064 @android.view.RemotableViewMethod 2065 public void setLines(int lines) { 2066 mMaximum = mMinimum = lines; 2067 mMaxMode = mMinMode = LINES; 2068 2069 requestLayout(); 2070 invalidate(); 2071 } 2072 2073 /** 2074 * Makes the TextView exactly this many pixels tall. 2075 * You could do the same thing by specifying this number in the 2076 * LayoutParams. 2077 * 2078 * @attr ref android.R.styleable#TextView_height 2079 */ 2080 @android.view.RemotableViewMethod 2081 public void setHeight(int pixels) { 2082 mMaximum = mMinimum = pixels; 2083 mMaxMode = mMinMode = PIXELS; 2084 2085 requestLayout(); 2086 invalidate(); 2087 } 2088 2089 /** 2090 * Makes the TextView at least this many ems wide 2091 * 2092 * @attr ref android.R.styleable#TextView_minEms 2093 */ 2094 @android.view.RemotableViewMethod 2095 public void setMinEms(int minems) { 2096 mMinWidth = minems; 2097 mMinWidthMode = EMS; 2098 2099 requestLayout(); 2100 invalidate(); 2101 } 2102 2103 /** 2104 * Makes the TextView at least this many pixels wide 2105 * 2106 * @attr ref android.R.styleable#TextView_minWidth 2107 */ 2108 @android.view.RemotableViewMethod 2109 public void setMinWidth(int minpixels) { 2110 mMinWidth = minpixels; 2111 mMinWidthMode = PIXELS; 2112 2113 requestLayout(); 2114 invalidate(); 2115 } 2116 2117 /** 2118 * Makes the TextView at most this many ems wide 2119 * 2120 * @attr ref android.R.styleable#TextView_maxEms 2121 */ 2122 @android.view.RemotableViewMethod 2123 public void setMaxEms(int maxems) { 2124 mMaxWidth = maxems; 2125 mMaxWidthMode = EMS; 2126 2127 requestLayout(); 2128 invalidate(); 2129 } 2130 2131 /** 2132 * Makes the TextView at most this many pixels wide 2133 * 2134 * @attr ref android.R.styleable#TextView_maxWidth 2135 */ 2136 @android.view.RemotableViewMethod 2137 public void setMaxWidth(int maxpixels) { 2138 mMaxWidth = maxpixels; 2139 mMaxWidthMode = PIXELS; 2140 2141 requestLayout(); 2142 invalidate(); 2143 } 2144 2145 /** 2146 * Makes the TextView exactly this many ems wide 2147 * 2148 * @attr ref android.R.styleable#TextView_ems 2149 */ 2150 @android.view.RemotableViewMethod 2151 public void setEms(int ems) { 2152 mMaxWidth = mMinWidth = ems; 2153 mMaxWidthMode = mMinWidthMode = EMS; 2154 2155 requestLayout(); 2156 invalidate(); 2157 } 2158 2159 /** 2160 * Makes the TextView exactly this many pixels wide. 2161 * You could do the same thing by specifying this number in the 2162 * LayoutParams. 2163 * 2164 * @attr ref android.R.styleable#TextView_width 2165 */ 2166 @android.view.RemotableViewMethod 2167 public void setWidth(int pixels) { 2168 mMaxWidth = mMinWidth = pixels; 2169 mMaxWidthMode = mMinWidthMode = PIXELS; 2170 2171 requestLayout(); 2172 invalidate(); 2173 } 2174 2175 2176 /** 2177 * Sets line spacing for this TextView. Each line will have its height 2178 * multiplied by <code>mult</code> and have <code>add</code> added to it. 2179 * 2180 * @attr ref android.R.styleable#TextView_lineSpacingExtra 2181 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 2182 */ 2183 public void setLineSpacing(float add, float mult) { 2184 mSpacingMult = mult; 2185 mSpacingAdd = add; 2186 2187 if (mLayout != null) { 2188 nullLayouts(); 2189 requestLayout(); 2190 invalidate(); 2191 } 2192 } 2193 2194 /** 2195 * Convenience method: Append the specified text to the TextView's 2196 * display buffer, upgrading it to BufferType.EDITABLE if it was 2197 * not already editable. 2198 */ 2199 public final void append(CharSequence text) { 2200 append(text, 0, text.length()); 2201 } 2202 2203 /** 2204 * Convenience method: Append the specified text slice to the TextView's 2205 * display buffer, upgrading it to BufferType.EDITABLE if it was 2206 * not already editable. 2207 */ 2208 public void append(CharSequence text, int start, int end) { 2209 if (!(mText instanceof Editable)) { 2210 setText(mText, BufferType.EDITABLE); 2211 } 2212 2213 ((Editable) mText).append(text, start, end); 2214 } 2215 2216 private void updateTextColors() { 2217 boolean inval = false; 2218 int color = mTextColor.getColorForState(getDrawableState(), 0); 2219 if (color != mCurTextColor) { 2220 mCurTextColor = color; 2221 inval = true; 2222 } 2223 if (mLinkTextColor != null) { 2224 color = mLinkTextColor.getColorForState(getDrawableState(), 0); 2225 if (color != mTextPaint.linkColor) { 2226 mTextPaint.linkColor = color; 2227 inval = true; 2228 } 2229 } 2230 if (mHintTextColor != null) { 2231 color = mHintTextColor.getColorForState(getDrawableState(), 0); 2232 if (color != mCurHintTextColor && mText.length() == 0) { 2233 mCurHintTextColor = color; 2234 inval = true; 2235 } 2236 } 2237 if (inval) { 2238 invalidate(); 2239 } 2240 } 2241 2242 @Override 2243 protected void drawableStateChanged() { 2244 super.drawableStateChanged(); 2245 if (mTextColor != null && mTextColor.isStateful() 2246 || (mHintTextColor != null && mHintTextColor.isStateful()) 2247 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 2248 updateTextColors(); 2249 } 2250 2251 final Drawables dr = mDrawables; 2252 if (dr != null) { 2253 int[] state = getDrawableState(); 2254 if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) { 2255 dr.mDrawableTop.setState(state); 2256 } 2257 if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) { 2258 dr.mDrawableBottom.setState(state); 2259 } 2260 if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) { 2261 dr.mDrawableLeft.setState(state); 2262 } 2263 if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) { 2264 dr.mDrawableRight.setState(state); 2265 } 2266 } 2267 } 2268 2269 /** 2270 * User interface state that is stored by TextView for implementing 2271 * {@link View#onSaveInstanceState}. 2272 */ 2273 public static class SavedState extends BaseSavedState { 2274 int selStart; 2275 int selEnd; 2276 CharSequence text; 2277 boolean frozenWithFocus; 2278 CharSequence error; 2279 2280 SavedState(Parcelable superState) { 2281 super(superState); 2282 } 2283 2284 @Override 2285 public void writeToParcel(Parcel out, int flags) { 2286 super.writeToParcel(out, flags); 2287 out.writeInt(selStart); 2288 out.writeInt(selEnd); 2289 out.writeInt(frozenWithFocus ? 1 : 0); 2290 TextUtils.writeToParcel(text, out, flags); 2291 2292 if (error == null) { 2293 out.writeInt(0); 2294 } else { 2295 out.writeInt(1); 2296 TextUtils.writeToParcel(error, out, flags); 2297 } 2298 } 2299 2300 @Override 2301 public String toString() { 2302 String str = "TextView.SavedState{" 2303 + Integer.toHexString(System.identityHashCode(this)) 2304 + " start=" + selStart + " end=" + selEnd; 2305 if (text != null) { 2306 str += " text=" + text; 2307 } 2308 return str + "}"; 2309 } 2310 2311 public static final Parcelable.Creator<SavedState> CREATOR 2312 = new Parcelable.Creator<SavedState>() { 2313 public SavedState createFromParcel(Parcel in) { 2314 return new SavedState(in); 2315 } 2316 2317 public SavedState[] newArray(int size) { 2318 return new SavedState[size]; 2319 } 2320 }; 2321 2322 private SavedState(Parcel in) { 2323 super(in); 2324 selStart = in.readInt(); 2325 selEnd = in.readInt(); 2326 frozenWithFocus = (in.readInt() != 0); 2327 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 2328 2329 if (in.readInt() != 0) { 2330 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 2331 } 2332 } 2333 } 2334 2335 @Override 2336 public Parcelable onSaveInstanceState() { 2337 Parcelable superState = super.onSaveInstanceState(); 2338 2339 // Save state if we are forced to 2340 boolean save = mFreezesText; 2341 int start = 0; 2342 int end = 0; 2343 2344 if (mText != null) { 2345 start = Selection.getSelectionStart(mText); 2346 end = Selection.getSelectionEnd(mText); 2347 if (start >= 0 || end >= 0) { 2348 // Or save state if there is a selection 2349 save = true; 2350 } 2351 } 2352 2353 if (save) { 2354 SavedState ss = new SavedState(superState); 2355 // XXX Should also save the current scroll position! 2356 ss.selStart = start; 2357 ss.selEnd = end; 2358 2359 if (mText instanceof Spanned) { 2360 /* 2361 * Calling setText() strips off any ChangeWatchers; 2362 * strip them now to avoid leaking references. 2363 * But do it to a copy so that if there are any 2364 * further changes to the text of this view, it 2365 * won't get into an inconsistent state. 2366 */ 2367 2368 Spannable sp = new SpannableString(mText); 2369 2370 for (ChangeWatcher cw : 2371 sp.getSpans(0, sp.length(), ChangeWatcher.class)) { 2372 sp.removeSpan(cw); 2373 } 2374 2375 ss.text = sp; 2376 } else { 2377 ss.text = mText.toString(); 2378 } 2379 2380 if (isFocused() && start >= 0 && end >= 0) { 2381 ss.frozenWithFocus = true; 2382 } 2383 2384 ss.error = mError; 2385 2386 return ss; 2387 } 2388 2389 return superState; 2390 } 2391 2392 @Override 2393 public void onRestoreInstanceState(Parcelable state) { 2394 if (!(state instanceof SavedState)) { 2395 super.onRestoreInstanceState(state); 2396 return; 2397 } 2398 2399 SavedState ss = (SavedState)state; 2400 super.onRestoreInstanceState(ss.getSuperState()); 2401 2402 // XXX restore buffer type too, as well as lots of other stuff 2403 if (ss.text != null) { 2404 setText(ss.text); 2405 } 2406 2407 if (ss.selStart >= 0 && ss.selEnd >= 0) { 2408 if (mText instanceof Spannable) { 2409 int len = mText.length(); 2410 2411 if (ss.selStart > len || ss.selEnd > len) { 2412 String restored = ""; 2413 2414 if (ss.text != null) { 2415 restored = "(restored) "; 2416 } 2417 2418 Log.e("TextView", "Saved cursor position " + ss.selStart + 2419 "/" + ss.selEnd + " out of range for " + restored + 2420 "text " + mText); 2421 } else { 2422 Selection.setSelection((Spannable) mText, ss.selStart, 2423 ss.selEnd); 2424 2425 if (ss.frozenWithFocus) { 2426 mFrozenWithFocus = true; 2427 } 2428 } 2429 } 2430 } 2431 2432 if (ss.error != null) { 2433 setError(ss.error); 2434 } 2435 } 2436 2437 /** 2438 * Control whether this text view saves its entire text contents when 2439 * freezing to an icicle, in addition to dynamic state such as cursor 2440 * position. By default this is false, not saving the text. Set to true 2441 * if the text in the text view is not being saved somewhere else in 2442 * persistent storage (such as in a content provider) so that if the 2443 * view is later thawed the user will not lose their data. 2444 * 2445 * @param freezesText Controls whether a frozen icicle should include the 2446 * entire text data: true to include it, false to not. 2447 * 2448 * @attr ref android.R.styleable#TextView_freezesText 2449 */ 2450 @android.view.RemotableViewMethod 2451 public void setFreezesText(boolean freezesText) { 2452 mFreezesText = freezesText; 2453 } 2454 2455 /** 2456 * Return whether this text view is including its entire text contents 2457 * in frozen icicles. 2458 * 2459 * @return Returns true if text is included, false if it isn't. 2460 * 2461 * @see #setFreezesText 2462 */ 2463 public boolean getFreezesText() { 2464 return mFreezesText; 2465 } 2466 2467 /////////////////////////////////////////////////////////////////////////// 2468 2469 /** 2470 * Sets the Factory used to create new Editables. 2471 */ 2472 public final void setEditableFactory(Editable.Factory factory) { 2473 mEditableFactory = factory; 2474 setText(mText); 2475 } 2476 2477 /** 2478 * Sets the Factory used to create new Spannables. 2479 */ 2480 public final void setSpannableFactory(Spannable.Factory factory) { 2481 mSpannableFactory = factory; 2482 setText(mText); 2483 } 2484 2485 /** 2486 * Sets the string value of the TextView. TextView <em>does not</em> accept 2487 * HTML-like formatting, which you can do with text strings in XML resource files. 2488 * To style your strings, attach android.text.style.* objects to a 2489 * {@link android.text.SpannableString SpannableString}, or see the 2490 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources"> 2491 * Available Resource Types</a> documentation for an example of setting 2492 * formatted text in the XML resource file. 2493 * 2494 * @attr ref android.R.styleable#TextView_text 2495 */ 2496 @android.view.RemotableViewMethod 2497 public final void setText(CharSequence text) { 2498 setText(text, mBufferType); 2499 } 2500 2501 /** 2502 * Like {@link #setText(CharSequence)}, 2503 * except that the cursor position (if any) is retained in the new text. 2504 * 2505 * @param text The new text to place in the text view. 2506 * 2507 * @see #setText(CharSequence) 2508 */ 2509 @android.view.RemotableViewMethod 2510 public final void setTextKeepState(CharSequence text) { 2511 setTextKeepState(text, mBufferType); 2512 } 2513 2514 /** 2515 * Sets the text that this TextView is to display (see 2516 * {@link #setText(CharSequence)}) and also sets whether it is stored 2517 * in a styleable/spannable buffer and whether it is editable. 2518 * 2519 * @attr ref android.R.styleable#TextView_text 2520 * @attr ref android.R.styleable#TextView_bufferType 2521 */ 2522 public void setText(CharSequence text, BufferType type) { 2523 setText(text, type, true, 0); 2524 2525 if (mCharWrapper != null) { 2526 mCharWrapper.mChars = null; 2527 } 2528 } 2529 2530 private void setText(CharSequence text, BufferType type, 2531 boolean notifyBefore, int oldlen) { 2532 if (text == null) { 2533 text = ""; 2534 } 2535 2536 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); 2537 2538 if (text instanceof Spanned && 2539 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 2540 setHorizontalFadingEdgeEnabled(true); 2541 setEllipsize(TextUtils.TruncateAt.MARQUEE); 2542 } 2543 2544 int n = mFilters.length; 2545 for (int i = 0; i < n; i++) { 2546 CharSequence out = mFilters[i].filter(text, 0, text.length(), 2547 EMPTY_SPANNED, 0, 0); 2548 if (out != null) { 2549 text = out; 2550 } 2551 } 2552 2553 if (notifyBefore) { 2554 if (mText != null) { 2555 oldlen = mText.length(); 2556 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 2557 } else { 2558 sendBeforeTextChanged("", 0, 0, text.length()); 2559 } 2560 } 2561 2562 boolean needEditableForNotification = false; 2563 2564 if (mListeners != null && mListeners.size() != 0) { 2565 needEditableForNotification = true; 2566 } 2567 2568 if (type == BufferType.EDITABLE || mInput != null || 2569 needEditableForNotification) { 2570 Editable t = mEditableFactory.newEditable(text); 2571 text = t; 2572 setFilters(t, mFilters); 2573 InputMethodManager imm = InputMethodManager.peekInstance(); 2574 if (imm != null) imm.restartInput(this); 2575 } else if (type == BufferType.SPANNABLE || mMovement != null) { 2576 text = mSpannableFactory.newSpannable(text); 2577 } else if (!(text instanceof CharWrapper)) { 2578 text = TextUtils.stringOrSpannedString(text); 2579 } 2580 2581 if (mAutoLinkMask != 0) { 2582 Spannable s2; 2583 2584 if (type == BufferType.EDITABLE || text instanceof Spannable) { 2585 s2 = (Spannable) text; 2586 } else { 2587 s2 = mSpannableFactory.newSpannable(text); 2588 } 2589 2590 if (Linkify.addLinks(s2, mAutoLinkMask)) { 2591 text = s2; 2592 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 2593 2594 /* 2595 * We must go ahead and set the text before changing the 2596 * movement method, because setMovementMethod() may call 2597 * setText() again to try to upgrade the buffer type. 2598 */ 2599 mText = text; 2600 2601 if (mLinksClickable) { 2602 setMovementMethod(LinkMovementMethod.getInstance()); 2603 } 2604 } 2605 } 2606 2607 mBufferType = type; 2608 mText = text; 2609 2610 if (mTransformation == null) 2611 mTransformed = text; 2612 else 2613 mTransformed = mTransformation.getTransformation(text, this); 2614 2615 final int textLength = text.length(); 2616 2617 if (text instanceof Spannable) { 2618 Spannable sp = (Spannable) text; 2619 2620 // Remove any ChangeWatchers that might have come 2621 // from other TextViews. 2622 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 2623 final int count = watchers.length; 2624 for (int i = 0; i < count; i++) 2625 sp.removeSpan(watchers[i]); 2626 2627 if (mChangeWatcher == null) 2628 mChangeWatcher = new ChangeWatcher(); 2629 2630 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE | 2631 (PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 2632 2633 if (mInput != null) { 2634 sp.setSpan(mInput, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 2635 } 2636 2637 if (mTransformation != null) { 2638 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 2639 2640 } 2641 2642 if (mMovement != null) { 2643 mMovement.initialize(this, (Spannable) text); 2644 2645 /* 2646 * Initializing the movement method will have set the 2647 * selection, so reset mSelectionMoved to keep that from 2648 * interfering with the normal on-focus selection-setting. 2649 */ 2650 mSelectionMoved = false; 2651 } 2652 } 2653 2654 if (mLayout != null) { 2655 checkForRelayout(); 2656 } 2657 2658 sendOnTextChanged(text, 0, oldlen, textLength); 2659 onTextChanged(text, 0, oldlen, textLength); 2660 2661 if (needEditableForNotification) { 2662 sendAfterTextChanged((Editable) text); 2663 } 2664 } 2665 2666 /** 2667 * Sets the TextView to display the specified slice of the specified 2668 * char array. You must promise that you will not change the contents 2669 * of the array except for right before another call to setText(), 2670 * since the TextView has no way to know that the text 2671 * has changed and that it needs to invalidate and re-layout. 2672 */ 2673 public final void setText(char[] text, int start, int len) { 2674 int oldlen = 0; 2675 2676 if (start < 0 || len < 0 || start + len > text.length) { 2677 throw new IndexOutOfBoundsException(start + ", " + len); 2678 } 2679 2680 /* 2681 * We must do the before-notification here ourselves because if 2682 * the old text is a CharWrapper we destroy it before calling 2683 * into the normal path. 2684 */ 2685 if (mText != null) { 2686 oldlen = mText.length(); 2687 sendBeforeTextChanged(mText, 0, oldlen, len); 2688 } else { 2689 sendBeforeTextChanged("", 0, 0, len); 2690 } 2691 2692 if (mCharWrapper == null) { 2693 mCharWrapper = new CharWrapper(text, start, len); 2694 } else { 2695 mCharWrapper.set(text, start, len); 2696 } 2697 2698 setText(mCharWrapper, mBufferType, false, oldlen); 2699 } 2700 2701 private static class CharWrapper 2702 implements CharSequence, GetChars, GraphicsOperations { 2703 private char[] mChars; 2704 private int mStart, mLength; 2705 2706 public CharWrapper(char[] chars, int start, int len) { 2707 mChars = chars; 2708 mStart = start; 2709 mLength = len; 2710 } 2711 2712 /* package */ void set(char[] chars, int start, int len) { 2713 mChars = chars; 2714 mStart = start; 2715 mLength = len; 2716 } 2717 2718 public int length() { 2719 return mLength; 2720 } 2721 2722 public char charAt(int off) { 2723 return mChars[off + mStart]; 2724 } 2725 2726 public String toString() { 2727 return new String(mChars, mStart, mLength); 2728 } 2729 2730 public CharSequence subSequence(int start, int end) { 2731 if (start < 0 || end < 0 || start > mLength || end > mLength) { 2732 throw new IndexOutOfBoundsException(start + ", " + end); 2733 } 2734 2735 return new String(mChars, start + mStart, end - start); 2736 } 2737 2738 public void getChars(int start, int end, char[] buf, int off) { 2739 if (start < 0 || end < 0 || start > mLength || end > mLength) { 2740 throw new IndexOutOfBoundsException(start + ", " + end); 2741 } 2742 2743 System.arraycopy(mChars, start + mStart, buf, off, end - start); 2744 } 2745 2746 public void drawText(Canvas c, int start, int end, 2747 float x, float y, Paint p) { 2748 c.drawText(mChars, start + mStart, end - start, x, y, p); 2749 } 2750 2751 public float measureText(int start, int end, Paint p) { 2752 return p.measureText(mChars, start + mStart, end - start); 2753 } 2754 2755 public int getTextWidths(int start, int end, float[] widths, Paint p) { 2756 return p.getTextWidths(mChars, start + mStart, end - start, widths); 2757 } 2758 } 2759 2760 /** 2761 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)}, 2762 * except that the cursor position (if any) is retained in the new text. 2763 * 2764 * @see #setText(CharSequence, android.widget.TextView.BufferType) 2765 */ 2766 public final void setTextKeepState(CharSequence text, BufferType type) { 2767 int start = getSelectionStart(); 2768 int end = getSelectionEnd(); 2769 int len = text.length(); 2770 2771 setText(text, type); 2772 2773 if (start >= 0 || end >= 0) { 2774 if (mText instanceof Spannable) { 2775 Selection.setSelection((Spannable) mText, 2776 Math.max(0, Math.min(start, len)), 2777 Math.max(0, Math.min(end, len))); 2778 } 2779 } 2780 } 2781 2782 @android.view.RemotableViewMethod 2783 public final void setText(int resid) { 2784 setText(getContext().getResources().getText(resid)); 2785 } 2786 2787 public final void setText(int resid, BufferType type) { 2788 setText(getContext().getResources().getText(resid), type); 2789 } 2790 2791 /** 2792 * Sets the text to be displayed when the text of the TextView is empty. 2793 * Null means to use the normal empty text. The hint does not currently 2794 * participate in determining the size of the view. 2795 * 2796 * This method is deprecated. Use {link #setHint(int, String)} or 2797 * {link #setHint(CharSequence, String)} instead. 2798 * 2799 * @attr ref android.R.styleable#TextView_hint 2800 */ 2801 @android.view.RemotableViewMethod 2802 public final void setHint(CharSequence hint) { 2803 mHint = TextUtils.stringOrSpannedString(hint); 2804 2805 if (mLayout != null) { 2806 checkForRelayout(); 2807 } 2808 2809 if (mText.length() == 0) 2810 invalidate(); 2811 } 2812 2813 /** 2814 * Sets the text to be displayed when the text of the TextView is empty, 2815 * from a resource. 2816 * 2817 * This method is deprecated. Use {link #setHint(int, String)} or 2818 * {link #setHint(CharSequence, String)} instead. 2819 * 2820 * @attr ref android.R.styleable#TextView_hint 2821 */ 2822 @android.view.RemotableViewMethod 2823 public final void setHint(int resid) { 2824 setHint(getContext().getResources().getText(resid)); 2825 } 2826 2827 /** 2828 * Returns the hint that is displayed when the text of the TextView 2829 * is empty. 2830 * 2831 * @attr ref android.R.styleable#TextView_hint 2832 */ 2833 @ViewDebug.CapturedViewProperty 2834 public CharSequence getHint() { 2835 return mHint; 2836 } 2837 2838 /** 2839 * Set the type of the content with a constant as defined for 2840 * {@link EditorInfo#inputType}. This will take care of changing 2841 * the key listener, by calling {@link #setKeyListener(KeyListener)}, to 2842 * match the given content type. If the given content type is 2843 * {@link EditorInfo#TYPE_NULL} then a soft keyboard will 2844 * not be displayed for this text view. 2845 * 2846 * @see #getInputType() 2847 * @see #setRawInputType(int) 2848 * @see android.text.InputType 2849 * @attr ref android.R.styleable#TextView_inputType 2850 */ 2851 public void setInputType(int type) { 2852 setInputType(type, false); 2853 final int variation = type&(EditorInfo.TYPE_MASK_CLASS 2854 |EditorInfo.TYPE_MASK_VARIATION); 2855 final boolean isPassword = variation 2856 == (EditorInfo.TYPE_CLASS_TEXT 2857 |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); 2858 boolean forceUpdate = false; 2859 if (isPassword) { 2860 setTransformationMethod(PasswordTransformationMethod.getInstance()); 2861 setTypefaceByIndex(MONOSPACE, 0); 2862 } else if (mTransformation == PasswordTransformationMethod.getInstance()) { 2863 // We need to clean up if we were previously in password mode. 2864 if (variation != (EditorInfo.TYPE_CLASS_TEXT 2865 |EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)) { 2866 setTypefaceByIndex(-1, -1); 2867 } 2868 forceUpdate = true; 2869 } else if (variation == (EditorInfo.TYPE_CLASS_TEXT 2870 |EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)) { 2871 setTypefaceByIndex(MONOSPACE, 0); 2872 } 2873 2874 boolean multiLine = (type&(EditorInfo.TYPE_MASK_CLASS 2875 | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) == 2876 (EditorInfo.TYPE_CLASS_TEXT 2877 | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 2878 2879 // We need to update the single line mode if it has changed or we 2880 // were previously in password mode. 2881 if (mSingleLine == multiLine || forceUpdate) { 2882 // Change single line mode, but only change the transformation if 2883 // we are not in password mode. 2884 applySingleLine(!multiLine, !isPassword); 2885 } 2886 2887 InputMethodManager imm = InputMethodManager.peekInstance(); 2888 if (imm != null) imm.restartInput(this); 2889 } 2890 2891 /** 2892 * Directly change the content type integer of the text view, without 2893 * modifying any other state. 2894 * @see #setInputType(int) 2895 * @see android.text.InputType 2896 * @attr ref android.R.styleable#TextView_inputType 2897 */ 2898 public void setRawInputType(int type) { 2899 mInputType = type; 2900 } 2901 2902 private void setInputType(int type, boolean direct) { 2903 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 2904 KeyListener input; 2905 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 2906 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) 2907 != 0; 2908 TextKeyListener.Capitalize cap; 2909 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 2910 cap = TextKeyListener.Capitalize.CHARACTERS; 2911 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 2912 cap = TextKeyListener.Capitalize.WORDS; 2913 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 2914 cap = TextKeyListener.Capitalize.SENTENCES; 2915 } else { 2916 cap = TextKeyListener.Capitalize.NONE; 2917 } 2918 input = TextKeyListener.getInstance(autotext, cap); 2919 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 2920 input = DigitsKeyListener.getInstance( 2921 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 2922 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 2923 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 2924 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 2925 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 2926 input = DateKeyListener.getInstance(); 2927 break; 2928 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 2929 input = TimeKeyListener.getInstance(); 2930 break; 2931 default: 2932 input = DateTimeKeyListener.getInstance(); 2933 break; 2934 } 2935 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 2936 input = DialerKeyListener.getInstance(); 2937 } else { 2938 input = TextKeyListener.getInstance(); 2939 } 2940 mInputType = type; 2941 if (direct) mInput = input; 2942 else { 2943 setKeyListenerOnly(input); 2944 } 2945 } 2946 2947 /** 2948 * Get the type of the content. 2949 * 2950 * @see #setInputType(int) 2951 * @see android.text.InputType 2952 */ 2953 public int getInputType() { 2954 return mInputType; 2955 } 2956 2957 /** 2958 * Change the editor type integer associated with the text view, which 2959 * will be reported to an IME with {@link EditorInfo#imeOptions} when it 2960 * has focus. 2961 * @see #getImeOptions 2962 * @see android.view.inputmethod.EditorInfo 2963 * @attr ref android.R.styleable#TextView_imeOptions 2964 */ 2965 public void setImeOptions(int imeOptions) { 2966 if (mInputContentType == null) { 2967 mInputContentType = new InputContentType(); 2968 } 2969 mInputContentType.imeOptions = imeOptions; 2970 } 2971 2972 /** 2973 * Get the type of the IME editor. 2974 * 2975 * @see #setImeOptions(int) 2976 * @see android.view.inputmethod.EditorInfo 2977 */ 2978 public int getImeOptions() { 2979 return mInputContentType != null 2980 ? mInputContentType.imeOptions : EditorInfo.IME_NULL; 2981 } 2982 2983 /** 2984 * Change the custom IME action associated with the text view, which 2985 * will be reported to an IME with {@link EditorInfo#actionLabel} 2986 * and {@link EditorInfo#actionId} when it has focus. 2987 * @see #getImeActionLabel 2988 * @see #getImeActionId 2989 * @see android.view.inputmethod.EditorInfo 2990 * @attr ref android.R.styleable#TextView_imeActionLabel 2991 * @attr ref android.R.styleable#TextView_imeActionId 2992 */ 2993 public void setImeActionLabel(CharSequence label, int actionId) { 2994 if (mInputContentType == null) { 2995 mInputContentType = new InputContentType(); 2996 } 2997 mInputContentType.imeActionLabel = label; 2998 mInputContentType.imeActionId = actionId; 2999 } 3000 3001 /** 3002 * Get the IME action label previous set with {@link #setImeActionLabel}. 3003 * 3004 * @see #setImeActionLabel 3005 * @see android.view.inputmethod.EditorInfo 3006 */ 3007 public CharSequence getImeActionLabel() { 3008 return mInputContentType != null 3009 ? mInputContentType.imeActionLabel : null; 3010 } 3011 3012 /** 3013 * Get the IME action ID previous set with {@link #setImeActionLabel}. 3014 * 3015 * @see #setImeActionLabel 3016 * @see android.view.inputmethod.EditorInfo 3017 */ 3018 public int getImeActionId() { 3019 return mInputContentType != null 3020 ? mInputContentType.imeActionId : 0; 3021 } 3022 3023 /** 3024 * Set a special listener to be called when an action is performed 3025 * on the text view. This will be called when the enter key is pressed, 3026 * or when an action supplied to the IME is selected by the user. Setting 3027 * this means that the normal hard key event will not insert a newline 3028 * into the text view, even if it is multi-line; holding down the ALT 3029 * modifier will, however, allow the user to insert a newline character. 3030 */ 3031 public void setOnEditorActionListener(OnEditorActionListener l) { 3032 if (mInputContentType == null) { 3033 mInputContentType = new InputContentType(); 3034 } 3035 mInputContentType.onEditorActionListener = l; 3036 } 3037 3038 /** 3039 * Called when an attached input method calls 3040 * {@link InputConnection#performEditorAction(int) 3041 * InputConnection.performEditorAction()} 3042 * for this text view. The default implementation will call your action 3043 * listener supplied to {@link #setOnEditorActionListener}, or perform 3044 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 3045 * EditorInfo.IME_ACTION_NEXT} or {@link EditorInfo#IME_ACTION_DONE 3046 * EditorInfo.IME_ACTION_DONE}. 3047 * 3048 * <p>For backwards compatibility, if no IME options have been set and the 3049 * text view would not normally advance focus on enter, then 3050 * the NEXT and DONE actions received here will be turned into an enter 3051 * key down/up pair to go through the normal key handling. 3052 * 3053 * @param actionCode The code of the action being performed. 3054 * 3055 * @see #setOnEditorActionListener 3056 */ 3057 public void onEditorAction(int actionCode) { 3058 final InputContentType ict = mInputContentType; 3059 if (ict != null) { 3060 if (ict.onEditorActionListener != null) { 3061 if (ict.onEditorActionListener.onEditorAction(this, 3062 actionCode, null)) { 3063 return; 3064 } 3065 } 3066 3067 // This is the handling for some default action. 3068 // Note that for backwards compatibility we don't do this 3069 // default handling if explicit ime options have not been given, 3070 // instead turning this into the normal enter key codes that an 3071 // app may be expecting. 3072 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 3073 View v = focusSearch(FOCUS_DOWN); 3074 if (v != null) { 3075 if (!v.requestFocus(FOCUS_DOWN)) { 3076 throw new IllegalStateException("focus search returned a view " + 3077 "that wasn't able to take focus!"); 3078 } 3079 } 3080 return; 3081 3082 } else if (actionCode == EditorInfo.IME_ACTION_DONE) { 3083 InputMethodManager imm = InputMethodManager.peekInstance(); 3084 if (imm != null) { 3085 imm.hideSoftInputFromWindow(getWindowToken(), 0); 3086 } 3087 return; 3088 } 3089 } 3090 3091 Handler h = getHandler(); 3092 if (h != null) { 3093 long eventTime = SystemClock.uptimeMillis(); 3094 h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME, 3095 new KeyEvent(eventTime, eventTime, 3096 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0, 3097 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 3098 | KeyEvent.FLAG_EDITOR_ACTION))); 3099 h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME, 3100 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 3101 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0, 3102 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 3103 | KeyEvent.FLAG_EDITOR_ACTION))); 3104 } 3105 } 3106 3107 /** 3108 * Set the private content type of the text, which is the 3109 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 3110 * field that will be filled in when creating an input connection. 3111 * 3112 * @see #getPrivateImeOptions() 3113 * @see EditorInfo#privateImeOptions 3114 * @attr ref android.R.styleable#TextView_privateImeOptions 3115 */ 3116 public void setPrivateImeOptions(String type) { 3117 if (mInputContentType == null) mInputContentType = new InputContentType(); 3118 mInputContentType.privateImeOptions = type; 3119 } 3120 3121 /** 3122 * Get the private type of the content. 3123 * 3124 * @see #setPrivateImeOptions(String) 3125 * @see EditorInfo#privateImeOptions 3126 */ 3127 public String getPrivateImeOptions() { 3128 return mInputContentType != null 3129 ? mInputContentType.privateImeOptions : null; 3130 } 3131 3132 /** 3133 * Set the extra input data of the text, which is the 3134 * {@link EditorInfo#extras TextBoxAttribute.extras} 3135 * Bundle that will be filled in when creating an input connection. The 3136 * given integer is the resource ID of an XML resource holding an 3137 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 3138 * 3139 * @see #getInputExtras(boolean) 3140 * @see EditorInfo#extras 3141 * @attr ref android.R.styleable#TextView_editorExtras 3142 */ 3143 public void setInputExtras(int xmlResId) 3144 throws XmlPullParserException, IOException { 3145 XmlResourceParser parser = getResources().getXml(xmlResId); 3146 if (mInputContentType == null) mInputContentType = new InputContentType(); 3147 mInputContentType.extras = new Bundle(); 3148 getResources().parseBundleExtras(parser, mInputContentType.extras); 3149 } 3150 3151 /** 3152 * Retrieve the input extras currently associated with the text view, which 3153 * can be viewed as well as modified. 3154 * 3155 * @param create If true, the extras will be created if they don't already 3156 * exist. Otherwise, null will be returned if none have been created. 3157 * @see #setInputExtras(int)View 3158 * @see EditorInfo#extras 3159 * @attr ref android.R.styleable#TextView_editorExtras 3160 */ 3161 public Bundle getInputExtras(boolean create) { 3162 if (mInputContentType == null) { 3163 if (!create) return null; 3164 mInputContentType = new InputContentType(); 3165 } 3166 if (mInputContentType.extras == null) { 3167 if (!create) return null; 3168 mInputContentType.extras = new Bundle(); 3169 } 3170 return mInputContentType.extras; 3171 } 3172 3173 /** 3174 * Returns the error message that was set to be displayed with 3175 * {@link #setError}, or <code>null</code> if no error was set 3176 * or if it the error was cleared by the widget after user input. 3177 */ 3178 public CharSequence getError() { 3179 return mError; 3180 } 3181 3182 /** 3183 * Sets the right-hand compound drawable of the TextView to the "error" 3184 * icon and sets an error message that will be displayed in a popup when 3185 * the TextView has focus. The icon and error message will be reset to 3186 * null when any key events cause changes to the TextView's text. If the 3187 * <code>error</code> is <code>null</code>, the error message and icon 3188 * will be cleared. 3189 */ 3190 @android.view.RemotableViewMethod 3191 public void setError(CharSequence error) { 3192 if (error == null) { 3193 setError(null, null); 3194 } else { 3195 Drawable dr = getContext().getResources(). 3196 getDrawable(com.android.internal.R.drawable. 3197 indicator_input_error); 3198 3199 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 3200 setError(error, dr); 3201 } 3202 } 3203 3204 /** 3205 * Sets the right-hand compound drawable of the TextView to the specified 3206 * icon and sets an error message that will be displayed in a popup when 3207 * the TextView has focus. The icon and error message will be reset to 3208 * null when any key events cause changes to the TextView's text. The 3209 * drawable must already have had {@link Drawable#setBounds} set on it. 3210 * If the <code>error</code> is <code>null</code>, the error message will 3211 * be cleared (and you should provide a <code>null</code> icon as well). 3212 */ 3213 public void setError(CharSequence error, Drawable icon) { 3214 error = TextUtils.stringOrSpannedString(error); 3215 3216 mError = error; 3217 mErrorWasChanged = true; 3218 final Drawables dr = mDrawables; 3219 if (dr != null) { 3220 setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop, 3221 icon, dr.mDrawableBottom); 3222 } else { 3223 setCompoundDrawables(null, null, icon, null); 3224 } 3225 3226 if (error == null) { 3227 if (mPopup != null) { 3228 if (mPopup.isShowing()) { 3229 mPopup.dismiss(); 3230 } 3231 3232 mPopup = null; 3233 } 3234 } else { 3235 if (isFocused()) { 3236 showError(); 3237 } 3238 } 3239 } 3240 3241 private void showError() { 3242 if (getWindowToken() == null) { 3243 mShowErrorAfterAttach = true; 3244 return; 3245 } 3246 3247 if (mPopup == null) { 3248 LayoutInflater inflater = LayoutInflater.from(getContext()); 3249 final TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint, 3250 null); 3251 3252 mPopup = new ErrorPopup(err, 200, 50); 3253 mPopup.setFocusable(false); 3254 // The user is entering text, so the input method is needed. We 3255 // don't want the popup to be displayed on top of it. 3256 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); 3257 } 3258 3259 TextView tv = (TextView) mPopup.getContentView(); 3260 chooseSize(mPopup, mError, tv); 3261 tv.setText(mError); 3262 3263 mPopup.showAsDropDown(this, getErrorX(), getErrorY()); 3264 mPopup.fixDirection(mPopup.isAboveAnchor()); 3265 } 3266 3267 private static class ErrorPopup extends PopupWindow { 3268 private boolean mAbove = false; 3269 private TextView mView; 3270 3271 ErrorPopup(TextView v, int width, int height) { 3272 super(v, width, height); 3273 mView = v; 3274 } 3275 3276 void fixDirection(boolean above) { 3277 mAbove = above; 3278 3279 if (above) { 3280 mView.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error_above); 3281 } else { 3282 mView.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error); 3283 } 3284 } 3285 3286 @Override 3287 public void update(int x, int y, int w, int h, boolean force) { 3288 super.update(x, y, w, h, force); 3289 3290 boolean above = isAboveAnchor(); 3291 if (above != mAbove) { 3292 fixDirection(above); 3293 } 3294 } 3295 } 3296 3297 /** 3298 * Returns the Y offset to make the pointy top of the error point 3299 * at the middle of the error icon. 3300 */ 3301 private int getErrorX() { 3302 /* 3303 * The "25" is the distance between the point and the right edge 3304 * of the background 3305 */ 3306 3307 final Drawables dr = mDrawables; 3308 return getWidth() - mPopup.getWidth() 3309 - getPaddingRight() 3310 - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + 25; 3311 } 3312 3313 /** 3314 * Returns the Y offset to make the pointy top of the error point 3315 * at the bottom of the error icon. 3316 */ 3317 private int getErrorY() { 3318 /* 3319 * Compound, not extended, because the icon is not clipped 3320 * if the text height is smaller. 3321 */ 3322 int vspace = mBottom - mTop - 3323 getCompoundPaddingBottom() - getCompoundPaddingTop(); 3324 3325 final Drawables dr = mDrawables; 3326 int icontop = getCompoundPaddingTop() 3327 + (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2; 3328 3329 /* 3330 * The "2" is the distance between the point and the top edge 3331 * of the background. 3332 */ 3333 3334 return icontop + (dr != null ? dr.mDrawableHeightRight : 0) 3335 - getHeight() - 2; 3336 } 3337 3338 private void hideError() { 3339 if (mPopup != null) { 3340 if (mPopup.isShowing()) { 3341 mPopup.dismiss(); 3342 } 3343 } 3344 3345 mShowErrorAfterAttach = false; 3346 } 3347 3348 private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) { 3349 int wid = tv.getPaddingLeft() + tv.getPaddingRight(); 3350 int ht = tv.getPaddingTop() + tv.getPaddingBottom(); 3351 3352 /* 3353 * Figure out how big the text would be if we laid it out to the 3354 * full width of this view minus the border. 3355 */ 3356 int cap = getWidth() - wid; 3357 if (cap < 0) { 3358 cap = 200; // We must not be measured yet -- setFrame() will fix it. 3359 } 3360 3361 Layout l = new StaticLayout(text, tv.getPaint(), cap, 3362 Layout.Alignment.ALIGN_NORMAL, 1, 0, true); 3363 float max = 0; 3364 for (int i = 0; i < l.getLineCount(); i++) { 3365 max = Math.max(max, l.getLineWidth(i)); 3366 } 3367 3368 /* 3369 * Now set the popup size to be big enough for the text plus the border. 3370 */ 3371 pop.setWidth(wid + (int) Math.ceil(max)); 3372 pop.setHeight(ht + l.getHeight()); 3373 } 3374 3375 3376 @Override 3377 protected boolean setFrame(int l, int t, int r, int b) { 3378 boolean result = super.setFrame(l, t, r, b); 3379 3380 if (mPopup != null) { 3381 TextView tv = (TextView) mPopup.getContentView(); 3382 chooseSize(mPopup, mError, tv); 3383 mPopup.update(this, getErrorX(), getErrorY(), -1, -1); 3384 } 3385 3386 restartMarqueeIfNeeded(); 3387 3388 return result; 3389 } 3390 3391 private void restartMarqueeIfNeeded() { 3392 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 3393 mRestartMarquee = false; 3394 startMarquee(); 3395 } 3396 } 3397 3398 /** 3399 * Sets the list of input filters that will be used if the buffer is 3400 * Editable. Has no effect otherwise. 3401 * 3402 * @attr ref android.R.styleable#TextView_maxLength 3403 */ 3404 public void setFilters(InputFilter[] filters) { 3405 if (filters == null) { 3406 throw new IllegalArgumentException(); 3407 } 3408 3409 mFilters = filters; 3410 3411 if (mText instanceof Editable) { 3412 setFilters((Editable) mText, filters); 3413 } 3414 } 3415 3416 /** 3417 * Sets the list of input filters on the specified Editable, 3418 * and includes mInput in the list if it is an InputFilter. 3419 */ 3420 private void setFilters(Editable e, InputFilter[] filters) { 3421 if (mInput instanceof InputFilter) { 3422 InputFilter[] nf = new InputFilter[filters.length + 1]; 3423 3424 System.arraycopy(filters, 0, nf, 0, filters.length); 3425 nf[filters.length] = (InputFilter) mInput; 3426 3427 e.setFilters(nf); 3428 } else { 3429 e.setFilters(filters); 3430 } 3431 } 3432 3433 /** 3434 * Returns the current list of input filters. 3435 */ 3436 public InputFilter[] getFilters() { 3437 return mFilters; 3438 } 3439 3440 ///////////////////////////////////////////////////////////////////////// 3441 3442 private int getVerticalOffset(boolean forceNormal) { 3443 int voffset = 0; 3444 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 3445 3446 Layout l = mLayout; 3447 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 3448 l = mHintLayout; 3449 } 3450 3451 if (gravity != Gravity.TOP) { 3452 int boxht; 3453 3454 if (l == mHintLayout) { 3455 boxht = getMeasuredHeight() - getCompoundPaddingTop() - 3456 getCompoundPaddingBottom(); 3457 } else { 3458 boxht = getMeasuredHeight() - getExtendedPaddingTop() - 3459 getExtendedPaddingBottom(); 3460 } 3461 int textht = l.getHeight(); 3462 3463 if (textht < boxht) { 3464 if (gravity == Gravity.BOTTOM) 3465 voffset = boxht - textht; 3466 else // (gravity == Gravity.CENTER_VERTICAL) 3467 voffset = (boxht - textht) >> 1; 3468 } 3469 } 3470 return voffset; 3471 } 3472 3473 private int getBottomVerticalOffset(boolean forceNormal) { 3474 int voffset = 0; 3475 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 3476 3477 Layout l = mLayout; 3478 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 3479 l = mHintLayout; 3480 } 3481 3482 if (gravity != Gravity.BOTTOM) { 3483 int boxht; 3484 3485 if (l == mHintLayout) { 3486 boxht = getMeasuredHeight() - getCompoundPaddingTop() - 3487 getCompoundPaddingBottom(); 3488 } else { 3489 boxht = getMeasuredHeight() - getExtendedPaddingTop() - 3490 getExtendedPaddingBottom(); 3491 } 3492 int textht = l.getHeight(); 3493 3494 if (textht < boxht) { 3495 if (gravity == Gravity.TOP) 3496 voffset = boxht - textht; 3497 else // (gravity == Gravity.CENTER_VERTICAL) 3498 voffset = (boxht - textht) >> 1; 3499 } 3500 } 3501 return voffset; 3502 } 3503 3504 private void invalidateCursorPath() { 3505 if (mHighlightPathBogus) { 3506 invalidateCursor(); 3507 } else { 3508 synchronized (sTempRect) { 3509 /* 3510 * The reason for this concern about the thickness of the 3511 * cursor and doing the floor/ceil on the coordinates is that 3512 * some EditTexts (notably textfields in the Browser) have 3513 * anti-aliased text where not all the characters are 3514 * necessarily at integer-multiple locations. This should 3515 * make sure the entire cursor gets invalidated instead of 3516 * sometimes missing half a pixel. 3517 */ 3518 3519 float thick = FloatMath.ceil(mTextPaint.getStrokeWidth()); 3520 if (thick < 1.0f) { 3521 thick = 1.0f; 3522 } 3523 3524 thick /= 2; 3525 3526 mHighlightPath.computeBounds(sTempRect, false); 3527 3528 int left = getCompoundPaddingLeft(); 3529 int top = getExtendedPaddingTop() + getVerticalOffset(true); 3530 3531 invalidate((int) FloatMath.floor(left + sTempRect.left - thick), 3532 (int) FloatMath.floor(top + sTempRect.top - thick), 3533 (int) FloatMath.ceil(left + sTempRect.right + thick), 3534 (int) FloatMath.ceil(top + sTempRect.bottom + thick)); 3535 } 3536 } 3537 } 3538 3539 private void invalidateCursor() { 3540 int where = Selection.getSelectionEnd(mText); 3541 3542 invalidateCursor(where, where, where); 3543 } 3544 3545 private void invalidateCursor(int a, int b, int c) { 3546 if (mLayout == null) { 3547 invalidate(); 3548 } else { 3549 if (a >= 0 || b >= 0 || c >= 0) { 3550 int first = Math.min(Math.min(a, b), c); 3551 int last = Math.max(Math.max(a, b), c); 3552 3553 int line = mLayout.getLineForOffset(first); 3554 int top = mLayout.getLineTop(line); 3555 3556 // This is ridiculous, but the descent from the line above 3557 // can hang down into the line we really want to redraw, 3558 // so we have to invalidate part of the line above to make 3559 // sure everything that needs to be redrawn really is. 3560 // (But not the whole line above, because that would cause 3561 // the same problem with the descenders on the line above it!) 3562 if (line > 0) { 3563 top -= mLayout.getLineDescent(line - 1); 3564 } 3565 3566 int line2; 3567 3568 if (first == last) 3569 line2 = line; 3570 else 3571 line2 = mLayout.getLineForOffset(last); 3572 3573 int bottom = mLayout.getLineTop(line2 + 1); 3574 int voffset = getVerticalOffset(true); 3575 3576 int left = getCompoundPaddingLeft() + mScrollX; 3577 invalidate(left, top + voffset + getExtendedPaddingTop(), 3578 left + getWidth() - getCompoundPaddingLeft() - 3579 getCompoundPaddingRight(), 3580 bottom + voffset + getExtendedPaddingTop()); 3581 } 3582 } 3583 } 3584 3585 private void registerForPreDraw() { 3586 final ViewTreeObserver observer = getViewTreeObserver(); 3587 if (observer == null) { 3588 return; 3589 } 3590 3591 if (mPreDrawState == PREDRAW_NOT_REGISTERED) { 3592 observer.addOnPreDrawListener(this); 3593 mPreDrawState = PREDRAW_PENDING; 3594 } else if (mPreDrawState == PREDRAW_DONE) { 3595 mPreDrawState = PREDRAW_PENDING; 3596 } 3597 3598 // else state is PREDRAW_PENDING, so keep waiting. 3599 } 3600 3601 /** 3602 * {@inheritDoc} 3603 */ 3604 public boolean onPreDraw() { 3605 if (mPreDrawState != PREDRAW_PENDING) { 3606 return true; 3607 } 3608 3609 if (mLayout == null) { 3610 assumeLayout(); 3611 } 3612 3613 boolean changed = false; 3614 3615 if (mMovement != null) { 3616 int curs = Selection.getSelectionEnd(mText); 3617 3618 /* 3619 * TODO: This should really only keep the end in view if 3620 * it already was before the text changed. I'm not sure 3621 * of a good way to tell from here if it was. 3622 */ 3623 if (curs < 0 && 3624 (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 3625 curs = mText.length(); 3626 } 3627 3628 if (curs >= 0) { 3629 changed = bringPointIntoView(curs); 3630 } 3631 } else { 3632 changed = bringTextIntoView(); 3633 } 3634 3635 mPreDrawState = PREDRAW_DONE; 3636 return !changed; 3637 } 3638 3639 @Override 3640 protected void onAttachedToWindow() { 3641 super.onAttachedToWindow(); 3642 3643 mTemporaryDetach = false; 3644 3645 if (mShowErrorAfterAttach) { 3646 showError(); 3647 mShowErrorAfterAttach = false; 3648 } 3649 } 3650 3651 @Override 3652 protected void onDetachedFromWindow() { 3653 super.onDetachedFromWindow(); 3654 3655 if (mPreDrawState != PREDRAW_NOT_REGISTERED) { 3656 final ViewTreeObserver observer = getViewTreeObserver(); 3657 if (observer != null) { 3658 observer.removeOnPreDrawListener(this); 3659 mPreDrawState = PREDRAW_NOT_REGISTERED; 3660 } 3661 } 3662 3663 if (mError != null) { 3664 hideError(); 3665 } 3666 } 3667 3668 @Override 3669 protected boolean isPaddingOffsetRequired() { 3670 return mShadowRadius != 0; 3671 } 3672 3673 @Override 3674 protected int getLeftPaddingOffset() { 3675 return (int) Math.min(0, mShadowDx - mShadowRadius); 3676 } 3677 3678 @Override 3679 protected int getTopPaddingOffset() { 3680 return (int) Math.min(0, mShadowDy - mShadowRadius); 3681 } 3682 3683 @Override 3684 protected int getBottomPaddingOffset() { 3685 return (int) Math.max(0, mShadowDy + mShadowRadius); 3686 } 3687 3688 @Override 3689 protected int getRightPaddingOffset() { 3690 return (int) Math.max(0, mShadowDx + mShadowRadius); 3691 } 3692 3693 @Override 3694 protected boolean verifyDrawable(Drawable who) { 3695 final boolean verified = super.verifyDrawable(who); 3696 if (!verified && mDrawables != null) { 3697 return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop || 3698 who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom; 3699 } 3700 return verified; 3701 } 3702 3703 @Override 3704 protected void onDraw(Canvas canvas) { 3705 restartMarqueeIfNeeded(); 3706 3707 // Draw the background for this view 3708 super.onDraw(canvas); 3709 3710 final int compoundPaddingLeft = getCompoundPaddingLeft(); 3711 final int compoundPaddingTop = getCompoundPaddingTop(); 3712 final int compoundPaddingRight = getCompoundPaddingRight(); 3713 final int compoundPaddingBottom = getCompoundPaddingBottom(); 3714 final int scrollX = mScrollX; 3715 final int scrollY = mScrollY; 3716 final int right = mRight; 3717 final int left = mLeft; 3718 final int bottom = mBottom; 3719 final int top = mTop; 3720 3721 final Drawables dr = mDrawables; 3722 if (dr != null) { 3723 /* 3724 * Compound, not extended, because the icon is not clipped 3725 * if the text height is smaller. 3726 */ 3727 3728 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 3729 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 3730 3731 if (dr.mDrawableLeft != null) { 3732 canvas.save(); 3733 canvas.translate(scrollX + mPaddingLeft, 3734 scrollY + compoundPaddingTop + 3735 (vspace - dr.mDrawableHeightLeft) / 2); 3736 dr.mDrawableLeft.draw(canvas); 3737 canvas.restore(); 3738 } 3739 3740 if (dr.mDrawableRight != null) { 3741 canvas.save(); 3742 canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight, 3743 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 3744 dr.mDrawableRight.draw(canvas); 3745 canvas.restore(); 3746 } 3747 3748 if (dr.mDrawableTop != null) { 3749 canvas.save(); 3750 canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2, 3751 scrollY + mPaddingTop); 3752 dr.mDrawableTop.draw(canvas); 3753 canvas.restore(); 3754 } 3755 3756 if (dr.mDrawableBottom != null) { 3757 canvas.save(); 3758 canvas.translate(scrollX + compoundPaddingLeft + 3759 (hspace - dr.mDrawableWidthBottom) / 2, 3760 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 3761 dr.mDrawableBottom.draw(canvas); 3762 canvas.restore(); 3763 } 3764 } 3765 3766 if (mPreDrawState == PREDRAW_DONE) { 3767 final ViewTreeObserver observer = getViewTreeObserver(); 3768 if (observer != null) { 3769 observer.removeOnPreDrawListener(this); 3770 mPreDrawState = PREDRAW_NOT_REGISTERED; 3771 } 3772 } 3773 3774 int color = mCurTextColor; 3775 3776 if (mLayout == null) { 3777 assumeLayout(); 3778 } 3779 3780 Layout layout = mLayout; 3781 int cursorcolor = color; 3782 3783 if (mHint != null && mText.length() == 0) { 3784 if (mHintTextColor != null) { 3785 color = mCurHintTextColor; 3786 } 3787 3788 layout = mHintLayout; 3789 } 3790 3791 mTextPaint.setColor(color); 3792 mTextPaint.drawableState = getDrawableState(); 3793 3794 canvas.save(); 3795 /* Would be faster if we didn't have to do this. Can we chop the 3796 (displayable) text so that we don't need to do this ever? 3797 */ 3798 3799 int extendedPaddingTop = getExtendedPaddingTop(); 3800 int extendedPaddingBottom = getExtendedPaddingBottom(); 3801 3802 float clipLeft = compoundPaddingLeft + scrollX; 3803 float clipTop = extendedPaddingTop + scrollY; 3804 float clipRight = right - left - compoundPaddingRight + scrollX; 3805 float clipBottom = bottom - top - extendedPaddingBottom + scrollY; 3806 3807 if (mShadowRadius != 0) { 3808 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 3809 clipRight += Math.max(0, mShadowDx + mShadowRadius); 3810 3811 clipTop += Math.min(0, mShadowDy - mShadowRadius); 3812 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 3813 } 3814 3815 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 3816 3817 int voffsetText = 0; 3818 int voffsetCursor = 0; 3819 3820 // translate in by our padding 3821 { 3822 /* shortcircuit calling getVerticaOffset() */ 3823 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 3824 voffsetText = getVerticalOffset(false); 3825 voffsetCursor = getVerticalOffset(true); 3826 } 3827 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 3828 } 3829 3830 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 3831 if (!mSingleLine && getLineCount() == 1 && canMarquee() && 3832 (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 3833 canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft - 3834 getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f); 3835 } 3836 3837 if (mMarquee != null && mMarquee.isRunning()) { 3838 canvas.translate(-mMarquee.mScroll, 0.0f); 3839 } 3840 } 3841 3842 Path highlight = null; 3843 int selStart = -1, selEnd = -1; 3844 3845 // If there is no movement method, then there can be no selection. 3846 // Check that first and attempt to skip everything having to do with 3847 // the cursor. 3848 // XXX This is not strictly true -- a program could set the 3849 // selection manually if it really wanted to. 3850 if (mMovement != null && (isFocused() || isPressed())) { 3851 selStart = Selection.getSelectionStart(mText); 3852 selEnd = Selection.getSelectionEnd(mText); 3853 3854 if (mCursorVisible && selStart >= 0 && isEnabled()) { 3855 if (mHighlightPath == null) 3856 mHighlightPath = new Path(); 3857 3858 if (selStart == selEnd) { 3859 if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) 3860 < BLINK) { 3861 if (mHighlightPathBogus) { 3862 mHighlightPath.reset(); 3863 mLayout.getCursorPath(selStart, mHighlightPath, mText); 3864 mHighlightPathBogus = false; 3865 } 3866 3867 // XXX should pass to skin instead of drawing directly 3868 mHighlightPaint.setColor(cursorcolor); 3869 mHighlightPaint.setStyle(Paint.Style.STROKE); 3870 3871 highlight = mHighlightPath; 3872 } 3873 } else { 3874 if (mHighlightPathBogus) { 3875 mHighlightPath.reset(); 3876 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 3877 mHighlightPathBogus = false; 3878 } 3879 3880 // XXX should pass to skin instead of drawing directly 3881 mHighlightPaint.setColor(mHighlightColor); 3882 mHighlightPaint.setStyle(Paint.Style.FILL); 3883 3884 highlight = mHighlightPath; 3885 } 3886 } 3887 } 3888 3889 /* Comment out until we decide what to do about animations 3890 boolean isLinearTextOn = false; 3891 if (currentTransformation != null) { 3892 isLinearTextOn = mTextPaint.isLinearTextOn(); 3893 Matrix m = currentTransformation.getMatrix(); 3894 if (!m.isIdentity()) { 3895 // mTextPaint.setLinearTextOn(true); 3896 } 3897 } 3898 */ 3899 3900 final InputMethodState ims = mInputMethodState; 3901 if (ims != null && ims.mBatchEditNesting == 0) { 3902 InputMethodManager imm = InputMethodManager.peekInstance(); 3903 if (imm != null) { 3904 if (imm.isActive(this)) { 3905 boolean reported = false; 3906 if (ims.mContentChanged || ims.mSelectionModeChanged) { 3907 // We are in extract mode and the content has changed 3908 // in some way... just report complete new text to the 3909 // input method. 3910 reported = reportExtractedText(); 3911 } 3912 if (!reported && highlight != null) { 3913 int candStart = -1; 3914 int candEnd = -1; 3915 if (mText instanceof Spannable) { 3916 Spannable sp = (Spannable)mText; 3917 candStart = EditableInputConnection.getComposingSpanStart(sp); 3918 candEnd = EditableInputConnection.getComposingSpanEnd(sp); 3919 } 3920 imm.updateSelection(this, selStart, selEnd, candStart, candEnd); 3921 } 3922 } 3923 3924 if (imm.isWatchingCursor(this) && highlight != null) { 3925 highlight.computeBounds(ims.mTmpRectF, true); 3926 ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0; 3927 3928 canvas.getMatrix().mapPoints(ims.mTmpOffset); 3929 ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]); 3930 3931 ims.mTmpRectF.offset(0, voffsetCursor - voffsetText); 3932 3933 ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5), 3934 (int)(ims.mTmpRectF.top + 0.5), 3935 (int)(ims.mTmpRectF.right + 0.5), 3936 (int)(ims.mTmpRectF.bottom + 0.5)); 3937 3938 imm.updateCursor(this, 3939 ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top, 3940 ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom); 3941 } 3942 } 3943 } 3944 3945 layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText); 3946 3947 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 3948 canvas.translate((int) mMarquee.getGhostOffset(), 0.0f); 3949 layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText); 3950 } 3951 3952 /* Comment out until we decide what to do about animations 3953 if (currentTransformation != null) { 3954 mTextPaint.setLinearTextOn(isLinearTextOn); 3955 } 3956 */ 3957 3958 canvas.restore(); 3959 } 3960 3961 @Override 3962 public void getFocusedRect(Rect r) { 3963 if (mLayout == null) { 3964 super.getFocusedRect(r); 3965 return; 3966 } 3967 3968 int sel = getSelectionEnd(); 3969 if (sel < 0) { 3970 super.getFocusedRect(r); 3971 return; 3972 } 3973 3974 int line = mLayout.getLineForOffset(sel); 3975 r.top = mLayout.getLineTop(line); 3976 r.bottom = mLayout.getLineBottom(line); 3977 3978 r.left = (int) mLayout.getPrimaryHorizontal(sel); 3979 r.right = r.left + 1; 3980 3981 // Adjust for padding and gravity. 3982 int paddingLeft = getCompoundPaddingLeft(); 3983 int paddingTop = getExtendedPaddingTop(); 3984 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 3985 paddingTop += getVerticalOffset(false); 3986 } 3987 r.offset(paddingLeft, paddingTop); 3988 } 3989 3990 /** 3991 * Return the number of lines of text, or 0 if the internal Layout has not 3992 * been built. 3993 */ 3994 public int getLineCount() { 3995 return mLayout != null ? mLayout.getLineCount() : 0; 3996 } 3997 3998 /** 3999 * Return the baseline for the specified line (0...getLineCount() - 1) 4000 * If bounds is not null, return the top, left, right, bottom extents 4001 * of the specified line in it. If the internal Layout has not been built, 4002 * return 0 and set bounds to (0, 0, 0, 0) 4003 * @param line which line to examine (0..getLineCount() - 1) 4004 * @param bounds Optional. If not null, it returns the extent of the line 4005 * @return the Y-coordinate of the baseline 4006 */ 4007 public int getLineBounds(int line, Rect bounds) { 4008 if (mLayout == null) { 4009 if (bounds != null) { 4010 bounds.set(0, 0, 0, 0); 4011 } 4012 return 0; 4013 } 4014 else { 4015 int baseline = mLayout.getLineBounds(line, bounds); 4016 4017 int voffset = getExtendedPaddingTop(); 4018 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 4019 voffset += getVerticalOffset(true); 4020 } 4021 if (bounds != null) { 4022 bounds.offset(getCompoundPaddingLeft(), voffset); 4023 } 4024 return baseline + voffset; 4025 } 4026 } 4027 4028 @Override 4029 public int getBaseline() { 4030 if (mLayout == null) { 4031 return super.getBaseline(); 4032 } 4033 4034 int voffset = 0; 4035 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 4036 voffset = getVerticalOffset(true); 4037 } 4038 4039 return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0); 4040 } 4041 4042 @Override 4043 public boolean onKeyDown(int keyCode, KeyEvent event) { 4044 int which = doKeyDown(keyCode, event, null); 4045 if (which == 0) { 4046 // Go through default dispatching. 4047 return super.onKeyDown(keyCode, event); 4048 } 4049 4050 return true; 4051 } 4052 4053 @Override 4054 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 4055 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 4056 4057 int which = doKeyDown(keyCode, down, event); 4058 if (which == 0) { 4059 // Go through default dispatching. 4060 return super.onKeyMultiple(keyCode, repeatCount, event); 4061 } 4062 if (which == -1) { 4063 // Consumed the whole thing. 4064 return true; 4065 } 4066 4067 repeatCount--; 4068 4069 // We are going to dispatch the remaining events to either the input 4070 // or movement method. To do this, we will just send a repeated stream 4071 // of down and up events until we have done the complete repeatCount. 4072 // It would be nice if those interfaces had an onKeyMultiple() method, 4073 // but adding that is a more complicated change. 4074 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 4075 if (which == 1) { 4076 mInput.onKeyUp(this, (Editable)mText, keyCode, up); 4077 while (--repeatCount > 0) { 4078 mInput.onKeyDown(this, (Editable)mText, keyCode, down); 4079 mInput.onKeyUp(this, (Editable)mText, keyCode, up); 4080 } 4081 if (mError != null && !mErrorWasChanged) { 4082 setError(null, null); 4083 } 4084 4085 } else if (which == 2) { 4086 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up); 4087 while (--repeatCount > 0) { 4088 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down); 4089 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up); 4090 } 4091 } 4092 4093 return true; 4094 } 4095 4096 /** 4097 * Returns true if pressing ENTER in this field advances focus instead 4098 * of inserting the character. This is true mostly in single-line fields, 4099 * but also in mail addresses and subjects which will display on multiple 4100 * lines but where it doesn't make sense to insert newlines. 4101 */ 4102 private boolean shouldAdvanceFocusOnEnter() { 4103 if (mInput == null) { 4104 return false; 4105 } 4106 4107 if (mSingleLine) { 4108 return true; 4109 } 4110 4111 if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { 4112 int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION; 4113 4114 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || 4115 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 4116 return true; 4117 } 4118 } 4119 4120 return false; 4121 } 4122 4123 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 4124 if (!isEnabled()) { 4125 return 0; 4126 } 4127 4128 switch (keyCode) { 4129 case KeyEvent.KEYCODE_ENTER: 4130 // If ALT modifier is held, then we always insert a 4131 // newline character. 4132 if ((event.getMetaState()&KeyEvent.META_ALT_ON) == 0) { 4133 4134 // When mInputContentType is set, we know that we are 4135 // running in a "modern" cupcake environment, so don't need 4136 // to worry about the application trying to capture 4137 // enter key events. 4138 if (mInputContentType != null) { 4139 4140 // If there is an action listener, given them a 4141 // chance to consume the event. 4142 if (mInputContentType.onEditorActionListener != null && 4143 mInputContentType.onEditorActionListener.onEditorAction( 4144 this, EditorInfo.IME_NULL, event)) { 4145 mInputContentType.enterDown = true; 4146 // We are consuming the enter key for them. 4147 return -1; 4148 } 4149 } 4150 4151 // If our editor should move focus when enter is pressed, or 4152 // this is a generated event from an IME action button, then 4153 // don't let it be inserted into the text. 4154 if ((event.getFlags()&KeyEvent.FLAG_EDITOR_ACTION) != 0 4155 || shouldAdvanceFocusOnEnter()) { 4156 return -1; 4157 } 4158 } 4159 break; 4160 4161 case KeyEvent.KEYCODE_DPAD_CENTER: 4162 if (shouldAdvanceFocusOnEnter()) { 4163 return 0; 4164 } 4165 } 4166 4167 if (mInput != null) { 4168 /* 4169 * Keep track of what the error was before doing the input 4170 * so that if an input filter changed the error, we leave 4171 * that error showing. Otherwise, we take down whatever 4172 * error was showing when the user types something. 4173 */ 4174 mErrorWasChanged = false; 4175 4176 boolean doDown = true; 4177 if (otherEvent != null) { 4178 try { 4179 beginBatchEdit(); 4180 boolean handled = mInput.onKeyOther(this, (Editable) mText, 4181 otherEvent); 4182 if (mError != null && !mErrorWasChanged) { 4183 setError(null, null); 4184 } 4185 doDown = false; 4186 if (handled) { 4187 return -1; 4188 } 4189 } catch (AbstractMethodError e) { 4190 // onKeyOther was added after 1.0, so if it isn't 4191 // implemented we need to try to dispatch as a regular down. 4192 } finally { 4193 endBatchEdit(); 4194 } 4195 } 4196 4197 if (doDown) { 4198 beginBatchEdit(); 4199 if (mInput.onKeyDown(this, (Editable) mText, keyCode, event)) { 4200 endBatchEdit(); 4201 if (mError != null && !mErrorWasChanged) { 4202 setError(null, null); 4203 } 4204 return 1; 4205 } 4206 endBatchEdit(); 4207 } 4208 } 4209 4210 // bug 650865: sometimes we get a key event before a layout. 4211 // don't try to move around if we don't know the layout. 4212 4213 if (mMovement != null && mLayout != null) { 4214 boolean doDown = true; 4215 if (otherEvent != null) { 4216 try { 4217 boolean handled = mMovement.onKeyOther(this, (Spannable) mText, 4218 otherEvent); 4219 doDown = false; 4220 if (handled) { 4221 return -1; 4222 } 4223 } catch (AbstractMethodError e) { 4224 // onKeyOther was added after 1.0, so if it isn't 4225 // implemented we need to try to dispatch as a regular down. 4226 } 4227 } 4228 if (doDown) { 4229 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) 4230 return 2; 4231 } 4232 } 4233 4234 return 0; 4235 } 4236 4237 @Override 4238 public boolean onKeyUp(int keyCode, KeyEvent event) { 4239 if (!isEnabled()) { 4240 return super.onKeyUp(keyCode, event); 4241 } 4242 4243 switch (keyCode) { 4244 case KeyEvent.KEYCODE_DPAD_CENTER: 4245 /* 4246 * If there is a click listener, just call through to 4247 * super, which will invoke it. 4248 * 4249 * If there isn't a click listener, try to show the soft 4250 * input method. (It will also 4251 * call performClick(), but that won't do anything in 4252 * this case.) 4253 */ 4254 if (mOnClickListener == null) { 4255 if (mMovement != null && mText instanceof Editable 4256 && mLayout != null && onCheckIsTextEditor()) { 4257 InputMethodManager imm = (InputMethodManager) 4258 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 4259 imm.showSoftInput(this, 0); 4260 } 4261 } 4262 return super.onKeyUp(keyCode, event); 4263 4264 case KeyEvent.KEYCODE_ENTER: 4265 if (mInputContentType != null 4266 && mInputContentType.onEditorActionListener != null 4267 && mInputContentType.enterDown) { 4268 mInputContentType.enterDown = false; 4269 if (mInputContentType.onEditorActionListener.onEditorAction( 4270 this, EditorInfo.IME_NULL, event)) { 4271 return true; 4272 } 4273 } 4274 4275 if ((event.getFlags()&KeyEvent.FLAG_EDITOR_ACTION) != 0 4276 || shouldAdvanceFocusOnEnter()) { 4277 /* 4278 * If there is a click listener, just call through to 4279 * super, which will invoke it. 4280 * 4281 * If there isn't a click listener, try to advance focus, 4282 * but still call through to super, which will reset the 4283 * pressed state and longpress state. (It will also 4284 * call performClick(), but that won't do anything in 4285 * this case.) 4286 */ 4287 if (mOnClickListener == null) { 4288 View v = focusSearch(FOCUS_DOWN); 4289 4290 if (v != null) { 4291 if (!v.requestFocus(FOCUS_DOWN)) { 4292 throw new IllegalStateException("focus search returned a view " + 4293 "that wasn't able to take focus!"); 4294 } 4295 4296 /* 4297 * Return true because we handled the key; super 4298 * will return false because there was no click 4299 * listener. 4300 */ 4301 super.onKeyUp(keyCode, event); 4302 return true; 4303 } else if ((event.getFlags() 4304 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 4305 // No target for next focus, but make sure the IME 4306 // if this came from it. 4307 InputMethodManager imm = InputMethodManager.peekInstance(); 4308 if (imm != null) { 4309 imm.hideSoftInputFromWindow(getWindowToken(), 0); 4310 } 4311 } 4312 } 4313 4314 return super.onKeyUp(keyCode, event); 4315 } 4316 } 4317 4318 if (mInput != null) 4319 if (mInput.onKeyUp(this, (Editable) mText, keyCode, event)) 4320 return true; 4321 4322 if (mMovement != null && mLayout != null) 4323 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event)) 4324 return true; 4325 4326 return super.onKeyUp(keyCode, event); 4327 } 4328 4329 @Override public boolean onCheckIsTextEditor() { 4330 return mInputType != EditorInfo.TYPE_NULL; 4331 } 4332 4333 @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 4334 if (onCheckIsTextEditor()) { 4335 if (mInputMethodState == null) { 4336 mInputMethodState = new InputMethodState(); 4337 } 4338 outAttrs.inputType = mInputType; 4339 if (mInputContentType != null) { 4340 outAttrs.imeOptions = mInputContentType.imeOptions; 4341 outAttrs.privateImeOptions = mInputContentType.privateImeOptions; 4342 outAttrs.actionLabel = mInputContentType.imeActionLabel; 4343 outAttrs.actionId = mInputContentType.imeActionId; 4344 outAttrs.extras = mInputContentType.extras; 4345 } else { 4346 outAttrs.imeOptions = EditorInfo.IME_NULL; 4347 } 4348 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION) 4349 == EditorInfo.IME_ACTION_UNSPECIFIED) { 4350 if (focusSearch(FOCUS_DOWN) != null) { 4351 // An action has not been set, but the enter key will move to 4352 // the next focus, so set the action to that. 4353 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 4354 } else { 4355 // An action has not been set, and there is no focus to move 4356 // to, so let's just supply a "done" action. 4357 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 4358 } 4359 if (!shouldAdvanceFocusOnEnter()) { 4360 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 4361 } 4362 } 4363 if ((outAttrs.inputType & (InputType.TYPE_MASK_CLASS 4364 | InputType.TYPE_TEXT_FLAG_MULTI_LINE)) 4365 == (InputType.TYPE_CLASS_TEXT 4366 | InputType.TYPE_TEXT_FLAG_MULTI_LINE)) { 4367 // Multi-line text editors should always show an enter key. 4368 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 4369 } 4370 outAttrs.hintText = mHint; 4371 if (mText instanceof Editable) { 4372 InputConnection ic = new EditableInputConnection(this); 4373 outAttrs.initialSelStart = Selection.getSelectionStart(mText); 4374 outAttrs.initialSelEnd = Selection.getSelectionEnd(mText); 4375 outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType); 4376 return ic; 4377 } 4378 } 4379 return null; 4380 } 4381 4382 /** 4383 * If this TextView contains editable content, extract a portion of it 4384 * based on the information in <var>request</var> in to <var>outText</var>. 4385 * @return Returns true if the text was successfully extracted, else false. 4386 */ 4387 public boolean extractText(ExtractedTextRequest request, 4388 ExtractedText outText) { 4389 return extractTextInternal(request, EXTRACT_UNKNOWN, EXTRACT_UNKNOWN, 4390 EXTRACT_UNKNOWN, outText); 4391 } 4392 4393 static final int EXTRACT_NOTHING = -2; 4394 static final int EXTRACT_UNKNOWN = -1; 4395 4396 boolean extractTextInternal(ExtractedTextRequest request, 4397 int partialStartOffset, int partialEndOffset, int delta, 4398 ExtractedText outText) { 4399 final CharSequence content = mText; 4400 if (content != null) { 4401 if (partialStartOffset != EXTRACT_NOTHING) { 4402 final int N = content.length(); 4403 if (partialStartOffset < 0) { 4404 outText.partialStartOffset = outText.partialEndOffset = -1; 4405 partialStartOffset = 0; 4406 partialEndOffset = N; 4407 } else { 4408 // Adjust offsets to ensure we contain full spans. 4409 if (content instanceof Spanned) { 4410 Spanned spanned = (Spanned)content; 4411 Object[] spans = spanned.getSpans(partialStartOffset, 4412 partialEndOffset, ParcelableSpan.class); 4413 int i = spans.length; 4414 while (i > 0) { 4415 i--; 4416 int j = spanned.getSpanStart(spans[i]); 4417 if (j < partialStartOffset) partialStartOffset = j; 4418 j = spanned.getSpanEnd(spans[i]); 4419 if (j > partialEndOffset) partialEndOffset = j; 4420 } 4421 } 4422 outText.partialStartOffset = partialStartOffset; 4423 outText.partialEndOffset = partialEndOffset; 4424 // Now use the delta to determine the actual amount of text 4425 // we need. 4426 partialEndOffset += delta; 4427 if (partialEndOffset > N) { 4428 partialEndOffset = N; 4429 } else if (partialEndOffset < 0) { 4430 partialEndOffset = 0; 4431 } 4432 } 4433 if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) { 4434 outText.text = content.subSequence(partialStartOffset, 4435 partialEndOffset); 4436 } else { 4437 outText.text = TextUtils.substring(content, partialStartOffset, 4438 partialEndOffset); 4439 } 4440 } 4441 outText.flags = 0; 4442 if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) { 4443 outText.flags |= ExtractedText.FLAG_SELECTING; 4444 } 4445 if (mSingleLine) { 4446 outText.flags |= ExtractedText.FLAG_SINGLE_LINE; 4447 } 4448 outText.startOffset = 0; 4449 outText.selectionStart = Selection.getSelectionStart(content); 4450 outText.selectionEnd = Selection.getSelectionEnd(content); 4451 return true; 4452 } 4453 return false; 4454 } 4455 4456 boolean reportExtractedText() { 4457 final InputMethodState ims = mInputMethodState; 4458 if (ims != null) { 4459 final boolean contentChanged = ims.mContentChanged; 4460 if (contentChanged || ims.mSelectionModeChanged) { 4461 ims.mContentChanged = false; 4462 ims.mSelectionModeChanged = false; 4463 final ExtractedTextRequest req = mInputMethodState.mExtracting; 4464 if (req != null) { 4465 InputMethodManager imm = InputMethodManager.peekInstance(); 4466 if (imm != null) { 4467 if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start=" 4468 + ims.mChangedStart + " end=" + ims.mChangedEnd 4469 + " delta=" + ims.mChangedDelta); 4470 if (ims.mChangedStart < 0 && !contentChanged) { 4471 ims.mChangedStart = EXTRACT_NOTHING; 4472 } 4473 if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd, 4474 ims.mChangedDelta, ims.mTmpExtracted)) { 4475 if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start=" 4476 + ims.mTmpExtracted.partialStartOffset 4477 + " end=" + ims.mTmpExtracted.partialEndOffset 4478 + ": " + ims.mTmpExtracted.text); 4479 imm.updateExtractedText(this, req.token, 4480 mInputMethodState.mTmpExtracted); 4481 return true; 4482 } 4483 } 4484 } 4485 } 4486 } 4487 return false; 4488 } 4489 4490 /** 4491 * This is used to remove all style-impacting spans from text before new 4492 * extracted text is being replaced into it, so that we don't have any 4493 * lingering spans applied during the replace. 4494 */ 4495 static void removeParcelableSpans(Spannable spannable, int start, int end) { 4496 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 4497 int i = spans.length; 4498 while (i > 0) { 4499 i--; 4500 spannable.removeSpan(spans[i]); 4501 } 4502 } 4503 4504 /** 4505 * Apply to this text view the given extracted text, as previously 4506 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 4507 */ 4508 public void setExtractedText(ExtractedText text) { 4509 Editable content = getEditableText(); 4510 if (text.text != null) { 4511 if (content == null) { 4512 setText(text.text, TextView.BufferType.EDITABLE); 4513 } else if (text.partialStartOffset < 0) { 4514 removeParcelableSpans(content, 0, content.length()); 4515 content.replace(0, content.length(), text.text); 4516 } else { 4517 final int N = content.length(); 4518 int start = text.partialStartOffset; 4519 if (start > N) start = N; 4520 int end = text.partialEndOffset; 4521 if (end > N) end = N; 4522 removeParcelableSpans(content, start, end); 4523 content.replace(start, end, text.text); 4524 } 4525 } 4526 4527 // Now set the selection position... make sure it is in range, to 4528 // avoid crashes. If this is a partial update, it is possible that 4529 // the underlying text may have changed, causing us problems here. 4530 // Also we just don't want to trust clients to do the right thing. 4531 Spannable sp = (Spannable)getText(); 4532 final int N = sp.length(); 4533 int start = text.selectionStart; 4534 if (start < 0) start = 0; 4535 else if (start > N) start = N; 4536 int end = text.selectionEnd; 4537 if (end < 0) end = 0; 4538 else if (end > N) end = N; 4539 Selection.setSelection(sp, start, end); 4540 4541 // Finally, update the selection mode. 4542 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) { 4543 MetaKeyKeyListener.startSelecting(this, sp); 4544 } else { 4545 MetaKeyKeyListener.stopSelecting(this, sp); 4546 } 4547 } 4548 4549 /** 4550 * @hide 4551 */ 4552 public void setExtracting(ExtractedTextRequest req) { 4553 if (mInputMethodState != null) { 4554 mInputMethodState.mExtracting = req; 4555 } 4556 } 4557 4558 /** 4559 * Called by the framework in response to a text completion from 4560 * the current input method, provided by it calling 4561 * {@link InputConnection#commitCompletion 4562 * InputConnection.commitCompletion()}. The default implementation does 4563 * nothing; text views that are supporting auto-completion should override 4564 * this to do their desired behavior. 4565 * 4566 * @param text The auto complete text the user has selected. 4567 */ 4568 public void onCommitCompletion(CompletionInfo text) { 4569 } 4570 4571 public void beginBatchEdit() { 4572 final InputMethodState ims = mInputMethodState; 4573 if (ims != null) { 4574 int nesting = ++ims.mBatchEditNesting; 4575 if (nesting == 1) { 4576 ims.mCursorChanged = false; 4577 ims.mChangedDelta = 0; 4578 if (ims.mContentChanged) { 4579 // We already have a pending change from somewhere else, 4580 // so turn this into a full update. 4581 ims.mChangedStart = 0; 4582 ims.mChangedEnd = mText.length(); 4583 } else { 4584 ims.mChangedStart = EXTRACT_UNKNOWN; 4585 ims.mChangedEnd = EXTRACT_UNKNOWN; 4586 ims.mContentChanged = false; 4587 } 4588 onBeginBatchEdit(); 4589 } 4590 } 4591 } 4592 4593 public void endBatchEdit() { 4594 final InputMethodState ims = mInputMethodState; 4595 if (ims != null) { 4596 int nesting = --ims.mBatchEditNesting; 4597 if (nesting == 0) { 4598 finishBatchEdit(ims); 4599 } 4600 } 4601 } 4602 4603 void ensureEndedBatchEdit() { 4604 final InputMethodState ims = mInputMethodState; 4605 if (ims != null && ims.mBatchEditNesting != 0) { 4606 ims.mBatchEditNesting = 0; 4607 finishBatchEdit(ims); 4608 } 4609 } 4610 4611 void finishBatchEdit(final InputMethodState ims) { 4612 onEndBatchEdit(); 4613 4614 if (ims.mContentChanged || ims.mSelectionModeChanged) { 4615 updateAfterEdit(); 4616 reportExtractedText(); 4617 } else if (ims.mCursorChanged) { 4618 // Cheezy way to get us to report the current cursor location. 4619 invalidateCursor(); 4620 } 4621 } 4622 4623 void updateAfterEdit() { 4624 invalidate(); 4625 int curs = Selection.getSelectionStart(mText); 4626 4627 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == 4628 Gravity.BOTTOM) { 4629 registerForPreDraw(); 4630 } 4631 4632 if (curs >= 0) { 4633 mHighlightPathBogus = true; 4634 4635 if (isFocused()) { 4636 mShowCursor = SystemClock.uptimeMillis(); 4637 makeBlink(); 4638 } 4639 } 4640 4641 checkForResize(); 4642 } 4643 4644 /** 4645 * Called by the framework in response to a request to begin a batch 4646 * of edit operations through a call to link {@link #beginBatchEdit()}. 4647 */ 4648 public void onBeginBatchEdit() { 4649 } 4650 4651 /** 4652 * Called by the framework in response to a request to end a batch 4653 * of edit operations through a call to link {@link #endBatchEdit}. 4654 */ 4655 public void onEndBatchEdit() { 4656 } 4657 4658 /** 4659 * Called by the framework in response to a private command from the 4660 * current method, provided by it calling 4661 * {@link InputConnection#performPrivateCommand 4662 * InputConnection.performPrivateCommand()}. 4663 * 4664 * @param action The action name of the command. 4665 * @param data Any additional data for the command. This may be null. 4666 * @return Return true if you handled the command, else false. 4667 */ 4668 public boolean onPrivateIMECommand(String action, Bundle data) { 4669 return false; 4670 } 4671 4672 private void nullLayouts() { 4673 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 4674 mSavedLayout = (BoringLayout) mLayout; 4675 } 4676 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 4677 mSavedHintLayout = (BoringLayout) mHintLayout; 4678 } 4679 4680 mLayout = mHintLayout = null; 4681 } 4682 4683 /** 4684 * Make a new Layout based on the already-measured size of the view, 4685 * on the assumption that it was measured correctly at some point. 4686 */ 4687 private void assumeLayout() { 4688 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 4689 4690 if (width < 1) { 4691 width = 0; 4692 } 4693 4694 int physicalWidth = width; 4695 4696 if (mHorizontallyScrolling) { 4697 width = VERY_WIDE; 4698 } 4699 4700 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 4701 physicalWidth, false); 4702 } 4703 4704 /** 4705 * The width passed in is now the desired layout width, 4706 * not the full view width with padding. 4707 * {@hide} 4708 */ 4709 protected void makeNewLayout(int w, int hintWidth, 4710 BoringLayout.Metrics boring, 4711 BoringLayout.Metrics hintBoring, 4712 int ellipsisWidth, boolean bringIntoView) { 4713 stopMarquee(); 4714 4715 mHighlightPathBogus = true; 4716 4717 if (w < 0) { 4718 w = 0; 4719 } 4720 if (hintWidth < 0) { 4721 hintWidth = 0; 4722 } 4723 4724 Layout.Alignment alignment; 4725 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 4726 case Gravity.CENTER_HORIZONTAL: 4727 alignment = Layout.Alignment.ALIGN_CENTER; 4728 break; 4729 4730 case Gravity.RIGHT: 4731 alignment = Layout.Alignment.ALIGN_OPPOSITE; 4732 break; 4733 4734 default: 4735 alignment = Layout.Alignment.ALIGN_NORMAL; 4736 } 4737 4738 if (mText instanceof Spannable) { 4739 mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w, 4740 alignment, mSpacingMult, 4741 mSpacingAdd, mIncludePad, mEllipsize, 4742 ellipsisWidth); 4743 } else { 4744 if (boring == UNKNOWN_BORING) { 4745 boring = BoringLayout.isBoring(mTransformed, mTextPaint, 4746 mBoring); 4747 if (boring != null) { 4748 mBoring = boring; 4749 } 4750 } 4751 4752 if (boring != null) { 4753 if (boring.width <= w && 4754 (mEllipsize == null || boring.width <= ellipsisWidth)) { 4755 if (mSavedLayout != null) { 4756 mLayout = mSavedLayout. 4757 replaceOrMake(mTransformed, mTextPaint, 4758 w, alignment, mSpacingMult, mSpacingAdd, 4759 boring, mIncludePad); 4760 } else { 4761 mLayout = BoringLayout.make(mTransformed, mTextPaint, 4762 w, alignment, mSpacingMult, mSpacingAdd, 4763 boring, mIncludePad); 4764 } 4765 // Log.e("aaa", "Boring: " + mTransformed); 4766 4767 mSavedLayout = (BoringLayout) mLayout; 4768 } else if (mEllipsize != null && boring.width <= w) { 4769 if (mSavedLayout != null) { 4770 mLayout = mSavedLayout. 4771 replaceOrMake(mTransformed, mTextPaint, 4772 w, alignment, mSpacingMult, mSpacingAdd, 4773 boring, mIncludePad, mEllipsize, 4774 ellipsisWidth); 4775 } else { 4776 mLayout = BoringLayout.make(mTransformed, mTextPaint, 4777 w, alignment, mSpacingMult, mSpacingAdd, 4778 boring, mIncludePad, mEllipsize, 4779 ellipsisWidth); 4780 } 4781 } else if (mEllipsize != null) { 4782 mLayout = new StaticLayout(mTransformed, 4783 0, mTransformed.length(), 4784 mTextPaint, w, alignment, mSpacingMult, 4785 mSpacingAdd, mIncludePad, mEllipsize, 4786 ellipsisWidth); 4787 } else { 4788 mLayout = new StaticLayout(mTransformed, mTextPaint, 4789 w, alignment, mSpacingMult, mSpacingAdd, 4790 mIncludePad); 4791 // Log.e("aaa", "Boring but wide: " + mTransformed); 4792 } 4793 } else if (mEllipsize != null) { 4794 mLayout = new StaticLayout(mTransformed, 4795 0, mTransformed.length(), 4796 mTextPaint, w, alignment, mSpacingMult, 4797 mSpacingAdd, mIncludePad, mEllipsize, 4798 ellipsisWidth); 4799 } else { 4800 mLayout = new StaticLayout(mTransformed, mTextPaint, 4801 w, alignment, mSpacingMult, mSpacingAdd, 4802 mIncludePad); 4803 } 4804 } 4805 4806 mHintLayout = null; 4807 4808 if (mHint != null) { 4809 if (hintBoring == UNKNOWN_BORING) { 4810 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, 4811 mHintBoring); 4812 if (hintBoring != null) { 4813 mHintBoring = hintBoring; 4814 } 4815 } 4816 4817 if (hintBoring != null) { 4818 if (hintBoring.width <= hintWidth) { 4819 if (mSavedHintLayout != null) { 4820 mHintLayout = mSavedHintLayout. 4821 replaceOrMake(mHint, mTextPaint, 4822 hintWidth, alignment, mSpacingMult, 4823 mSpacingAdd, hintBoring, mIncludePad); 4824 } else { 4825 mHintLayout = BoringLayout.make(mHint, mTextPaint, 4826 hintWidth, alignment, mSpacingMult, 4827 mSpacingAdd, hintBoring, mIncludePad); 4828 } 4829 4830 mSavedHintLayout = (BoringLayout) mHintLayout; 4831 } else { 4832 mHintLayout = new StaticLayout(mHint, mTextPaint, 4833 hintWidth, alignment, mSpacingMult, mSpacingAdd, 4834 mIncludePad); 4835 } 4836 } else { 4837 mHintLayout = new StaticLayout(mHint, mTextPaint, 4838 hintWidth, alignment, mSpacingMult, mSpacingAdd, 4839 mIncludePad); 4840 } 4841 } 4842 4843 if (bringIntoView) { 4844 registerForPreDraw(); 4845 } 4846 4847 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 4848 if (!compressText(ellipsisWidth)) { 4849 final int height = mLayoutParams.height; 4850 // If the size of the view does not depend on the size of the text, try to 4851 // start the marquee immediately 4852 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.FILL_PARENT) { 4853 startMarquee(); 4854 } else { 4855 // Defer the start of the marquee until we know our width (see setFrame()) 4856 mRestartMarquee = true; 4857 } 4858 } 4859 } 4860 } 4861 4862 private boolean compressText(float width) { 4863 // Only compress the text if it hasn't been compressed by the previous pass 4864 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX && 4865 mTextPaint.getTextScaleX() == 1.0f) { 4866 final float textWidth = mLayout.getLineWidth(0); 4867 final float overflow = (textWidth + 1.0f - width) / width; 4868 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 4869 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 4870 post(new Runnable() { 4871 public void run() { 4872 requestLayout(); 4873 } 4874 }); 4875 return true; 4876 } 4877 } 4878 4879 return false; 4880 } 4881 4882 private static int desired(Layout layout) { 4883 int n = layout.getLineCount(); 4884 CharSequence text = layout.getText(); 4885 float max = 0; 4886 4887 // if any line was wrapped, we can't use it. 4888 // but it's ok for the last line not to have a newline 4889 4890 for (int i = 0; i < n - 1; i++) { 4891 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') 4892 return -1; 4893 } 4894 4895 for (int i = 0; i < n; i++) { 4896 max = Math.max(max, layout.getLineWidth(i)); 4897 } 4898 4899 return (int) FloatMath.ceil(max); 4900 } 4901 4902 /** 4903 * Set whether the TextView includes extra top and bottom padding to make 4904 * room for accents that go above the normal ascent and descent. 4905 * The default is true. 4906 * 4907 * @attr ref android.R.styleable#TextView_includeFontPadding 4908 */ 4909 public void setIncludeFontPadding(boolean includepad) { 4910 mIncludePad = includepad; 4911 4912 if (mLayout != null) { 4913 nullLayouts(); 4914 requestLayout(); 4915 invalidate(); 4916 } 4917 } 4918 4919 private static final BoringLayout.Metrics UNKNOWN_BORING = 4920 new BoringLayout.Metrics(); 4921 4922 @Override 4923 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 4924 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 4925 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 4926 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 4927 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 4928 4929 int width; 4930 int height; 4931 4932 BoringLayout.Metrics boring = UNKNOWN_BORING; 4933 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 4934 4935 int des = -1; 4936 boolean fromexisting = false; 4937 4938 if (widthMode == MeasureSpec.EXACTLY) { 4939 // Parent has told us how big to be. So be it. 4940 width = widthSize; 4941 } else { 4942 if (mLayout != null && mEllipsize == null) { 4943 des = desired(mLayout); 4944 } 4945 4946 if (des < 0) { 4947 boring = BoringLayout.isBoring(mTransformed, mTextPaint, 4948 mBoring); 4949 if (boring != null) { 4950 mBoring = boring; 4951 } 4952 } else { 4953 fromexisting = true; 4954 } 4955 4956 if (boring == null || boring == UNKNOWN_BORING) { 4957 if (des < 0) { 4958 des = (int) FloatMath.ceil(Layout. 4959 getDesiredWidth(mTransformed, mTextPaint)); 4960 } 4961 4962 width = des; 4963 } else { 4964 width = boring.width; 4965 } 4966 4967 final Drawables dr = mDrawables; 4968 if (dr != null) { 4969 width = Math.max(width, dr.mDrawableWidthTop); 4970 width = Math.max(width, dr.mDrawableWidthBottom); 4971 } 4972 4973 if (mHint != null) { 4974 int hintDes = -1; 4975 int hintWidth; 4976 4977 if (mHintLayout != null) { 4978 hintDes = desired(mHintLayout); 4979 } 4980 4981 if (hintDes < 0) { 4982 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, 4983 mHintBoring); 4984 if (hintBoring != null) { 4985 mHintBoring = hintBoring; 4986 } 4987 } 4988 4989 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 4990 if (hintDes < 0) { 4991 hintDes = (int) FloatMath.ceil(Layout. 4992 getDesiredWidth(mHint, mTextPaint)); 4993 } 4994 4995 hintWidth = hintDes; 4996 } else { 4997 hintWidth = hintBoring.width; 4998 } 4999 5000 if (hintWidth > width) { 5001 width = hintWidth; 5002 } 5003 } 5004 5005 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 5006 5007 if (mMaxWidthMode == EMS) { 5008 width = Math.min(width, mMaxWidth * getLineHeight()); 5009 } else { 5010 width = Math.min(width, mMaxWidth); 5011 } 5012 5013 if (mMinWidthMode == EMS) { 5014 width = Math.max(width, mMinWidth * getLineHeight()); 5015 } else { 5016 width = Math.max(width, mMinWidth); 5017 } 5018 5019 // Check against our minimum width 5020 width = Math.max(width, getSuggestedMinimumWidth()); 5021 5022 if (widthMode == MeasureSpec.AT_MOST) { 5023 width = Math.min(widthSize, width); 5024 } 5025 } 5026 5027 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 5028 int unpaddedWidth = want; 5029 int hintWant = want; 5030 5031 if (mHorizontallyScrolling) 5032 want = VERY_WIDE; 5033 5034 int hintWidth = mHintLayout == null ? hintWant : mHintLayout.getWidth(); 5035 5036 if (mLayout == null) { 5037 makeNewLayout(want, hintWant, boring, hintBoring, 5038 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), 5039 false); 5040 } else if ((mLayout.getWidth() != want) || (hintWidth != hintWant) || 5041 (mLayout.getEllipsizedWidth() != 5042 width - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 5043 if (mHint == null && mEllipsize == null && 5044 want > mLayout.getWidth() && 5045 (mLayout instanceof BoringLayout || 5046 (fromexisting && des >= 0 && des <= want))) { 5047 mLayout.increaseWidthTo(want); 5048 } else { 5049 makeNewLayout(want, hintWant, boring, hintBoring, 5050 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), 5051 false); 5052 } 5053 } else { 5054 // Width has not changed. 5055 } 5056 5057 if (heightMode == MeasureSpec.EXACTLY) { 5058 // Parent has told us how big to be. So be it. 5059 height = heightSize; 5060 mDesiredHeightAtMeasure = -1; 5061 } else { 5062 int desired = getDesiredHeight(); 5063 5064 height = desired; 5065 mDesiredHeightAtMeasure = desired; 5066 5067 if (heightMode == MeasureSpec.AT_MOST) { 5068 height = Math.min(desired, height); 5069 } 5070 } 5071 5072 int unpaddedHeight = height - getCompoundPaddingTop() - 5073 getCompoundPaddingBottom(); 5074 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 5075 unpaddedHeight = Math.min(unpaddedHeight, 5076 mLayout.getLineTop(mMaximum)); 5077 } 5078 5079 /* 5080 * We didn't let makeNewLayout() register to bring the cursor into view, 5081 * so do it here if there is any possibility that it is needed. 5082 */ 5083 if (mMovement != null || 5084 mLayout.getWidth() > unpaddedWidth || 5085 mLayout.getHeight() > unpaddedHeight) { 5086 registerForPreDraw(); 5087 } else { 5088 scrollTo(0, 0); 5089 } 5090 5091 setMeasuredDimension(width, height); 5092 } 5093 5094 private int getDesiredHeight() { 5095 return Math.max(getDesiredHeight(mLayout, true), 5096 getDesiredHeight(mHintLayout, false)); 5097 } 5098 5099 private int getDesiredHeight(Layout layout, boolean cap) { 5100 if (layout == null) { 5101 return 0; 5102 } 5103 5104 int linecount = layout.getLineCount(); 5105 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom(); 5106 int desired = layout.getLineTop(linecount); 5107 5108 final Drawables dr = mDrawables; 5109 if (dr != null) { 5110 desired = Math.max(desired, dr.mDrawableHeightLeft); 5111 desired = Math.max(desired, dr.mDrawableHeightRight); 5112 } 5113 5114 desired += pad; 5115 5116 if (mMaxMode == LINES) { 5117 /* 5118 * Don't cap the hint to a certain number of lines. 5119 * (Do cap it, though, if we have a maximum pixel height.) 5120 */ 5121 if (cap) { 5122 if (linecount > mMaximum) { 5123 desired = layout.getLineTop(mMaximum) + 5124 layout.getBottomPadding(); 5125 5126 if (dr != null) { 5127 desired = Math.max(desired, dr.mDrawableHeightLeft); 5128 desired = Math.max(desired, dr.mDrawableHeightRight); 5129 } 5130 5131 desired += pad; 5132 linecount = mMaximum; 5133 } 5134 } 5135 } else { 5136 desired = Math.min(desired, mMaximum); 5137 } 5138 5139 if (mMinMode == LINES) { 5140 if (linecount < mMinimum) { 5141 desired += getLineHeight() * (mMinimum - linecount); 5142 } 5143 } else { 5144 desired = Math.max(desired, mMinimum); 5145 } 5146 5147 // Check against our minimum height 5148 desired = Math.max(desired, getSuggestedMinimumHeight()); 5149 5150 return desired; 5151 } 5152 5153 /** 5154 * Check whether a change to the existing text layout requires a 5155 * new view layout. 5156 */ 5157 private void checkForResize() { 5158 boolean sizeChanged = false; 5159 5160 if (mLayout != null) { 5161 // Check if our width changed 5162 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 5163 sizeChanged = true; 5164 invalidate(); 5165 } 5166 5167 // Check if our height changed 5168 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 5169 int desiredHeight = getDesiredHeight(); 5170 5171 if (desiredHeight != this.getHeight()) { 5172 sizeChanged = true; 5173 } 5174 } else if (mLayoutParams.height == LayoutParams.FILL_PARENT) { 5175 if (mDesiredHeightAtMeasure >= 0) { 5176 int desiredHeight = getDesiredHeight(); 5177 5178 if (desiredHeight != mDesiredHeightAtMeasure) { 5179 sizeChanged = true; 5180 } 5181 } 5182 } 5183 } 5184 5185 if (sizeChanged) { 5186 requestLayout(); 5187 // caller will have already invalidated 5188 } 5189 } 5190 5191 /** 5192 * Check whether entirely new text requires a new view layout 5193 * or merely a new text layout. 5194 */ 5195 private void checkForRelayout() { 5196 // If we have a fixed width, we can just swap in a new text layout 5197 // if the text height stays the same or if the view height is fixed. 5198 5199 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT || 5200 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) && 5201 (mHint == null || mHintLayout != null) && 5202 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 5203 // Static width, so try making a new text layout. 5204 5205 int oldht = mLayout.getHeight(); 5206 int want = mLayout.getWidth(); 5207 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 5208 5209 /* 5210 * No need to bring the text into view, since the size is not 5211 * changing (unless we do the requestLayout(), in which case it 5212 * will happen at measure). 5213 */ 5214 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 5215 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 5216 5217 // In a fixed-height view, so use our new text layout. 5218 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT && 5219 mLayoutParams.height != LayoutParams.FILL_PARENT) { 5220 invalidate(); 5221 return; 5222 } 5223 5224 // Dynamic height, but height has stayed the same, 5225 // so use our new text layout. 5226 if (mLayout.getHeight() == oldht && 5227 (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 5228 invalidate(); 5229 return; 5230 } 5231 5232 // We lose: the height has changed and we have a dynamic height. 5233 // Request a new view layout using our new text layout. 5234 requestLayout(); 5235 invalidate(); 5236 } else { 5237 // Dynamic width, so we have no choice but to request a new 5238 // view layout with a new text layout. 5239 5240 nullLayouts(); 5241 requestLayout(); 5242 invalidate(); 5243 } 5244 } 5245 5246 /** 5247 * Returns true if anything changed. 5248 */ 5249 private boolean bringTextIntoView() { 5250 int line = 0; 5251 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 5252 line = mLayout.getLineCount() - 1; 5253 } 5254 5255 Layout.Alignment a = mLayout.getParagraphAlignment(line); 5256 int dir = mLayout.getParagraphDirection(line); 5257 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 5258 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 5259 int ht = mLayout.getHeight(); 5260 5261 int scrollx, scrolly; 5262 5263 if (a == Layout.Alignment.ALIGN_CENTER) { 5264 /* 5265 * Keep centered if possible, or, if it is too wide to fit, 5266 * keep leading edge in view. 5267 */ 5268 5269 int left = (int) FloatMath.floor(mLayout.getLineLeft(line)); 5270 int right = (int) FloatMath.ceil(mLayout.getLineRight(line)); 5271 5272 if (right - left < hspace) { 5273 scrollx = (right + left) / 2 - hspace / 2; 5274 } else { 5275 if (dir < 0) { 5276 scrollx = right - hspace; 5277 } else { 5278 scrollx = left; 5279 } 5280 } 5281 } else if (a == Layout.Alignment.ALIGN_NORMAL) { 5282 /* 5283 * Keep leading edge in view. 5284 */ 5285 5286 if (dir < 0) { 5287 int right = (int) FloatMath.ceil(mLayout.getLineRight(line)); 5288 scrollx = right - hspace; 5289 } else { 5290 scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line)); 5291 } 5292 } else /* a == Layout.Alignment.ALIGN_OPPOSITE */ { 5293 /* 5294 * Keep trailing edge in view. 5295 */ 5296 5297 if (dir < 0) { 5298 scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line)); 5299 } else { 5300 int right = (int) FloatMath.ceil(mLayout.getLineRight(line)); 5301 scrollx = right - hspace; 5302 } 5303 } 5304 5305 if (ht < vspace) { 5306 scrolly = 0; 5307 } else { 5308 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 5309 scrolly = ht - vspace; 5310 } else { 5311 scrolly = 0; 5312 } 5313 } 5314 5315 if (scrollx != mScrollX || scrolly != mScrollY) { 5316 scrollTo(scrollx, scrolly); 5317 return true; 5318 } else { 5319 return false; 5320 } 5321 } 5322 5323 /** 5324 * Move the point, specified by the offset, into the view if it is needed. 5325 * This has to be called after layout. Returns true if anything changed. 5326 */ 5327 public boolean bringPointIntoView(int offset) { 5328 boolean changed = false; 5329 5330 int line = mLayout.getLineForOffset(offset); 5331 5332 // FIXME: Is it okay to truncate this, or should we round? 5333 final int x = (int)mLayout.getPrimaryHorizontal(offset); 5334 final int top = mLayout.getLineTop(line); 5335 final int bottom = mLayout.getLineTop(line+1); 5336 5337 int left = (int) FloatMath.floor(mLayout.getLineLeft(line)); 5338 int right = (int) FloatMath.ceil(mLayout.getLineRight(line)); 5339 int ht = mLayout.getHeight(); 5340 5341 int grav; 5342 5343 switch (mLayout.getParagraphAlignment(line)) { 5344 case ALIGN_NORMAL: 5345 grav = 1; 5346 break; 5347 5348 case ALIGN_OPPOSITE: 5349 grav = -1; 5350 break; 5351 5352 default: 5353 grav = 0; 5354 } 5355 5356 grav *= mLayout.getParagraphDirection(line); 5357 5358 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 5359 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 5360 5361 int hslack = (bottom - top) / 2; 5362 int vslack = hslack; 5363 5364 if (vslack > vspace / 4) 5365 vslack = vspace / 4; 5366 if (hslack > hspace / 4) 5367 hslack = hspace / 4; 5368 5369 int hs = mScrollX; 5370 int vs = mScrollY; 5371 5372 if (top - vs < vslack) 5373 vs = top - vslack; 5374 if (bottom - vs > vspace - vslack) 5375 vs = bottom - (vspace - vslack); 5376 if (ht - vs < vspace) 5377 vs = ht - vspace; 5378 if (0 - vs > 0) 5379 vs = 0; 5380 5381 if (grav != 0) { 5382 if (x - hs < hslack) { 5383 hs = x - hslack; 5384 } 5385 if (x - hs > hspace - hslack) { 5386 hs = x - (hspace - hslack); 5387 } 5388 } 5389 5390 if (grav < 0) { 5391 if (left - hs > 0) 5392 hs = left; 5393 if (right - hs < hspace) 5394 hs = right - hspace; 5395 } else if (grav > 0) { 5396 if (right - hs < hspace) 5397 hs = right - hspace; 5398 if (left - hs > 0) 5399 hs = left; 5400 } else /* grav == 0 */ { 5401 if (right - left <= hspace) { 5402 /* 5403 * If the entire text fits, center it exactly. 5404 */ 5405 hs = left - (hspace - (right - left)) / 2; 5406 } else if (x > right - hslack) { 5407 /* 5408 * If we are near the right edge, keep the right edge 5409 * at the edge of the view. 5410 */ 5411 hs = right - hspace; 5412 } else if (x < left + hslack) { 5413 /* 5414 * If we are near the left edge, keep the left edge 5415 * at the edge of the view. 5416 */ 5417 hs = left; 5418 } else if (left > hs) { 5419 /* 5420 * Is there whitespace visible at the left? Fix it if so. 5421 */ 5422 hs = left; 5423 } else if (right < hs + hspace) { 5424 /* 5425 * Is there whitespace visible at the right? Fix it if so. 5426 */ 5427 hs = right - hspace; 5428 } else { 5429 /* 5430 * Otherwise, float as needed. 5431 */ 5432 if (x - hs < hslack) { 5433 hs = x - hslack; 5434 } 5435 if (x - hs > hspace - hslack) { 5436 hs = x - (hspace - hslack); 5437 } 5438 } 5439 } 5440 5441 if (hs != mScrollX || vs != mScrollY) { 5442 if (mScroller == null) { 5443 scrollTo(hs, vs); 5444 } else { 5445 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 5446 int dx = hs - mScrollX; 5447 int dy = vs - mScrollY; 5448 5449 if (duration > ANIMATED_SCROLL_GAP) { 5450 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 5451 invalidate(); 5452 } else { 5453 if (!mScroller.isFinished()) { 5454 mScroller.abortAnimation(); 5455 } 5456 5457 scrollBy(dx, dy); 5458 } 5459 5460 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 5461 } 5462 5463 changed = true; 5464 } 5465 5466 if (isFocused()) { 5467 // This offsets because getInterestingRect() is in terms of 5468 // viewport coordinates, but requestRectangleOnScreen() 5469 // is in terms of content coordinates. 5470 5471 Rect r = new Rect(); 5472 getInterestingRect(r, x, top, bottom, line); 5473 r.offset(mScrollX, mScrollY); 5474 5475 if (requestRectangleOnScreen(r)) { 5476 changed = true; 5477 } 5478 } 5479 5480 return changed; 5481 } 5482 5483 /** 5484 * Move the cursor, if needed, so that it is at an offset that is visible 5485 * to the user. This will not move the cursor if it represents more than 5486 * one character (a selection range). This will only work if the 5487 * TextView contains spannable text; otherwise it will do nothing. 5488 */ 5489 public boolean moveCursorToVisibleOffset() { 5490 if (!(mText instanceof Spannable)) { 5491 return false; 5492 } 5493 int start = Selection.getSelectionStart(mText); 5494 int end = Selection.getSelectionEnd(mText); 5495 if (start != end) { 5496 return false; 5497 } 5498 5499 // First: make sure the line is visible on screen: 5500 5501 int line = mLayout.getLineForOffset(start); 5502 5503 final int top = mLayout.getLineTop(line); 5504 final int bottom = mLayout.getLineTop(line+1); 5505 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 5506 int vslack = (bottom - top) / 2; 5507 if (vslack > vspace / 4) 5508 vslack = vspace / 4; 5509 final int vs = mScrollY; 5510 5511 if (top < (vs+vslack)) { 5512 line = mLayout.getLineForVertical(vs+vslack+(bottom-top)); 5513 } else if (bottom > (vspace+vs-vslack)) { 5514 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top)); 5515 } 5516 5517 // Next: make sure the character is visible on screen: 5518 5519 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 5520 final int hs = mScrollX; 5521 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 5522 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs); 5523 5524 int newStart = start; 5525 if (newStart < leftChar) { 5526 newStart = leftChar; 5527 } else if (newStart > rightChar) { 5528 newStart = rightChar; 5529 } 5530 5531 if (newStart != start) { 5532 Selection.setSelection((Spannable)mText, newStart); 5533 return true; 5534 } 5535 5536 return false; 5537 } 5538 5539 @Override 5540 public void computeScroll() { 5541 if (mScroller != null) { 5542 if (mScroller.computeScrollOffset()) { 5543 mScrollX = mScroller.getCurrX(); 5544 mScrollY = mScroller.getCurrY(); 5545 postInvalidate(); // So we draw again 5546 } 5547 } 5548 } 5549 5550 private void getInterestingRect(Rect r, int h, int top, int bottom, 5551 int line) { 5552 int paddingTop = getExtendedPaddingTop(); 5553 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5554 paddingTop += getVerticalOffset(false); 5555 } 5556 top += paddingTop; 5557 bottom += paddingTop; 5558 h += getCompoundPaddingLeft(); 5559 5560 if (line == 0) 5561 top -= getExtendedPaddingTop(); 5562 if (line == mLayout.getLineCount() - 1) 5563 bottom += getExtendedPaddingBottom(); 5564 5565 r.set(h, top, h+1, bottom); 5566 r.offset(-mScrollX, -mScrollY); 5567 } 5568 5569 @Override 5570 public void debug(int depth) { 5571 super.debug(depth); 5572 5573 String output = debugIndent(depth); 5574 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 5575 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 5576 + "} "; 5577 5578 if (mText != null) { 5579 5580 output += "mText=\"" + mText + "\" "; 5581 if (mLayout != null) { 5582 output += "mLayout width=" + mLayout.getWidth() 5583 + " height=" + mLayout.getHeight(); 5584 } 5585 } else { 5586 output += "mText=NULL"; 5587 } 5588 Log.d(VIEW_LOG_TAG, output); 5589 } 5590 5591 /** 5592 * Convenience for {@link Selection#getSelectionStart}. 5593 */ 5594 public int getSelectionStart() { 5595 return Selection.getSelectionStart(getText()); 5596 } 5597 5598 /** 5599 * Convenience for {@link Selection#getSelectionEnd}. 5600 */ 5601 public int getSelectionEnd() { 5602 return Selection.getSelectionEnd(getText()); 5603 } 5604 5605 /** 5606 * Return true iff there is a selection inside this text view. 5607 */ 5608 public boolean hasSelection() { 5609 return getSelectionStart() != getSelectionEnd(); 5610 } 5611 5612 /** 5613 * Sets the properties of this field (lines, horizontally scrolling, 5614 * transformation method) to be for a single-line input. 5615 * 5616 * @attr ref android.R.styleable#TextView_singleLine 5617 */ 5618 public void setSingleLine() { 5619 setSingleLine(true); 5620 } 5621 5622 /** 5623 * If true, sets the properties of this field (lines, horizontally 5624 * scrolling, transformation method) to be for a single-line input; 5625 * if false, restores these to the default conditions. 5626 * Note that calling this with false restores default conditions, 5627 * not necessarily those that were in effect prior to calling 5628 * it with true. 5629 * 5630 * @attr ref android.R.styleable#TextView_singleLine 5631 */ 5632 @android.view.RemotableViewMethod 5633 public void setSingleLine(boolean singleLine) { 5634 if ((mInputType&EditorInfo.TYPE_MASK_CLASS) 5635 == EditorInfo.TYPE_CLASS_TEXT) { 5636 if (singleLine) { 5637 mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 5638 } else { 5639 mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 5640 } 5641 } 5642 applySingleLine(singleLine, true); 5643 } 5644 5645 private void applySingleLine(boolean singleLine, boolean applyTransformation) { 5646 mSingleLine = singleLine; 5647 if (singleLine) { 5648 setLines(1); 5649 setHorizontallyScrolling(true); 5650 if (applyTransformation) { 5651 setTransformationMethod(SingleLineTransformationMethod. 5652 getInstance()); 5653 } 5654 } else { 5655 setMaxLines(Integer.MAX_VALUE); 5656 setHorizontallyScrolling(false); 5657 if (applyTransformation) { 5658 setTransformationMethod(null); 5659 } 5660 } 5661 } 5662 5663 /** 5664 * Causes words in the text that are longer than the view is wide 5665 * to be ellipsized instead of broken in the middle. You may also 5666 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 5667 * to constrain the text toa single line. Use <code>null</code> 5668 * to turn off ellipsizing. 5669 * 5670 * @attr ref android.R.styleable#TextView_ellipsize 5671 */ 5672 public void setEllipsize(TextUtils.TruncateAt where) { 5673 mEllipsize = where; 5674 5675 if (mLayout != null) { 5676 nullLayouts(); 5677 requestLayout(); 5678 invalidate(); 5679 } 5680 } 5681 5682 /** 5683 * Sets how many times to repeat the marquee animation. Only applied if the 5684 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 5685 * 5686 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 5687 */ 5688 public void setMarqueeRepeatLimit(int marqueeLimit) { 5689 mMarqueeRepeatLimit = marqueeLimit; 5690 } 5691 5692 /** 5693 * Returns where, if anywhere, words that are longer than the view 5694 * is wide should be ellipsized. 5695 */ 5696 @ViewDebug.ExportedProperty 5697 public TextUtils.TruncateAt getEllipsize() { 5698 return mEllipsize; 5699 } 5700 5701 /** 5702 * Set the TextView so that when it takes focus, all the text is 5703 * selected. 5704 * 5705 * @attr ref android.R.styleable#TextView_selectAllOnFocus 5706 */ 5707 @android.view.RemotableViewMethod 5708 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 5709 mSelectAllOnFocus = selectAllOnFocus; 5710 5711 if (selectAllOnFocus && !(mText instanceof Spannable)) { 5712 setText(mText, BufferType.SPANNABLE); 5713 } 5714 } 5715 5716 /** 5717 * Set whether the cursor is visible. The default is true. 5718 * 5719 * @attr ref android.R.styleable#TextView_cursorVisible 5720 */ 5721 @android.view.RemotableViewMethod 5722 public void setCursorVisible(boolean visible) { 5723 mCursorVisible = visible; 5724 invalidate(); 5725 5726 if (visible) { 5727 makeBlink(); 5728 } else if (mBlink != null) { 5729 mBlink.removeCallbacks(mBlink); 5730 } 5731 } 5732 5733 private boolean canMarquee() { 5734 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight()); 5735 return width > 0 && mLayout.getLineWidth(0) > width; 5736 } 5737 5738 private void startMarquee() { 5739 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 5740 return; 5741 } 5742 5743 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) && 5744 getLineCount() == 1 && canMarquee()) { 5745 5746 if (mMarquee == null) mMarquee = new Marquee(this); 5747 mMarquee.start(mMarqueeRepeatLimit); 5748 } 5749 } 5750 5751 private void stopMarquee() { 5752 if (mMarquee != null && !mMarquee.isStopped()) { 5753 mMarquee.stop(); 5754 } 5755 } 5756 5757 private void startStopMarquee(boolean start) { 5758 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 5759 if (start) { 5760 startMarquee(); 5761 } else { 5762 stopMarquee(); 5763 } 5764 } 5765 } 5766 5767 private static final class Marquee extends Handler { 5768 // TODO: Add an option to configure this 5769 private static final float MARQUEE_DELTA_MAX = 0.07f; 5770 private static final int MARQUEE_DELAY = 1200; 5771 private static final int MARQUEE_RESTART_DELAY = 1200; 5772 private static final int MARQUEE_RESOLUTION = 1000 / 30; 5773 private static final int MARQUEE_PIXELS_PER_SECOND = 30; 5774 5775 private static final byte MARQUEE_STOPPED = 0x0; 5776 private static final byte MARQUEE_STARTING = 0x1; 5777 private static final byte MARQUEE_RUNNING = 0x2; 5778 5779 private static final int MESSAGE_START = 0x1; 5780 private static final int MESSAGE_TICK = 0x2; 5781 private static final int MESSAGE_RESTART = 0x3; 5782 5783 private final WeakReference<TextView> mView; 5784 5785 private byte mStatus = MARQUEE_STOPPED; 5786 private float mScrollUnit; 5787 private float mMaxScroll; 5788 float mMaxFadeScroll; 5789 private float mGhostStart; 5790 private float mGhostOffset; 5791 private float mFadeStop; 5792 private int mRepeatLimit; 5793 5794 float mScroll; 5795 5796 Marquee(TextView v) { 5797 final float density = v.getContext().getResources().getDisplayMetrics().density; 5798 mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / (float) MARQUEE_RESOLUTION; 5799 mView = new WeakReference<TextView>(v); 5800 } 5801 5802 @Override 5803 public void handleMessage(Message msg) { 5804 switch (msg.what) { 5805 case MESSAGE_START: 5806 mStatus = MARQUEE_RUNNING; 5807 tick(); 5808 break; 5809 case MESSAGE_TICK: 5810 tick(); 5811 break; 5812 case MESSAGE_RESTART: 5813 if (mStatus == MARQUEE_RUNNING) { 5814 if (mRepeatLimit >= 0) { 5815 mRepeatLimit--; 5816 } 5817 start(mRepeatLimit); 5818 } 5819 break; 5820 } 5821 } 5822 5823 void tick() { 5824 if (mStatus != MARQUEE_RUNNING) { 5825 return; 5826 } 5827 5828 removeMessages(MESSAGE_TICK); 5829 5830 final TextView textView = mView.get(); 5831 if (textView != null && (textView.isFocused() || textView.isSelected())) { 5832 mScroll += mScrollUnit; 5833 if (mScroll > mMaxScroll) { 5834 mScroll = mMaxScroll; 5835 sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY); 5836 } else { 5837 sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION); 5838 } 5839 textView.invalidate(); 5840 } 5841 } 5842 5843 void stop() { 5844 mStatus = MARQUEE_STOPPED; 5845 removeMessages(MESSAGE_START); 5846 removeMessages(MESSAGE_RESTART); 5847 removeMessages(MESSAGE_TICK); 5848 resetScroll(); 5849 } 5850 5851 private void resetScroll() { 5852 mScroll = 0.0f; 5853 final TextView textView = mView.get(); 5854 if (textView != null) textView.invalidate(); 5855 } 5856 5857 void start(int repeatLimit) { 5858 if (repeatLimit == 0) { 5859 stop(); 5860 return; 5861 } 5862 mRepeatLimit = repeatLimit; 5863 final TextView textView = mView.get(); 5864 if (textView != null && textView.mLayout != null) { 5865 mStatus = MARQUEE_STARTING; 5866 mScroll = 0.0f; 5867 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() - 5868 textView.getCompoundPaddingRight(); 5869 final float lineWidth = textView.mLayout.getLineWidth(0); 5870 final float gap = textWidth / 3.0f; 5871 mGhostStart = lineWidth - textWidth + gap; 5872 mMaxScroll = mGhostStart + textWidth; 5873 mGhostOffset = lineWidth + gap; 5874 mFadeStop = lineWidth + textWidth / 6.0f; 5875 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 5876 5877 textView.invalidate(); 5878 sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY); 5879 } 5880 } 5881 5882 float getGhostOffset() { 5883 return mGhostOffset; 5884 } 5885 5886 boolean shouldDrawLeftFade() { 5887 return mScroll <= mFadeStop; 5888 } 5889 5890 boolean shouldDrawGhost() { 5891 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 5892 } 5893 5894 boolean isRunning() { 5895 return mStatus == MARQUEE_RUNNING; 5896 } 5897 5898 boolean isStopped() { 5899 return mStatus == MARQUEE_STOPPED; 5900 } 5901 } 5902 5903 /** 5904 * This method is called when the text is changed, in case any 5905 * subclasses would like to know. 5906 * 5907 * @param text The text the TextView is displaying. 5908 * @param start The offset of the start of the range of the text 5909 * that was modified. 5910 * @param before The offset of the former end of the range of the 5911 * text that was modified. If text was simply inserted, 5912 * this will be the same as <code>start</code>. 5913 * If text was replaced with new text or deleted, the 5914 * length of the old text was <code>before-start</code>. 5915 * @param after The offset of the end of the range of the text 5916 * that was modified. If text was simply deleted, 5917 * this will be the same as <code>start</code>. 5918 * If text was replaced with new text or inserted, 5919 * the length of the new text is <code>after-start</code>. 5920 */ 5921 protected void onTextChanged(CharSequence text, 5922 int start, int before, int after) { 5923 } 5924 5925 /** 5926 * This method is called when the selection has changed, in case any 5927 * subclasses would like to know. 5928 * 5929 * @param selStart The new selection start location. 5930 * @param selEnd The new selection end location. 5931 */ 5932 protected void onSelectionChanged(int selStart, int selEnd) { 5933 } 5934 5935 /** 5936 * Adds a TextWatcher to the list of those whose methods are called 5937 * whenever this TextView's text changes. 5938 * <p> 5939 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 5940 * not called after {@link #setText} calls. Now, doing {@link #setText} 5941 * if there are any text changed listeners forces the buffer type to 5942 * Editable if it would not otherwise be and does call this method. 5943 */ 5944 public void addTextChangedListener(TextWatcher watcher) { 5945 if (mListeners == null) { 5946 mListeners = new ArrayList<TextWatcher>(); 5947 } 5948 5949 mListeners.add(watcher); 5950 } 5951 5952 /** 5953 * Removes the specified TextWatcher from the list of those whose 5954 * methods are called 5955 * whenever this TextView's text changes. 5956 */ 5957 public void removeTextChangedListener(TextWatcher watcher) { 5958 if (mListeners != null) { 5959 int i = mListeners.indexOf(watcher); 5960 5961 if (i >= 0) { 5962 mListeners.remove(i); 5963 } 5964 } 5965 } 5966 5967 private void sendBeforeTextChanged(CharSequence text, int start, int before, 5968 int after) { 5969 if (mListeners != null) { 5970 final ArrayList<TextWatcher> list = mListeners; 5971 final int count = list.size(); 5972 for (int i = 0; i < count; i++) { 5973 list.get(i).beforeTextChanged(text, start, before, after); 5974 } 5975 } 5976 } 5977 5978 /** 5979 * Not private so it can be called from an inner class without going 5980 * through a thunk. 5981 */ 5982 void sendOnTextChanged(CharSequence text, int start, int before, 5983 int after) { 5984 if (mListeners != null) { 5985 final ArrayList<TextWatcher> list = mListeners; 5986 final int count = list.size(); 5987 for (int i = 0; i < count; i++) { 5988 list.get(i).onTextChanged(text, start, before, after); 5989 } 5990 } 5991 } 5992 5993 /** 5994 * Not private so it can be called from an inner class without going 5995 * through a thunk. 5996 */ 5997 void sendAfterTextChanged(Editable text) { 5998 if (mListeners != null) { 5999 final ArrayList<TextWatcher> list = mListeners; 6000 final int count = list.size(); 6001 for (int i = 0; i < count; i++) { 6002 list.get(i).afterTextChanged(text); 6003 } 6004 } 6005 } 6006 6007 /** 6008 * Not private so it can be called from an inner class without going 6009 * through a thunk. 6010 */ 6011 void handleTextChanged(CharSequence buffer, int start, 6012 int before, int after) { 6013 final InputMethodState ims = mInputMethodState; 6014 if (ims == null || ims.mBatchEditNesting == 0) { 6015 updateAfterEdit(); 6016 } 6017 if (ims != null) { 6018 ims.mContentChanged = true; 6019 if (ims.mChangedStart < 0) { 6020 ims.mChangedStart = start; 6021 ims.mChangedEnd = start+before; 6022 } else { 6023 if (ims.mChangedStart > start) ims.mChangedStart = start; 6024 if (ims.mChangedEnd < (start+before)) ims.mChangedEnd = start+before; 6025 } 6026 ims.mChangedDelta += after-before; 6027 } 6028 6029 sendOnTextChanged(buffer, start, before, after); 6030 onTextChanged(buffer, start, before, after); 6031 } 6032 6033 /** 6034 * Not private so it can be called from an inner class without going 6035 * through a thunk. 6036 */ 6037 void spanChange(Spanned buf, Object what, int oldStart, int newStart, 6038 int oldEnd, int newEnd) { 6039 // XXX Make the start and end move together if this ends up 6040 // spending too much time invalidating. 6041 6042 boolean selChanged = false; 6043 int newSelStart=-1, newSelEnd=-1; 6044 6045 final InputMethodState ims = mInputMethodState; 6046 6047 if (what == Selection.SELECTION_END) { 6048 mHighlightPathBogus = true; 6049 selChanged = true; 6050 newSelEnd = newStart; 6051 6052 if (!isFocused()) { 6053 mSelectionMoved = true; 6054 } 6055 6056 if (oldStart >= 0 || newStart >= 0) { 6057 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 6058 registerForPreDraw(); 6059 6060 if (isFocused()) { 6061 mShowCursor = SystemClock.uptimeMillis(); 6062 makeBlink(); 6063 } 6064 } 6065 } 6066 6067 if (what == Selection.SELECTION_START) { 6068 mHighlightPathBogus = true; 6069 selChanged = true; 6070 newSelStart = newStart; 6071 6072 if (!isFocused()) { 6073 mSelectionMoved = true; 6074 } 6075 6076 if (oldStart >= 0 || newStart >= 0) { 6077 int end = Selection.getSelectionEnd(buf); 6078 invalidateCursor(end, oldStart, newStart); 6079 } 6080 } 6081 6082 if (selChanged) { 6083 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) { 6084 if (newSelStart < 0) { 6085 newSelStart = Selection.getSelectionStart(buf); 6086 } 6087 if (newSelEnd < 0) { 6088 newSelEnd = Selection.getSelectionEnd(buf); 6089 } 6090 onSelectionChanged(newSelStart, newSelEnd); 6091 } 6092 } 6093 6094 if (what instanceof UpdateAppearance || 6095 what instanceof ParagraphStyle) { 6096 if (ims == null || ims.mBatchEditNesting == 0) { 6097 invalidate(); 6098 mHighlightPathBogus = true; 6099 checkForResize(); 6100 } else { 6101 ims.mContentChanged = true; 6102 } 6103 } 6104 6105 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 6106 mHighlightPathBogus = true; 6107 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 6108 ims.mSelectionModeChanged = true; 6109 } 6110 6111 if (Selection.getSelectionStart(buf) >= 0) { 6112 if (ims == null || ims.mBatchEditNesting == 0) { 6113 invalidateCursor(); 6114 } else { 6115 ims.mCursorChanged = true; 6116 } 6117 } 6118 } 6119 6120 if (what instanceof ParcelableSpan) { 6121 // If this is a span that can be sent to a remote process, 6122 // the current extract editor would be interested in it. 6123 if (ims != null && ims.mExtracting != null) { 6124 if (ims.mBatchEditNesting != 0) { 6125 if (oldStart >= 0) { 6126 if (ims.mChangedStart > oldStart) { 6127 ims.mChangedStart = oldStart; 6128 } 6129 if (ims.mChangedStart > oldEnd) { 6130 ims.mChangedStart = oldEnd; 6131 } 6132 } 6133 if (newStart >= 0) { 6134 if (ims.mChangedStart > newStart) { 6135 ims.mChangedStart = newStart; 6136 } 6137 if (ims.mChangedStart > newEnd) { 6138 ims.mChangedStart = newEnd; 6139 } 6140 } 6141 } else { 6142 if (DEBUG_EXTRACT) Log.v(TAG, "Span change outside of batch: " 6143 + oldStart + "-" + oldEnd + "," 6144 + newStart + "-" + newEnd + what); 6145 ims.mContentChanged = true; 6146 } 6147 } 6148 } 6149 } 6150 6151 private class ChangeWatcher 6152 implements TextWatcher, SpanWatcher { 6153 6154 private CharSequence mBeforeText; 6155 6156 public void beforeTextChanged(CharSequence buffer, int start, 6157 int before, int after) { 6158 if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start 6159 + " before=" + before + " after=" + after + ": " + buffer); 6160 6161 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 6162 mBeforeText = buffer.toString(); 6163 } 6164 6165 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 6166 } 6167 6168 public void onTextChanged(CharSequence buffer, int start, 6169 int before, int after) { 6170 if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start 6171 + " before=" + before + " after=" + after + ": " + buffer); 6172 TextView.this.handleTextChanged(buffer, start, before, after); 6173 6174 if (AccessibilityManager.getInstance(mContext).isEnabled() && 6175 (isFocused() || isSelected() && 6176 isShown())) { 6177 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 6178 mBeforeText = null; 6179 } 6180 } 6181 6182 public void afterTextChanged(Editable buffer) { 6183 if (DEBUG_EXTRACT) Log.v(TAG, "afterTextChanged: " + buffer); 6184 TextView.this.sendAfterTextChanged(buffer); 6185 6186 if (MetaKeyKeyListener.getMetaState(buffer, 6187 MetaKeyKeyListener.META_SELECTING) != 0) { 6188 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 6189 } 6190 } 6191 6192 public void onSpanChanged(Spannable buf, 6193 Object what, int s, int e, int st, int en) { 6194 if (DEBUG_EXTRACT) Log.v(TAG, "onSpanChanged s=" + s + " e=" + e 6195 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 6196 TextView.this.spanChange(buf, what, s, st, e, en); 6197 } 6198 6199 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 6200 if (DEBUG_EXTRACT) Log.v(TAG, "onSpanAdded s=" + s + " e=" + e 6201 + " what=" + what + ": " + buf); 6202 TextView.this.spanChange(buf, what, -1, s, -1, e); 6203 } 6204 6205 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 6206 if (DEBUG_EXTRACT) Log.v(TAG, "onSpanRemoved s=" + s + " e=" + e 6207 + " what=" + what + ": " + buf); 6208 TextView.this.spanChange(buf, what, s, -1, e, -1); 6209 } 6210 } 6211 6212 private void makeBlink() { 6213 if (!mCursorVisible) { 6214 if (mBlink != null) { 6215 mBlink.removeCallbacks(mBlink); 6216 } 6217 6218 return; 6219 } 6220 6221 if (mBlink == null) 6222 mBlink = new Blink(this); 6223 6224 mBlink.removeCallbacks(mBlink); 6225 mBlink.postAtTime(mBlink, mShowCursor + BLINK); 6226 } 6227 6228 @Override 6229 public void onStartTemporaryDetach() { 6230 mTemporaryDetach = true; 6231 } 6232 6233 @Override 6234 public void onFinishTemporaryDetach() { 6235 mTemporaryDetach = false; 6236 } 6237 6238 @Override 6239 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 6240 if (mTemporaryDetach) { 6241 // If we are temporarily in the detach state, then do nothing. 6242 super.onFocusChanged(focused, direction, previouslyFocusedRect); 6243 return; 6244 } 6245 6246 mShowCursor = SystemClock.uptimeMillis(); 6247 6248 ensureEndedBatchEdit(); 6249 6250 if (focused) { 6251 int selStart = getSelectionStart(); 6252 int selEnd = getSelectionEnd(); 6253 6254 if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) { 6255 boolean selMoved = mSelectionMoved; 6256 6257 if (mMovement != null) { 6258 mMovement.onTakeFocus(this, (Spannable) mText, direction); 6259 } 6260 6261 if (mSelectAllOnFocus) { 6262 Selection.setSelection((Spannable) mText, 0, mText.length()); 6263 } 6264 6265 if (selMoved && selStart >= 0 && selEnd >= 0) { 6266 /* 6267 * Someone intentionally set the selection, so let them 6268 * do whatever it is that they wanted to do instead of 6269 * the default on-focus behavior. We reset the selection 6270 * here instead of just skipping the onTakeFocus() call 6271 * because some movement methods do something other than 6272 * just setting the selection in theirs and we still 6273 * need to go through that path. 6274 */ 6275 6276 Selection.setSelection((Spannable) mText, selStart, selEnd); 6277 } 6278 mTouchFocusSelected = true; 6279 } 6280 6281 mFrozenWithFocus = false; 6282 mSelectionMoved = false; 6283 6284 if (mText instanceof Spannable) { 6285 Spannable sp = (Spannable) mText; 6286 MetaKeyKeyListener.resetMetaState(sp); 6287 } 6288 6289 makeBlink(); 6290 6291 if (mError != null) { 6292 showError(); 6293 } 6294 } else { 6295 if (mError != null) { 6296 hideError(); 6297 } 6298 // Don't leave us in the middle of a batch edit. 6299 onEndBatchEdit(); 6300 } 6301 6302 startStopMarquee(focused); 6303 6304 if (mTransformation != null) { 6305 mTransformation.onFocusChanged(this, mText, focused, direction, 6306 previouslyFocusedRect); 6307 } 6308 6309 super.onFocusChanged(focused, direction, previouslyFocusedRect); 6310 } 6311 6312 @Override 6313 public void onWindowFocusChanged(boolean hasWindowFocus) { 6314 super.onWindowFocusChanged(hasWindowFocus); 6315 6316 if (hasWindowFocus) { 6317 if (mBlink != null) { 6318 mBlink.uncancel(); 6319 6320 if (isFocused()) { 6321 mShowCursor = SystemClock.uptimeMillis(); 6322 makeBlink(); 6323 } 6324 } 6325 } else { 6326 if (mBlink != null) { 6327 mBlink.cancel(); 6328 } 6329 // Don't leave us in the middle of a batch edit. 6330 onEndBatchEdit(); 6331 if (mInputContentType != null) { 6332 mInputContentType.enterDown = false; 6333 } 6334 } 6335 6336 startStopMarquee(hasWindowFocus); 6337 } 6338 6339 /** 6340 * Use {@link BaseInputConnection#removeComposingSpans 6341 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 6342 * state from this text view. 6343 */ 6344 public void clearComposingText() { 6345 if (mText instanceof Spannable) { 6346 BaseInputConnection.removeComposingSpans((Spannable)mText); 6347 } 6348 } 6349 6350 @Override 6351 public void setSelected(boolean selected) { 6352 boolean wasSelected = isSelected(); 6353 6354 super.setSelected(selected); 6355 6356 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 6357 if (selected) { 6358 startMarquee(); 6359 } else { 6360 stopMarquee(); 6361 } 6362 } 6363 } 6364 6365 class CommitSelectionReceiver extends ResultReceiver { 6366 int mNewStart; 6367 int mNewEnd; 6368 6369 CommitSelectionReceiver() { 6370 super(getHandler()); 6371 } 6372 6373 protected void onReceiveResult(int resultCode, Bundle resultData) { 6374 if (resultCode != InputMethodManager.RESULT_SHOWN) { 6375 Selection.setSelection((Spannable)mText, mNewStart, mNewEnd); 6376 } 6377 } 6378 } 6379 6380 @Override 6381 public boolean onTouchEvent(MotionEvent event) { 6382 final int action = event.getAction(); 6383 if (action == MotionEvent.ACTION_DOWN) { 6384 // Reset this state; it will be re-set if super.onTouchEvent 6385 // causes focus to move to the view. 6386 mTouchFocusSelected = false; 6387 } 6388 6389 final boolean superResult = super.onTouchEvent(event); 6390 6391 /* 6392 * Don't handle the release after a long press, because it will 6393 * move the selection away from whatever the menu action was 6394 * trying to affect. 6395 */ 6396 if (mEatTouchRelease && action == MotionEvent.ACTION_UP) { 6397 mEatTouchRelease = false; 6398 return superResult; 6399 } 6400 6401 if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) { 6402 6403 if (action == MotionEvent.ACTION_DOWN) { 6404 mScrolled = false; 6405 } 6406 6407 boolean handled = false; 6408 6409 int oldSelStart = Selection.getSelectionStart(mText); 6410 int oldSelEnd = Selection.getSelectionEnd(mText); 6411 6412 if (mMovement != null) { 6413 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event); 6414 } 6415 6416 if (mText instanceof Editable && onCheckIsTextEditor()) { 6417 if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) { 6418 InputMethodManager imm = (InputMethodManager) 6419 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 6420 6421 // This is going to be gross... if tapping on the text view 6422 // causes the IME to be displayed, we don't want the selection 6423 // to change. But the selection has already changed, and 6424 // we won't know right away whether the IME is getting 6425 // displayed, so... 6426 6427 int newSelStart = Selection.getSelectionStart(mText); 6428 int newSelEnd = Selection.getSelectionEnd(mText); 6429 CommitSelectionReceiver csr = null; 6430 if (newSelStart != oldSelStart || newSelEnd != oldSelEnd) { 6431 csr = new CommitSelectionReceiver(); 6432 csr.mNewStart = newSelStart; 6433 csr.mNewEnd = newSelEnd; 6434 } 6435 6436 if (imm.showSoftInput(this, 0, csr) && csr != null) { 6437 // The IME might get shown -- revert to the old 6438 // selection, and change to the new when we finally 6439 // find out of it is okay. 6440 Selection.setSelection((Spannable)mText, oldSelStart, oldSelEnd); 6441 handled = true; 6442 } 6443 } 6444 } 6445 6446 if (handled) { 6447 return true; 6448 } 6449 } 6450 6451 return superResult; 6452 } 6453 6454 /** 6455 * Returns true, only while processing a touch gesture, if the initial 6456 * touch down event caused focus to move to the text view and as a result 6457 * its selection changed. Only valid while processing the touch gesture 6458 * of interest. 6459 */ 6460 public boolean didTouchFocusSelect() { 6461 return mTouchFocusSelected; 6462 } 6463 6464 @Override 6465 public void cancelLongPress() { 6466 super.cancelLongPress(); 6467 mScrolled = true; 6468 } 6469 6470 @Override 6471 public boolean onTrackballEvent(MotionEvent event) { 6472 if (mMovement != null && mText instanceof Spannable && 6473 mLayout != null) { 6474 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) { 6475 return true; 6476 } 6477 } 6478 6479 return super.onTrackballEvent(event); 6480 } 6481 6482 public void setScroller(Scroller s) { 6483 mScroller = s; 6484 } 6485 6486 private static class Blink extends Handler implements Runnable { 6487 private WeakReference<TextView> mView; 6488 private boolean mCancelled; 6489 6490 public Blink(TextView v) { 6491 mView = new WeakReference<TextView>(v); 6492 } 6493 6494 public void run() { 6495 if (mCancelled) { 6496 return; 6497 } 6498 6499 removeCallbacks(Blink.this); 6500 6501 TextView tv = mView.get(); 6502 6503 if (tv != null && tv.isFocused()) { 6504 int st = Selection.getSelectionStart(tv.mText); 6505 int en = Selection.getSelectionEnd(tv.mText); 6506 6507 if (st == en && st >= 0 && en >= 0) { 6508 if (tv.mLayout != null) { 6509 tv.invalidateCursorPath(); 6510 } 6511 6512 postAtTime(this, SystemClock.uptimeMillis() + BLINK); 6513 } 6514 } 6515 } 6516 6517 void cancel() { 6518 if (!mCancelled) { 6519 removeCallbacks(Blink.this); 6520 mCancelled = true; 6521 } 6522 } 6523 6524 void uncancel() { 6525 mCancelled = false; 6526 } 6527 } 6528 6529 @Override 6530 protected float getLeftFadingEdgeStrength() { 6531 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 6532 if (mMarquee != null && !mMarquee.isStopped()) { 6533 final Marquee marquee = mMarquee; 6534 if (marquee.shouldDrawLeftFade()) { 6535 return marquee.mScroll / getHorizontalFadingEdgeLength(); 6536 } else { 6537 return 0.0f; 6538 } 6539 } else if (getLineCount() == 1) { 6540 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 6541 case Gravity.LEFT: 6542 return 0.0f; 6543 case Gravity.RIGHT: 6544 return (mLayout.getLineRight(0) - (mRight - mLeft) - 6545 getCompoundPaddingLeft() - getCompoundPaddingRight() - 6546 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength(); 6547 case Gravity.CENTER_HORIZONTAL: 6548 return 0.0f; 6549 } 6550 } 6551 } 6552 return super.getLeftFadingEdgeStrength(); 6553 } 6554 6555 @Override 6556 protected float getRightFadingEdgeStrength() { 6557 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 6558 if (mMarquee != null && !mMarquee.isStopped()) { 6559 final Marquee marquee = mMarquee; 6560 return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength(); 6561 } else if (getLineCount() == 1) { 6562 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 6563 case Gravity.LEFT: 6564 return (mLayout.getLineRight(0) - mScrollX - (mRight - mLeft) - 6565 getCompoundPaddingLeft() - getCompoundPaddingRight()) / 6566 getHorizontalFadingEdgeLength(); 6567 case Gravity.RIGHT: 6568 return 0.0f; 6569 case Gravity.CENTER_HORIZONTAL: 6570 return (mLayout.getLineWidth(0) - ((mRight - mLeft) - 6571 getCompoundPaddingLeft() - getCompoundPaddingRight())) / 6572 getHorizontalFadingEdgeLength(); 6573 } 6574 } 6575 } 6576 return super.getRightFadingEdgeStrength(); 6577 } 6578 6579 @Override 6580 protected int computeHorizontalScrollRange() { 6581 if (mLayout != null) 6582 return mLayout.getWidth(); 6583 6584 return super.computeHorizontalScrollRange(); 6585 } 6586 6587 @Override 6588 protected int computeVerticalScrollRange() { 6589 if (mLayout != null) 6590 return mLayout.getHeight(); 6591 6592 return super.computeVerticalScrollRange(); 6593 } 6594 6595 @Override 6596 protected int computeVerticalScrollExtent() { 6597 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 6598 } 6599 6600 public enum BufferType { 6601 NORMAL, SPANNABLE, EDITABLE, 6602 } 6603 6604 /** 6605 * Returns the TextView_textColor attribute from the 6606 * Resources.StyledAttributes, if set, or the TextAppearance_textColor 6607 * from the TextView_textAppearance attribute, if TextView_textColor 6608 * was not set directly. 6609 */ 6610 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 6611 ColorStateList colors; 6612 colors = attrs.getColorStateList(com.android.internal.R.styleable. 6613 TextView_textColor); 6614 6615 if (colors == null) { 6616 int ap = attrs.getResourceId(com.android.internal.R.styleable. 6617 TextView_textAppearance, -1); 6618 if (ap != -1) { 6619 TypedArray appearance; 6620 appearance = context.obtainStyledAttributes(ap, 6621 com.android.internal.R.styleable.TextAppearance); 6622 colors = appearance.getColorStateList(com.android.internal.R.styleable. 6623 TextAppearance_textColor); 6624 appearance.recycle(); 6625 } 6626 } 6627 6628 return colors; 6629 } 6630 6631 /** 6632 * Returns the default color from the TextView_textColor attribute 6633 * from the AttributeSet, if set, or the default color from the 6634 * TextAppearance_textColor from the TextView_textAppearance attribute, 6635 * if TextView_textColor was not set directly. 6636 */ 6637 public static int getTextColor(Context context, 6638 TypedArray attrs, 6639 int def) { 6640 ColorStateList colors = getTextColors(context, attrs); 6641 6642 if (colors == null) { 6643 return def; 6644 } else { 6645 return colors.getDefaultColor(); 6646 } 6647 } 6648 6649 @Override 6650 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 6651 switch (keyCode) { 6652 case KeyEvent.KEYCODE_A: 6653 if (canSelectAll()) { 6654 return onTextContextMenuItem(ID_SELECT_ALL); 6655 } 6656 6657 break; 6658 6659 case KeyEvent.KEYCODE_X: 6660 if (canCut()) { 6661 return onTextContextMenuItem(ID_CUT); 6662 } 6663 6664 break; 6665 6666 case KeyEvent.KEYCODE_C: 6667 if (canCopy()) { 6668 return onTextContextMenuItem(ID_COPY); 6669 } 6670 6671 break; 6672 6673 case KeyEvent.KEYCODE_V: 6674 if (canPaste()) { 6675 return onTextContextMenuItem(ID_PASTE); 6676 } 6677 6678 break; 6679 } 6680 6681 return super.onKeyShortcut(keyCode, event); 6682 } 6683 6684 private boolean canSelectAll() { 6685 if (mText instanceof Spannable && mText.length() != 0 && 6686 mMovement != null && mMovement.canSelectArbitrarily()) { 6687 return true; 6688 } 6689 6690 return false; 6691 } 6692 6693 private boolean canSelectText() { 6694 if (mText instanceof Spannable && mText.length() != 0 && 6695 mMovement != null && mMovement.canSelectArbitrarily()) { 6696 return true; 6697 } 6698 6699 return false; 6700 } 6701 6702 private boolean canCut() { 6703 if (mTransformation instanceof PasswordTransformationMethod) { 6704 return false; 6705 } 6706 6707 if (mText.length() > 0 && getSelectionStart() >= 0) { 6708 if (mText instanceof Editable && mInput != null) { 6709 return true; 6710 } 6711 } 6712 6713 return false; 6714 } 6715 6716 private boolean canCopy() { 6717 if (mTransformation instanceof PasswordTransformationMethod) { 6718 return false; 6719 } 6720 6721 if (mText.length() > 0 && getSelectionStart() >= 0) { 6722 return true; 6723 } 6724 6725 return false; 6726 } 6727 6728 private boolean canPaste() { 6729 if (mText instanceof Editable && mInput != null && 6730 getSelectionStart() >= 0 && getSelectionEnd() >= 0) { 6731 ClipboardManager clip = (ClipboardManager)getContext() 6732 .getSystemService(Context.CLIPBOARD_SERVICE); 6733 if (clip.hasText()) { 6734 return true; 6735 } 6736 } 6737 6738 return false; 6739 } 6740 6741 /** 6742 * Returns a word to add to the dictionary from the context menu, 6743 * or null if there is no cursor or no word at the cursor. 6744 */ 6745 private String getWordForDictionary() { 6746 /* 6747 * Quick return if the input type is one where adding words 6748 * to the dictionary doesn't make any sense. 6749 */ 6750 int klass = mInputType & InputType.TYPE_MASK_CLASS; 6751 if (klass == InputType.TYPE_CLASS_NUMBER || 6752 klass == InputType.TYPE_CLASS_PHONE || 6753 klass == InputType.TYPE_CLASS_DATETIME) { 6754 return null; 6755 } 6756 6757 int variation = mInputType & InputType.TYPE_MASK_VARIATION; 6758 if (variation == InputType.TYPE_TEXT_VARIATION_URI || 6759 variation == InputType.TYPE_TEXT_VARIATION_PASSWORD || 6760 variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD || 6761 variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || 6762 variation == InputType.TYPE_TEXT_VARIATION_FILTER) { 6763 return null; 6764 } 6765 6766 int end = getSelectionEnd(); 6767 6768 if (end < 0) { 6769 return null; 6770 } 6771 6772 int start = end; 6773 int len = mText.length(); 6774 6775 for (; start > 0; start--) { 6776 char c = mTransformed.charAt(start - 1); 6777 int type = Character.getType(c); 6778 6779 if (c != '\'' && 6780 type != Character.UPPERCASE_LETTER && 6781 type != Character.LOWERCASE_LETTER && 6782 type != Character.TITLECASE_LETTER && 6783 type != Character.MODIFIER_LETTER && 6784 type != Character.DECIMAL_DIGIT_NUMBER) { 6785 break; 6786 } 6787 } 6788 6789 for (; end < len; end++) { 6790 char c = mTransformed.charAt(end); 6791 int type = Character.getType(c); 6792 6793 if (c != '\'' && 6794 type != Character.UPPERCASE_LETTER && 6795 type != Character.LOWERCASE_LETTER && 6796 type != Character.TITLECASE_LETTER && 6797 type != Character.MODIFIER_LETTER && 6798 type != Character.DECIMAL_DIGIT_NUMBER) { 6799 break; 6800 } 6801 } 6802 6803 if (start == end) { 6804 return null; 6805 } 6806 6807 if (end - start > 48) { 6808 return null; 6809 } 6810 6811 return TextUtils.substring(mTransformed, start, end); 6812 } 6813 6814 @Override 6815 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 6816 boolean isPassword = 6817 (mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) == 6818 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); 6819 6820 if (!isPassword) { 6821 CharSequence text = getText(); 6822 if (TextUtils.isEmpty(text)) { 6823 text = getHint(); 6824 } 6825 if (!TextUtils.isEmpty(text)) { 6826 if (text.length() > AccessibilityEvent.MAX_TEXT_LENGTH) { 6827 text = text.subSequence(0, AccessibilityEvent.MAX_TEXT_LENGTH + 1); 6828 } 6829 event.getText().add(text); 6830 } 6831 } else { 6832 event.setPassword(isPassword); 6833 } 6834 return false; 6835 } 6836 6837 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 6838 int fromIndex, int removedCount, int addedCount) { 6839 AccessibilityEvent event = 6840 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 6841 event.setFromIndex(fromIndex); 6842 event.setRemovedCount(removedCount); 6843 event.setAddedCount(addedCount); 6844 event.setBeforeText(beforeText); 6845 sendAccessibilityEventUnchecked(event); 6846 } 6847 6848 @Override 6849 protected void onCreateContextMenu(ContextMenu menu) { 6850 super.onCreateContextMenu(menu); 6851 boolean added = false; 6852 6853 if (!isFocused()) { 6854 if (isFocusable() && mInput != null) { 6855 if (canCopy()) { 6856 MenuHandler handler = new MenuHandler(); 6857 int name = com.android.internal.R.string.copyAll; 6858 6859 menu.add(0, ID_COPY, 0, name). 6860 setOnMenuItemClickListener(handler). 6861 setAlphabeticShortcut('c'); 6862 menu.setHeaderTitle(com.android.internal.R.string. 6863 editTextMenuTitle); 6864 } 6865 } 6866 6867 return; 6868 } 6869 6870 MenuHandler handler = new MenuHandler(); 6871 6872 if (canSelectAll()) { 6873 menu.add(0, ID_SELECT_ALL, 0, 6874 com.android.internal.R.string.selectAll). 6875 setOnMenuItemClickListener(handler). 6876 setAlphabeticShortcut('a'); 6877 added = true; 6878 } 6879 6880 boolean selection = getSelectionStart() != getSelectionEnd(); 6881 6882 if (canSelectText()) { 6883 if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) { 6884 menu.add(0, ID_STOP_SELECTING_TEXT, 0, 6885 com.android.internal.R.string.stopSelectingText). 6886 setOnMenuItemClickListener(handler); 6887 added = true; 6888 } else { 6889 menu.add(0, ID_START_SELECTING_TEXT, 0, 6890 com.android.internal.R.string.selectText). 6891 setOnMenuItemClickListener(handler); 6892 added = true; 6893 } 6894 } 6895 6896 if (canCut()) { 6897 int name; 6898 if (selection) { 6899 name = com.android.internal.R.string.cut; 6900 } else { 6901 name = com.android.internal.R.string.cutAll; 6902 } 6903 6904 menu.add(0, ID_CUT, 0, name). 6905 setOnMenuItemClickListener(handler). 6906 setAlphabeticShortcut('x'); 6907 added = true; 6908 } 6909 6910 if (canCopy()) { 6911 int name; 6912 if (selection) { 6913 name = com.android.internal.R.string.copy; 6914 } else { 6915 name = com.android.internal.R.string.copyAll; 6916 } 6917 6918 menu.add(0, ID_COPY, 0, name). 6919 setOnMenuItemClickListener(handler). 6920 setAlphabeticShortcut('c'); 6921 added = true; 6922 } 6923 6924 if (canPaste()) { 6925 menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste). 6926 setOnMenuItemClickListener(handler). 6927 setAlphabeticShortcut('v'); 6928 added = true; 6929 } 6930 6931 if (mText instanceof Spanned) { 6932 int selStart = getSelectionStart(); 6933 int selEnd = getSelectionEnd(); 6934 6935 int min = Math.min(selStart, selEnd); 6936 int max = Math.max(selStart, selEnd); 6937 6938 URLSpan[] urls = ((Spanned) mText).getSpans(min, max, 6939 URLSpan.class); 6940 if (urls.length == 1) { 6941 menu.add(0, ID_COPY_URL, 0, 6942 com.android.internal.R.string.copyUrl). 6943 setOnMenuItemClickListener(handler); 6944 added = true; 6945 } 6946 } 6947 6948 if (isInputMethodTarget()) { 6949 menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod). 6950 setOnMenuItemClickListener(handler); 6951 added = true; 6952 } 6953 6954 String word = getWordForDictionary(); 6955 if (word != null) { 6956 menu.add(1, ID_ADD_TO_DICTIONARY, 0, 6957 getContext().getString(com.android.internal.R.string.addToDictionary, word)). 6958 setOnMenuItemClickListener(handler); 6959 added = true; 6960 6961 } 6962 6963 if (added) { 6964 menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle); 6965 } 6966 } 6967 6968 /** 6969 * Returns whether this text view is a current input method target. The 6970 * default implementation just checks with {@link InputMethodManager}. 6971 */ 6972 public boolean isInputMethodTarget() { 6973 InputMethodManager imm = InputMethodManager.peekInstance(); 6974 return imm != null && imm.isActive(this); 6975 } 6976 6977 private static final int ID_SELECT_ALL = android.R.id.selectAll; 6978 private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText; 6979 private static final int ID_STOP_SELECTING_TEXT = android.R.id.stopSelectingText; 6980 private static final int ID_CUT = android.R.id.cut; 6981 private static final int ID_COPY = android.R.id.copy; 6982 private static final int ID_PASTE = android.R.id.paste; 6983 private static final int ID_COPY_URL = android.R.id.copyUrl; 6984 private static final int ID_SWITCH_INPUT_METHOD = android.R.id.switchInputMethod; 6985 private static final int ID_ADD_TO_DICTIONARY = android.R.id.addToDictionary; 6986 6987 private class MenuHandler implements MenuItem.OnMenuItemClickListener { 6988 public boolean onMenuItemClick(MenuItem item) { 6989 return onTextContextMenuItem(item.getItemId()); 6990 } 6991 } 6992 6993 /** 6994 * Called when a context menu option for the text view is selected. Currently 6995 * this will be one of: {@link android.R.id#selectAll}, 6996 * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText}, 6997 * {@link android.R.id#cut}, {@link android.R.id#copy}, 6998 * {@link android.R.id#paste}, {@link android.R.id#copyUrl}, 6999 * or {@link android.R.id#switchInputMethod}. 7000 */ 7001 public boolean onTextContextMenuItem(int id) { 7002 int selStart = getSelectionStart(); 7003 int selEnd = getSelectionEnd(); 7004 7005 if (!isFocused()) { 7006 selStart = 0; 7007 selEnd = mText.length(); 7008 } 7009 7010 int min = Math.min(selStart, selEnd); 7011 int max = Math.max(selStart, selEnd); 7012 7013 if (min < 0) { 7014 min = 0; 7015 } 7016 if (max < 0) { 7017 max = 0; 7018 } 7019 7020 ClipboardManager clip = (ClipboardManager)getContext() 7021 .getSystemService(Context.CLIPBOARD_SERVICE); 7022 7023 switch (id) { 7024 case ID_SELECT_ALL: 7025 Selection.setSelection((Spannable) mText, 0, 7026 mText.length()); 7027 return true; 7028 7029 case ID_START_SELECTING_TEXT: 7030 MetaKeyKeyListener.startSelecting(this, (Spannable) mText); 7031 return true; 7032 7033 case ID_STOP_SELECTING_TEXT: 7034 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); 7035 Selection.setSelection((Spannable) mText, getSelectionEnd()); 7036 return true; 7037 7038 case ID_CUT: 7039 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); 7040 7041 if (min == max) { 7042 min = 0; 7043 max = mText.length(); 7044 } 7045 7046 clip.setText(mTransformed.subSequence(min, max)); 7047 ((Editable) mText).delete(min, max); 7048 return true; 7049 7050 case ID_COPY: 7051 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); 7052 7053 if (min == max) { 7054 min = 0; 7055 max = mText.length(); 7056 } 7057 7058 clip.setText(mTransformed.subSequence(min, max)); 7059 return true; 7060 7061 case ID_PASTE: 7062 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); 7063 7064 CharSequence paste = clip.getText(); 7065 7066 if (paste != null) { 7067 Selection.setSelection((Spannable) mText, max); 7068 ((Editable) mText).replace(min, max, paste); 7069 } 7070 7071 return true; 7072 7073 case ID_COPY_URL: 7074 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); 7075 7076 URLSpan[] urls = ((Spanned) mText).getSpans(min, max, 7077 URLSpan.class); 7078 if (urls.length == 1) { 7079 clip.setText(urls[0].getURL()); 7080 } 7081 7082 return true; 7083 7084 case ID_SWITCH_INPUT_METHOD: 7085 InputMethodManager imm = InputMethodManager.peekInstance(); 7086 if (imm != null) { 7087 imm.showInputMethodPicker(); 7088 } 7089 return true; 7090 7091 case ID_ADD_TO_DICTIONARY: 7092 String word = getWordForDictionary(); 7093 7094 if (word != null) { 7095 Intent i = new Intent("com.android.settings.USER_DICTIONARY_INSERT"); 7096 i.putExtra("word", word); 7097 getContext().startActivity(i); 7098 } 7099 7100 return true; 7101 } 7102 7103 return false; 7104 } 7105 7106 public boolean performLongClick() { 7107 if (super.performLongClick()) { 7108 mEatTouchRelease = true; 7109 return true; 7110 } 7111 7112 return false; 7113 } 7114 7115 @ViewDebug.ExportedProperty 7116 private CharSequence mText; 7117 private CharSequence mTransformed; 7118 private BufferType mBufferType = BufferType.NORMAL; 7119 7120 private int mInputType = EditorInfo.TYPE_NULL; 7121 private CharSequence mHint; 7122 private Layout mHintLayout; 7123 7124 private KeyListener mInput; 7125 7126 private MovementMethod mMovement; 7127 private TransformationMethod mTransformation; 7128 private ChangeWatcher mChangeWatcher; 7129 7130 private ArrayList<TextWatcher> mListeners = null; 7131 7132 // display attributes 7133 private TextPaint mTextPaint; 7134 private boolean mUserSetTextScaleX; 7135 private Paint mHighlightPaint; 7136 private int mHighlightColor = 0xFFBBDDFF; 7137 private Layout mLayout; 7138 7139 private long mShowCursor; 7140 private Blink mBlink; 7141 private boolean mCursorVisible = true; 7142 7143 private boolean mSelectAllOnFocus = false; 7144 7145 private int mGravity = Gravity.TOP | Gravity.LEFT; 7146 private boolean mHorizontallyScrolling; 7147 7148 private int mAutoLinkMask; 7149 private boolean mLinksClickable = true; 7150 7151 private float mSpacingMult = 1; 7152 private float mSpacingAdd = 0; 7153 7154 private static final int LINES = 1; 7155 private static final int EMS = LINES; 7156 private static final int PIXELS = 2; 7157 7158 private int mMaximum = Integer.MAX_VALUE; 7159 private int mMaxMode = LINES; 7160 private int mMinimum = 0; 7161 private int mMinMode = LINES; 7162 7163 private int mMaxWidth = Integer.MAX_VALUE; 7164 private int mMaxWidthMode = PIXELS; 7165 private int mMinWidth = 0; 7166 private int mMinWidthMode = PIXELS; 7167 7168 private boolean mSingleLine; 7169 private int mDesiredHeightAtMeasure = -1; 7170 private boolean mIncludePad = true; 7171 7172 // tmp primitives, so we don't alloc them on each draw 7173 private Path mHighlightPath; 7174 private boolean mHighlightPathBogus = true; 7175 private static final RectF sTempRect = new RectF(); 7176 7177 // XXX should be much larger 7178 private static final int VERY_WIDE = 16384; 7179 7180 private static final int BLINK = 500; 7181 7182 private static final int ANIMATED_SCROLL_GAP = 250; 7183 private long mLastScroll; 7184 private Scroller mScroller = null; 7185 7186 private BoringLayout.Metrics mBoring; 7187 private BoringLayout.Metrics mHintBoring; 7188 7189 private BoringLayout mSavedLayout, mSavedHintLayout; 7190 7191 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 7192 private InputFilter[] mFilters = NO_FILTERS; 7193 private static final Spanned EMPTY_SPANNED = new SpannedString(""); 7194} 7195