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