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