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