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