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 17 package android.widget; 18 19 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; 20 import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 21 import static android.view.ContentInfo.FLAG_CONVERT_TO_PLAIN_TEXT; 22 import static android.view.ContentInfo.SOURCE_AUTOFILL; 23 import static android.view.ContentInfo.SOURCE_CLIPBOARD; 24 import static android.view.ContentInfo.SOURCE_PROCESS_TEXT; 25 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY; 26 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH; 27 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX; 28 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; 29 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; 30 31 import android.R; 32 import android.annotation.CallSuper; 33 import android.annotation.CheckResult; 34 import android.annotation.ColorInt; 35 import android.annotation.DrawableRes; 36 import android.annotation.FloatRange; 37 import android.annotation.IntDef; 38 import android.annotation.IntRange; 39 import android.annotation.NonNull; 40 import android.annotation.Nullable; 41 import android.annotation.Px; 42 import android.annotation.RequiresPermission; 43 import android.annotation.Size; 44 import android.annotation.StringRes; 45 import android.annotation.StyleRes; 46 import android.annotation.TestApi; 47 import android.annotation.XmlRes; 48 import android.app.Activity; 49 import android.app.PendingIntent; 50 import android.app.assist.AssistStructure; 51 import android.app.compat.CompatChanges; 52 import android.compat.annotation.ChangeId; 53 import android.compat.annotation.EnabledSince; 54 import android.compat.annotation.UnsupportedAppUsage; 55 import android.content.ClipData; 56 import android.content.ClipDescription; 57 import android.content.ClipboardManager; 58 import android.content.Context; 59 import android.content.Intent; 60 import android.content.UndoManager; 61 import android.content.pm.PackageManager; 62 import android.content.res.ColorStateList; 63 import android.content.res.CompatibilityInfo; 64 import android.content.res.Configuration; 65 import android.content.res.FontScaleConverterFactory; 66 import android.content.res.Resources; 67 import android.content.res.TypedArray; 68 import android.content.res.XmlResourceParser; 69 import android.graphics.BaseCanvas; 70 import android.graphics.BlendMode; 71 import android.graphics.Canvas; 72 import android.graphics.Color; 73 import android.graphics.Insets; 74 import android.graphics.Matrix; 75 import android.graphics.Paint; 76 import android.graphics.Paint.FontMetricsInt; 77 import android.graphics.Path; 78 import android.graphics.PointF; 79 import android.graphics.PorterDuff; 80 import android.graphics.Rect; 81 import android.graphics.RectF; 82 import android.graphics.Typeface; 83 import android.graphics.drawable.Drawable; 84 import android.graphics.fonts.FontStyle; 85 import android.graphics.fonts.FontVariationAxis; 86 import android.graphics.text.LineBreakConfig; 87 import android.icu.text.DecimalFormatSymbols; 88 import android.os.AsyncTask; 89 import android.os.Build; 90 import android.os.Build.VERSION_CODES; 91 import android.os.Bundle; 92 import android.os.CancellationSignal; 93 import android.os.Handler; 94 import android.os.LocaleList; 95 import android.os.Parcel; 96 import android.os.Parcelable; 97 import android.os.ParcelableParcel; 98 import android.os.Process; 99 import android.os.SystemClock; 100 import android.os.UserHandle; 101 import android.provider.Settings; 102 import android.text.BoringLayout; 103 import android.text.DynamicLayout; 104 import android.text.Editable; 105 import android.text.GetChars; 106 import android.text.GraphemeClusterSegmentFinder; 107 import android.text.GraphicsOperations; 108 import android.text.Highlights; 109 import android.text.InputFilter; 110 import android.text.InputType; 111 import android.text.Layout; 112 import android.text.NoCopySpan; 113 import android.text.ParcelableSpan; 114 import android.text.PrecomputedText; 115 import android.text.SegmentFinder; 116 import android.text.Selection; 117 import android.text.SpanWatcher; 118 import android.text.Spannable; 119 import android.text.SpannableStringBuilder; 120 import android.text.Spanned; 121 import android.text.SpannedString; 122 import android.text.StaticLayout; 123 import android.text.TextDirectionHeuristic; 124 import android.text.TextDirectionHeuristics; 125 import android.text.TextPaint; 126 import android.text.TextUtils; 127 import android.text.TextUtils.TruncateAt; 128 import android.text.TextWatcher; 129 import android.text.WordSegmentFinder; 130 import android.text.method.AllCapsTransformationMethod; 131 import android.text.method.ArrowKeyMovementMethod; 132 import android.text.method.DateKeyListener; 133 import android.text.method.DateTimeKeyListener; 134 import android.text.method.DialerKeyListener; 135 import android.text.method.DigitsKeyListener; 136 import android.text.method.KeyListener; 137 import android.text.method.LinkMovementMethod; 138 import android.text.method.MetaKeyKeyListener; 139 import android.text.method.MovementMethod; 140 import android.text.method.OffsetMapping; 141 import android.text.method.PasswordTransformationMethod; 142 import android.text.method.SingleLineTransformationMethod; 143 import android.text.method.TextKeyListener; 144 import android.text.method.TimeKeyListener; 145 import android.text.method.TransformationMethod; 146 import android.text.method.TransformationMethod2; 147 import android.text.method.WordIterator; 148 import android.text.style.CharacterStyle; 149 import android.text.style.ClickableSpan; 150 import android.text.style.ParagraphStyle; 151 import android.text.style.SpellCheckSpan; 152 import android.text.style.SuggestionSpan; 153 import android.text.style.URLSpan; 154 import android.text.style.UpdateAppearance; 155 import android.text.util.Linkify; 156 import android.util.ArraySet; 157 import android.util.AttributeSet; 158 import android.util.DisplayMetrics; 159 import android.util.IntArray; 160 import android.util.Log; 161 import android.util.SparseIntArray; 162 import android.util.TypedValue; 163 import android.view.AccessibilityIterators.TextSegmentIterator; 164 import android.view.ActionMode; 165 import android.view.Choreographer; 166 import android.view.ContentInfo; 167 import android.view.ContextMenu; 168 import android.view.DragEvent; 169 import android.view.Gravity; 170 import android.view.HapticFeedbackConstants; 171 import android.view.InputDevice; 172 import android.view.KeyCharacterMap; 173 import android.view.KeyEvent; 174 import android.view.MotionEvent; 175 import android.view.PointerIcon; 176 import android.view.View; 177 import android.view.ViewConfiguration; 178 import android.view.ViewDebug; 179 import android.view.ViewGroup.LayoutParams; 180 import android.view.ViewHierarchyEncoder; 181 import android.view.ViewParent; 182 import android.view.ViewRootImpl; 183 import android.view.ViewStructure; 184 import android.view.ViewTreeObserver; 185 import android.view.accessibility.AccessibilityEvent; 186 import android.view.accessibility.AccessibilityManager; 187 import android.view.accessibility.AccessibilityNodeInfo; 188 import android.view.animation.AnimationUtils; 189 import android.view.autofill.AutofillManager; 190 import android.view.autofill.AutofillValue; 191 import android.view.contentcapture.ContentCaptureManager; 192 import android.view.contentcapture.ContentCaptureSession; 193 import android.view.inputmethod.BaseInputConnection; 194 import android.view.inputmethod.CompletionInfo; 195 import android.view.inputmethod.CorrectionInfo; 196 import android.view.inputmethod.CursorAnchorInfo; 197 import android.view.inputmethod.DeleteGesture; 198 import android.view.inputmethod.DeleteRangeGesture; 199 import android.view.inputmethod.EditorBoundsInfo; 200 import android.view.inputmethod.EditorInfo; 201 import android.view.inputmethod.ExtractedText; 202 import android.view.inputmethod.ExtractedTextRequest; 203 import android.view.inputmethod.HandwritingGesture; 204 import android.view.inputmethod.InputConnection; 205 import android.view.inputmethod.InputMethodManager; 206 import android.view.inputmethod.InsertGesture; 207 import android.view.inputmethod.InsertModeGesture; 208 import android.view.inputmethod.JoinOrSplitGesture; 209 import android.view.inputmethod.PreviewableHandwritingGesture; 210 import android.view.inputmethod.RemoveSpaceGesture; 211 import android.view.inputmethod.SelectGesture; 212 import android.view.inputmethod.SelectRangeGesture; 213 import android.view.inputmethod.TextAppearanceInfo; 214 import android.view.inputmethod.TextBoundsInfo; 215 import android.view.inspector.InspectableProperty; 216 import android.view.inspector.InspectableProperty.EnumEntry; 217 import android.view.inspector.InspectableProperty.FlagEntry; 218 import android.view.textclassifier.TextClassification; 219 import android.view.textclassifier.TextClassificationContext; 220 import android.view.textclassifier.TextClassificationManager; 221 import android.view.textclassifier.TextClassifier; 222 import android.view.textclassifier.TextLinks; 223 import android.view.textservice.SpellCheckerSubtype; 224 import android.view.textservice.TextServicesManager; 225 import android.view.translation.TranslationRequestValue; 226 import android.view.translation.TranslationSpec; 227 import android.view.translation.UiTranslationController; 228 import android.view.translation.ViewTranslationCallback; 229 import android.view.translation.ViewTranslationRequest; 230 import android.widget.RemoteViews.RemoteView; 231 232 import com.android.internal.accessibility.util.AccessibilityUtils; 233 import com.android.internal.annotations.VisibleForTesting; 234 import com.android.internal.graphics.ColorUtils; 235 import com.android.internal.inputmethod.EditableInputConnection; 236 import com.android.internal.logging.MetricsLogger; 237 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 238 import com.android.internal.util.ArrayUtils; 239 import com.android.internal.util.FastMath; 240 import com.android.internal.util.Preconditions; 241 242 import libcore.util.EmptyArray; 243 244 import org.xmlpull.v1.XmlPullParserException; 245 246 import java.io.IOException; 247 import java.lang.annotation.Retention; 248 import java.lang.annotation.RetentionPolicy; 249 import java.lang.ref.WeakReference; 250 import java.util.ArrayList; 251 import java.util.Arrays; 252 import java.util.List; 253 import java.util.Locale; 254 import java.util.Objects; 255 import java.util.Set; 256 import java.util.concurrent.CompletableFuture; 257 import java.util.concurrent.TimeUnit; 258 import java.util.function.Consumer; 259 import java.util.function.Supplier; 260 import java.util.regex.Matcher; 261 import java.util.regex.Pattern; 262 263 /** 264 * A user interface element that displays text to the user. 265 * To provide user-editable text, see {@link EditText}. 266 * <p> 267 * The following code sample shows a typical use, with an XML layout 268 * and code to modify the contents of the text view: 269 * </p> 270 271 * <pre> 272 * <LinearLayout 273 xmlns:android="http://schemas.android.com/apk/res/android" 274 android:layout_width="match_parent" 275 android:layout_height="match_parent"> 276 * <TextView 277 * android:id="@+id/text_view_id" 278 * android:layout_height="wrap_content" 279 * android:layout_width="wrap_content" 280 * android:text="@string/hello" /> 281 * </LinearLayout> 282 * </pre> 283 * <p> 284 * This code sample demonstrates how to modify the contents of the text view 285 * defined in the previous XML layout: 286 * </p> 287 * <pre> 288 * public class MainActivity extends Activity { 289 * 290 * protected void onCreate(Bundle savedInstanceState) { 291 * super.onCreate(savedInstanceState); 292 * setContentView(R.layout.activity_main); 293 * final TextView helloTextView = (TextView) findViewById(R.id.text_view_id); 294 * helloTextView.setText(R.string.user_greeting); 295 * } 296 * } 297 * </pre> 298 * <p> 299 * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>. 300 * </p> 301 * <p> 302 * <b>XML attributes</b> 303 * <p> 304 * See {@link android.R.styleable#TextView TextView Attributes}, 305 * {@link android.R.styleable#View View Attributes} 306 * 307 * @attr ref android.R.styleable#TextView_text 308 * @attr ref android.R.styleable#TextView_bufferType 309 * @attr ref android.R.styleable#TextView_hint 310 * @attr ref android.R.styleable#TextView_textColor 311 * @attr ref android.R.styleable#TextView_textColorHighlight 312 * @attr ref android.R.styleable#TextView_textColorHint 313 * @attr ref android.R.styleable#TextView_textAppearance 314 * @attr ref android.R.styleable#TextView_textColorLink 315 * @attr ref android.R.styleable#TextView_textFontWeight 316 * @attr ref android.R.styleable#TextView_textSize 317 * @attr ref android.R.styleable#TextView_textScaleX 318 * @attr ref android.R.styleable#TextView_fontFamily 319 * @attr ref android.R.styleable#TextView_typeface 320 * @attr ref android.R.styleable#TextView_textStyle 321 * @attr ref android.R.styleable#TextView_cursorVisible 322 * @attr ref android.R.styleable#TextView_maxLines 323 * @attr ref android.R.styleable#TextView_maxHeight 324 * @attr ref android.R.styleable#TextView_lines 325 * @attr ref android.R.styleable#TextView_height 326 * @attr ref android.R.styleable#TextView_minLines 327 * @attr ref android.R.styleable#TextView_minHeight 328 * @attr ref android.R.styleable#TextView_maxEms 329 * @attr ref android.R.styleable#TextView_maxWidth 330 * @attr ref android.R.styleable#TextView_ems 331 * @attr ref android.R.styleable#TextView_width 332 * @attr ref android.R.styleable#TextView_minEms 333 * @attr ref android.R.styleable#TextView_minWidth 334 * @attr ref android.R.styleable#TextView_gravity 335 * @attr ref android.R.styleable#TextView_scrollHorizontally 336 * @attr ref android.R.styleable#TextView_password 337 * @attr ref android.R.styleable#TextView_singleLine 338 * @attr ref android.R.styleable#TextView_selectAllOnFocus 339 * @attr ref android.R.styleable#TextView_includeFontPadding 340 * @attr ref android.R.styleable#TextView_maxLength 341 * @attr ref android.R.styleable#TextView_shadowColor 342 * @attr ref android.R.styleable#TextView_shadowDx 343 * @attr ref android.R.styleable#TextView_shadowDy 344 * @attr ref android.R.styleable#TextView_shadowRadius 345 * @attr ref android.R.styleable#TextView_autoLink 346 * @attr ref android.R.styleable#TextView_linksClickable 347 * @attr ref android.R.styleable#TextView_numeric 348 * @attr ref android.R.styleable#TextView_digits 349 * @attr ref android.R.styleable#TextView_phoneNumber 350 * @attr ref android.R.styleable#TextView_inputMethod 351 * @attr ref android.R.styleable#TextView_capitalize 352 * @attr ref android.R.styleable#TextView_autoText 353 * @attr ref android.R.styleable#TextView_editable 354 * @attr ref android.R.styleable#TextView_freezesText 355 * @attr ref android.R.styleable#TextView_ellipsize 356 * @attr ref android.R.styleable#TextView_drawableTop 357 * @attr ref android.R.styleable#TextView_drawableBottom 358 * @attr ref android.R.styleable#TextView_drawableRight 359 * @attr ref android.R.styleable#TextView_drawableLeft 360 * @attr ref android.R.styleable#TextView_drawableStart 361 * @attr ref android.R.styleable#TextView_drawableEnd 362 * @attr ref android.R.styleable#TextView_drawablePadding 363 * @attr ref android.R.styleable#TextView_drawableTint 364 * @attr ref android.R.styleable#TextView_drawableTintMode 365 * @attr ref android.R.styleable#TextView_lineSpacingExtra 366 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 367 * @attr ref android.R.styleable#TextView_justificationMode 368 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 369 * @attr ref android.R.styleable#TextView_inputType 370 * @attr ref android.R.styleable#TextView_imeOptions 371 * @attr ref android.R.styleable#TextView_privateImeOptions 372 * @attr ref android.R.styleable#TextView_imeActionLabel 373 * @attr ref android.R.styleable#TextView_imeActionId 374 * @attr ref android.R.styleable#TextView_editorExtras 375 * @attr ref android.R.styleable#TextView_elegantTextHeight 376 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 377 * @attr ref android.R.styleable#TextView_letterSpacing 378 * @attr ref android.R.styleable#TextView_fontFeatureSettings 379 * @attr ref android.R.styleable#TextView_fontVariationSettings 380 * @attr ref android.R.styleable#TextView_breakStrategy 381 * @attr ref android.R.styleable#TextView_hyphenationFrequency 382 * @attr ref android.R.styleable#TextView_lineBreakStyle 383 * @attr ref android.R.styleable#TextView_lineBreakWordStyle 384 * @attr ref android.R.styleable#TextView_autoSizeTextType 385 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 386 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 387 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 388 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 389 * @attr ref android.R.styleable#TextView_textCursorDrawable 390 * @attr ref android.R.styleable#TextView_textSelectHandle 391 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 392 * @attr ref android.R.styleable#TextView_textSelectHandleRight 393 * @attr ref android.R.styleable#TextView_allowUndo 394 * @attr ref android.R.styleable#TextView_enabled 395 */ 396 @RemoteView 397 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { 398 static final String LOG_TAG = "TextView"; 399 static final boolean DEBUG_EXTRACT = false; 400 static final boolean DEBUG_CURSOR = false; 401 402 private static final float[] TEMP_POSITION = new float[2]; 403 404 // Enum for the "typeface" XML parameter. 405 // TODO: How can we get this from the XML instead of hardcoding it here? 406 /** @hide */ 407 @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE}) 408 @Retention(RetentionPolicy.SOURCE) 409 public @interface XMLTypefaceAttr{} 410 private static final int DEFAULT_TYPEFACE = -1; 411 private static final int SANS = 1; 412 private static final int SERIF = 2; 413 private static final int MONOSPACE = 3; 414 415 // Enum for the "ellipsize" XML parameter. 416 private static final int ELLIPSIZE_NOT_SET = -1; 417 private static final int ELLIPSIZE_NONE = 0; 418 private static final int ELLIPSIZE_START = 1; 419 private static final int ELLIPSIZE_MIDDLE = 2; 420 private static final int ELLIPSIZE_END = 3; 421 private static final int ELLIPSIZE_MARQUEE = 4; 422 423 // Bitfield for the "numeric" XML parameter. 424 // TODO: How can we get this from the XML instead of hardcoding it here? 425 private static final int SIGNED = 2; 426 private static final int DECIMAL = 4; 427 428 /** 429 * Draw marquee text with fading edges as usual 430 */ 431 private static final int MARQUEE_FADE_NORMAL = 0; 432 433 /** 434 * Draw marquee text as ellipsize end while inactive instead of with the fade. 435 * (Useful for devices where the fade can be expensive if overdone) 436 */ 437 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1; 438 439 /** 440 * Draw marquee text with fading edges because it is currently active/animating. 441 */ 442 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2; 443 444 @UnsupportedAppUsage 445 private static final int LINES = 1; 446 private static final int EMS = LINES; 447 private static final int PIXELS = 2; 448 449 // Maximum text length for single line input. 450 private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000; 451 private InputFilter.LengthFilter mSingleLineLengthFilter = null; 452 453 private static final RectF TEMP_RECTF = new RectF(); 454 455 /** @hide */ 456 static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger 457 private static final int ANIMATED_SCROLL_GAP = 250; 458 459 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 460 private static final Spanned EMPTY_SPANNED = new SpannedString(""); 461 462 private static final int CHANGE_WATCHER_PRIORITY = 100; 463 464 /** 465 * The span priority of the {@link OffsetMapping} that is set on the text. It must be 466 * higher than the {@link DynamicLayout}'s {@link TextWatcher}, so that the transformed text is 467 * updated before {@link DynamicLayout#reflow(CharSequence, int, int, int)} being triggered 468 * by {@link TextWatcher#onTextChanged(CharSequence, int, int, int)}. 469 */ 470 private static final int OFFSET_MAPPING_SPAN_PRIORITY = 200; 471 472 // New state used to change background based on whether this TextView is multiline. 473 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline }; 474 475 // Accessibility action to share selected text. 476 private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000; 477 478 /** 479 * @hide 480 */ 481 // Accessibility action start id for "process text" actions. 482 static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100; 483 484 /** Accessibility action start id for "smart" actions. @hide */ 485 static final int ACCESSIBILITY_ACTION_SMART_START_ID = 0x10001000; 486 487 /** 488 * @hide 489 */ 490 @TestApi 491 public static final int PROCESS_TEXT_REQUEST_CODE = 100; 492 493 /** 494 * Return code of {@link #doKeyDown}. 495 */ 496 private static final int KEY_EVENT_NOT_HANDLED = 0; 497 private static final int KEY_EVENT_HANDLED = -1; 498 private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1; 499 private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2; 500 501 private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500; 502 503 // The default value of the line break style. 504 private static final int DEFAULT_LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_NONE; 505 506 // The default value of the line break word style. 507 private static final int DEFAULT_LINE_BREAK_WORD_STYLE = 508 LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; 509 510 /** 511 * This change ID enables the fallback text line spacing (line height) for BoringLayout. 512 * @hide 513 */ 514 @ChangeId 515 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) 516 public static final long BORINGLAYOUT_FALLBACK_LINESPACING = 210923482L; // buganizer id 517 518 /** 519 * This change ID enables the fallback text line spacing (line height) for StaticLayout. 520 * @hide 521 */ 522 @ChangeId 523 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.P) 524 public static final long STATICLAYOUT_FALLBACK_LINESPACING = 37756858; // buganizer id 525 526 // System wide time for last cut, copy or text changed action. 527 static long sLastCutCopyOrTextChangedTime; 528 529 private ColorStateList mTextColor; 530 private ColorStateList mHintTextColor; 531 private ColorStateList mLinkTextColor; 532 @ViewDebug.ExportedProperty(category = "text") 533 534 /** 535 * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead. 536 */ 537 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 538 private int mCurTextColor; 539 540 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 541 private int mCurHintTextColor; 542 private boolean mFreezesText; 543 544 @UnsupportedAppUsage 545 private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); 546 @UnsupportedAppUsage 547 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); 548 549 @UnsupportedAppUsage 550 private float mShadowRadius; 551 @UnsupportedAppUsage 552 private float mShadowDx; 553 @UnsupportedAppUsage 554 private float mShadowDy; 555 private int mShadowColor; 556 557 private boolean mPreDrawRegistered; 558 private boolean mPreDrawListenerDetached; 559 560 private TextClassifier mTextClassifier; 561 private TextClassifier mTextClassificationSession; 562 private TextClassificationContext mTextClassificationContext; 563 564 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is 565 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse 566 // the view hierarchy. On the other hand, if the user is using the movement key to traverse 567 // views (i.e. the first movement was to traverse out of this view, or this view was traversed 568 // into by the user holding the movement key down) then we shouldn't prevent the focus from 569 // changing. 570 private boolean mPreventDefaultMovement; 571 572 private TextUtils.TruncateAt mEllipsize; 573 574 // A flag to indicate the cursor was hidden by IME. 575 private boolean mImeIsConsumingInput; 576 577 // Whether cursor is visible without regard to {@link mImeConsumesInput}. 578 // {@code true} is the default value. 579 private boolean mCursorVisibleFromAttr = true; 580 581 static class Drawables { 582 static final int LEFT = 0; 583 static final int TOP = 1; 584 static final int RIGHT = 2; 585 static final int BOTTOM = 3; 586 587 static final int DRAWABLE_NONE = -1; 588 static final int DRAWABLE_RIGHT = 0; 589 static final int DRAWABLE_LEFT = 1; 590 591 final Rect mCompoundRect = new Rect(); 592 593 final Drawable[] mShowing = new Drawable[4]; 594 595 ColorStateList mTintList; 596 BlendMode mBlendMode; 597 boolean mHasTint; 598 boolean mHasTintMode; 599 600 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp; 601 Drawable mDrawableLeftInitial, mDrawableRightInitial; 602 603 boolean mIsRtlCompatibilityMode; 604 boolean mOverride; 605 606 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight, 607 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp; 608 609 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight, 610 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp; 611 612 int mDrawablePadding; 613 614 int mDrawableSaved = DRAWABLE_NONE; 615 Drawables(Context context)616 public Drawables(Context context) { 617 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 618 mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1 619 || !context.getApplicationInfo().hasRtlSupport(); 620 mOverride = false; 621 } 622 623 /** 624 * @return {@code true} if this object contains metadata that needs to 625 * be retained, {@code false} otherwise 626 */ 627 public boolean hasMetadata() { 628 return mDrawablePadding != 0 || mHasTintMode || mHasTint; 629 } 630 631 /** 632 * Updates the list of displayed drawables to account for the current 633 * layout direction. 634 * 635 * @param layoutDirection the current layout direction 636 * @return {@code true} if the displayed drawables changed 637 */ 638 public boolean resolveWithLayoutDirection(int layoutDirection) { 639 final Drawable previousLeft = mShowing[Drawables.LEFT]; 640 final Drawable previousRight = mShowing[Drawables.RIGHT]; 641 642 // First reset "left" and "right" drawables to their initial values 643 mShowing[Drawables.LEFT] = mDrawableLeftInitial; 644 mShowing[Drawables.RIGHT] = mDrawableRightInitial; 645 646 if (mIsRtlCompatibilityMode) { 647 // Use "start" drawable as "left" drawable if the "left" drawable was not defined 648 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) { 649 mShowing[Drawables.LEFT] = mDrawableStart; 650 mDrawableSizeLeft = mDrawableSizeStart; 651 mDrawableHeightLeft = mDrawableHeightStart; 652 } 653 // Use "end" drawable as "right" drawable if the "right" drawable was not defined 654 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) { 655 mShowing[Drawables.RIGHT] = mDrawableEnd; 656 mDrawableSizeRight = mDrawableSizeEnd; 657 mDrawableHeightRight = mDrawableHeightEnd; 658 } 659 } else { 660 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right" 661 // drawable if and only if they have been defined 662 switch(layoutDirection) { 663 case LAYOUT_DIRECTION_RTL: 664 if (mOverride) { 665 mShowing[Drawables.RIGHT] = mDrawableStart; 666 mDrawableSizeRight = mDrawableSizeStart; 667 mDrawableHeightRight = mDrawableHeightStart; 668 669 mShowing[Drawables.LEFT] = mDrawableEnd; 670 mDrawableSizeLeft = mDrawableSizeEnd; 671 mDrawableHeightLeft = mDrawableHeightEnd; 672 } 673 break; 674 675 case LAYOUT_DIRECTION_LTR: 676 default: 677 if (mOverride) { 678 mShowing[Drawables.LEFT] = mDrawableStart; 679 mDrawableSizeLeft = mDrawableSizeStart; 680 mDrawableHeightLeft = mDrawableHeightStart; 681 682 mShowing[Drawables.RIGHT] = mDrawableEnd; 683 mDrawableSizeRight = mDrawableSizeEnd; 684 mDrawableHeightRight = mDrawableHeightEnd; 685 } 686 break; 687 } 688 } 689 690 applyErrorDrawableIfNeeded(layoutDirection); 691 692 return mShowing[Drawables.LEFT] != previousLeft 693 || mShowing[Drawables.RIGHT] != previousRight; 694 } 695 696 public void setErrorDrawable(Drawable dr, TextView tv) { 697 if (mDrawableError != dr && mDrawableError != null) { 698 mDrawableError.setCallback(null); 699 } 700 mDrawableError = dr; 701 702 if (mDrawableError != null) { 703 final Rect compoundRect = mCompoundRect; 704 final int[] state = tv.getDrawableState(); 705 706 mDrawableError.setState(state); 707 mDrawableError.copyBounds(compoundRect); 708 mDrawableError.setCallback(tv); 709 mDrawableSizeError = compoundRect.width(); 710 mDrawableHeightError = compoundRect.height(); 711 } else { 712 mDrawableSizeError = mDrawableHeightError = 0; 713 } 714 } 715 716 private void applyErrorDrawableIfNeeded(int layoutDirection) { 717 // first restore the initial state if needed 718 switch (mDrawableSaved) { 719 case DRAWABLE_LEFT: 720 mShowing[Drawables.LEFT] = mDrawableTemp; 721 mDrawableSizeLeft = mDrawableSizeTemp; 722 mDrawableHeightLeft = mDrawableHeightTemp; 723 break; 724 case DRAWABLE_RIGHT: 725 mShowing[Drawables.RIGHT] = mDrawableTemp; 726 mDrawableSizeRight = mDrawableSizeTemp; 727 mDrawableHeightRight = mDrawableHeightTemp; 728 break; 729 case DRAWABLE_NONE: 730 default: 731 } 732 // then, if needed, assign the Error drawable to the correct location 733 if (mDrawableError != null) { 734 switch(layoutDirection) { 735 case LAYOUT_DIRECTION_RTL: 736 mDrawableSaved = DRAWABLE_LEFT; 737 738 mDrawableTemp = mShowing[Drawables.LEFT]; 739 mDrawableSizeTemp = mDrawableSizeLeft; 740 mDrawableHeightTemp = mDrawableHeightLeft; 741 742 mShowing[Drawables.LEFT] = mDrawableError; 743 mDrawableSizeLeft = mDrawableSizeError; 744 mDrawableHeightLeft = mDrawableHeightError; 745 break; 746 case LAYOUT_DIRECTION_LTR: 747 default: 748 mDrawableSaved = DRAWABLE_RIGHT; 749 750 mDrawableTemp = mShowing[Drawables.RIGHT]; 751 mDrawableSizeTemp = mDrawableSizeRight; 752 mDrawableHeightTemp = mDrawableHeightRight; 753 754 mShowing[Drawables.RIGHT] = mDrawableError; 755 mDrawableSizeRight = mDrawableSizeError; 756 mDrawableHeightRight = mDrawableHeightError; 757 break; 758 } 759 } 760 } 761 } 762 763 @UnsupportedAppUsage 764 Drawables mDrawables; 765 766 @UnsupportedAppUsage 767 private CharWrapper mCharWrapper; 768 769 @UnsupportedAppUsage(trackingBug = 124050217) 770 private Marquee mMarquee; 771 @UnsupportedAppUsage 772 private boolean mRestartMarquee; 773 774 private int mMarqueeRepeatLimit = 3; 775 776 private int mLastLayoutDirection = -1; 777 778 /** 779 * On some devices the fading edges add a performance penalty if used 780 * extensively in the same layout. This mode indicates how the marquee 781 * is currently being shown, if applicable. (mEllipsize will == MARQUEE) 782 */ 783 @UnsupportedAppUsage 784 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 785 786 /** 787 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores 788 * the layout that should be used when the mode switches. 789 */ 790 @UnsupportedAppUsage 791 private Layout mSavedMarqueeModeLayout; 792 793 // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal() 794 @ViewDebug.ExportedProperty(category = "text") 795 @UnsupportedAppUsage 796 private @Nullable CharSequence mText; 797 private @Nullable Spannable mSpannable; 798 private @Nullable PrecomputedText mPrecomputed; 799 800 @UnsupportedAppUsage 801 private CharSequence mTransformed; 802 @UnsupportedAppUsage 803 private BufferType mBufferType = BufferType.NORMAL; 804 805 private CharSequence mHint; 806 @UnsupportedAppUsage 807 private Layout mHintLayout; 808 private boolean mHideHint; 809 810 private MovementMethod mMovement; 811 812 private TransformationMethod mTransformation; 813 @UnsupportedAppUsage 814 private boolean mAllowTransformationLengthChange; 815 @UnsupportedAppUsage 816 private ChangeWatcher mChangeWatcher; 817 818 @UnsupportedAppUsage(trackingBug = 123769451) 819 private ArrayList<TextWatcher> mListeners; 820 821 // display attributes 822 @UnsupportedAppUsage 823 private final TextPaint mTextPaint; 824 @UnsupportedAppUsage 825 private boolean mUserSetTextScaleX; 826 @UnsupportedAppUsage 827 private Layout mLayout; 828 private boolean mLocalesChanged = false; 829 private int mTextSizeUnit = -1; 830 private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE; 831 private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE; 832 833 // This is used to reflect the current user preference for changing font weight and making text 834 // more bold. 835 private int mFontWeightAdjustment; 836 private Typeface mOriginalTypeface; 837 838 // True if setKeyListener() has been explicitly called 839 private boolean mListenerChanged = false; 840 // True if internationalized input should be used for numbers and date and time. 841 private final boolean mUseInternationalizedInput; 842 843 // Fallback fonts that end up getting used should be allowed to affect line spacing. 844 private static final int FALLBACK_LINE_SPACING_NONE = 0; 845 private static final int FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY = 1; 846 private static final int FALLBACK_LINE_SPACING_ALL = 2; 847 848 private int mUseFallbackLineSpacing; 849 // True if the view text can be padded for compat reasons, when the view is translated. 850 private final boolean mUseTextPaddingForUiTranslation; 851 852 @ViewDebug.ExportedProperty(category = "text") 853 @UnsupportedAppUsage 854 private int mGravity = Gravity.TOP | Gravity.START; 855 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 856 private boolean mHorizontallyScrolling; 857 858 private int mAutoLinkMask; 859 private boolean mLinksClickable = true; 860 861 @UnsupportedAppUsage 862 private float mSpacingMult = 1.0f; 863 @UnsupportedAppUsage 864 private float mSpacingAdd = 0.0f; 865 866 /** 867 * Remembers what line height was set to originally, before we broke it down into raw pixels. 868 * 869 * <p>This is stored as a complex dimension with both value and unit packed into one field! 870 * {@see TypedValue} 871 */ 872 private int mLineHeightComplexDimen; 873 874 private int mBreakStrategy; 875 private int mHyphenationFrequency; 876 private int mJustificationMode; 877 878 @UnsupportedAppUsage 879 private int mMaximum = Integer.MAX_VALUE; 880 @UnsupportedAppUsage 881 private int mMaxMode = LINES; 882 @UnsupportedAppUsage 883 private int mMinimum = 0; 884 @UnsupportedAppUsage 885 private int mMinMode = LINES; 886 887 @UnsupportedAppUsage 888 private int mOldMaximum = mMaximum; 889 @UnsupportedAppUsage 890 private int mOldMaxMode = mMaxMode; 891 892 @UnsupportedAppUsage 893 private int mMaxWidth = Integer.MAX_VALUE; 894 @UnsupportedAppUsage 895 private int mMaxWidthMode = PIXELS; 896 @UnsupportedAppUsage 897 private int mMinWidth = 0; 898 @UnsupportedAppUsage 899 private int mMinWidthMode = PIXELS; 900 901 @UnsupportedAppUsage 902 private boolean mSingleLine; 903 @UnsupportedAppUsage 904 private int mDesiredHeightAtMeasure = -1; 905 @UnsupportedAppUsage 906 private boolean mIncludePad = true; 907 private int mDeferScroll = -1; 908 909 // tmp primitives, so we don't alloc them on each draw 910 private Rect mTempRect; 911 private long mLastScroll; 912 private Scroller mScroller; 913 private TextPaint mTempTextPaint; 914 915 private Object mTempCursor; 916 917 @UnsupportedAppUsage 918 private BoringLayout.Metrics mBoring; 919 @UnsupportedAppUsage 920 private BoringLayout.Metrics mHintBoring; 921 @UnsupportedAppUsage 922 private BoringLayout mSavedLayout; 923 @UnsupportedAppUsage 924 private BoringLayout mSavedHintLayout; 925 926 @UnsupportedAppUsage 927 private TextDirectionHeuristic mTextDir; 928 929 private InputFilter[] mFilters = NO_FILTERS; 930 931 /** 932 * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is 933 * the same as {@link Process#myUserHandle()}. 934 * 935 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 936 * other apps may need to set this so that the system can use right user's resources and 937 * services such as input methods and spell checkers.</p> 938 * 939 * @see #setTextOperationUser(UserHandle) 940 */ 941 @Nullable 942 private UserHandle mTextOperationUser; 943 944 private volatile Locale mCurrentSpellCheckerLocaleCache; 945 946 // It is possible to have a selection even when mEditor is null (programmatically set, like when 947 // a link is pressed). These highlight-related fields do not go in mEditor. 948 @UnsupportedAppUsage 949 int mHighlightColor = 0x6633B5E5; 950 private Path mHighlightPath; 951 @UnsupportedAppUsage 952 private final Paint mHighlightPaint; 953 @UnsupportedAppUsage 954 private boolean mHighlightPathBogus = true; 955 956 private List<Path> mHighlightPaths; 957 private List<Paint> mHighlightPaints; 958 private Highlights mHighlights; 959 private int[] mSearchResultHighlights = null; 960 private Paint mSearchResultHighlightPaint = null; 961 private Paint mFocusedSearchResultHighlightPaint = null; 962 private int mFocusedSearchResultHighlightColor = 0xFFFF9632; 963 private int mSearchResultHighlightColor = 0xFFFFFF00; 964 965 private int mFocusedSearchResultIndex = -1; 966 private int mGesturePreviewHighlightStart = -1; 967 private int mGesturePreviewHighlightEnd = -1; 968 private Paint mGesturePreviewHighlightPaint; 969 private final List<Path> mPathRecyclePool = new ArrayList<>(); 970 private boolean mHighlightPathsBogus = true; 971 972 // Although these fields are specific to editable text, they are not added to Editor because 973 // they are defined by the TextView's style and are theme-dependent. 974 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 975 int mCursorDrawableRes; 976 private Drawable mCursorDrawable; 977 // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code 978 // by removing it, but we would break apps targeting <= P that use it by reflection. 979 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 980 int mTextSelectHandleLeftRes; 981 private Drawable mTextSelectHandleLeft; 982 // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code 983 // by removing it, but we would break apps targeting <= P that use it by reflection. 984 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 985 int mTextSelectHandleRightRes; 986 private Drawable mTextSelectHandleRight; 987 // Note: this might be stale if setTextSelectHandle is used. We could simplify the code 988 // by removing it, but we would break apps targeting <= P that use it by reflection. 989 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 990 int mTextSelectHandleRes; 991 private Drawable mTextSelectHandle; 992 int mTextEditSuggestionItemLayout; 993 int mTextEditSuggestionContainerLayout; 994 int mTextEditSuggestionHighlightStyle; 995 996 private static final int NO_POINTER_ID = -1; 997 /** 998 * The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among 999 * TextView and the handle views which are rendered on popup windows. 1000 */ 1001 private int mPrimePointerId = NO_POINTER_ID; 1002 1003 /** 1004 * Whether the prime pointer is from the event delivered to selection handle or insertion 1005 * handle. 1006 */ 1007 private boolean mIsPrimePointerFromHandleView; 1008 1009 /** 1010 * {@link EditText} specific data, created on demand when one of the Editor fields is used. 1011 * See {@link #createEditorIfNeeded()}. 1012 */ 1013 @UnsupportedAppUsage 1014 private Editor mEditor; 1015 1016 private static final int DEVICE_PROVISIONED_UNKNOWN = 0; 1017 private static final int DEVICE_PROVISIONED_NO = 1; 1018 private static final int DEVICE_PROVISIONED_YES = 2; 1019 1020 /** 1021 * Some special options such as sharing selected text should only be shown if the device 1022 * is provisioned. Only check the provisioned state once for a given view instance. 1023 */ 1024 private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN; 1025 1026 /** 1027 * The last input source on this TextView. 1028 * 1029 * Use the SOURCE_TOUCHSCREEN as the default value for backward compatibility. There could be a 1030 * non UI event originated ActionMode initiation, e.g. API call, a11y events, etc. 1031 */ 1032 private int mLastInputSource = InputDevice.SOURCE_TOUCHSCREEN; 1033 1034 /** 1035 * The TextView does not auto-size text (default). 1036 */ 1037 public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; 1038 1039 /** 1040 * The TextView scales text size both horizontally and vertically to fit within the 1041 * container. 1042 */ 1043 public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; 1044 1045 /** @hide */ 1046 @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = { 1047 AUTO_SIZE_TEXT_TYPE_NONE, 1048 AUTO_SIZE_TEXT_TYPE_UNIFORM 1049 }) 1050 @Retention(RetentionPolicy.SOURCE) 1051 public @interface AutoSizeTextType {} 1052 // Default minimum size for auto-sizing text in scaled pixels. 1053 private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12; 1054 // Default maximum size for auto-sizing text in scaled pixels. 1055 private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112; 1056 // Default value for the step size in pixels. 1057 private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1; 1058 // Use this to specify that any of the auto-size configuration int values have not been set. 1059 private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f; 1060 // Auto-size text type. 1061 private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 1062 // Specify if auto-size text is needed. 1063 private boolean mNeedsAutoSizeText = false; 1064 // Step size for auto-sizing in pixels. 1065 private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1066 // Minimum text size for auto-sizing in pixels. 1067 private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1068 // Maximum text size for auto-sizing in pixels. 1069 private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1070 // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from 1071 // when auto-sizing text. 1072 private int[] mAutoSizeTextSizesInPx = EmptyArray.INT; 1073 // Specifies whether auto-size should use the provided auto size steps set or if it should 1074 // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and 1075 // mAutoSizeStepGranularityInPx. 1076 private boolean mHasPresetAutoSizeValues = false; 1077 1078 // Autofill-related attributes 1079 // 1080 // Indicates whether the text was set statically or dynamically, so it can be used to 1081 // sanitize autofill requests. 1082 private boolean mTextSetFromXmlOrResourceId = false; 1083 // Resource id used to set the text. 1084 private @StringRes int mTextId = Resources.ID_NULL; 1085 // Resource id used to set the hint. 1086 private @StringRes int mHintId = Resources.ID_NULL; 1087 // 1088 // End of autofill-related attributes 1089 1090 private Pattern mWhitespacePattern; 1091 1092 /** 1093 * Kick-start the font cache for the zygote process (to pay the cost of 1094 * initializing freetype for our default font only once). 1095 * @hide 1096 */ 1097 public static void preloadFontCache() { 1098 if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) { 1099 return; 1100 } 1101 Paint p = new Paint(); 1102 p.setAntiAlias(true); 1103 // Ensure that the Typeface is loaded here. 1104 // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto. 1105 // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here 1106 // since Paint.measureText can not be called without Typeface static initializer. 1107 p.setTypeface(Typeface.DEFAULT); 1108 // We don't care about the result, just the side-effect of measuring. 1109 p.measureText("H"); 1110 } 1111 1112 /** 1113 * Interface definition for a callback to be invoked when an action is 1114 * performed on the editor. 1115 */ 1116 public interface OnEditorActionListener { 1117 /** 1118 * Called when an action is being performed. 1119 * 1120 * @param v The view that was clicked. 1121 * @param actionId Identifier of the action. This will be either the 1122 * identifier you supplied, or {@link EditorInfo#IME_NULL 1123 * EditorInfo.IME_NULL} if being called due to the enter key 1124 * being pressed. Starting from Android 14, the action identifier will 1125 * also be included when triggered by an enter key if the input is 1126 * constrained to a single line. 1127 * @param event If triggered by an enter key, this is the event; 1128 * otherwise, this is null. 1129 * @return Return true if you have consumed the action, else false. 1130 */ 1131 boolean onEditorAction(TextView v, int actionId, KeyEvent event); 1132 } 1133 1134 public TextView(Context context) { 1135 this(context, null); 1136 } 1137 1138 public TextView(Context context, @Nullable AttributeSet attrs) { 1139 this(context, attrs, com.android.internal.R.attr.textViewStyle); 1140 } 1141 1142 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 1143 this(context, attrs, defStyleAttr, 0); 1144 } 1145 1146 @SuppressWarnings("deprecation") 1147 public TextView( 1148 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 1149 super(context, attrs, defStyleAttr, defStyleRes); 1150 1151 // TextView is important by default, unless app developer overrode attribute. 1152 if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { 1153 setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); 1154 } 1155 if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) { 1156 setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES); 1157 } 1158 1159 setTextInternal(""); 1160 1161 final Resources res = getResources(); 1162 final CompatibilityInfo compat = res.getCompatibilityInfo(); 1163 1164 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 1165 mTextPaint.density = res.getDisplayMetrics().density; 1166 mTextPaint.setCompatibilityScaling(compat.applicationScale); 1167 1168 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1169 mHighlightPaint.setCompatibilityScaling(compat.applicationScale); 1170 1171 mMovement = getDefaultMovementMethod(); 1172 1173 mTransformation = null; 1174 1175 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 1176 attributes.mTextColor = ColorStateList.valueOf(0xFF000000); 1177 attributes.mTextSize = 15; 1178 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; 1179 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; 1180 mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; 1181 1182 final Resources.Theme theme = context.getTheme(); 1183 1184 /* 1185 * Look the appearance up without checking first if it exists because 1186 * almost every TextView has one and it greatly simplifies the logic 1187 * to be able to parse the appearance first and then let specific tags 1188 * for this View override it. 1189 */ 1190 TypedArray a = theme.obtainStyledAttributes(attrs, 1191 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); 1192 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance, 1193 attrs, a, defStyleAttr, defStyleRes); 1194 TypedArray appearance = null; 1195 int ap = a.getResourceId( 1196 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1); 1197 a.recycle(); 1198 if (ap != -1) { 1199 appearance = theme.obtainStyledAttributes( 1200 ap, com.android.internal.R.styleable.TextAppearance); 1201 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance, 1202 null, appearance, 0, ap); 1203 } 1204 if (appearance != null) { 1205 readTextAppearance(context, appearance, attributes, false /* styleArray */); 1206 attributes.mFontFamilyExplicit = false; 1207 appearance.recycle(); 1208 } 1209 1210 boolean editable = getDefaultEditable(); 1211 CharSequence inputMethod = null; 1212 int numeric = 0; 1213 CharSequence digits = null; 1214 boolean phone = false; 1215 boolean autotext = false; 1216 int autocap = -1; 1217 int buffertype = 0; 1218 boolean selectallonfocus = false; 1219 Drawable drawableLeft = null, drawableTop = null, drawableRight = null, 1220 drawableBottom = null, drawableStart = null, drawableEnd = null; 1221 ColorStateList drawableTint = null; 1222 BlendMode drawableTintMode = null; 1223 int drawablePadding = 0; 1224 int ellipsize = ELLIPSIZE_NOT_SET; 1225 boolean singleLine = false; 1226 int maxlength = -1; 1227 CharSequence text = ""; 1228 CharSequence hint = null; 1229 boolean password = false; 1230 float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1231 float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1232 float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 1233 int inputType = EditorInfo.TYPE_NULL; 1234 a = theme.obtainStyledAttributes( 1235 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes); 1236 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a, 1237 defStyleAttr, defStyleRes); 1238 int firstBaselineToTopHeight = -1; 1239 int lastBaselineToBottomHeight = -1; 1240 float lineHeight = -1f; 1241 int lineHeightUnit = -1; 1242 1243 readTextAppearance(context, a, attributes, true /* styleArray */); 1244 1245 int n = a.getIndexCount(); 1246 1247 // Must set id in a temporary variable because it will be reset by setText() 1248 boolean textIsSetFromXml = false; 1249 for (int i = 0; i < n; i++) { 1250 int attr = a.getIndex(i); 1251 1252 switch (attr) { 1253 case com.android.internal.R.styleable.TextView_editable: 1254 editable = a.getBoolean(attr, editable); 1255 break; 1256 1257 case com.android.internal.R.styleable.TextView_inputMethod: 1258 inputMethod = a.getText(attr); 1259 break; 1260 1261 case com.android.internal.R.styleable.TextView_numeric: 1262 numeric = a.getInt(attr, numeric); 1263 break; 1264 1265 case com.android.internal.R.styleable.TextView_digits: 1266 digits = a.getText(attr); 1267 break; 1268 1269 case com.android.internal.R.styleable.TextView_phoneNumber: 1270 phone = a.getBoolean(attr, phone); 1271 break; 1272 1273 case com.android.internal.R.styleable.TextView_autoText: 1274 autotext = a.getBoolean(attr, autotext); 1275 break; 1276 1277 case com.android.internal.R.styleable.TextView_capitalize: 1278 autocap = a.getInt(attr, autocap); 1279 break; 1280 1281 case com.android.internal.R.styleable.TextView_bufferType: 1282 buffertype = a.getInt(attr, buffertype); 1283 break; 1284 1285 case com.android.internal.R.styleable.TextView_selectAllOnFocus: 1286 selectallonfocus = a.getBoolean(attr, selectallonfocus); 1287 break; 1288 1289 case com.android.internal.R.styleable.TextView_autoLink: 1290 mAutoLinkMask = a.getInt(attr, 0); 1291 break; 1292 1293 case com.android.internal.R.styleable.TextView_linksClickable: 1294 mLinksClickable = a.getBoolean(attr, true); 1295 break; 1296 1297 case com.android.internal.R.styleable.TextView_drawableLeft: 1298 drawableLeft = a.getDrawable(attr); 1299 break; 1300 1301 case com.android.internal.R.styleable.TextView_drawableTop: 1302 drawableTop = a.getDrawable(attr); 1303 break; 1304 1305 case com.android.internal.R.styleable.TextView_drawableRight: 1306 drawableRight = a.getDrawable(attr); 1307 break; 1308 1309 case com.android.internal.R.styleable.TextView_drawableBottom: 1310 drawableBottom = a.getDrawable(attr); 1311 break; 1312 1313 case com.android.internal.R.styleable.TextView_drawableStart: 1314 drawableStart = a.getDrawable(attr); 1315 break; 1316 1317 case com.android.internal.R.styleable.TextView_drawableEnd: 1318 drawableEnd = a.getDrawable(attr); 1319 break; 1320 1321 case com.android.internal.R.styleable.TextView_drawableTint: 1322 drawableTint = a.getColorStateList(attr); 1323 break; 1324 1325 case com.android.internal.R.styleable.TextView_drawableTintMode: 1326 drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1), 1327 drawableTintMode); 1328 break; 1329 1330 case com.android.internal.R.styleable.TextView_drawablePadding: 1331 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); 1332 break; 1333 1334 case com.android.internal.R.styleable.TextView_maxLines: 1335 setMaxLines(a.getInt(attr, -1)); 1336 break; 1337 1338 case com.android.internal.R.styleable.TextView_maxHeight: 1339 setMaxHeight(a.getDimensionPixelSize(attr, -1)); 1340 break; 1341 1342 case com.android.internal.R.styleable.TextView_lines: 1343 setLines(a.getInt(attr, -1)); 1344 break; 1345 1346 case com.android.internal.R.styleable.TextView_height: 1347 setHeight(a.getDimensionPixelSize(attr, -1)); 1348 break; 1349 1350 case com.android.internal.R.styleable.TextView_minLines: 1351 setMinLines(a.getInt(attr, -1)); 1352 break; 1353 1354 case com.android.internal.R.styleable.TextView_minHeight: 1355 setMinHeight(a.getDimensionPixelSize(attr, -1)); 1356 break; 1357 1358 case com.android.internal.R.styleable.TextView_maxEms: 1359 setMaxEms(a.getInt(attr, -1)); 1360 break; 1361 1362 case com.android.internal.R.styleable.TextView_maxWidth: 1363 setMaxWidth(a.getDimensionPixelSize(attr, -1)); 1364 break; 1365 1366 case com.android.internal.R.styleable.TextView_ems: 1367 setEms(a.getInt(attr, -1)); 1368 break; 1369 1370 case com.android.internal.R.styleable.TextView_width: 1371 setWidth(a.getDimensionPixelSize(attr, -1)); 1372 break; 1373 1374 case com.android.internal.R.styleable.TextView_minEms: 1375 setMinEms(a.getInt(attr, -1)); 1376 break; 1377 1378 case com.android.internal.R.styleable.TextView_minWidth: 1379 setMinWidth(a.getDimensionPixelSize(attr, -1)); 1380 break; 1381 1382 case com.android.internal.R.styleable.TextView_gravity: 1383 setGravity(a.getInt(attr, -1)); 1384 break; 1385 1386 case com.android.internal.R.styleable.TextView_hint: 1387 mHintId = a.getResourceId(attr, Resources.ID_NULL); 1388 hint = a.getText(attr); 1389 break; 1390 1391 case com.android.internal.R.styleable.TextView_text: 1392 textIsSetFromXml = true; 1393 mTextId = a.getResourceId(attr, Resources.ID_NULL); 1394 text = a.getText(attr); 1395 break; 1396 1397 case com.android.internal.R.styleable.TextView_scrollHorizontally: 1398 if (a.getBoolean(attr, false)) { 1399 setHorizontallyScrolling(true); 1400 } 1401 break; 1402 1403 case com.android.internal.R.styleable.TextView_singleLine: 1404 singleLine = a.getBoolean(attr, singleLine); 1405 break; 1406 1407 case com.android.internal.R.styleable.TextView_ellipsize: 1408 ellipsize = a.getInt(attr, ellipsize); 1409 break; 1410 1411 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: 1412 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); 1413 break; 1414 1415 case com.android.internal.R.styleable.TextView_includeFontPadding: 1416 if (!a.getBoolean(attr, true)) { 1417 setIncludeFontPadding(false); 1418 } 1419 break; 1420 1421 case com.android.internal.R.styleable.TextView_cursorVisible: 1422 if (!a.getBoolean(attr, true)) { 1423 setCursorVisible(false); 1424 } 1425 break; 1426 1427 case com.android.internal.R.styleable.TextView_maxLength: 1428 maxlength = a.getInt(attr, -1); 1429 break; 1430 1431 case com.android.internal.R.styleable.TextView_textScaleX: 1432 setTextScaleX(a.getFloat(attr, 1.0f)); 1433 break; 1434 1435 case com.android.internal.R.styleable.TextView_freezesText: 1436 mFreezesText = a.getBoolean(attr, false); 1437 break; 1438 1439 case com.android.internal.R.styleable.TextView_enabled: 1440 setEnabled(a.getBoolean(attr, isEnabled())); 1441 break; 1442 1443 case com.android.internal.R.styleable.TextView_password: 1444 password = a.getBoolean(attr, password); 1445 break; 1446 1447 case com.android.internal.R.styleable.TextView_lineSpacingExtra: 1448 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); 1449 break; 1450 1451 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: 1452 mSpacingMult = a.getFloat(attr, mSpacingMult); 1453 break; 1454 1455 case com.android.internal.R.styleable.TextView_inputType: 1456 inputType = a.getInt(attr, EditorInfo.TYPE_NULL); 1457 break; 1458 1459 case com.android.internal.R.styleable.TextView_allowUndo: 1460 createEditorIfNeeded(); 1461 mEditor.mAllowUndo = a.getBoolean(attr, true); 1462 break; 1463 1464 case com.android.internal.R.styleable.TextView_imeOptions: 1465 createEditorIfNeeded(); 1466 mEditor.createInputContentTypeIfNeeded(); 1467 mEditor.mInputContentType.imeOptions = a.getInt(attr, 1468 mEditor.mInputContentType.imeOptions); 1469 break; 1470 1471 case com.android.internal.R.styleable.TextView_imeActionLabel: 1472 createEditorIfNeeded(); 1473 mEditor.createInputContentTypeIfNeeded(); 1474 mEditor.mInputContentType.imeActionLabel = a.getText(attr); 1475 break; 1476 1477 case com.android.internal.R.styleable.TextView_imeActionId: 1478 createEditorIfNeeded(); 1479 mEditor.createInputContentTypeIfNeeded(); 1480 mEditor.mInputContentType.imeActionId = a.getInt(attr, 1481 mEditor.mInputContentType.imeActionId); 1482 break; 1483 1484 case com.android.internal.R.styleable.TextView_privateImeOptions: 1485 setPrivateImeOptions(a.getString(attr)); 1486 break; 1487 1488 case com.android.internal.R.styleable.TextView_editorExtras: 1489 try { 1490 setInputExtras(a.getResourceId(attr, 0)); 1491 } catch (XmlPullParserException e) { 1492 Log.w(LOG_TAG, "Failure reading input extras", e); 1493 } catch (IOException e) { 1494 Log.w(LOG_TAG, "Failure reading input extras", e); 1495 } 1496 break; 1497 1498 case com.android.internal.R.styleable.TextView_textCursorDrawable: 1499 mCursorDrawableRes = a.getResourceId(attr, 0); 1500 break; 1501 1502 case com.android.internal.R.styleable.TextView_textSelectHandleLeft: 1503 mTextSelectHandleLeftRes = a.getResourceId(attr, 0); 1504 break; 1505 1506 case com.android.internal.R.styleable.TextView_textSelectHandleRight: 1507 mTextSelectHandleRightRes = a.getResourceId(attr, 0); 1508 break; 1509 1510 case com.android.internal.R.styleable.TextView_textSelectHandle: 1511 mTextSelectHandleRes = a.getResourceId(attr, 0); 1512 break; 1513 1514 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout: 1515 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0); 1516 break; 1517 1518 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout: 1519 mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0); 1520 break; 1521 1522 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle: 1523 mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0); 1524 break; 1525 1526 case com.android.internal.R.styleable.TextView_textIsSelectable: 1527 setTextIsSelectable(a.getBoolean(attr, false)); 1528 break; 1529 1530 case com.android.internal.R.styleable.TextView_breakStrategy: 1531 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE); 1532 break; 1533 1534 case com.android.internal.R.styleable.TextView_hyphenationFrequency: 1535 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE); 1536 break; 1537 1538 case com.android.internal.R.styleable.TextView_lineBreakStyle: 1539 mLineBreakStyle = a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE); 1540 break; 1541 1542 case com.android.internal.R.styleable.TextView_lineBreakWordStyle: 1543 mLineBreakWordStyle = a.getInt(attr, 1544 LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE); 1545 break; 1546 1547 case com.android.internal.R.styleable.TextView_autoSizeTextType: 1548 mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE); 1549 break; 1550 1551 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity: 1552 autoSizeStepGranularityInPx = a.getDimension(attr, 1553 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1554 break; 1555 1556 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize: 1557 autoSizeMinTextSizeInPx = a.getDimension(attr, 1558 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1559 break; 1560 1561 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize: 1562 autoSizeMaxTextSizeInPx = a.getDimension(attr, 1563 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); 1564 break; 1565 1566 case com.android.internal.R.styleable.TextView_autoSizePresetSizes: 1567 final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0); 1568 if (autoSizeStepSizeArrayResId > 0) { 1569 final TypedArray autoSizePresetTextSizes = a.getResources() 1570 .obtainTypedArray(autoSizeStepSizeArrayResId); 1571 setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes); 1572 autoSizePresetTextSizes.recycle(); 1573 } 1574 break; 1575 case com.android.internal.R.styleable.TextView_justificationMode: 1576 mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE); 1577 break; 1578 1579 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight: 1580 firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1); 1581 break; 1582 1583 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight: 1584 lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1); 1585 break; 1586 1587 case com.android.internal.R.styleable.TextView_lineHeight: 1588 TypedValue peekValue = a.peekValue(attr); 1589 if (peekValue != null && peekValue.type == TypedValue.TYPE_DIMENSION) { 1590 lineHeightUnit = peekValue.getComplexUnit(); 1591 lineHeight = TypedValue.complexToFloat(peekValue.data); 1592 } else { 1593 lineHeight = a.getDimensionPixelSize(attr, -1); 1594 } 1595 break; 1596 } 1597 } 1598 1599 a.recycle(); 1600 1601 BufferType bufferType = BufferType.EDITABLE; 1602 1603 final int variation = 1604 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 1605 final boolean passwordInputType = variation 1606 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); 1607 final boolean webPasswordInputType = variation 1608 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD); 1609 final boolean numberPasswordInputType = variation 1610 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 1611 1612 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 1613 mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O; 1614 if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) { 1615 mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_ALL; 1616 } else if (CompatChanges.isChangeEnabled(STATICLAYOUT_FALLBACK_LINESPACING)) { 1617 mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; 1618 } else { 1619 mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE; 1620 } 1621 // TODO(b/179693024): Use a ChangeId instead. 1622 mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R; 1623 1624 if (inputMethod != null) { 1625 Class<?> c; 1626 1627 try { 1628 c = Class.forName(inputMethod.toString()); 1629 } catch (ClassNotFoundException ex) { 1630 throw new RuntimeException(ex); 1631 } 1632 1633 try { 1634 createEditorIfNeeded(); 1635 mEditor.mKeyListener = (KeyListener) c.newInstance(); 1636 } catch (InstantiationException ex) { 1637 throw new RuntimeException(ex); 1638 } catch (IllegalAccessException ex) { 1639 throw new RuntimeException(ex); 1640 } 1641 try { 1642 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1643 ? inputType 1644 : mEditor.mKeyListener.getInputType(); 1645 } catch (IncompatibleClassChangeError e) { 1646 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1647 } 1648 } else if (digits != null) { 1649 createEditorIfNeeded(); 1650 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString()); 1651 // If no input type was specified, we will default to generic 1652 // text, since we can't tell the IME about the set of digits 1653 // that was selected. 1654 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1655 ? inputType : EditorInfo.TYPE_CLASS_TEXT; 1656 } else if (inputType != EditorInfo.TYPE_NULL) { 1657 setInputType(inputType, true); 1658 // If set, the input type overrides what was set using the deprecated singleLine flag. 1659 singleLine = !isMultilineInputType(inputType); 1660 } else if (phone) { 1661 createEditorIfNeeded(); 1662 mEditor.mKeyListener = DialerKeyListener.getInstance(); 1663 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE; 1664 } else if (numeric != 0) { 1665 createEditorIfNeeded(); 1666 mEditor.mKeyListener = DigitsKeyListener.getInstance( 1667 null, // locale 1668 (numeric & SIGNED) != 0, 1669 (numeric & DECIMAL) != 0); 1670 inputType = mEditor.mKeyListener.getInputType(); 1671 mEditor.mInputType = inputType; 1672 } else if (autotext || autocap != -1) { 1673 TextKeyListener.Capitalize cap; 1674 1675 inputType = EditorInfo.TYPE_CLASS_TEXT; 1676 1677 switch (autocap) { 1678 case 1: 1679 cap = TextKeyListener.Capitalize.SENTENCES; 1680 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; 1681 break; 1682 1683 case 2: 1684 cap = TextKeyListener.Capitalize.WORDS; 1685 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; 1686 break; 1687 1688 case 3: 1689 cap = TextKeyListener.Capitalize.CHARACTERS; 1690 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; 1691 break; 1692 1693 default: 1694 cap = TextKeyListener.Capitalize.NONE; 1695 break; 1696 } 1697 1698 createEditorIfNeeded(); 1699 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap); 1700 mEditor.mInputType = inputType; 1701 } else if (editable) { 1702 createEditorIfNeeded(); 1703 mEditor.mKeyListener = TextKeyListener.getInstance(); 1704 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1705 } else if (isTextSelectable()) { 1706 // Prevent text changes from keyboard. 1707 if (mEditor != null) { 1708 mEditor.mKeyListener = null; 1709 mEditor.mInputType = EditorInfo.TYPE_NULL; 1710 } 1711 bufferType = BufferType.SPANNABLE; 1712 // So that selection can be changed using arrow keys and touch is handled. 1713 setMovementMethod(ArrowKeyMovementMethod.getInstance()); 1714 } else { 1715 if (mEditor != null) mEditor.mKeyListener = null; 1716 1717 switch (buffertype) { 1718 case 0: 1719 bufferType = BufferType.NORMAL; 1720 break; 1721 case 1: 1722 bufferType = BufferType.SPANNABLE; 1723 break; 1724 case 2: 1725 bufferType = BufferType.EDITABLE; 1726 break; 1727 } 1728 } 1729 1730 if (mEditor != null) { 1731 mEditor.adjustInputType(password, passwordInputType, webPasswordInputType, 1732 numberPasswordInputType); 1733 } 1734 1735 if (selectallonfocus) { 1736 createEditorIfNeeded(); 1737 mEditor.mSelectAllOnFocus = true; 1738 1739 if (bufferType == BufferType.NORMAL) { 1740 bufferType = BufferType.SPANNABLE; 1741 } 1742 } 1743 1744 // Set up the tint (if needed) before setting the drawables so that it 1745 // gets applied correctly. 1746 if (drawableTint != null || drawableTintMode != null) { 1747 if (mDrawables == null) { 1748 mDrawables = new Drawables(context); 1749 } 1750 if (drawableTint != null) { 1751 mDrawables.mTintList = drawableTint; 1752 mDrawables.mHasTint = true; 1753 } 1754 if (drawableTintMode != null) { 1755 mDrawables.mBlendMode = drawableTintMode; 1756 mDrawables.mHasTintMode = true; 1757 } 1758 } 1759 1760 // This call will save the initial left/right drawables 1761 setCompoundDrawablesWithIntrinsicBounds( 1762 drawableLeft, drawableTop, drawableRight, drawableBottom); 1763 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd); 1764 setCompoundDrawablePadding(drawablePadding); 1765 1766 // Same as setSingleLine(), but make sure the transformation method and the maximum number 1767 // of lines of height are unchanged for multi-line TextViews. 1768 setInputTypeSingleLine(singleLine); 1769 applySingleLine(singleLine, singleLine, singleLine, 1770 // Does not apply automated max length filter since length filter will be resolved 1771 // later in this function. 1772 false 1773 ); 1774 1775 if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) { 1776 ellipsize = ELLIPSIZE_END; 1777 } 1778 1779 switch (ellipsize) { 1780 case ELLIPSIZE_START: 1781 setEllipsize(TextUtils.TruncateAt.START); 1782 break; 1783 case ELLIPSIZE_MIDDLE: 1784 setEllipsize(TextUtils.TruncateAt.MIDDLE); 1785 break; 1786 case ELLIPSIZE_END: 1787 setEllipsize(TextUtils.TruncateAt.END); 1788 break; 1789 case ELLIPSIZE_MARQUEE: 1790 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) { 1791 setHorizontalFadingEdgeEnabled(true); 1792 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 1793 } else { 1794 setHorizontalFadingEdgeEnabled(false); 1795 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 1796 } 1797 setEllipsize(TextUtils.TruncateAt.MARQUEE); 1798 break; 1799 } 1800 1801 final boolean isPassword = password || passwordInputType || webPasswordInputType 1802 || numberPasswordInputType; 1803 final boolean isMonospaceEnforced = isPassword || (mEditor != null 1804 && (mEditor.mInputType 1805 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) 1806 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)); 1807 if (isMonospaceEnforced) { 1808 attributes.mTypefaceIndex = MONOSPACE; 1809 } 1810 1811 mFontWeightAdjustment = getContext().getResources().getConfiguration().fontWeightAdjustment; 1812 applyTextAppearance(attributes); 1813 1814 if (isPassword) { 1815 setTransformationMethod(PasswordTransformationMethod.getInstance()); 1816 } 1817 1818 // For addressing b/145128646 1819 // For the performance reason, we limit characters for single line text field. 1820 if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) { 1821 mSingleLineLengthFilter = new InputFilter.LengthFilter( 1822 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT); 1823 } 1824 1825 if (mSingleLineLengthFilter != null) { 1826 setFilters(new InputFilter[] { mSingleLineLengthFilter }); 1827 } else if (maxlength >= 0) { 1828 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); 1829 } else { 1830 setFilters(NO_FILTERS); 1831 } 1832 1833 setText(text, bufferType); 1834 if (mText == null) { 1835 mText = ""; 1836 } 1837 if (mTransformed == null) { 1838 mTransformed = ""; 1839 } 1840 1841 if (textIsSetFromXml) { 1842 mTextSetFromXmlOrResourceId = true; 1843 } 1844 1845 if (hint != null) setHint(hint); 1846 1847 /* 1848 * Views are not normally clickable unless specified to be. 1849 * However, TextViews that have input or movement methods *are* 1850 * clickable by default. By setting clickable here, we implicitly set focusable as well 1851 * if not overridden by the developer. 1852 */ 1853 a = context.obtainStyledAttributes( 1854 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); 1855 boolean canInputOrMove = (mMovement != null || getKeyListener() != null); 1856 boolean clickable = canInputOrMove || isClickable(); 1857 boolean longClickable = canInputOrMove || isLongClickable(); 1858 int focusable = getFocusable(); 1859 boolean isAutoHandwritingEnabled = true; 1860 1861 n = a.getIndexCount(); 1862 for (int i = 0; i < n; i++) { 1863 int attr = a.getIndex(i); 1864 1865 switch (attr) { 1866 case com.android.internal.R.styleable.View_focusable: 1867 TypedValue val = new TypedValue(); 1868 if (a.getValue(attr, val)) { 1869 focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN) 1870 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE) 1871 : val.data; 1872 } 1873 break; 1874 1875 case com.android.internal.R.styleable.View_clickable: 1876 clickable = a.getBoolean(attr, clickable); 1877 break; 1878 1879 case com.android.internal.R.styleable.View_longClickable: 1880 longClickable = a.getBoolean(attr, longClickable); 1881 break; 1882 1883 case com.android.internal.R.styleable.View_autoHandwritingEnabled: 1884 isAutoHandwritingEnabled = a.getBoolean(attr, true); 1885 break; 1886 } 1887 } 1888 a.recycle(); 1889 1890 // Some apps were relying on the undefined behavior of focusable winning over 1891 // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually 1892 // when starting with EditText and setting only focusable=false). To keep those apps from 1893 // breaking, re-apply the focusable attribute here. 1894 if (focusable != getFocusable()) { 1895 setFocusable(focusable); 1896 } 1897 setClickable(clickable); 1898 setLongClickable(longClickable); 1899 setAutoHandwritingEnabled(isAutoHandwritingEnabled); 1900 1901 if (mEditor != null) mEditor.prepareCursorControllers(); 1902 1903 // If not explicitly specified this view is important for accessibility. 1904 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 1905 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 1906 } 1907 1908 if (supportsAutoSizeText()) { 1909 if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 1910 // If uniform auto-size has been specified but preset values have not been set then 1911 // replace the auto-size configuration values that have not been specified with the 1912 // defaults. 1913 if (!mHasPresetAutoSizeValues) { 1914 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1915 1916 if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1917 autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1918 TypedValue.COMPLEX_UNIT_SP, 1919 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1920 displayMetrics); 1921 } 1922 1923 if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1924 autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1925 TypedValue.COMPLEX_UNIT_SP, 1926 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1927 displayMetrics); 1928 } 1929 1930 if (autoSizeStepGranularityInPx 1931 == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { 1932 autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX; 1933 } 1934 1935 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 1936 autoSizeMaxTextSizeInPx, 1937 autoSizeStepGranularityInPx); 1938 } 1939 1940 setupAutoSizeText(); 1941 } 1942 } else { 1943 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 1944 } 1945 1946 if (firstBaselineToTopHeight >= 0) { 1947 setFirstBaselineToTopHeight(firstBaselineToTopHeight); 1948 } 1949 if (lastBaselineToBottomHeight >= 0) { 1950 setLastBaselineToBottomHeight(lastBaselineToBottomHeight); 1951 } 1952 if (lineHeight >= 0) { 1953 if (lineHeightUnit == -1) { 1954 setLineHeightPx(lineHeight); 1955 } else { 1956 setLineHeight(lineHeightUnit, lineHeight); 1957 } 1958 } 1959 } 1960 1961 // Update mText and mPrecomputed setTextInternal(@ullable CharSequence text)1962 private void setTextInternal(@Nullable CharSequence text) { 1963 mText = text; 1964 mSpannable = (text instanceof Spannable) ? (Spannable) text : null; 1965 mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 1966 } 1967 1968 /** 1969 * Specify whether this widget should automatically scale the text to try to perfectly fit 1970 * within the layout bounds by using the default auto-size configuration. 1971 * 1972 * @param autoSizeTextType the type of auto-size. Must be one of 1973 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 1974 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 1975 * 1976 * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above. 1977 * 1978 * @attr ref android.R.styleable#TextView_autoSizeTextType 1979 * 1980 * @see #getAutoSizeTextType() 1981 */ setAutoSizeTextTypeWithDefaults(@utoSizeTextType int autoSizeTextType)1982 public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) { 1983 if (supportsAutoSizeText()) { 1984 switch (autoSizeTextType) { 1985 case AUTO_SIZE_TEXT_TYPE_NONE: 1986 clearAutoSizeConfiguration(); 1987 break; 1988 case AUTO_SIZE_TEXT_TYPE_UNIFORM: 1989 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 1990 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 1991 TypedValue.COMPLEX_UNIT_SP, 1992 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, 1993 displayMetrics); 1994 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 1995 TypedValue.COMPLEX_UNIT_SP, 1996 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, 1997 displayMetrics); 1998 1999 validateAndSetAutoSizeTextTypeUniformConfiguration( 2000 autoSizeMinTextSizeInPx, 2001 autoSizeMaxTextSizeInPx, 2002 DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX); 2003 if (setupAutoSizeText()) { 2004 autoSizeText(); 2005 invalidate(); 2006 } 2007 break; 2008 default: 2009 throw new IllegalArgumentException( 2010 "Unknown auto-size text type: " + autoSizeTextType); 2011 } 2012 } 2013 } 2014 2015 /** 2016 * Specify whether this widget should automatically scale the text to try to perfectly fit 2017 * within the layout bounds. If all the configuration params are valid the type of auto-size is 2018 * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 2019 * 2020 * @param autoSizeMinTextSize the minimum text size available for auto-size 2021 * @param autoSizeMaxTextSize the maximum text size available for auto-size 2022 * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with 2023 * the minimum and maximum text size in order to build the set of 2024 * text sizes the system uses to choose from when auto-sizing 2025 * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the 2026 * possible dimension units 2027 * 2028 * @throws IllegalArgumentException if any of the configuration params are invalid. 2029 * 2030 * @attr ref android.R.styleable#TextView_autoSizeTextType 2031 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 2032 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 2033 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 2034 * 2035 * @see #setAutoSizeTextTypeWithDefaults(int) 2036 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2037 * @see #getAutoSizeMinTextSize() 2038 * @see #getAutoSizeMaxTextSize() 2039 * @see #getAutoSizeStepGranularity() 2040 * @see #getAutoSizeTextAvailableSizes() 2041 */ setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)2042 public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, 2043 int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) { 2044 if (supportsAutoSizeText()) { 2045 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 2046 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( 2047 unit, autoSizeMinTextSize, displayMetrics); 2048 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( 2049 unit, autoSizeMaxTextSize, displayMetrics); 2050 final float autoSizeStepGranularityInPx = TypedValue.applyDimension( 2051 unit, autoSizeStepGranularity, displayMetrics); 2052 2053 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, 2054 autoSizeMaxTextSizeInPx, 2055 autoSizeStepGranularityInPx); 2056 2057 if (setupAutoSizeText()) { 2058 autoSizeText(); 2059 invalidate(); 2060 } 2061 } 2062 } 2063 2064 /** 2065 * Specify whether this widget should automatically scale the text to try to perfectly fit 2066 * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid 2067 * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. 2068 * 2069 * @param presetSizes an {@code int} array of sizes in pixels 2070 * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for 2071 * the possible dimension units 2072 * 2073 * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid. 2074 * 2075 * @attr ref android.R.styleable#TextView_autoSizeTextType 2076 * @attr ref android.R.styleable#TextView_autoSizePresetSizes 2077 * 2078 * @see #setAutoSizeTextTypeWithDefaults(int) 2079 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2080 * @see #getAutoSizeMinTextSize() 2081 * @see #getAutoSizeMaxTextSize() 2082 * @see #getAutoSizeTextAvailableSizes() 2083 */ setAutoSizeTextTypeUniformWithPresetSizes(@onNull int[] presetSizes, int unit)2084 public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) { 2085 if (supportsAutoSizeText()) { 2086 final int presetSizesLength = presetSizes.length; 2087 if (presetSizesLength > 0) { 2088 int[] presetSizesInPx = new int[presetSizesLength]; 2089 2090 if (unit == TypedValue.COMPLEX_UNIT_PX) { 2091 presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength); 2092 } else { 2093 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 2094 // Convert all to sizes to pixels. 2095 for (int i = 0; i < presetSizesLength; i++) { 2096 presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit, 2097 presetSizes[i], displayMetrics)); 2098 } 2099 } 2100 2101 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx); 2102 if (!setupAutoSizeUniformPresetSizesConfiguration()) { 2103 throw new IllegalArgumentException("None of the preset sizes is valid: " 2104 + Arrays.toString(presetSizes)); 2105 } 2106 } else { 2107 mHasPresetAutoSizeValues = false; 2108 } 2109 2110 if (setupAutoSizeText()) { 2111 autoSizeText(); 2112 invalidate(); 2113 } 2114 } 2115 } 2116 2117 /** 2118 * Returns the type of auto-size set for this widget. 2119 * 2120 * @return an {@code int} corresponding to one of the auto-size types: 2121 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or 2122 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} 2123 * 2124 * @attr ref android.R.styleable#TextView_autoSizeTextType 2125 * 2126 * @see #setAutoSizeTextTypeWithDefaults(int) 2127 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2128 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2129 */ 2130 @InspectableProperty(enumMapping = { 2131 @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE), 2132 @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM) 2133 }) 2134 @AutoSizeTextType getAutoSizeTextType()2135 public int getAutoSizeTextType() { 2136 return mAutoSizeTextType; 2137 } 2138 2139 /** 2140 * @return the current auto-size step granularity in pixels. 2141 * 2142 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity 2143 * 2144 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2145 */ 2146 @InspectableProperty getAutoSizeStepGranularity()2147 public int getAutoSizeStepGranularity() { 2148 return Math.round(mAutoSizeStepGranularityInPx); 2149 } 2150 2151 /** 2152 * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that 2153 * if auto-size has not been configured this function returns {@code -1}. 2154 * 2155 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize 2156 * 2157 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2158 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2159 */ 2160 @InspectableProperty getAutoSizeMinTextSize()2161 public int getAutoSizeMinTextSize() { 2162 return Math.round(mAutoSizeMinTextSizeInPx); 2163 } 2164 2165 /** 2166 * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that 2167 * if auto-size has not been configured this function returns {@code -1}. 2168 * 2169 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize 2170 * 2171 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2172 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2173 */ 2174 @InspectableProperty getAutoSizeMaxTextSize()2175 public int getAutoSizeMaxTextSize() { 2176 return Math.round(mAutoSizeMaxTextSizeInPx); 2177 } 2178 2179 /** 2180 * @return the current auto-size {@code int} sizes array (in pixels). 2181 * 2182 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) 2183 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) 2184 */ getAutoSizeTextAvailableSizes()2185 public int[] getAutoSizeTextAvailableSizes() { 2186 return mAutoSizeTextSizesInPx; 2187 } 2188 setupAutoSizeUniformPresetSizes(TypedArray textSizes)2189 private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) { 2190 final int textSizesLength = textSizes.length(); 2191 final int[] parsedSizes = new int[textSizesLength]; 2192 2193 if (textSizesLength > 0) { 2194 for (int i = 0; i < textSizesLength; i++) { 2195 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1); 2196 } 2197 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes); 2198 setupAutoSizeUniformPresetSizesConfiguration(); 2199 } 2200 } 2201 setupAutoSizeUniformPresetSizesConfiguration()2202 private boolean setupAutoSizeUniformPresetSizesConfiguration() { 2203 final int sizesLength = mAutoSizeTextSizesInPx.length; 2204 mHasPresetAutoSizeValues = sizesLength > 0; 2205 if (mHasPresetAutoSizeValues) { 2206 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 2207 mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0]; 2208 mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1]; 2209 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2210 } 2211 return mHasPresetAutoSizeValues; 2212 } 2213 2214 /** 2215 * If all params are valid then save the auto-size configuration. 2216 * 2217 * @throws IllegalArgumentException if any of the params are invalid 2218 */ validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx)2219 private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, 2220 float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) { 2221 // First validate. 2222 if (autoSizeMinTextSizeInPx <= 0) { 2223 throw new IllegalArgumentException("Minimum auto-size text size (" 2224 + autoSizeMinTextSizeInPx + "px) is less or equal to (0px)"); 2225 } 2226 2227 if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) { 2228 throw new IllegalArgumentException("Maximum auto-size text size (" 2229 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size " 2230 + "text size (" + autoSizeMinTextSizeInPx + "px)"); 2231 } 2232 2233 if (autoSizeStepGranularityInPx <= 0) { 2234 throw new IllegalArgumentException("The auto-size step granularity (" 2235 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)"); 2236 } 2237 2238 // All good, persist the configuration. 2239 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; 2240 mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx; 2241 mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx; 2242 mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx; 2243 mHasPresetAutoSizeValues = false; 2244 } 2245 clearAutoSizeConfiguration()2246 private void clearAutoSizeConfiguration() { 2247 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; 2248 mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2249 mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2250 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; 2251 mAutoSizeTextSizesInPx = EmptyArray.INT; 2252 mNeedsAutoSizeText = false; 2253 } 2254 2255 // Returns distinct sorted positive values. cleanupAutoSizePresetSizes(int[] presetValues)2256 private int[] cleanupAutoSizePresetSizes(int[] presetValues) { 2257 final int presetValuesLength = presetValues.length; 2258 if (presetValuesLength == 0) { 2259 return presetValues; 2260 } 2261 Arrays.sort(presetValues); 2262 2263 final IntArray uniqueValidSizes = new IntArray(); 2264 for (int i = 0; i < presetValuesLength; i++) { 2265 final int currentPresetValue = presetValues[i]; 2266 2267 if (currentPresetValue > 0 2268 && uniqueValidSizes.binarySearch(currentPresetValue) < 0) { 2269 uniqueValidSizes.add(currentPresetValue); 2270 } 2271 } 2272 2273 return presetValuesLength == uniqueValidSizes.size() 2274 ? presetValues 2275 : uniqueValidSizes.toArray(); 2276 } 2277 setupAutoSizeText()2278 private boolean setupAutoSizeText() { 2279 if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { 2280 // Calculate the sizes set based on minimum size, maximum size and step size if we do 2281 // not have a predefined set of sizes or if the current sizes array is empty. 2282 if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) { 2283 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx 2284 - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1; 2285 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength]; 2286 for (int i = 0; i < autoSizeValuesLength; i++) { 2287 autoSizeTextSizesInPx[i] = Math.round( 2288 mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx)); 2289 } 2290 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx); 2291 } 2292 2293 mNeedsAutoSizeText = true; 2294 } else { 2295 mNeedsAutoSizeText = false; 2296 } 2297 2298 return mNeedsAutoSizeText; 2299 } 2300 parseDimensionArray(TypedArray dimens)2301 private int[] parseDimensionArray(TypedArray dimens) { 2302 if (dimens == null) { 2303 return null; 2304 } 2305 int[] result = new int[dimens.length()]; 2306 for (int i = 0; i < result.length; i++) { 2307 result[i] = dimens.getDimensionPixelSize(i, 0); 2308 } 2309 return result; 2310 } 2311 2312 /** 2313 * @hide 2314 */ 2315 @TestApi 2316 @Override onActivityResult(int requestCode, int resultCode, @Nullable Intent data)2317 public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 2318 if (requestCode == PROCESS_TEXT_REQUEST_CODE) { 2319 if (resultCode == Activity.RESULT_OK && data != null) { 2320 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT); 2321 if (result != null) { 2322 if (isTextEditable()) { 2323 ClipData clip = ClipData.newPlainText("", result); 2324 ContentInfo payload = 2325 new ContentInfo.Builder(clip, SOURCE_PROCESS_TEXT).build(); 2326 performReceiveContent(payload); 2327 if (mEditor != null) { 2328 mEditor.refreshTextActionMode(); 2329 } 2330 } else { 2331 if (result.length() > 0) { 2332 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG) 2333 .show(); 2334 } 2335 } 2336 } 2337 } else if (mSpannable != null) { 2338 // Reset the selection. 2339 Selection.setSelection(mSpannable, getSelectionEnd()); 2340 } 2341 } 2342 } 2343 2344 /** 2345 * Sets the Typeface taking into account the given attributes. 2346 * 2347 * @param typeface a typeface 2348 * @param familyName family name string, e.g. "serif" 2349 * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF. 2350 * @param style a typeface style 2351 * @param weight a weight value for the Typeface or {@code FontStyle.FONT_WEIGHT_UNSPECIFIED} 2352 * if not specified. 2353 */ setTypefaceFromAttrs(@ullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) int weight)2354 private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName, 2355 @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, 2356 @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) 2357 int weight) { 2358 if (typeface == null && familyName != null) { 2359 // Lookup normal Typeface from system font map. 2360 final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL); 2361 resolveStyleAndSetTypeface(normalTypeface, style, weight); 2362 } else if (typeface != null) { 2363 resolveStyleAndSetTypeface(typeface, style, weight); 2364 } else { // both typeface and familyName is null. 2365 switch (typefaceIndex) { 2366 case SANS: 2367 resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight); 2368 break; 2369 case SERIF: 2370 resolveStyleAndSetTypeface(Typeface.SERIF, style, weight); 2371 break; 2372 case MONOSPACE: 2373 resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight); 2374 break; 2375 case DEFAULT_TYPEFACE: 2376 default: 2377 resolveStyleAndSetTypeface(null, style, weight); 2378 break; 2379 } 2380 } 2381 } 2382 resolveStyleAndSetTypeface(@onNull Typeface typeface, @Typeface.Style int style, @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) int weight)2383 private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style, 2384 @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) 2385 int weight) { 2386 if (weight >= 0) { 2387 weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight); 2388 final boolean italic = (style & Typeface.ITALIC) != 0; 2389 setTypeface(Typeface.create(typeface, weight, italic)); 2390 } else { 2391 setTypeface(typeface, style); 2392 } 2393 } 2394 setRelativeDrawablesIfNeeded(Drawable start, Drawable end)2395 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) { 2396 boolean hasRelativeDrawables = (start != null) || (end != null); 2397 if (hasRelativeDrawables) { 2398 Drawables dr = mDrawables; 2399 if (dr == null) { 2400 mDrawables = dr = new Drawables(getContext()); 2401 } 2402 mDrawables.mOverride = true; 2403 final Rect compoundRect = dr.mCompoundRect; 2404 int[] state = getDrawableState(); 2405 if (start != null) { 2406 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 2407 start.setState(state); 2408 start.copyBounds(compoundRect); 2409 start.setCallback(this); 2410 2411 dr.mDrawableStart = start; 2412 dr.mDrawableSizeStart = compoundRect.width(); 2413 dr.mDrawableHeightStart = compoundRect.height(); 2414 } else { 2415 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2416 } 2417 if (end != null) { 2418 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 2419 end.setState(state); 2420 end.copyBounds(compoundRect); 2421 end.setCallback(this); 2422 2423 dr.mDrawableEnd = end; 2424 dr.mDrawableSizeEnd = compoundRect.width(); 2425 dr.mDrawableHeightEnd = compoundRect.height(); 2426 } else { 2427 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2428 } 2429 resetResolvedDrawables(); 2430 resolveDrawables(); 2431 applyCompoundDrawableTint(); 2432 } 2433 } 2434 2435 @android.view.RemotableViewMethod 2436 @Override setEnabled(boolean enabled)2437 public void setEnabled(boolean enabled) { 2438 if (enabled == isEnabled()) { 2439 return; 2440 } 2441 2442 if (!enabled) { 2443 // Hide the soft input if the currently active TextView is disabled 2444 InputMethodManager imm = getInputMethodManager(); 2445 if (imm != null && imm.isActive(this)) { 2446 imm.hideSoftInputFromWindow(getWindowToken(), 0); 2447 } 2448 } 2449 2450 super.setEnabled(enabled); 2451 2452 if (enabled) { 2453 // Make sure IME is updated with current editor info. 2454 InputMethodManager imm = getInputMethodManager(); 2455 if (imm != null) imm.restartInput(this); 2456 } 2457 2458 // Will change text color 2459 if (mEditor != null) { 2460 mEditor.invalidateTextDisplayList(); 2461 mEditor.prepareCursorControllers(); 2462 2463 // start or stop the cursor blinking as appropriate 2464 mEditor.makeBlink(); 2465 } 2466 } 2467 2468 /** 2469 * Sets the typeface and style in which the text should be displayed, 2470 * and turns on the fake bold and italic bits in the Paint if the 2471 * Typeface that you provided does not have all the bits in the 2472 * style that you specified. 2473 * 2474 * @attr ref android.R.styleable#TextView_typeface 2475 * @attr ref android.R.styleable#TextView_textStyle 2476 */ setTypeface(@ullable Typeface tf, @Typeface.Style int style)2477 public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) { 2478 if (style > 0) { 2479 if (tf == null) { 2480 tf = Typeface.defaultFromStyle(style); 2481 } else { 2482 tf = Typeface.create(tf, style); 2483 } 2484 2485 setTypeface(tf); 2486 // now compute what (if any) algorithmic styling is needed 2487 int typefaceStyle = tf != null ? tf.getStyle() : 0; 2488 int need = style & ~typefaceStyle; 2489 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 2490 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 2491 } else { 2492 mTextPaint.setFakeBoldText(false); 2493 mTextPaint.setTextSkewX(0); 2494 setTypeface(tf); 2495 } 2496 } 2497 2498 /** 2499 * Subclasses override this to specify that they have a KeyListener 2500 * by default even if not specifically called for in the XML options. 2501 */ getDefaultEditable()2502 protected boolean getDefaultEditable() { 2503 return false; 2504 } 2505 2506 /** 2507 * Subclasses override this to specify a default movement method. 2508 */ getDefaultMovementMethod()2509 protected MovementMethod getDefaultMovementMethod() { 2510 return null; 2511 } 2512 2513 /** 2514 * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called 2515 * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE} 2516 * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast 2517 * the return value from this method to Spannable or Editable, respectively. 2518 * 2519 * <p>The content of the return value should not be modified. If you want a modifiable one, you 2520 * should make your own copy first.</p> 2521 * 2522 * @return The text displayed by the text view. 2523 * @attr ref android.R.styleable#TextView_text 2524 */ 2525 @ViewDebug.CapturedViewProperty 2526 @InspectableProperty getText()2527 public CharSequence getText() { 2528 if (mUseTextPaddingForUiTranslation) { 2529 ViewTranslationCallback callback = getViewTranslationCallback(); 2530 if (callback != null && callback instanceof TextViewTranslationCallback) { 2531 TextViewTranslationCallback defaultCallback = 2532 (TextViewTranslationCallback) callback; 2533 if (defaultCallback.isTextPaddingEnabled() 2534 && defaultCallback.isShowingTranslation()) { 2535 return defaultCallback.getPaddedText(mText, mTransformed); 2536 } 2537 } 2538 } 2539 return mText; 2540 } 2541 2542 /** 2543 * Returns the length, in characters, of the text managed by this TextView 2544 * @return The length of the text managed by the TextView in characters. 2545 */ length()2546 public int length() { 2547 return mText.length(); 2548 } 2549 2550 /** 2551 * Return the text that TextView is displaying as an Editable object. If the text is not 2552 * editable, null is returned. 2553 * 2554 * @see #getText 2555 */ getEditableText()2556 public Editable getEditableText() { 2557 return (mText instanceof Editable) ? (Editable) mText : null; 2558 } 2559 2560 /** 2561 * @hide 2562 */ 2563 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) getTransformed()2564 public CharSequence getTransformed() { 2565 return mTransformed; 2566 } 2567 2568 /** 2569 * Gets the vertical distance between lines of text, in pixels. 2570 * Note that markup within the text can cause individual lines 2571 * to be taller or shorter than this height, and the layout may 2572 * contain additional first-or last-line padding. 2573 * @return The height of one standard line in pixels. 2574 */ 2575 @InspectableProperty getLineHeight()2576 public int getLineHeight() { 2577 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd); 2578 } 2579 2580 /** 2581 * Gets the {@link android.text.Layout} that is currently being used to display the text. 2582 * This value can be null if the text or width has recently changed. 2583 * @return The Layout that is currently being used to display the text. 2584 */ getLayout()2585 public final Layout getLayout() { 2586 return mLayout; 2587 } 2588 2589 /** 2590 * @return the {@link android.text.Layout} that is currently being used to 2591 * display the hint text. This can be null. 2592 */ 2593 @UnsupportedAppUsage getHintLayout()2594 final Layout getHintLayout() { 2595 return mHintLayout; 2596 } 2597 2598 /** 2599 * Retrieve the {@link android.content.UndoManager} that is currently associated 2600 * with this TextView. By default there is no associated UndoManager, so null 2601 * is returned. One can be associated with the TextView through 2602 * {@link #setUndoManager(android.content.UndoManager, String)} 2603 * 2604 * @hide 2605 */ getUndoManager()2606 public final UndoManager getUndoManager() { 2607 // TODO: Consider supporting a global undo manager. 2608 throw new UnsupportedOperationException("not implemented"); 2609 } 2610 2611 2612 /** 2613 * @hide 2614 */ 2615 @VisibleForTesting getEditorForTesting()2616 public final Editor getEditorForTesting() { 2617 return mEditor; 2618 } 2619 2620 /** 2621 * Associate an {@link android.content.UndoManager} with this TextView. Once 2622 * done, all edit operations on the TextView will result in appropriate 2623 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's 2624 * stack. 2625 * 2626 * @param undoManager The {@link android.content.UndoManager} to associate with 2627 * this TextView, or null to clear any existing association. 2628 * @param tag String tag identifying this particular TextView owner in the 2629 * UndoManager. This is used to keep the correct association with the 2630 * {@link android.content.UndoOwner} of any operations inside of the UndoManager. 2631 * 2632 * @hide 2633 */ setUndoManager(UndoManager undoManager, String tag)2634 public final void setUndoManager(UndoManager undoManager, String tag) { 2635 // TODO: Consider supporting a global undo manager. An implementation will need to: 2636 // * createEditorIfNeeded() 2637 // * Promote to BufferType.EDITABLE if needed. 2638 // * Update the UndoManager and UndoOwner. 2639 // Likewise it will need to be able to restore the default UndoManager. 2640 throw new UnsupportedOperationException("not implemented"); 2641 } 2642 2643 /** 2644 * Gets the current {@link KeyListener} for the TextView. 2645 * This will frequently be null for non-EditText TextViews. 2646 * @return the current key listener for this TextView. 2647 * 2648 * @attr ref android.R.styleable#TextView_numeric 2649 * @attr ref android.R.styleable#TextView_digits 2650 * @attr ref android.R.styleable#TextView_phoneNumber 2651 * @attr ref android.R.styleable#TextView_inputMethod 2652 * @attr ref android.R.styleable#TextView_capitalize 2653 * @attr ref android.R.styleable#TextView_autoText 2654 */ getKeyListener()2655 public final KeyListener getKeyListener() { 2656 return mEditor == null ? null : mEditor.mKeyListener; 2657 } 2658 2659 /** 2660 * Sets the key listener to be used with this TextView. This can be null 2661 * to disallow user input. Note that this method has significant and 2662 * subtle interactions with soft keyboards and other input method: 2663 * see {@link KeyListener#getInputType() KeyListener.getInputType()} 2664 * for important details. Calling this method will replace the current 2665 * content type of the text view with the content type returned by the 2666 * key listener. 2667 * <p> 2668 * Be warned that if you want a TextView with a key listener or movement 2669 * method not to be focusable, or if you want a TextView without a 2670 * key listener or movement method to be focusable, you must call 2671 * {@link #setFocusable} again after calling this to get the focusability 2672 * back the way you want it. 2673 * 2674 * @attr ref android.R.styleable#TextView_numeric 2675 * @attr ref android.R.styleable#TextView_digits 2676 * @attr ref android.R.styleable#TextView_phoneNumber 2677 * @attr ref android.R.styleable#TextView_inputMethod 2678 * @attr ref android.R.styleable#TextView_capitalize 2679 * @attr ref android.R.styleable#TextView_autoText 2680 */ setKeyListener(KeyListener input)2681 public void setKeyListener(KeyListener input) { 2682 mListenerChanged = true; 2683 setKeyListenerOnly(input); 2684 fixFocusableAndClickableSettings(); 2685 2686 if (input != null) { 2687 createEditorIfNeeded(); 2688 setInputTypeFromEditor(); 2689 } else { 2690 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL; 2691 } 2692 2693 InputMethodManager imm = getInputMethodManager(); 2694 if (imm != null) imm.restartInput(this); 2695 } 2696 setInputTypeFromEditor()2697 private void setInputTypeFromEditor() { 2698 try { 2699 mEditor.mInputType = mEditor.mKeyListener.getInputType(); 2700 } catch (IncompatibleClassChangeError e) { 2701 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 2702 } 2703 // Change inputType, without affecting transformation. 2704 // No need to applySingleLine since mSingleLine is unchanged. 2705 setInputTypeSingleLine(mSingleLine); 2706 } 2707 setKeyListenerOnly(KeyListener input)2708 private void setKeyListenerOnly(KeyListener input) { 2709 if (mEditor == null && input == null) return; // null is the default value 2710 2711 createEditorIfNeeded(); 2712 if (mEditor.mKeyListener != input) { 2713 mEditor.mKeyListener = input; 2714 if (input != null && !(mText instanceof Editable)) { 2715 setText(mText); 2716 } 2717 2718 setFilters((Editable) mText, mFilters); 2719 } 2720 } 2721 2722 /** 2723 * Gets the {@link android.text.method.MovementMethod} being used for this TextView, 2724 * which provides positioning, scrolling, and text selection functionality. 2725 * This will frequently be null for non-EditText TextViews. 2726 * @return the movement method being used for this TextView. 2727 * @see android.text.method.MovementMethod 2728 */ getMovementMethod()2729 public final MovementMethod getMovementMethod() { 2730 return mMovement; 2731 } 2732 2733 /** 2734 * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement 2735 * for this TextView. This can be null to disallow using the arrow keys to move the 2736 * cursor or scroll the view. 2737 * <p> 2738 * Be warned that if you want a TextView with a key listener or movement 2739 * method not to be focusable, or if you want a TextView without a 2740 * key listener or movement method to be focusable, you must call 2741 * {@link #setFocusable} again after calling this to get the focusability 2742 * back the way you want it. 2743 */ setMovementMethod(MovementMethod movement)2744 public final void setMovementMethod(MovementMethod movement) { 2745 if (mMovement != movement) { 2746 mMovement = movement; 2747 2748 if (movement != null && mSpannable == null) { 2749 setText(mText); 2750 } 2751 2752 fixFocusableAndClickableSettings(); 2753 2754 // SelectionModifierCursorController depends on textCanBeSelected, which depends on 2755 // mMovement 2756 if (mEditor != null) mEditor.prepareCursorControllers(); 2757 } 2758 } 2759 fixFocusableAndClickableSettings()2760 private void fixFocusableAndClickableSettings() { 2761 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) { 2762 setFocusable(FOCUSABLE); 2763 setClickable(true); 2764 setLongClickable(true); 2765 } else { 2766 setFocusable(FOCUSABLE_AUTO); 2767 setClickable(false); 2768 setLongClickable(false); 2769 } 2770 } 2771 2772 /** 2773 * Gets the current {@link android.text.method.TransformationMethod} for the TextView. 2774 * This is frequently null, except for single-line and password fields. 2775 * @return the current transformation method for this TextView. 2776 * 2777 * @attr ref android.R.styleable#TextView_password 2778 * @attr ref android.R.styleable#TextView_singleLine 2779 */ getTransformationMethod()2780 public final TransformationMethod getTransformationMethod() { 2781 return mTransformation; 2782 } 2783 2784 /** 2785 * Sets the transformation that is applied to the text that this 2786 * TextView is displaying. 2787 * 2788 * @attr ref android.R.styleable#TextView_password 2789 * @attr ref android.R.styleable#TextView_singleLine 2790 */ setTransformationMethod(TransformationMethod method)2791 public final void setTransformationMethod(TransformationMethod method) { 2792 if (mEditor != null) { 2793 mEditor.setTransformationMethod(method); 2794 } else { 2795 setTransformationMethodInternal(method, /* updateText */ true); 2796 } 2797 } 2798 2799 /** 2800 * Set the transformation that is applied to the text that this TextView is displaying, 2801 * optionally call the setText. 2802 * @param method the new transformation method to be set. 2803 * @param updateText whether the call {@link #setText} which will update the TextView to display 2804 * the new content. This method is helpful when updating 2805 * {@link TransformationMethod} inside {@link #setText}. It should only be 2806 * false if text will be updated immediately after this call, otherwise the 2807 * TextView will enter an inconsistent state. 2808 */ setTransformationMethodInternal(@ullable TransformationMethod method, boolean updateText)2809 void setTransformationMethodInternal(@Nullable TransformationMethod method, 2810 boolean updateText) { 2811 if (method == mTransformation) { 2812 // Avoid the setText() below if the transformation is 2813 // the same. 2814 return; 2815 } 2816 if (mTransformation != null) { 2817 if (mSpannable != null) { 2818 mSpannable.removeSpan(mTransformation); 2819 } 2820 } 2821 2822 mTransformation = method; 2823 2824 if (method instanceof TransformationMethod2) { 2825 TransformationMethod2 method2 = (TransformationMethod2) method; 2826 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable); 2827 method2.setLengthChangesAllowed(mAllowTransformationLengthChange); 2828 } else { 2829 mAllowTransformationLengthChange = false; 2830 } 2831 2832 if (updateText) { 2833 setText(mText); 2834 } 2835 2836 if (hasPasswordTransformationMethod()) { 2837 notifyViewAccessibilityStateChangedIfNeeded( 2838 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 2839 } 2840 2841 // PasswordTransformationMethod always have LTR text direction heuristics returned by 2842 // getTextDirectionHeuristic, needs reset 2843 mTextDir = getTextDirectionHeuristic(); 2844 } 2845 2846 /** 2847 * Returns the top padding of the view, plus space for the top 2848 * Drawable if any. 2849 */ getCompoundPaddingTop()2850 public int getCompoundPaddingTop() { 2851 final Drawables dr = mDrawables; 2852 if (dr == null || dr.mShowing[Drawables.TOP] == null) { 2853 return mPaddingTop; 2854 } else { 2855 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; 2856 } 2857 } 2858 2859 /** 2860 * Returns the bottom padding of the view, plus space for the bottom 2861 * Drawable if any. 2862 */ getCompoundPaddingBottom()2863 public int getCompoundPaddingBottom() { 2864 final Drawables dr = mDrawables; 2865 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) { 2866 return mPaddingBottom; 2867 } else { 2868 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; 2869 } 2870 } 2871 2872 /** 2873 * Returns the left padding of the view, plus space for the left 2874 * Drawable if any. 2875 */ getCompoundPaddingLeft()2876 public int getCompoundPaddingLeft() { 2877 final Drawables dr = mDrawables; 2878 if (dr == null || dr.mShowing[Drawables.LEFT] == null) { 2879 return mPaddingLeft; 2880 } else { 2881 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; 2882 } 2883 } 2884 2885 /** 2886 * Returns the right padding of the view, plus space for the right 2887 * Drawable if any. 2888 */ getCompoundPaddingRight()2889 public int getCompoundPaddingRight() { 2890 final Drawables dr = mDrawables; 2891 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) { 2892 return mPaddingRight; 2893 } else { 2894 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; 2895 } 2896 } 2897 2898 /** 2899 * Returns the start padding of the view, plus space for the start 2900 * Drawable if any. 2901 */ getCompoundPaddingStart()2902 public int getCompoundPaddingStart() { 2903 resolveDrawables(); 2904 switch(getLayoutDirection()) { 2905 default: 2906 case LAYOUT_DIRECTION_LTR: 2907 return getCompoundPaddingLeft(); 2908 case LAYOUT_DIRECTION_RTL: 2909 return getCompoundPaddingRight(); 2910 } 2911 } 2912 2913 /** 2914 * Returns the end padding of the view, plus space for the end 2915 * Drawable if any. 2916 */ getCompoundPaddingEnd()2917 public int getCompoundPaddingEnd() { 2918 resolveDrawables(); 2919 switch(getLayoutDirection()) { 2920 default: 2921 case LAYOUT_DIRECTION_LTR: 2922 return getCompoundPaddingRight(); 2923 case LAYOUT_DIRECTION_RTL: 2924 return getCompoundPaddingLeft(); 2925 } 2926 } 2927 2928 /** 2929 * Returns the extended top padding of the view, including both the 2930 * top Drawable if any and any extra space to keep more than maxLines 2931 * of text from showing. It is only valid to call this after measuring. 2932 */ getExtendedPaddingTop()2933 public int getExtendedPaddingTop() { 2934 if (mMaxMode != LINES) { 2935 return getCompoundPaddingTop(); 2936 } 2937 2938 if (mLayout == null) { 2939 assumeLayout(); 2940 } 2941 2942 if (mLayout.getLineCount() <= mMaximum) { 2943 return getCompoundPaddingTop(); 2944 } 2945 2946 int top = getCompoundPaddingTop(); 2947 int bottom = getCompoundPaddingBottom(); 2948 int viewht = getHeight() - top - bottom; 2949 int layoutht = mLayout.getLineTop(mMaximum); 2950 2951 if (layoutht >= viewht) { 2952 return top; 2953 } 2954 2955 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 2956 if (gravity == Gravity.TOP) { 2957 return top; 2958 } else if (gravity == Gravity.BOTTOM) { 2959 return top + viewht - layoutht; 2960 } else { // (gravity == Gravity.CENTER_VERTICAL) 2961 return top + (viewht - layoutht) / 2; 2962 } 2963 } 2964 2965 /** 2966 * Returns the extended bottom padding of the view, including both the 2967 * bottom Drawable if any and any extra space to keep more than maxLines 2968 * of text from showing. It is only valid to call this after measuring. 2969 */ getExtendedPaddingBottom()2970 public int getExtendedPaddingBottom() { 2971 if (mMaxMode != LINES) { 2972 return getCompoundPaddingBottom(); 2973 } 2974 2975 if (mLayout == null) { 2976 assumeLayout(); 2977 } 2978 2979 if (mLayout.getLineCount() <= mMaximum) { 2980 return getCompoundPaddingBottom(); 2981 } 2982 2983 int top = getCompoundPaddingTop(); 2984 int bottom = getCompoundPaddingBottom(); 2985 int viewht = getHeight() - top - bottom; 2986 int layoutht = mLayout.getLineTop(mMaximum); 2987 2988 if (layoutht >= viewht) { 2989 return bottom; 2990 } 2991 2992 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 2993 if (gravity == Gravity.TOP) { 2994 return bottom + viewht - layoutht; 2995 } else if (gravity == Gravity.BOTTOM) { 2996 return bottom; 2997 } else { // (gravity == Gravity.CENTER_VERTICAL) 2998 return bottom + (viewht - layoutht) / 2; 2999 } 3000 } 3001 3002 /** 3003 * Returns the total left padding of the view, including the left 3004 * Drawable if any. 3005 */ getTotalPaddingLeft()3006 public int getTotalPaddingLeft() { 3007 return getCompoundPaddingLeft(); 3008 } 3009 3010 /** 3011 * Returns the total right padding of the view, including the right 3012 * Drawable if any. 3013 */ getTotalPaddingRight()3014 public int getTotalPaddingRight() { 3015 return getCompoundPaddingRight(); 3016 } 3017 3018 /** 3019 * Returns the total start padding of the view, including the start 3020 * Drawable if any. 3021 */ getTotalPaddingStart()3022 public int getTotalPaddingStart() { 3023 return getCompoundPaddingStart(); 3024 } 3025 3026 /** 3027 * Returns the total end padding of the view, including the end 3028 * Drawable if any. 3029 */ getTotalPaddingEnd()3030 public int getTotalPaddingEnd() { 3031 return getCompoundPaddingEnd(); 3032 } 3033 3034 /** 3035 * Returns the total top padding of the view, including the top 3036 * Drawable if any, the extra space to keep more than maxLines 3037 * from showing, and the vertical offset for gravity, if any. 3038 */ getTotalPaddingTop()3039 public int getTotalPaddingTop() { 3040 return getExtendedPaddingTop() + getVerticalOffset(true); 3041 } 3042 3043 /** 3044 * Returns the total bottom padding of the view, including the bottom 3045 * Drawable if any, the extra space to keep more than maxLines 3046 * from showing, and the vertical offset for gravity, if any. 3047 */ getTotalPaddingBottom()3048 public int getTotalPaddingBottom() { 3049 return getExtendedPaddingBottom() + getBottomVerticalOffset(true); 3050 } 3051 3052 /** 3053 * Sets the Drawables (if any) to appear to the left of, above, to the 3054 * right of, and below the text. Use {@code null} if you do not want a 3055 * Drawable there. The Drawables must already have had 3056 * {@link Drawable#setBounds} called. 3057 * <p> 3058 * Calling this method will overwrite any Drawables previously set using 3059 * {@link #setCompoundDrawablesRelative} or related methods. 3060 * 3061 * @attr ref android.R.styleable#TextView_drawableLeft 3062 * @attr ref android.R.styleable#TextView_drawableTop 3063 * @attr ref android.R.styleable#TextView_drawableRight 3064 * @attr ref android.R.styleable#TextView_drawableBottom 3065 */ setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)3066 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top, 3067 @Nullable Drawable right, @Nullable Drawable bottom) { 3068 Drawables dr = mDrawables; 3069 3070 // We're switching to absolute, discard relative. 3071 if (dr != null) { 3072 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 3073 dr.mDrawableStart = null; 3074 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null); 3075 dr.mDrawableEnd = null; 3076 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3077 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3078 } 3079 3080 final boolean drawables = left != null || top != null || right != null || bottom != null; 3081 if (!drawables) { 3082 // Clearing drawables... can we free the data structure? 3083 if (dr != null) { 3084 if (!dr.hasMetadata()) { 3085 mDrawables = null; 3086 } else { 3087 // We need to retain the last set padding, so just clear 3088 // out all of the fields in the existing structure. 3089 for (int i = dr.mShowing.length - 1; i >= 0; i--) { 3090 if (dr.mShowing[i] != null) { 3091 dr.mShowing[i].setCallback(null); 3092 } 3093 dr.mShowing[i] = null; 3094 } 3095 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 3096 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 3097 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3098 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3099 } 3100 } 3101 } else { 3102 if (dr == null) { 3103 mDrawables = dr = new Drawables(getContext()); 3104 } 3105 3106 mDrawables.mOverride = false; 3107 3108 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) { 3109 dr.mShowing[Drawables.LEFT].setCallback(null); 3110 } 3111 dr.mShowing[Drawables.LEFT] = left; 3112 3113 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 3114 dr.mShowing[Drawables.TOP].setCallback(null); 3115 } 3116 dr.mShowing[Drawables.TOP] = top; 3117 3118 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) { 3119 dr.mShowing[Drawables.RIGHT].setCallback(null); 3120 } 3121 dr.mShowing[Drawables.RIGHT] = right; 3122 3123 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 3124 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3125 } 3126 dr.mShowing[Drawables.BOTTOM] = bottom; 3127 3128 final Rect compoundRect = dr.mCompoundRect; 3129 int[] state; 3130 3131 state = getDrawableState(); 3132 3133 if (left != null) { 3134 left.setState(state); 3135 left.copyBounds(compoundRect); 3136 left.setCallback(this); 3137 dr.mDrawableSizeLeft = compoundRect.width(); 3138 dr.mDrawableHeightLeft = compoundRect.height(); 3139 } else { 3140 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 3141 } 3142 3143 if (right != null) { 3144 right.setState(state); 3145 right.copyBounds(compoundRect); 3146 right.setCallback(this); 3147 dr.mDrawableSizeRight = compoundRect.width(); 3148 dr.mDrawableHeightRight = compoundRect.height(); 3149 } else { 3150 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 3151 } 3152 3153 if (top != null) { 3154 top.setState(state); 3155 top.copyBounds(compoundRect); 3156 top.setCallback(this); 3157 dr.mDrawableSizeTop = compoundRect.height(); 3158 dr.mDrawableWidthTop = compoundRect.width(); 3159 } else { 3160 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3161 } 3162 3163 if (bottom != null) { 3164 bottom.setState(state); 3165 bottom.copyBounds(compoundRect); 3166 bottom.setCallback(this); 3167 dr.mDrawableSizeBottom = compoundRect.height(); 3168 dr.mDrawableWidthBottom = compoundRect.width(); 3169 } else { 3170 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3171 } 3172 } 3173 3174 // Save initial left/right drawables 3175 if (dr != null) { 3176 dr.mDrawableLeftInitial = left; 3177 dr.mDrawableRightInitial = right; 3178 } 3179 3180 resetResolvedDrawables(); 3181 resolveDrawables(); 3182 applyCompoundDrawableTint(); 3183 invalidate(); 3184 requestLayout(); 3185 } 3186 3187 /** 3188 * Sets the Drawables (if any) to appear to the left of, above, to the 3189 * right of, and below the text. Use 0 if you do not want a Drawable there. 3190 * The Drawables' bounds will be set to their intrinsic bounds. 3191 * <p> 3192 * Calling this method will overwrite any Drawables previously set using 3193 * {@link #setCompoundDrawablesRelative} or related methods. 3194 * 3195 * @param left Resource identifier of the left Drawable. 3196 * @param top Resource identifier of the top Drawable. 3197 * @param right Resource identifier of the right Drawable. 3198 * @param bottom Resource identifier of the bottom Drawable. 3199 * 3200 * @attr ref android.R.styleable#TextView_drawableLeft 3201 * @attr ref android.R.styleable#TextView_drawableTop 3202 * @attr ref android.R.styleable#TextView_drawableRight 3203 * @attr ref android.R.styleable#TextView_drawableBottom 3204 */ 3205 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(@rawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom)3206 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left, 3207 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) { 3208 final Context context = getContext(); 3209 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null, 3210 top != 0 ? context.getDrawable(top) : null, 3211 right != 0 ? context.getDrawable(right) : null, 3212 bottom != 0 ? context.getDrawable(bottom) : null); 3213 } 3214 3215 /** 3216 * Sets the Drawables (if any) to appear to the left of, above, to the 3217 * right of, and below the text. Use {@code null} if you do not want a 3218 * Drawable there. The Drawables' bounds will be set to their intrinsic 3219 * bounds. 3220 * <p> 3221 * Calling this method will overwrite any Drawables previously set using 3222 * {@link #setCompoundDrawablesRelative} or related methods. 3223 * 3224 * @attr ref android.R.styleable#TextView_drawableLeft 3225 * @attr ref android.R.styleable#TextView_drawableTop 3226 * @attr ref android.R.styleable#TextView_drawableRight 3227 * @attr ref android.R.styleable#TextView_drawableBottom 3228 */ 3229 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)3230 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left, 3231 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) { 3232 3233 if (left != null) { 3234 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); 3235 } 3236 if (right != null) { 3237 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); 3238 } 3239 if (top != null) { 3240 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3241 } 3242 if (bottom != null) { 3243 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3244 } 3245 setCompoundDrawables(left, top, right, bottom); 3246 } 3247 3248 /** 3249 * Sets the Drawables (if any) to appear to the start of, above, to the end 3250 * of, and below the text. Use {@code null} if you do not want a Drawable 3251 * there. The Drawables must already have had {@link Drawable#setBounds} 3252 * called. 3253 * <p> 3254 * Calling this method will overwrite any Drawables previously set using 3255 * {@link #setCompoundDrawables} or related methods. 3256 * 3257 * @attr ref android.R.styleable#TextView_drawableStart 3258 * @attr ref android.R.styleable#TextView_drawableTop 3259 * @attr ref android.R.styleable#TextView_drawableEnd 3260 * @attr ref android.R.styleable#TextView_drawableBottom 3261 */ 3262 @android.view.RemotableViewMethod setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3263 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top, 3264 @Nullable Drawable end, @Nullable Drawable bottom) { 3265 Drawables dr = mDrawables; 3266 3267 // We're switching to relative, discard absolute. 3268 if (dr != null) { 3269 if (dr.mShowing[Drawables.LEFT] != null) { 3270 dr.mShowing[Drawables.LEFT].setCallback(null); 3271 } 3272 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null; 3273 if (dr.mShowing[Drawables.RIGHT] != null) { 3274 dr.mShowing[Drawables.RIGHT].setCallback(null); 3275 } 3276 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null; 3277 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 3278 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 3279 } 3280 3281 final boolean drawables = start != null || top != null 3282 || end != null || bottom != null; 3283 3284 if (!drawables) { 3285 // Clearing drawables... can we free the data structure? 3286 if (dr != null) { 3287 if (!dr.hasMetadata()) { 3288 mDrawables = null; 3289 } else { 3290 // We need to retain the last set padding, so just clear 3291 // out all of the fields in the existing structure. 3292 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 3293 dr.mDrawableStart = null; 3294 if (dr.mShowing[Drawables.TOP] != null) { 3295 dr.mShowing[Drawables.TOP].setCallback(null); 3296 } 3297 dr.mShowing[Drawables.TOP] = null; 3298 if (dr.mDrawableEnd != null) { 3299 dr.mDrawableEnd.setCallback(null); 3300 } 3301 dr.mDrawableEnd = null; 3302 if (dr.mShowing[Drawables.BOTTOM] != null) { 3303 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3304 } 3305 dr.mShowing[Drawables.BOTTOM] = null; 3306 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3307 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3308 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3309 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3310 } 3311 } 3312 } else { 3313 if (dr == null) { 3314 mDrawables = dr = new Drawables(getContext()); 3315 } 3316 3317 mDrawables.mOverride = true; 3318 3319 if (dr.mDrawableStart != start && dr.mDrawableStart != null) { 3320 dr.mDrawableStart.setCallback(null); 3321 } 3322 dr.mDrawableStart = start; 3323 3324 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { 3325 dr.mShowing[Drawables.TOP].setCallback(null); 3326 } 3327 dr.mShowing[Drawables.TOP] = top; 3328 3329 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) { 3330 dr.mDrawableEnd.setCallback(null); 3331 } 3332 dr.mDrawableEnd = end; 3333 3334 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { 3335 dr.mShowing[Drawables.BOTTOM].setCallback(null); 3336 } 3337 dr.mShowing[Drawables.BOTTOM] = bottom; 3338 3339 final Rect compoundRect = dr.mCompoundRect; 3340 int[] state; 3341 3342 state = getDrawableState(); 3343 3344 if (start != null) { 3345 start.setState(state); 3346 start.copyBounds(compoundRect); 3347 start.setCallback(this); 3348 dr.mDrawableSizeStart = compoundRect.width(); 3349 dr.mDrawableHeightStart = compoundRect.height(); 3350 } else { 3351 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 3352 } 3353 3354 if (end != null) { 3355 end.setState(state); 3356 end.copyBounds(compoundRect); 3357 end.setCallback(this); 3358 dr.mDrawableSizeEnd = compoundRect.width(); 3359 dr.mDrawableHeightEnd = compoundRect.height(); 3360 } else { 3361 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 3362 } 3363 3364 if (top != null) { 3365 top.setState(state); 3366 top.copyBounds(compoundRect); 3367 top.setCallback(this); 3368 dr.mDrawableSizeTop = compoundRect.height(); 3369 dr.mDrawableWidthTop = compoundRect.width(); 3370 } else { 3371 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 3372 } 3373 3374 if (bottom != null) { 3375 bottom.setState(state); 3376 bottom.copyBounds(compoundRect); 3377 bottom.setCallback(this); 3378 dr.mDrawableSizeBottom = compoundRect.height(); 3379 dr.mDrawableWidthBottom = compoundRect.width(); 3380 } else { 3381 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 3382 } 3383 } 3384 3385 resetResolvedDrawables(); 3386 resolveDrawables(); 3387 invalidate(); 3388 requestLayout(); 3389 } 3390 3391 /** 3392 * Sets the Drawables (if any) to appear to the start of, above, to the end 3393 * of, and below the text. Use 0 if you do not want a Drawable there. The 3394 * Drawables' bounds will be set to their intrinsic bounds. 3395 * <p> 3396 * Calling this method will overwrite any Drawables previously set using 3397 * {@link #setCompoundDrawables} or related methods. 3398 * 3399 * @param start Resource identifier of the start Drawable. 3400 * @param top Resource identifier of the top Drawable. 3401 * @param end Resource identifier of the end Drawable. 3402 * @param bottom Resource identifier of the bottom Drawable. 3403 * 3404 * @attr ref android.R.styleable#TextView_drawableStart 3405 * @attr ref android.R.styleable#TextView_drawableTop 3406 * @attr ref android.R.styleable#TextView_drawableEnd 3407 * @attr ref android.R.styleable#TextView_drawableBottom 3408 */ 3409 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(@rawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom)3410 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start, 3411 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) { 3412 final Context context = getContext(); 3413 setCompoundDrawablesRelativeWithIntrinsicBounds( 3414 start != 0 ? context.getDrawable(start) : null, 3415 top != 0 ? context.getDrawable(top) : null, 3416 end != 0 ? context.getDrawable(end) : null, 3417 bottom != 0 ? context.getDrawable(bottom) : null); 3418 } 3419 3420 /** 3421 * Sets the Drawables (if any) to appear to the start of, above, to the end 3422 * of, and below the text. Use {@code null} if you do not want a Drawable 3423 * there. The Drawables' bounds will be set to their intrinsic bounds. 3424 * <p> 3425 * Calling this method will overwrite any Drawables previously set using 3426 * {@link #setCompoundDrawables} or related methods. 3427 * 3428 * @attr ref android.R.styleable#TextView_drawableStart 3429 * @attr ref android.R.styleable#TextView_drawableTop 3430 * @attr ref android.R.styleable#TextView_drawableEnd 3431 * @attr ref android.R.styleable#TextView_drawableBottom 3432 */ 3433 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)3434 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start, 3435 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) { 3436 3437 if (start != null) { 3438 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 3439 } 3440 if (end != null) { 3441 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 3442 } 3443 if (top != null) { 3444 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 3445 } 3446 if (bottom != null) { 3447 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 3448 } 3449 setCompoundDrawablesRelative(start, top, end, bottom); 3450 } 3451 3452 /** 3453 * Returns drawables for the left, top, right, and bottom borders. 3454 * 3455 * @attr ref android.R.styleable#TextView_drawableLeft 3456 * @attr ref android.R.styleable#TextView_drawableTop 3457 * @attr ref android.R.styleable#TextView_drawableRight 3458 * @attr ref android.R.styleable#TextView_drawableBottom 3459 */ 3460 @NonNull getCompoundDrawables()3461 public Drawable[] getCompoundDrawables() { 3462 final Drawables dr = mDrawables; 3463 if (dr != null) { 3464 return dr.mShowing.clone(); 3465 } else { 3466 return new Drawable[] { null, null, null, null }; 3467 } 3468 } 3469 3470 /** 3471 * Returns drawables for the start, top, end, and bottom borders. 3472 * 3473 * @attr ref android.R.styleable#TextView_drawableStart 3474 * @attr ref android.R.styleable#TextView_drawableTop 3475 * @attr ref android.R.styleable#TextView_drawableEnd 3476 * @attr ref android.R.styleable#TextView_drawableBottom 3477 */ 3478 @NonNull getCompoundDrawablesRelative()3479 public Drawable[] getCompoundDrawablesRelative() { 3480 final Drawables dr = mDrawables; 3481 if (dr != null) { 3482 return new Drawable[] { 3483 dr.mDrawableStart, dr.mShowing[Drawables.TOP], 3484 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM] 3485 }; 3486 } else { 3487 return new Drawable[] { null, null, null, null }; 3488 } 3489 } 3490 3491 /** 3492 * Sets the size of the padding between the compound drawables and 3493 * the text. 3494 * 3495 * @attr ref android.R.styleable#TextView_drawablePadding 3496 */ 3497 @android.view.RemotableViewMethod setCompoundDrawablePadding(int pad)3498 public void setCompoundDrawablePadding(int pad) { 3499 Drawables dr = mDrawables; 3500 if (pad == 0) { 3501 if (dr != null) { 3502 dr.mDrawablePadding = pad; 3503 } 3504 } else { 3505 if (dr == null) { 3506 mDrawables = dr = new Drawables(getContext()); 3507 } 3508 dr.mDrawablePadding = pad; 3509 } 3510 3511 invalidate(); 3512 requestLayout(); 3513 } 3514 3515 /** 3516 * Returns the padding between the compound drawables and the text. 3517 * 3518 * @attr ref android.R.styleable#TextView_drawablePadding 3519 */ 3520 @InspectableProperty(name = "drawablePadding") getCompoundDrawablePadding()3521 public int getCompoundDrawablePadding() { 3522 final Drawables dr = mDrawables; 3523 return dr != null ? dr.mDrawablePadding : 0; 3524 } 3525 3526 /** 3527 * Applies a tint to the compound drawables. Does not modify the 3528 * current tint mode, which is {@link BlendMode#SRC_IN} by default. 3529 * <p> 3530 * Subsequent calls to 3531 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} 3532 * and related methods will automatically mutate the drawables and apply 3533 * the specified tint and tint mode using 3534 * {@link Drawable#setTintList(ColorStateList)}. 3535 * 3536 * @param tint the tint to apply, may be {@code null} to clear tint 3537 * 3538 * @attr ref android.R.styleable#TextView_drawableTint 3539 * @see #getCompoundDrawableTintList() 3540 * @see Drawable#setTintList(ColorStateList) 3541 */ setCompoundDrawableTintList(@ullable ColorStateList tint)3542 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) { 3543 if (mDrawables == null) { 3544 mDrawables = new Drawables(getContext()); 3545 } 3546 mDrawables.mTintList = tint; 3547 mDrawables.mHasTint = true; 3548 3549 applyCompoundDrawableTint(); 3550 } 3551 3552 /** 3553 * @return the tint applied to the compound drawables 3554 * @attr ref android.R.styleable#TextView_drawableTint 3555 * @see #setCompoundDrawableTintList(ColorStateList) 3556 */ 3557 @InspectableProperty(name = "drawableTint") getCompoundDrawableTintList()3558 public ColorStateList getCompoundDrawableTintList() { 3559 return mDrawables != null ? mDrawables.mTintList : null; 3560 } 3561 3562 /** 3563 * Specifies the blending mode used to apply the tint specified by 3564 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3565 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3566 * 3567 * @param tintMode the blending mode used to apply the tint, may be 3568 * {@code null} to clear tint 3569 * @attr ref android.R.styleable#TextView_drawableTintMode 3570 * @see #setCompoundDrawableTintList(ColorStateList) 3571 * @see Drawable#setTintMode(PorterDuff.Mode) 3572 */ setCompoundDrawableTintMode(@ullable PorterDuff.Mode tintMode)3573 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) { 3574 setCompoundDrawableTintBlendMode(tintMode != null 3575 ? BlendMode.fromValue(tintMode.nativeInt) : null); 3576 } 3577 3578 /** 3579 * Specifies the blending mode used to apply the tint specified by 3580 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound 3581 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. 3582 * 3583 * @param blendMode the blending mode used to apply the tint, may be 3584 * {@code null} to clear tint 3585 * @attr ref android.R.styleable#TextView_drawableTintMode 3586 * @see #setCompoundDrawableTintList(ColorStateList) 3587 * @see Drawable#setTintBlendMode(BlendMode) 3588 */ setCompoundDrawableTintBlendMode(@ullable BlendMode blendMode)3589 public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) { 3590 if (mDrawables == null) { 3591 mDrawables = new Drawables(getContext()); 3592 } 3593 mDrawables.mBlendMode = blendMode; 3594 mDrawables.mHasTintMode = true; 3595 3596 applyCompoundDrawableTint(); 3597 } 3598 3599 /** 3600 * Returns the blending mode used to apply the tint to the compound 3601 * drawables, if specified. 3602 * 3603 * @return the blending mode used to apply the tint to the compound 3604 * drawables 3605 * @attr ref android.R.styleable#TextView_drawableTintMode 3606 * @see #setCompoundDrawableTintMode(PorterDuff.Mode) 3607 * 3608 */ 3609 @InspectableProperty(name = "drawableTintMode") getCompoundDrawableTintMode()3610 public PorterDuff.Mode getCompoundDrawableTintMode() { 3611 BlendMode mode = getCompoundDrawableTintBlendMode(); 3612 return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; 3613 } 3614 3615 /** 3616 * Returns the blending mode used to apply the tint to the compound 3617 * drawables, if specified. 3618 * 3619 * @return the blending mode used to apply the tint to the compound 3620 * drawables 3621 * @attr ref android.R.styleable#TextView_drawableTintMode 3622 * @see #setCompoundDrawableTintBlendMode(BlendMode) 3623 */ 3624 @InspectableProperty(name = "drawableBlendMode", 3625 attributeId = com.android.internal.R.styleable.TextView_drawableTintMode) getCompoundDrawableTintBlendMode()3626 public @Nullable BlendMode getCompoundDrawableTintBlendMode() { 3627 return mDrawables != null ? mDrawables.mBlendMode : null; 3628 } 3629 applyCompoundDrawableTint()3630 private void applyCompoundDrawableTint() { 3631 if (mDrawables == null) { 3632 return; 3633 } 3634 3635 if (mDrawables.mHasTint || mDrawables.mHasTintMode) { 3636 final ColorStateList tintList = mDrawables.mTintList; 3637 final BlendMode blendMode = mDrawables.mBlendMode; 3638 final boolean hasTint = mDrawables.mHasTint; 3639 final boolean hasTintMode = mDrawables.mHasTintMode; 3640 final int[] state = getDrawableState(); 3641 3642 for (Drawable dr : mDrawables.mShowing) { 3643 if (dr == null) { 3644 continue; 3645 } 3646 3647 if (dr == mDrawables.mDrawableError) { 3648 // From a developer's perspective, the error drawable isn't 3649 // a compound drawable. Don't apply the generic compound 3650 // drawable tint to it. 3651 continue; 3652 } 3653 3654 dr.mutate(); 3655 3656 if (hasTint) { 3657 dr.setTintList(tintList); 3658 } 3659 3660 if (hasTintMode) { 3661 dr.setTintBlendMode(blendMode); 3662 } 3663 3664 // The drawable (or one of its children) may not have been 3665 // stateful before applying the tint, so let's try again. 3666 if (dr.isStateful()) { 3667 dr.setState(state); 3668 } 3669 } 3670 } 3671 } 3672 3673 /** 3674 * @inheritDoc 3675 * 3676 * @see #setFirstBaselineToTopHeight(int) 3677 * @see #setLastBaselineToBottomHeight(int) 3678 */ 3679 @Override setPadding(int left, int top, int right, int bottom)3680 public void setPadding(int left, int top, int right, int bottom) { 3681 if (left != mPaddingLeft 3682 || right != mPaddingRight 3683 || top != mPaddingTop 3684 || bottom != mPaddingBottom) { 3685 nullLayouts(); 3686 } 3687 3688 // the super call will requestLayout() 3689 super.setPadding(left, top, right, bottom); 3690 invalidate(); 3691 } 3692 3693 /** 3694 * @inheritDoc 3695 * 3696 * @see #setFirstBaselineToTopHeight(int) 3697 * @see #setLastBaselineToBottomHeight(int) 3698 */ 3699 @Override setPaddingRelative(int start, int top, int end, int bottom)3700 public void setPaddingRelative(int start, int top, int end, int bottom) { 3701 if (start != getPaddingStart() 3702 || end != getPaddingEnd() 3703 || top != mPaddingTop 3704 || bottom != mPaddingBottom) { 3705 nullLayouts(); 3706 } 3707 3708 // the super call will requestLayout() 3709 super.setPaddingRelative(start, top, end, bottom); 3710 invalidate(); 3711 } 3712 3713 /** 3714 * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is 3715 * the distance between the top of the TextView and first line's baseline. 3716 * <p> 3717 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3718 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3719 * 3720 * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was 3721 * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated. 3722 * Moreover since this function sets the top padding, if the height of the TextView is less than 3723 * the sum of top padding, line height and bottom padding, top of the line will be pushed 3724 * down and bottom will be clipped. 3725 * 3726 * @param firstBaselineToTopHeight distance between first baseline to top of the container 3727 * in pixels 3728 * 3729 * @see #getFirstBaselineToTopHeight() 3730 * @see #setLastBaselineToBottomHeight(int) 3731 * @see #setPadding(int, int, int, int) 3732 * @see #setPaddingRelative(int, int, int, int) 3733 * 3734 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3735 */ setFirstBaselineToTopHeight(@x @ntRangefrom = 0) int firstBaselineToTopHeight)3736 public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) { 3737 Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight); 3738 3739 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3740 final int fontMetricsTop; 3741 if (getIncludeFontPadding()) { 3742 fontMetricsTop = fontMetrics.top; 3743 } else { 3744 fontMetricsTop = fontMetrics.ascent; 3745 } 3746 3747 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3748 // in settings). At the moment, we don't. 3749 3750 if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) { 3751 final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop); 3752 setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom()); 3753 } 3754 } 3755 3756 /** 3757 * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is 3758 * the distance between the bottom of the TextView and the last line's baseline. 3759 * <p> 3760 * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" /> 3761 * <figcaption>First and last baseline metrics for a TextView.</figcaption> 3762 * 3763 * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was 3764 * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated. 3765 * Moreover since this function sets the bottom padding, if the height of the TextView is less 3766 * than the sum of top padding, line height and bottom padding, bottom of the text will be 3767 * clipped. 3768 * 3769 * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container 3770 * in pixels 3771 * 3772 * @see #getLastBaselineToBottomHeight() 3773 * @see #setFirstBaselineToTopHeight(int) 3774 * @see #setPadding(int, int, int, int) 3775 * @see #setPaddingRelative(int, int, int, int) 3776 * 3777 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3778 */ setLastBaselineToBottomHeight( @x @ntRangefrom = 0) int lastBaselineToBottomHeight)3779 public void setLastBaselineToBottomHeight( 3780 @Px @IntRange(from = 0) int lastBaselineToBottomHeight) { 3781 Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight); 3782 3783 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); 3784 final int fontMetricsBottom; 3785 if (getIncludeFontPadding()) { 3786 fontMetricsBottom = fontMetrics.bottom; 3787 } else { 3788 fontMetricsBottom = fontMetrics.descent; 3789 } 3790 3791 // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size 3792 // in settings). At the moment, we don't. 3793 3794 if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) { 3795 final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom; 3796 setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom); 3797 } 3798 } 3799 3800 /** 3801 * Returns the distance between the first text baseline and the top of this TextView. 3802 * 3803 * @see #setFirstBaselineToTopHeight(int) 3804 * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight 3805 */ 3806 @InspectableProperty getFirstBaselineToTopHeight()3807 public int getFirstBaselineToTopHeight() { 3808 return getPaddingTop() - getPaint().getFontMetricsInt().top; 3809 } 3810 3811 /** 3812 * Returns the distance between the last text baseline and the bottom of this TextView. 3813 * 3814 * @see #setLastBaselineToBottomHeight(int) 3815 * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight 3816 */ 3817 @InspectableProperty getLastBaselineToBottomHeight()3818 public int getLastBaselineToBottomHeight() { 3819 return getPaddingBottom() + getPaint().getFontMetricsInt().bottom; 3820 } 3821 3822 /** 3823 * Gets the autolink mask of the text. 3824 * 3825 * See {@link Linkify#ALL} and peers for possible values. 3826 * 3827 * @attr ref android.R.styleable#TextView_autoLink 3828 */ 3829 @InspectableProperty(name = "autoLink", flagMapping = { 3830 @FlagEntry(name = "web", target = Linkify.WEB_URLS), 3831 @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES), 3832 @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS), 3833 @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES) 3834 }) getAutoLinkMask()3835 public final int getAutoLinkMask() { 3836 return mAutoLinkMask; 3837 } 3838 3839 /** 3840 * Sets the Drawable corresponding to the selection handle used for 3841 * positioning the cursor within text. The Drawable defaults to the value 3842 * of the textSelectHandle attribute. 3843 * Note that any change applied to the handle Drawable will not be visible 3844 * until the handle is hidden and then drawn again. 3845 * 3846 * @see #setTextSelectHandle(int) 3847 * @attr ref android.R.styleable#TextView_textSelectHandle 3848 */ 3849 @android.view.RemotableViewMethod setTextSelectHandle(@onNull Drawable textSelectHandle)3850 public void setTextSelectHandle(@NonNull Drawable textSelectHandle) { 3851 Preconditions.checkNotNull(textSelectHandle, 3852 "The text select handle should not be null."); 3853 mTextSelectHandle = textSelectHandle; 3854 mTextSelectHandleRes = 0; 3855 if (mEditor != null) { 3856 mEditor.loadHandleDrawables(true /* overwrite */); 3857 } 3858 } 3859 3860 /** 3861 * Sets the Drawable corresponding to the selection handle used for 3862 * positioning the cursor within text. The Drawable defaults to the value 3863 * of the textSelectHandle attribute. 3864 * Note that any change applied to the handle Drawable will not be visible 3865 * until the handle is hidden and then drawn again. 3866 * 3867 * @see #setTextSelectHandle(Drawable) 3868 * @attr ref android.R.styleable#TextView_textSelectHandle 3869 */ 3870 @android.view.RemotableViewMethod setTextSelectHandle(@rawableRes int textSelectHandle)3871 public void setTextSelectHandle(@DrawableRes int textSelectHandle) { 3872 Preconditions.checkArgument(textSelectHandle != 0, 3873 "The text select handle should be a valid drawable resource id."); 3874 setTextSelectHandle(mContext.getDrawable(textSelectHandle)); 3875 } 3876 3877 /** 3878 * Returns the Drawable corresponding to the selection handle used 3879 * for positioning the cursor within text. 3880 * Note that any change applied to the handle Drawable will not be visible 3881 * until the handle is hidden and then drawn again. 3882 * 3883 * @return the text select handle drawable 3884 * 3885 * @see #setTextSelectHandle(Drawable) 3886 * @see #setTextSelectHandle(int) 3887 * @attr ref android.R.styleable#TextView_textSelectHandle 3888 */ getTextSelectHandle()3889 @Nullable public Drawable getTextSelectHandle() { 3890 if (mTextSelectHandle == null && mTextSelectHandleRes != 0) { 3891 mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes); 3892 } 3893 return mTextSelectHandle; 3894 } 3895 3896 /** 3897 * Sets the Drawable corresponding to the left handle used 3898 * for selecting text. The Drawable defaults to the value of the 3899 * textSelectHandleLeft attribute. 3900 * Note that any change applied to the handle Drawable will not be visible 3901 * until the handle is hidden and then drawn again. 3902 * 3903 * @see #setTextSelectHandleLeft(int) 3904 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3905 */ 3906 @android.view.RemotableViewMethod setTextSelectHandleLeft(@onNull Drawable textSelectHandleLeft)3907 public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) { 3908 Preconditions.checkNotNull(textSelectHandleLeft, 3909 "The left text select handle should not be null."); 3910 mTextSelectHandleLeft = textSelectHandleLeft; 3911 mTextSelectHandleLeftRes = 0; 3912 if (mEditor != null) { 3913 mEditor.loadHandleDrawables(true /* overwrite */); 3914 } 3915 } 3916 3917 /** 3918 * Sets the Drawable corresponding to the left handle used 3919 * for selecting text. The Drawable defaults to the value of the 3920 * textSelectHandleLeft attribute. 3921 * Note that any change applied to the handle Drawable will not be visible 3922 * until the handle is hidden and then drawn again. 3923 * 3924 * @see #setTextSelectHandleLeft(Drawable) 3925 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3926 */ 3927 @android.view.RemotableViewMethod setTextSelectHandleLeft(@rawableRes int textSelectHandleLeft)3928 public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) { 3929 Preconditions.checkArgument(textSelectHandleLeft != 0, 3930 "The text select left handle should be a valid drawable resource id."); 3931 setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft)); 3932 } 3933 3934 /** 3935 * Returns the Drawable corresponding to the left handle used 3936 * for selecting text. 3937 * Note that any change applied to the handle Drawable will not be visible 3938 * until the handle is hidden and then drawn again. 3939 * 3940 * @return the left text selection handle drawable 3941 * 3942 * @see #setTextSelectHandleLeft(Drawable) 3943 * @see #setTextSelectHandleLeft(int) 3944 * @attr ref android.R.styleable#TextView_textSelectHandleLeft 3945 */ getTextSelectHandleLeft()3946 @Nullable public Drawable getTextSelectHandleLeft() { 3947 if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) { 3948 mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes); 3949 } 3950 return mTextSelectHandleLeft; 3951 } 3952 3953 /** 3954 * Sets the Drawable corresponding to the right handle used 3955 * for selecting text. The Drawable defaults to the value of the 3956 * textSelectHandleRight attribute. 3957 * Note that any change applied to the handle Drawable will not be visible 3958 * until the handle is hidden and then drawn again. 3959 * 3960 * @see #setTextSelectHandleRight(int) 3961 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3962 */ 3963 @android.view.RemotableViewMethod setTextSelectHandleRight(@onNull Drawable textSelectHandleRight)3964 public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) { 3965 Preconditions.checkNotNull(textSelectHandleRight, 3966 "The right text select handle should not be null."); 3967 mTextSelectHandleRight = textSelectHandleRight; 3968 mTextSelectHandleRightRes = 0; 3969 if (mEditor != null) { 3970 mEditor.loadHandleDrawables(true /* overwrite */); 3971 } 3972 } 3973 3974 /** 3975 * Sets the Drawable corresponding to the right handle used 3976 * for selecting text. The Drawable defaults to the value of the 3977 * textSelectHandleRight attribute. 3978 * Note that any change applied to the handle Drawable will not be visible 3979 * until the handle is hidden and then drawn again. 3980 * 3981 * @see #setTextSelectHandleRight(Drawable) 3982 * @attr ref android.R.styleable#TextView_textSelectHandleRight 3983 */ 3984 @android.view.RemotableViewMethod setTextSelectHandleRight(@rawableRes int textSelectHandleRight)3985 public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) { 3986 Preconditions.checkArgument(textSelectHandleRight != 0, 3987 "The text select right handle should be a valid drawable resource id."); 3988 setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight)); 3989 } 3990 3991 /** 3992 * Returns the Drawable corresponding to the right handle used 3993 * for selecting text. 3994 * Note that any change applied to the handle Drawable will not be visible 3995 * until the handle is hidden and then drawn again. 3996 * 3997 * @return the right text selection handle drawable 3998 * 3999 * @see #setTextSelectHandleRight(Drawable) 4000 * @see #setTextSelectHandleRight(int) 4001 * @attr ref android.R.styleable#TextView_textSelectHandleRight 4002 */ getTextSelectHandleRight()4003 @Nullable public Drawable getTextSelectHandleRight() { 4004 if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) { 4005 mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes); 4006 } 4007 return mTextSelectHandleRight; 4008 } 4009 4010 /** 4011 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 4012 * value of the textCursorDrawable attribute. 4013 * Note that any change applied to the cursor Drawable will not be visible 4014 * until the cursor is hidden and then drawn again. 4015 * 4016 * @see #setTextCursorDrawable(int) 4017 * @attr ref android.R.styleable#TextView_textCursorDrawable 4018 */ setTextCursorDrawable(@ullable Drawable textCursorDrawable)4019 public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) { 4020 mCursorDrawable = textCursorDrawable; 4021 mCursorDrawableRes = 0; 4022 if (mEditor != null) { 4023 mEditor.loadCursorDrawable(); 4024 } 4025 } 4026 4027 /** 4028 * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the 4029 * value of the textCursorDrawable attribute. 4030 * Note that any change applied to the cursor Drawable will not be visible 4031 * until the cursor is hidden and then drawn again. 4032 * 4033 * @see #setTextCursorDrawable(Drawable) 4034 * @attr ref android.R.styleable#TextView_textCursorDrawable 4035 */ setTextCursorDrawable(@rawableRes int textCursorDrawable)4036 public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) { 4037 setTextCursorDrawable( 4038 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null); 4039 } 4040 4041 /** 4042 * Returns the Drawable corresponding to the text cursor. 4043 * Note that any change applied to the cursor Drawable will not be visible 4044 * until the cursor is hidden and then drawn again. 4045 * 4046 * @return the text cursor drawable 4047 * 4048 * @see #setTextCursorDrawable(Drawable) 4049 * @see #setTextCursorDrawable(int) 4050 * @attr ref android.R.styleable#TextView_textCursorDrawable 4051 */ getTextCursorDrawable()4052 @Nullable public Drawable getTextCursorDrawable() { 4053 if (mCursorDrawable == null && mCursorDrawableRes != 0) { 4054 mCursorDrawable = mContext.getDrawable(mCursorDrawableRes); 4055 } 4056 return mCursorDrawable; 4057 } 4058 4059 /** 4060 * Sets the text appearance from the specified style resource. 4061 * <p> 4062 * Use a framework-defined {@code TextAppearance} style like 4063 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1} 4064 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the 4065 * set of attributes that can be used in a custom style. 4066 * 4067 * @param resId the resource identifier of the style to apply 4068 * @attr ref android.R.styleable#TextView_textAppearance 4069 */ 4070 @SuppressWarnings("deprecation") setTextAppearance(@tyleRes int resId)4071 public void setTextAppearance(@StyleRes int resId) { 4072 setTextAppearance(mContext, resId); 4073 } 4074 4075 /** 4076 * Sets the text color, size, style, hint color, and highlight color 4077 * from the specified TextAppearance resource. 4078 * 4079 * @deprecated Use {@link #setTextAppearance(int)} instead. 4080 */ 4081 @Deprecated setTextAppearance(Context context, @StyleRes int resId)4082 public void setTextAppearance(Context context, @StyleRes int resId) { 4083 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance); 4084 final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); 4085 readTextAppearance(context, ta, attributes, false /* styleArray */); 4086 ta.recycle(); 4087 applyTextAppearance(attributes); 4088 } 4089 4090 /** 4091 * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code 4092 * that reads these attributes in the constructor and in {@link #setTextAppearance}. 4093 */ 4094 private static class TextAppearanceAttributes { 4095 int mTextColorHighlight = 0; 4096 int mSearchResultHighlightColor = 0; 4097 int mFocusedSearchResultHighlightColor = 0; 4098 ColorStateList mTextColor = null; 4099 ColorStateList mTextColorHint = null; 4100 ColorStateList mTextColorLink = null; 4101 int mTextSize = -1; 4102 int mTextSizeUnit = -1; 4103 LocaleList mTextLocales = null; 4104 String mFontFamily = null; 4105 Typeface mFontTypeface = null; 4106 boolean mFontFamilyExplicit = false; 4107 int mTypefaceIndex = -1; 4108 int mTextStyle = 0; 4109 int mFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED; 4110 boolean mAllCaps = false; 4111 int mShadowColor = 0; 4112 float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0; 4113 boolean mHasElegant = false; 4114 boolean mElegant = false; 4115 boolean mHasFallbackLineSpacing = false; 4116 boolean mFallbackLineSpacing = false; 4117 boolean mHasLetterSpacing = false; 4118 float mLetterSpacing = 0; 4119 String mFontFeatureSettings = null; 4120 String mFontVariationSettings = null; 4121 boolean mHasLineBreakStyle = false; 4122 boolean mHasLineBreakWordStyle = false; 4123 int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE; 4124 int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE; 4125 4126 @Override toString()4127 public String toString() { 4128 return "TextAppearanceAttributes {\n" 4129 + " mTextColorHighlight:" + mTextColorHighlight + "\n" 4130 + " mSearchResultHighlightColor: " + mSearchResultHighlightColor + "\n" 4131 + " mFocusedSearchResultHighlightColor: " 4132 + mFocusedSearchResultHighlightColor + "\n" 4133 + " mTextColor:" + mTextColor + "\n" 4134 + " mTextColorHint:" + mTextColorHint + "\n" 4135 + " mTextColorLink:" + mTextColorLink + "\n" 4136 + " mTextSize:" + mTextSize + "\n" 4137 + " mTextSizeUnit:" + mTextSizeUnit + "\n" 4138 + " mTextLocales:" + mTextLocales + "\n" 4139 + " mFontFamily:" + mFontFamily + "\n" 4140 + " mFontTypeface:" + mFontTypeface + "\n" 4141 + " mFontFamilyExplicit:" + mFontFamilyExplicit + "\n" 4142 + " mTypefaceIndex:" + mTypefaceIndex + "\n" 4143 + " mTextStyle:" + mTextStyle + "\n" 4144 + " mFontWeight:" + mFontWeight + "\n" 4145 + " mAllCaps:" + mAllCaps + "\n" 4146 + " mShadowColor:" + mShadowColor + "\n" 4147 + " mShadowDx:" + mShadowDx + "\n" 4148 + " mShadowDy:" + mShadowDy + "\n" 4149 + " mShadowRadius:" + mShadowRadius + "\n" 4150 + " mHasElegant:" + mHasElegant + "\n" 4151 + " mElegant:" + mElegant + "\n" 4152 + " mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n" 4153 + " mFallbackLineSpacing:" + mFallbackLineSpacing + "\n" 4154 + " mHasLetterSpacing:" + mHasLetterSpacing + "\n" 4155 + " mLetterSpacing:" + mLetterSpacing + "\n" 4156 + " mFontFeatureSettings:" + mFontFeatureSettings + "\n" 4157 + " mFontVariationSettings:" + mFontVariationSettings + "\n" 4158 + " mHasLineBreakStyle:" + mHasLineBreakStyle + "\n" 4159 + " mHasLineBreakWordStyle:" + mHasLineBreakWordStyle + "\n" 4160 + " mLineBreakStyle:" + mLineBreakStyle + "\n" 4161 + " mLineBreakWordStyle:" + mLineBreakWordStyle + "\n" 4162 + "}"; 4163 } 4164 } 4165 4166 // Maps styleable attributes that exist both in TextView style and TextAppearance. 4167 private static final SparseIntArray sAppearanceValues = new SparseIntArray(); 4168 static { sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight)4169 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, 4170 com.android.internal.R.styleable.TextAppearance_textColorHighlight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_searchResultHighlightColor, com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor)4171 sAppearanceValues.put(com.android.internal.R.styleable.TextView_searchResultHighlightColor, 4172 com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor); sAppearanceValues.put( com.android.internal.R.styleable.TextView_focusedSearchResultHighlightColor, com.android.internal.R.styleable.TextAppearance_focusedSearchResultHighlightColor)4173 sAppearanceValues.put( 4174 com.android.internal.R.styleable.TextView_focusedSearchResultHighlightColor, 4175 com.android.internal.R.styleable.TextAppearance_focusedSearchResultHighlightColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor)4176 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, 4177 com.android.internal.R.styleable.TextAppearance_textColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint)4178 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, 4179 com.android.internal.R.styleable.TextAppearance_textColorHint); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink)4180 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, 4181 com.android.internal.R.styleable.TextAppearance_textColorLink); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize)4182 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, 4183 com.android.internal.R.styleable.TextAppearance_textSize); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, com.android.internal.R.styleable.TextAppearance_textLocale)4184 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, 4185 com.android.internal.R.styleable.TextAppearance_textLocale); sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface)4186 sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, 4187 com.android.internal.R.styleable.TextAppearance_typeface); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily)4188 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, 4189 com.android.internal.R.styleable.TextAppearance_fontFamily); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle)4190 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, 4191 com.android.internal.R.styleable.TextAppearance_textStyle); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight)4192 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, 4193 com.android.internal.R.styleable.TextAppearance_textFontWeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps)4194 sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, 4195 com.android.internal.R.styleable.TextAppearance_textAllCaps); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor)4196 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, 4197 com.android.internal.R.styleable.TextAppearance_shadowColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx)4198 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, 4199 com.android.internal.R.styleable.TextAppearance_shadowDx); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy)4200 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, 4201 com.android.internal.R.styleable.TextAppearance_shadowDy); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius)4202 sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, 4203 com.android.internal.R.styleable.TextAppearance_shadowRadius); sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight)4204 sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, 4205 com.android.internal.R.styleable.TextAppearance_elegantTextHeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing)4206 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, 4207 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing)4208 sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, 4209 com.android.internal.R.styleable.TextAppearance_letterSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)4210 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, 4211 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings)4212 sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, 4213 com.android.internal.R.styleable.TextAppearance_fontVariationSettings); sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle, com.android.internal.R.styleable.TextAppearance_lineBreakStyle)4214 sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle, 4215 com.android.internal.R.styleable.TextAppearance_lineBreakStyle); sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle, com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle)4216 sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle, 4217 com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle); 4218 } 4219 4220 /** 4221 * Read the Text Appearance attributes from a given TypedArray and set its values to the given 4222 * set. If the TypedArray contains a value that was already set in the given attributes, that 4223 * will be overridden. 4224 * 4225 * @param context The Context to be used 4226 * @param appearance The TypedArray to read properties from 4227 * @param attributes the TextAppearanceAttributes to fill in 4228 * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines 4229 * what attribute indexes will be used to read the properties. 4230 */ readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray)4231 private void readTextAppearance(Context context, TypedArray appearance, 4232 TextAppearanceAttributes attributes, boolean styleArray) { 4233 final int n = appearance.getIndexCount(); 4234 for (int i = 0; i < n; i++) { 4235 final int attr = appearance.getIndex(i); 4236 int index = attr; 4237 // Translate style array index ids to TextAppearance ids. 4238 if (styleArray) { 4239 index = sAppearanceValues.get(attr, -1); 4240 if (index == -1) { 4241 // This value is not part of a Text Appearance and should be ignored. 4242 continue; 4243 } 4244 } 4245 switch (index) { 4246 case com.android.internal.R.styleable.TextAppearance_textColorHighlight: 4247 attributes.mTextColorHighlight = 4248 appearance.getColor(attr, attributes.mTextColorHighlight); 4249 break; 4250 case com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor: 4251 attributes.mSearchResultHighlightColor = 4252 appearance.getColor(attr, attributes.mSearchResultHighlightColor); 4253 break; 4254 case com.android.internal.R.styleable 4255 .TextAppearance_focusedSearchResultHighlightColor: 4256 attributes.mFocusedSearchResultHighlightColor = 4257 appearance.getColor(attr, 4258 attributes.mFocusedSearchResultHighlightColor); 4259 break; 4260 case com.android.internal.R.styleable.TextAppearance_textColor: 4261 attributes.mTextColor = appearance.getColorStateList(attr); 4262 break; 4263 case com.android.internal.R.styleable.TextAppearance_textColorHint: 4264 attributes.mTextColorHint = appearance.getColorStateList(attr); 4265 break; 4266 case com.android.internal.R.styleable.TextAppearance_textColorLink: 4267 attributes.mTextColorLink = appearance.getColorStateList(attr); 4268 break; 4269 case com.android.internal.R.styleable.TextAppearance_textSize: 4270 attributes.mTextSize = 4271 appearance.getDimensionPixelSize(attr, attributes.mTextSize); 4272 attributes.mTextSizeUnit = appearance.peekValue(attr).getComplexUnit(); 4273 break; 4274 case com.android.internal.R.styleable.TextAppearance_textLocale: 4275 final String localeString = appearance.getString(attr); 4276 if (localeString != null) { 4277 final LocaleList localeList = LocaleList.forLanguageTags(localeString); 4278 if (!localeList.isEmpty()) { 4279 attributes.mTextLocales = localeList; 4280 } 4281 } 4282 break; 4283 case com.android.internal.R.styleable.TextAppearance_typeface: 4284 attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex); 4285 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 4286 attributes.mFontFamily = null; 4287 } 4288 break; 4289 case com.android.internal.R.styleable.TextAppearance_fontFamily: 4290 if (!context.isRestricted() && context.canLoadUnsafeResources()) { 4291 try { 4292 attributes.mFontTypeface = appearance.getFont(attr); 4293 } catch (UnsupportedOperationException | Resources.NotFoundException e) { 4294 // Expected if it is not a font resource. 4295 } 4296 } 4297 if (attributes.mFontTypeface == null) { 4298 attributes.mFontFamily = appearance.getString(attr); 4299 } 4300 attributes.mFontFamilyExplicit = true; 4301 break; 4302 case com.android.internal.R.styleable.TextAppearance_textStyle: 4303 attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle); 4304 break; 4305 case com.android.internal.R.styleable.TextAppearance_textFontWeight: 4306 attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight); 4307 break; 4308 case com.android.internal.R.styleable.TextAppearance_textAllCaps: 4309 attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps); 4310 break; 4311 case com.android.internal.R.styleable.TextAppearance_shadowColor: 4312 attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor); 4313 break; 4314 case com.android.internal.R.styleable.TextAppearance_shadowDx: 4315 attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx); 4316 break; 4317 case com.android.internal.R.styleable.TextAppearance_shadowDy: 4318 attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy); 4319 break; 4320 case com.android.internal.R.styleable.TextAppearance_shadowRadius: 4321 attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius); 4322 break; 4323 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight: 4324 attributes.mHasElegant = true; 4325 attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant); 4326 break; 4327 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing: 4328 attributes.mHasFallbackLineSpacing = true; 4329 attributes.mFallbackLineSpacing = appearance.getBoolean(attr, 4330 attributes.mFallbackLineSpacing); 4331 break; 4332 case com.android.internal.R.styleable.TextAppearance_letterSpacing: 4333 attributes.mHasLetterSpacing = true; 4334 attributes.mLetterSpacing = 4335 appearance.getFloat(attr, attributes.mLetterSpacing); 4336 break; 4337 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings: 4338 attributes.mFontFeatureSettings = appearance.getString(attr); 4339 break; 4340 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings: 4341 attributes.mFontVariationSettings = appearance.getString(attr); 4342 break; 4343 case com.android.internal.R.styleable.TextAppearance_lineBreakStyle: 4344 attributes.mHasLineBreakStyle = true; 4345 attributes.mLineBreakStyle = 4346 appearance.getInt(attr, attributes.mLineBreakStyle); 4347 break; 4348 case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle: 4349 attributes.mHasLineBreakWordStyle = true; 4350 attributes.mLineBreakWordStyle = 4351 appearance.getInt(attr, attributes.mLineBreakWordStyle); 4352 break; 4353 default: 4354 } 4355 } 4356 } 4357 applyTextAppearance(TextAppearanceAttributes attributes)4358 private void applyTextAppearance(TextAppearanceAttributes attributes) { 4359 if (attributes.mTextColor != null) { 4360 setTextColor(attributes.mTextColor); 4361 } 4362 4363 if (attributes.mTextColorHint != null) { 4364 setHintTextColor(attributes.mTextColorHint); 4365 } 4366 4367 if (attributes.mTextColorLink != null) { 4368 setLinkTextColor(attributes.mTextColorLink); 4369 } 4370 4371 if (attributes.mTextColorHighlight != 0) { 4372 setHighlightColor(attributes.mTextColorHighlight); 4373 } 4374 4375 if (attributes.mSearchResultHighlightColor != 0) { 4376 setSearchResultHighlightColor(attributes.mSearchResultHighlightColor); 4377 } 4378 4379 if (attributes.mFocusedSearchResultHighlightColor != 0) { 4380 setFocusedSearchResultHighlightColor(attributes.mFocusedSearchResultHighlightColor); 4381 } 4382 4383 if (attributes.mTextSize != -1) { 4384 mTextSizeUnit = attributes.mTextSizeUnit; 4385 setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */); 4386 } 4387 4388 if (attributes.mTextLocales != null) { 4389 setTextLocales(attributes.mTextLocales); 4390 } 4391 4392 if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { 4393 attributes.mFontFamily = null; 4394 } 4395 setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily, 4396 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight); 4397 4398 if (attributes.mShadowColor != 0) { 4399 setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy, 4400 attributes.mShadowColor); 4401 } 4402 4403 if (attributes.mAllCaps) { 4404 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 4405 } 4406 4407 if (attributes.mHasElegant) { 4408 setElegantTextHeight(attributes.mElegant); 4409 } 4410 4411 if (attributes.mHasFallbackLineSpacing) { 4412 setFallbackLineSpacing(attributes.mFallbackLineSpacing); 4413 } 4414 4415 if (attributes.mHasLetterSpacing) { 4416 setLetterSpacing(attributes.mLetterSpacing); 4417 } 4418 4419 if (attributes.mFontFeatureSettings != null) { 4420 setFontFeatureSettings(attributes.mFontFeatureSettings); 4421 } 4422 4423 if (attributes.mFontVariationSettings != null) { 4424 setFontVariationSettings(attributes.mFontVariationSettings); 4425 } 4426 4427 if (attributes.mHasLineBreakStyle || attributes.mHasLineBreakWordStyle) { 4428 updateLineBreakConfigFromTextAppearance(attributes.mHasLineBreakStyle, 4429 attributes.mHasLineBreakWordStyle, attributes.mLineBreakStyle, 4430 attributes.mLineBreakWordStyle); 4431 } 4432 } 4433 4434 /** 4435 * Updates the LineBreakConfig from the TextAppearance. 4436 * 4437 * This method updates the given line configuration from the TextAppearance. This method will 4438 * request new layout if line break config has been changed. 4439 * 4440 * @param isLineBreakStyleSpecified true if the line break style is specified. 4441 * @param isLineBreakWordStyleSpecified true if the line break word style is specified. 4442 * @param lineBreakStyle the value of the line break style in the TextAppearance. 4443 * @param lineBreakWordStyle the value of the line break word style in the TextAppearance. 4444 */ updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified, boolean isLineBreakWordStyleSpecified, @LineBreakConfig.LineBreakStyle int lineBreakStyle, @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle)4445 private void updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified, 4446 boolean isLineBreakWordStyleSpecified, 4447 @LineBreakConfig.LineBreakStyle int lineBreakStyle, 4448 @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { 4449 boolean updated = false; 4450 if (isLineBreakStyleSpecified && mLineBreakStyle != lineBreakStyle) { 4451 mLineBreakStyle = lineBreakStyle; 4452 updated = true; 4453 } 4454 if (isLineBreakWordStyleSpecified && mLineBreakWordStyle != lineBreakWordStyle) { 4455 mLineBreakWordStyle = lineBreakWordStyle; 4456 updated = true; 4457 } 4458 if (updated && mLayout != null) { 4459 nullLayouts(); 4460 requestLayout(); 4461 invalidate(); 4462 } 4463 } 4464 /** 4465 * Get the default primary {@link Locale} of the text in this TextView. This will always be 4466 * the first member of {@link #getTextLocales()}. 4467 * @return the default primary {@link Locale} of the text in this TextView. 4468 */ 4469 @NonNull getTextLocale()4470 public Locale getTextLocale() { 4471 return mTextPaint.getTextLocale(); 4472 } 4473 4474 /** 4475 * Get the default {@link LocaleList} of the text in this TextView. 4476 * @return the default {@link LocaleList} of the text in this TextView. 4477 */ 4478 @NonNull @Size(min = 1) getTextLocales()4479 public LocaleList getTextLocales() { 4480 return mTextPaint.getTextLocales(); 4481 } 4482 changeListenerLocaleTo(@ullable Locale locale)4483 private void changeListenerLocaleTo(@Nullable Locale locale) { 4484 if (mListenerChanged) { 4485 // If a listener has been explicitly set, don't change it. We may break something. 4486 return; 4487 } 4488 // The following null check is not absolutely necessary since all calling points of 4489 // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left 4490 // here in case others would want to call this method in the future. 4491 if (mEditor != null) { 4492 KeyListener listener = mEditor.mKeyListener; 4493 if (listener instanceof DigitsKeyListener) { 4494 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener); 4495 } else if (listener instanceof DateKeyListener) { 4496 listener = DateKeyListener.getInstance(locale); 4497 } else if (listener instanceof TimeKeyListener) { 4498 listener = TimeKeyListener.getInstance(locale); 4499 } else if (listener instanceof DateTimeKeyListener) { 4500 listener = DateTimeKeyListener.getInstance(locale); 4501 } else { 4502 return; 4503 } 4504 final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType); 4505 setKeyListenerOnly(listener); 4506 setInputTypeFromEditor(); 4507 if (wasPasswordType) { 4508 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS; 4509 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) { 4510 mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 4511 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) { 4512 mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD; 4513 } 4514 } 4515 } 4516 } 4517 4518 /** 4519 * Set the default {@link Locale} of the text in this TextView to a one-member 4520 * {@link LocaleList} containing just the given Locale. 4521 * 4522 * @param locale the {@link Locale} for drawing text, must not be null. 4523 * 4524 * @see #setTextLocales 4525 */ setTextLocale(@onNull Locale locale)4526 public void setTextLocale(@NonNull Locale locale) { 4527 mLocalesChanged = true; 4528 mTextPaint.setTextLocale(locale); 4529 if (mLayout != null) { 4530 nullLayouts(); 4531 requestLayout(); 4532 invalidate(); 4533 } 4534 } 4535 4536 /** 4537 * Set the default {@link LocaleList} of the text in this TextView to the given value. 4538 * 4539 * This value is used to choose appropriate typefaces for ambiguous characters (typically used 4540 * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects 4541 * other aspects of text display, including line breaking. 4542 * 4543 * @param locales the {@link LocaleList} for drawing text, must not be null or empty. 4544 * 4545 * @see Paint#setTextLocales 4546 */ setTextLocales(@onNull @izemin = 1) LocaleList locales)4547 public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) { 4548 mLocalesChanged = true; 4549 mTextPaint.setTextLocales(locales); 4550 if (mLayout != null) { 4551 nullLayouts(); 4552 requestLayout(); 4553 invalidate(); 4554 } 4555 } 4556 4557 @Override onConfigurationChanged(Configuration newConfig)4558 protected void onConfigurationChanged(Configuration newConfig) { 4559 super.onConfigurationChanged(newConfig); 4560 if (!mLocalesChanged) { 4561 mTextPaint.setTextLocales(LocaleList.getDefault()); 4562 if (mLayout != null) { 4563 nullLayouts(); 4564 requestLayout(); 4565 invalidate(); 4566 } 4567 } 4568 if (mFontWeightAdjustment != newConfig.fontWeightAdjustment) { 4569 mFontWeightAdjustment = newConfig.fontWeightAdjustment; 4570 setTypeface(getTypeface()); 4571 } 4572 } 4573 4574 /** 4575 * @return the size (in pixels) of the default text size in this TextView. 4576 */ 4577 @InspectableProperty 4578 @ViewDebug.ExportedProperty(category = "text") getTextSize()4579 public float getTextSize() { 4580 return mTextPaint.getTextSize(); 4581 } 4582 4583 /** 4584 * @return the size (in scaled pixels) of the default text size in this TextView. 4585 * @hide 4586 */ 4587 @ViewDebug.ExportedProperty(category = "text") getScaledTextSize()4588 public float getScaledTextSize() { 4589 return mTextPaint.getTextSize() / mTextPaint.density; 4590 } 4591 4592 /** @hide */ 4593 @ViewDebug.ExportedProperty(category = "text", mapping = { 4594 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"), 4595 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"), 4596 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"), 4597 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC") 4598 }) getTypefaceStyle()4599 public int getTypefaceStyle() { 4600 Typeface typeface = mTextPaint.getTypeface(); 4601 return typeface != null ? typeface.getStyle() : Typeface.NORMAL; 4602 } 4603 4604 /** 4605 * Set the default text size to the given value, interpreted as "scaled 4606 * pixel" units. This size is adjusted based on the current density and 4607 * user font size preference. 4608 * 4609 * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op. 4610 * 4611 * @param size The scaled pixel size. 4612 * 4613 * @attr ref android.R.styleable#TextView_textSize 4614 */ 4615 @android.view.RemotableViewMethod setTextSize(float size)4616 public void setTextSize(float size) { 4617 setTextSize(TypedValue.COMPLEX_UNIT_SP, size); 4618 } 4619 4620 /** 4621 * Set the default text size to a given unit and value. See {@link 4622 * TypedValue} for the possible dimension units. 4623 * 4624 * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op. 4625 * 4626 * @param unit The desired dimension unit. 4627 * @param size The desired size in the given units. 4628 * 4629 * @attr ref android.R.styleable#TextView_textSize 4630 */ setTextSize(int unit, float size)4631 public void setTextSize(int unit, float size) { 4632 if (!isAutoSizeEnabled()) { 4633 setTextSizeInternal(unit, size, true /* shouldRequestLayout */); 4634 } 4635 } 4636 4637 @NonNull getDisplayMetricsOrSystem()4638 private DisplayMetrics getDisplayMetricsOrSystem() { 4639 Context c = getContext(); 4640 Resources r; 4641 4642 if (c == null) { 4643 r = Resources.getSystem(); 4644 } else { 4645 r = c.getResources(); 4646 } 4647 4648 return r.getDisplayMetrics(); 4649 } 4650 setTextSizeInternal(int unit, float size, boolean shouldRequestLayout)4651 private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) { 4652 mTextSizeUnit = unit; 4653 setRawTextSize(TypedValue.applyDimension(unit, size, getDisplayMetricsOrSystem()), 4654 shouldRequestLayout); 4655 } 4656 4657 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) setRawTextSize(float size, boolean shouldRequestLayout)4658 private void setRawTextSize(float size, boolean shouldRequestLayout) { 4659 if (size != mTextPaint.getTextSize()) { 4660 mTextPaint.setTextSize(size); 4661 4662 maybeRecalculateLineHeight(); 4663 if (shouldRequestLayout && mLayout != null) { 4664 // Do not auto-size right after setting the text size. 4665 mNeedsAutoSizeText = false; 4666 nullLayouts(); 4667 requestLayout(); 4668 invalidate(); 4669 } 4670 } 4671 } 4672 4673 /** 4674 * Gets the text size unit defined by the developer. It may be specified in resources or be 4675 * passed as the unit argument of {@link #setTextSize(int, float)} at runtime. 4676 * 4677 * @return the dimension type of the text size unit originally defined. 4678 * @see TypedValue#TYPE_DIMENSION 4679 */ getTextSizeUnit()4680 public int getTextSizeUnit() { 4681 return mTextSizeUnit; 4682 } 4683 4684 /** 4685 * Gets the extent by which text should be stretched horizontally. 4686 * This will usually be 1.0. 4687 * @return The horizontal scale factor. 4688 */ 4689 @InspectableProperty getTextScaleX()4690 public float getTextScaleX() { 4691 return mTextPaint.getTextScaleX(); 4692 } 4693 4694 /** 4695 * Sets the horizontal scale factor for text. The default value 4696 * is 1.0. Values greater than 1.0 stretch the text wider. 4697 * Values less than 1.0 make the text narrower. By default, this value is 1.0. 4698 * @param size The horizontal scale factor. 4699 * @attr ref android.R.styleable#TextView_textScaleX 4700 */ 4701 @android.view.RemotableViewMethod setTextScaleX(float size)4702 public void setTextScaleX(float size) { 4703 if (size != mTextPaint.getTextScaleX()) { 4704 mUserSetTextScaleX = true; 4705 mTextPaint.setTextScaleX(size); 4706 4707 if (mLayout != null) { 4708 nullLayouts(); 4709 requestLayout(); 4710 invalidate(); 4711 } 4712 } 4713 } 4714 4715 /** 4716 * Sets the typeface and style in which the text should be displayed. 4717 * Note that not all Typeface families actually have bold and italic 4718 * variants, so you may need to use 4719 * {@link #setTypeface(Typeface, int)} to get the appearance 4720 * that you actually want. 4721 * 4722 * @see #getTypeface() 4723 * 4724 * @attr ref android.R.styleable#TextView_fontFamily 4725 * @attr ref android.R.styleable#TextView_typeface 4726 * @attr ref android.R.styleable#TextView_textStyle 4727 */ setTypeface(@ullable Typeface tf)4728 public void setTypeface(@Nullable Typeface tf) { 4729 mOriginalTypeface = tf; 4730 if (mFontWeightAdjustment != 0 4731 && mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) { 4732 if (tf == null) { 4733 tf = Typeface.DEFAULT; 4734 } else { 4735 int newWeight = Math.min( 4736 Math.max(tf.getWeight() + mFontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN), 4737 FontStyle.FONT_WEIGHT_MAX); 4738 int typefaceStyle = tf != null ? tf.getStyle() : 0; 4739 boolean italic = (typefaceStyle & Typeface.ITALIC) != 0; 4740 tf = Typeface.create(tf, newWeight, italic); 4741 } 4742 } 4743 if (mTextPaint.getTypeface() != tf) { 4744 mTextPaint.setTypeface(tf); 4745 4746 if (mLayout != null) { 4747 nullLayouts(); 4748 requestLayout(); 4749 invalidate(); 4750 } 4751 } 4752 } 4753 4754 /** 4755 * Gets the current {@link Typeface} that is used to style the text. 4756 * @return The current Typeface. 4757 * 4758 * @see #setTypeface(Typeface) 4759 * 4760 * @attr ref android.R.styleable#TextView_fontFamily 4761 * @attr ref android.R.styleable#TextView_typeface 4762 * @attr ref android.R.styleable#TextView_textStyle 4763 */ 4764 @InspectableProperty getTypeface()4765 public Typeface getTypeface() { 4766 return mOriginalTypeface; 4767 } 4768 4769 /** 4770 * Set the TextView's elegant height metrics flag. This setting selects font 4771 * variants that have not been compacted to fit Latin-based vertical 4772 * metrics, and also increases top and bottom bounds to provide more space. 4773 * 4774 * @param elegant set the paint's elegant metrics flag. 4775 * 4776 * @see #isElegantTextHeight() 4777 * @see Paint#isElegantTextHeight() 4778 * 4779 * @attr ref android.R.styleable#TextView_elegantTextHeight 4780 */ setElegantTextHeight(boolean elegant)4781 public void setElegantTextHeight(boolean elegant) { 4782 if (elegant != mTextPaint.isElegantTextHeight()) { 4783 mTextPaint.setElegantTextHeight(elegant); 4784 if (mLayout != null) { 4785 nullLayouts(); 4786 requestLayout(); 4787 invalidate(); 4788 } 4789 } 4790 } 4791 4792 /** 4793 * Set whether to respect the ascent and descent of the fallback fonts that are used in 4794 * displaying the text (which is needed to avoid text from consecutive lines running into 4795 * each other). If set, fallback fonts that end up getting used can increase the ascent 4796 * and descent of the lines that they are used on. 4797 * <p/> 4798 * It is required to be true if text could be in languages like Burmese or Tibetan where text 4799 * is typically much taller or deeper than Latin text. 4800 * 4801 * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default 4802 * 4803 * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean) 4804 * 4805 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 4806 */ setFallbackLineSpacing(boolean enabled)4807 public void setFallbackLineSpacing(boolean enabled) { 4808 int fallbackStrategy; 4809 if (enabled) { 4810 if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) { 4811 fallbackStrategy = FALLBACK_LINE_SPACING_ALL; 4812 } else { 4813 fallbackStrategy = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; 4814 } 4815 } else { 4816 fallbackStrategy = FALLBACK_LINE_SPACING_NONE; 4817 } 4818 if (mUseFallbackLineSpacing != fallbackStrategy) { 4819 mUseFallbackLineSpacing = fallbackStrategy; 4820 if (mLayout != null) { 4821 nullLayouts(); 4822 requestLayout(); 4823 invalidate(); 4824 } 4825 } 4826 } 4827 4828 /** 4829 * @return whether fallback line spacing is enabled, {@code true} by default 4830 * 4831 * @see #setFallbackLineSpacing(boolean) 4832 * 4833 * @attr ref android.R.styleable#TextView_fallbackLineSpacing 4834 */ 4835 @InspectableProperty isFallbackLineSpacing()4836 public boolean isFallbackLineSpacing() { 4837 return mUseFallbackLineSpacing != FALLBACK_LINE_SPACING_NONE; 4838 } 4839 isFallbackLineSpacingForBoringLayout()4840 private boolean isFallbackLineSpacingForBoringLayout() { 4841 return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL; 4842 } 4843 4844 // Package privte for accessing from Editor.java isFallbackLineSpacingForStaticLayout()4845 /* package */ boolean isFallbackLineSpacingForStaticLayout() { 4846 return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL 4847 || mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; 4848 } 4849 4850 /** 4851 * Get the value of the TextView's elegant height metrics flag. This setting selects font 4852 * variants that have not been compacted to fit Latin-based vertical 4853 * metrics, and also increases top and bottom bounds to provide more space. 4854 * @return {@code true} if the elegant height metrics flag is set. 4855 * 4856 * @see #setElegantTextHeight(boolean) 4857 * @see Paint#setElegantTextHeight(boolean) 4858 */ 4859 @InspectableProperty isElegantTextHeight()4860 public boolean isElegantTextHeight() { 4861 return mTextPaint.isElegantTextHeight(); 4862 } 4863 4864 /** 4865 * Gets the text letter-space value, which determines the spacing between characters. 4866 * The value returned is in ems. Normally, this value is 0.0. 4867 * @return The text letter-space value in ems. 4868 * 4869 * @see #setLetterSpacing(float) 4870 * @see Paint#setLetterSpacing 4871 */ 4872 @InspectableProperty getLetterSpacing()4873 public float getLetterSpacing() { 4874 return mTextPaint.getLetterSpacing(); 4875 } 4876 4877 /** 4878 * Sets text letter-spacing in em units. Typical values 4879 * for slight expansion will be around 0.05. Negative values tighten text. 4880 * 4881 * @see #getLetterSpacing() 4882 * @see Paint#getLetterSpacing 4883 * 4884 * @param letterSpacing A text letter-space value in ems. 4885 * @attr ref android.R.styleable#TextView_letterSpacing 4886 */ 4887 @android.view.RemotableViewMethod setLetterSpacing(float letterSpacing)4888 public void setLetterSpacing(float letterSpacing) { 4889 if (letterSpacing != mTextPaint.getLetterSpacing()) { 4890 mTextPaint.setLetterSpacing(letterSpacing); 4891 4892 if (mLayout != null) { 4893 nullLayouts(); 4894 requestLayout(); 4895 invalidate(); 4896 } 4897 } 4898 } 4899 4900 /** 4901 * Returns the font feature settings. The format is the same as the CSS 4902 * font-feature-settings attribute: 4903 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 4904 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 4905 * 4906 * @return the currently set font feature settings. Default is null. 4907 * 4908 * @see #setFontFeatureSettings(String) 4909 * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String) 4910 */ 4911 @InspectableProperty 4912 @Nullable getFontFeatureSettings()4913 public String getFontFeatureSettings() { 4914 return mTextPaint.getFontFeatureSettings(); 4915 } 4916 4917 /** 4918 * Returns the font variation settings. 4919 * 4920 * @return the currently set font variation settings. Returns null if no variation is 4921 * specified. 4922 * 4923 * @see #setFontVariationSettings(String) 4924 * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String) 4925 */ 4926 @Nullable getFontVariationSettings()4927 public String getFontVariationSettings() { 4928 return mTextPaint.getFontVariationSettings(); 4929 } 4930 4931 /** 4932 * Sets the break strategy for breaking paragraphs into lines. The default value for 4933 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for 4934 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the 4935 * text "dancing" when being edited. 4936 * <p> 4937 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 4938 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 4939 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 4940 * improves the structure of text layout however has performance impact and requires more time 4941 * to do the text layout.</p> 4942 * <p> 4943 * Compared with {@link #setLineBreakStyle(int)}, line break style with different strictness is 4944 * evaluated in the ICU to identify the potential breakpoints. In 4945 * {@link #setBreakStrategy(int)}, line break strategy handles the post processing of ICU's line 4946 * break result. It aims to evaluate ICU's breakpoints and break the lines based on the 4947 * constraint. 4948 * </p> 4949 * 4950 * @attr ref android.R.styleable#TextView_breakStrategy 4951 * @see #getBreakStrategy() 4952 * @see #setHyphenationFrequency(int) 4953 */ setBreakStrategy(@ayout.BreakStrategy int breakStrategy)4954 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) { 4955 mBreakStrategy = breakStrategy; 4956 if (mLayout != null) { 4957 nullLayouts(); 4958 requestLayout(); 4959 invalidate(); 4960 } 4961 } 4962 4963 /** 4964 * Gets the current strategy for breaking paragraphs into lines. 4965 * @return the current strategy for breaking paragraphs into lines. 4966 * 4967 * @attr ref android.R.styleable#TextView_breakStrategy 4968 * @see #setBreakStrategy(int) 4969 */ 4970 @InspectableProperty(enumMapping = { 4971 @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE), 4972 @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY), 4973 @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED) 4974 }) 4975 @Layout.BreakStrategy getBreakStrategy()4976 public int getBreakStrategy() { 4977 return mBreakStrategy; 4978 } 4979 4980 /** 4981 * Sets the frequency of automatic hyphenation to use when determining word breaks. 4982 * The default value for both TextView and {@link EditText} is 4983 * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value 4984 * is set from the theme. 4985 * <p/> 4986 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or 4987 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of 4988 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY} 4989 * improves the structure of text layout however has performance impact and requires more time 4990 * to do the text layout. 4991 * <p/> 4992 * Note: Before Android Q, in the theme hyphenation frequency is set to 4993 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into 4994 * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q. 4995 * 4996 * @param hyphenationFrequency the hyphenation frequency to use, one of 4997 * {@link Layout#HYPHENATION_FREQUENCY_NONE}, 4998 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}, 4999 * {@link Layout#HYPHENATION_FREQUENCY_FULL} 5000 * @attr ref android.R.styleable#TextView_hyphenationFrequency 5001 * @see #getHyphenationFrequency() 5002 * @see #getBreakStrategy() 5003 */ setHyphenationFrequency(@ayout.HyphenationFrequency int hyphenationFrequency)5004 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) { 5005 mHyphenationFrequency = hyphenationFrequency; 5006 if (mLayout != null) { 5007 nullLayouts(); 5008 requestLayout(); 5009 invalidate(); 5010 } 5011 } 5012 5013 /** 5014 * Gets the current frequency of automatic hyphenation to be used when determining word breaks. 5015 * @return the current frequency of automatic hyphenation to be used when determining word 5016 * breaks. 5017 * 5018 * @attr ref android.R.styleable#TextView_hyphenationFrequency 5019 * @see #setHyphenationFrequency(int) 5020 */ 5021 @InspectableProperty(enumMapping = { 5022 @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE), 5023 @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL), 5024 @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL) 5025 }) 5026 @Layout.HyphenationFrequency getHyphenationFrequency()5027 public int getHyphenationFrequency() { 5028 return mHyphenationFrequency; 5029 } 5030 5031 /** 5032 * Sets the line-break style for text wrapping. 5033 * 5034 * <p>Line-break style specifies the line-break strategies that can be used 5035 * for text wrapping. The line-break style affects rule-based line breaking 5036 * by specifying the strictness of line-breaking rules. 5037 * 5038 * <p>The following are types of line-break styles: 5039 * <ul> 5040 * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE} 5041 * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} 5042 * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT} 5043 * </ul> 5044 * 5045 * <p>The default line-break style is 5046 * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no 5047 * line-breaking rules are used. 5048 * 5049 * <p>See the 5050 * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external"> 5051 * line-break property</a> for more information. 5052 * 5053 * @param lineBreakStyle The line-break style for the text. 5054 */ setLineBreakStyle(@ineBreakConfig.LineBreakStyle int lineBreakStyle)5055 public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) { 5056 if (mLineBreakStyle != lineBreakStyle) { 5057 mLineBreakStyle = lineBreakStyle; 5058 if (mLayout != null) { 5059 nullLayouts(); 5060 requestLayout(); 5061 invalidate(); 5062 } 5063 } 5064 } 5065 5066 /** 5067 * Sets the line-break word style for text wrapping. 5068 * 5069 * <p>The line-break word style affects dictionary-based line breaking by 5070 * providing phrase-based line-breaking opportunities. Use 5071 * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify 5072 * phrase-based line breaking. 5073 * 5074 * <p>The default line-break word style is 5075 * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that 5076 * no line-breaking word style is used. 5077 * 5078 * <p>See the 5079 * <a href="https://www.w3.org/TR/css-text-3/#word-break-property" class="external"> 5080 * word-break property</a> for more information. 5081 * 5082 * @param lineBreakWordStyle The line-break word style for the text. 5083 */ setLineBreakWordStyle(@ineBreakConfig.LineBreakWordStyle int lineBreakWordStyle)5084 public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { 5085 if (mLineBreakWordStyle != lineBreakWordStyle) { 5086 mLineBreakWordStyle = lineBreakWordStyle; 5087 if (mLayout != null) { 5088 nullLayouts(); 5089 requestLayout(); 5090 invalidate(); 5091 } 5092 } 5093 } 5094 5095 /** 5096 * Gets the current line-break style for text wrapping. 5097 * 5098 * @return The line-break style to be used for text wrapping. 5099 */ getLineBreakStyle()5100 public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() { 5101 return mLineBreakStyle; 5102 } 5103 5104 /** 5105 * Gets the current line-break word style for text wrapping. 5106 * 5107 * @return The line-break word style to be used for text wrapping. 5108 */ getLineBreakWordStyle()5109 public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() { 5110 return mLineBreakWordStyle; 5111 } 5112 5113 /** 5114 * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}. 5115 * 5116 * @return a current {@link PrecomputedText.Params} 5117 * @see PrecomputedText 5118 */ getTextMetricsParams()5119 public @NonNull PrecomputedText.Params getTextMetricsParams() { 5120 return new PrecomputedText.Params(new TextPaint(mTextPaint), 5121 LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle), 5122 getTextDirectionHeuristic(), 5123 mBreakStrategy, mHyphenationFrequency); 5124 } 5125 5126 /** 5127 * Apply the text layout parameter. 5128 * 5129 * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}. 5130 * @see PrecomputedText 5131 */ setTextMetricsParams(@onNull PrecomputedText.Params params)5132 public void setTextMetricsParams(@NonNull PrecomputedText.Params params) { 5133 mTextPaint.set(params.getTextPaint()); 5134 mUserSetTextScaleX = true; 5135 mTextDir = params.getTextDirection(); 5136 mBreakStrategy = params.getBreakStrategy(); 5137 mHyphenationFrequency = params.getHyphenationFrequency(); 5138 LineBreakConfig lineBreakConfig = params.getLineBreakConfig(); 5139 mLineBreakStyle = lineBreakConfig.getLineBreakStyle(); 5140 mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle(); 5141 if (mLayout != null) { 5142 nullLayouts(); 5143 requestLayout(); 5144 invalidate(); 5145 } 5146 } 5147 5148 /** 5149 * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the 5150 * last line is too short for justification, the last line will be displayed with the 5151 * alignment set by {@link android.view.View#setTextAlignment}. 5152 * 5153 * @see #getJustificationMode() 5154 */ 5155 @Layout.JustificationMode 5156 @android.view.RemotableViewMethod setJustificationMode(@ayout.JustificationMode int justificationMode)5157 public void setJustificationMode(@Layout.JustificationMode int justificationMode) { 5158 mJustificationMode = justificationMode; 5159 if (mLayout != null) { 5160 nullLayouts(); 5161 requestLayout(); 5162 invalidate(); 5163 } 5164 } 5165 5166 /** 5167 * @return true if currently paragraph justification mode. 5168 * 5169 * @see #setJustificationMode(int) 5170 */ 5171 @InspectableProperty(enumMapping = { 5172 @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE), 5173 @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD) 5174 }) getJustificationMode()5175 public @Layout.JustificationMode int getJustificationMode() { 5176 return mJustificationMode; 5177 } 5178 5179 /** 5180 * Sets font feature settings. The format is the same as the CSS 5181 * font-feature-settings attribute: 5182 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> 5183 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> 5184 * 5185 * @param fontFeatureSettings font feature settings represented as CSS compatible string 5186 * 5187 * @see #getFontFeatureSettings() 5188 * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings() 5189 * 5190 * @attr ref android.R.styleable#TextView_fontFeatureSettings 5191 */ 5192 @android.view.RemotableViewMethod setFontFeatureSettings(@ullable String fontFeatureSettings)5193 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) { 5194 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) { 5195 mTextPaint.setFontFeatureSettings(fontFeatureSettings); 5196 5197 if (mLayout != null) { 5198 nullLayouts(); 5199 requestLayout(); 5200 invalidate(); 5201 } 5202 } 5203 } 5204 5205 5206 /** 5207 * Sets TrueType or OpenType font variation settings. The settings string is constructed from 5208 * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters 5209 * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that 5210 * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E 5211 * are invalid. If a specified axis name is not defined in the font, the settings will be 5212 * ignored. 5213 * 5214 * <p> 5215 * Examples, 5216 * <ul> 5217 * <li>Set font width to 150. 5218 * <pre> 5219 * <code> 5220 * TextView textView = (TextView) findViewById(R.id.textView); 5221 * textView.setFontVariationSettings("'wdth' 150"); 5222 * </code> 5223 * </pre> 5224 * </li> 5225 * 5226 * <li>Set the font slant to 20 degrees and ask for italic style. 5227 * <pre> 5228 * <code> 5229 * TextView textView = (TextView) findViewById(R.id.textView); 5230 * textView.setFontVariationSettings("'slnt' 20, 'ital' 1"); 5231 * </code> 5232 * </pre> 5233 * </p> 5234 * </li> 5235 * </ul> 5236 * 5237 * @param fontVariationSettings font variation settings. You can pass null or empty string as 5238 * no variation settings. 5239 * @return true if the given settings is effective to at least one font file underlying this 5240 * TextView. This function also returns true for empty settings string. Otherwise 5241 * returns false. 5242 * 5243 * @throws IllegalArgumentException If given string is not a valid font variation settings 5244 * format. 5245 * 5246 * @see #getFontVariationSettings() 5247 * @see FontVariationAxis 5248 * 5249 * @attr ref android.R.styleable#TextView_fontVariationSettings 5250 */ setFontVariationSettings(@ullable String fontVariationSettings)5251 public boolean setFontVariationSettings(@Nullable String fontVariationSettings) { 5252 final String existingSettings = mTextPaint.getFontVariationSettings(); 5253 if (fontVariationSettings == existingSettings 5254 || (fontVariationSettings != null 5255 && fontVariationSettings.equals(existingSettings))) { 5256 return true; 5257 } 5258 boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings); 5259 5260 if (effective && mLayout != null) { 5261 nullLayouts(); 5262 requestLayout(); 5263 invalidate(); 5264 } 5265 return effective; 5266 } 5267 5268 /** 5269 * Sets the text color for all the states (normal, selected, 5270 * focused) to be this color. 5271 * 5272 * @param color A color value in the form 0xAARRGGBB. 5273 * Do not pass a resource ID. To get a color value from a resource ID, call 5274 * {@link androidx.core.content.ContextCompat#getColor(Context, int) getColor}. 5275 * 5276 * @see #setTextColor(ColorStateList) 5277 * @see #getTextColors() 5278 * 5279 * @attr ref android.R.styleable#TextView_textColor 5280 */ 5281 @android.view.RemotableViewMethod setTextColor(@olorInt int color)5282 public void setTextColor(@ColorInt int color) { 5283 mTextColor = ColorStateList.valueOf(color); 5284 updateTextColors(); 5285 } 5286 5287 /** 5288 * Sets the text color. 5289 * 5290 * @see #setTextColor(int) 5291 * @see #getTextColors() 5292 * @see #setHintTextColor(ColorStateList) 5293 * @see #setLinkTextColor(ColorStateList) 5294 * 5295 * @attr ref android.R.styleable#TextView_textColor 5296 */ 5297 @android.view.RemotableViewMethod setTextColor(ColorStateList colors)5298 public void setTextColor(ColorStateList colors) { 5299 if (colors == null) { 5300 throw new NullPointerException(); 5301 } 5302 5303 mTextColor = colors; 5304 updateTextColors(); 5305 } 5306 5307 /** 5308 * Gets the text colors for the different states (normal, selected, focused) of the TextView. 5309 * 5310 * @see #setTextColor(ColorStateList) 5311 * @see #setTextColor(int) 5312 * 5313 * @attr ref android.R.styleable#TextView_textColor 5314 */ 5315 @InspectableProperty(name = "textColor") getTextColors()5316 public final ColorStateList getTextColors() { 5317 return mTextColor; 5318 } 5319 5320 /** 5321 * Return the current color selected for normal text. 5322 * 5323 * @return Returns the current text color. 5324 */ 5325 @ColorInt getCurrentTextColor()5326 public final int getCurrentTextColor() { 5327 return mCurTextColor; 5328 } 5329 5330 /** 5331 * Sets the color used to display the selection highlight. 5332 * 5333 * @attr ref android.R.styleable#TextView_textColorHighlight 5334 */ 5335 @android.view.RemotableViewMethod setHighlightColor(@olorInt int color)5336 public void setHighlightColor(@ColorInt int color) { 5337 if (mHighlightColor != color) { 5338 mHighlightColor = color; 5339 invalidate(); 5340 } 5341 } 5342 5343 /** 5344 * @return the color used to display the selection highlight 5345 * 5346 * @see #setHighlightColor(int) 5347 * 5348 * @attr ref android.R.styleable#TextView_textColorHighlight 5349 */ 5350 @InspectableProperty(name = "textColorHighlight") 5351 @ColorInt getHighlightColor()5352 public int getHighlightColor() { 5353 return mHighlightColor; 5354 } 5355 5356 /** 5357 * Sets whether the soft input method will be made visible when this 5358 * TextView gets focused. The default is true. 5359 */ 5360 @android.view.RemotableViewMethod setShowSoftInputOnFocus(boolean show)5361 public final void setShowSoftInputOnFocus(boolean show) { 5362 createEditorIfNeeded(); 5363 mEditor.mShowSoftInputOnFocus = show; 5364 } 5365 5366 /** 5367 * Returns whether the soft input method will be made visible when this 5368 * TextView gets focused. The default is true. 5369 */ getShowSoftInputOnFocus()5370 public final boolean getShowSoftInputOnFocus() { 5371 // When there is no Editor, return default true value 5372 return mEditor == null || mEditor.mShowSoftInputOnFocus; 5373 } 5374 5375 /** 5376 * Gives the text a shadow of the specified blur radius and color, the specified 5377 * distance from its drawn position. 5378 * <p> 5379 * The text shadow produced does not interact with the properties on view 5380 * that are responsible for real time shadows, 5381 * {@link View#getElevation() elevation} and 5382 * {@link View#getTranslationZ() translationZ}. 5383 * 5384 * @see Paint#setShadowLayer(float, float, float, int) 5385 * 5386 * @attr ref android.R.styleable#TextView_shadowColor 5387 * @attr ref android.R.styleable#TextView_shadowDx 5388 * @attr ref android.R.styleable#TextView_shadowDy 5389 * @attr ref android.R.styleable#TextView_shadowRadius 5390 */ setShadowLayer(float radius, float dx, float dy, int color)5391 public void setShadowLayer(float radius, float dx, float dy, int color) { 5392 mTextPaint.setShadowLayer(radius, dx, dy, color); 5393 5394 mShadowRadius = radius; 5395 mShadowDx = dx; 5396 mShadowDy = dy; 5397 mShadowColor = color; 5398 5399 // Will change text clip region 5400 if (mEditor != null) { 5401 mEditor.invalidateTextDisplayList(); 5402 mEditor.invalidateHandlesAndActionMode(); 5403 } 5404 invalidate(); 5405 } 5406 5407 /** 5408 * Gets the radius of the shadow layer. 5409 * 5410 * @return the radius of the shadow layer. If 0, the shadow layer is not visible 5411 * 5412 * @see #setShadowLayer(float, float, float, int) 5413 * 5414 * @attr ref android.R.styleable#TextView_shadowRadius 5415 */ 5416 @InspectableProperty getShadowRadius()5417 public float getShadowRadius() { 5418 return mShadowRadius; 5419 } 5420 5421 /** 5422 * @return the horizontal offset of the shadow layer 5423 * 5424 * @see #setShadowLayer(float, float, float, int) 5425 * 5426 * @attr ref android.R.styleable#TextView_shadowDx 5427 */ 5428 @InspectableProperty getShadowDx()5429 public float getShadowDx() { 5430 return mShadowDx; 5431 } 5432 5433 /** 5434 * Gets the vertical offset of the shadow layer. 5435 * @return The vertical offset of the shadow layer. 5436 * 5437 * @see #setShadowLayer(float, float, float, int) 5438 * 5439 * @attr ref android.R.styleable#TextView_shadowDy 5440 */ 5441 @InspectableProperty getShadowDy()5442 public float getShadowDy() { 5443 return mShadowDy; 5444 } 5445 5446 /** 5447 * Gets the color of the shadow layer. 5448 * @return the color of the shadow layer 5449 * 5450 * @see #setShadowLayer(float, float, float, int) 5451 * 5452 * @attr ref android.R.styleable#TextView_shadowColor 5453 */ 5454 @InspectableProperty 5455 @ColorInt getShadowColor()5456 public int getShadowColor() { 5457 return mShadowColor; 5458 } 5459 5460 /** 5461 * Gets the {@link TextPaint} used for the text. 5462 * Use this only to consult the Paint's properties and not to change them. 5463 * @return The base paint used for the text. 5464 */ getPaint()5465 public TextPaint getPaint() { 5466 return mTextPaint; 5467 } 5468 5469 /** 5470 * Sets the autolink mask of the text. See {@link 5471 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 5472 * possible values. 5473 * 5474 * <p class="note"><b>Note:</b> 5475 * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES} 5476 * is deprecated and should be avoided; see its documentation. 5477 * 5478 * @attr ref android.R.styleable#TextView_autoLink 5479 */ 5480 @android.view.RemotableViewMethod setAutoLinkMask(int mask)5481 public final void setAutoLinkMask(int mask) { 5482 mAutoLinkMask = mask; 5483 } 5484 5485 /** 5486 * Sets whether the movement method will automatically be set to 5487 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 5488 * set to nonzero and links are detected in {@link #setText}. 5489 * The default is true. 5490 * 5491 * @attr ref android.R.styleable#TextView_linksClickable 5492 */ 5493 @android.view.RemotableViewMethod setLinksClickable(boolean whether)5494 public final void setLinksClickable(boolean whether) { 5495 mLinksClickable = whether; 5496 } 5497 5498 /** 5499 * Returns whether the movement method will automatically be set to 5500 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 5501 * set to nonzero and links are detected in {@link #setText}. 5502 * The default is true. 5503 * 5504 * @attr ref android.R.styleable#TextView_linksClickable 5505 */ 5506 @InspectableProperty getLinksClickable()5507 public final boolean getLinksClickable() { 5508 return mLinksClickable; 5509 } 5510 5511 /** 5512 * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text 5513 * (by {@link Linkify} or otherwise) if any. You can call 5514 * {@link URLSpan#getURL} on them to find where they link to 5515 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} 5516 * to find the region of the text they are attached to. 5517 */ getUrls()5518 public URLSpan[] getUrls() { 5519 if (mText instanceof Spanned) { 5520 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); 5521 } else { 5522 return new URLSpan[0]; 5523 } 5524 } 5525 5526 /** 5527 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this 5528 * TextView. 5529 * 5530 * @see #setHintTextColor(ColorStateList) 5531 * @see #getHintTextColors() 5532 * @see #setTextColor(int) 5533 * 5534 * @attr ref android.R.styleable#TextView_textColorHint 5535 */ 5536 @android.view.RemotableViewMethod setHintTextColor(@olorInt int color)5537 public final void setHintTextColor(@ColorInt int color) { 5538 mHintTextColor = ColorStateList.valueOf(color); 5539 updateTextColors(); 5540 } 5541 5542 /** 5543 * Sets the color of the hint text. 5544 * 5545 * @see #getHintTextColors() 5546 * @see #setHintTextColor(int) 5547 * @see #setTextColor(ColorStateList) 5548 * @see #setLinkTextColor(ColorStateList) 5549 * 5550 * @attr ref android.R.styleable#TextView_textColorHint 5551 */ setHintTextColor(ColorStateList colors)5552 public final void setHintTextColor(ColorStateList colors) { 5553 mHintTextColor = colors; 5554 updateTextColors(); 5555 } 5556 5557 /** 5558 * @return the color of the hint text, for the different states of this TextView. 5559 * 5560 * @see #setHintTextColor(ColorStateList) 5561 * @see #setHintTextColor(int) 5562 * @see #setTextColor(ColorStateList) 5563 * @see #setLinkTextColor(ColorStateList) 5564 * 5565 * @attr ref android.R.styleable#TextView_textColorHint 5566 */ 5567 @InspectableProperty(name = "textColorHint") getHintTextColors()5568 public final ColorStateList getHintTextColors() { 5569 return mHintTextColor; 5570 } 5571 5572 /** 5573 * <p>Return the current color selected to paint the hint text.</p> 5574 * 5575 * @return Returns the current hint text color. 5576 */ 5577 @ColorInt getCurrentHintTextColor()5578 public final int getCurrentHintTextColor() { 5579 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; 5580 } 5581 5582 /** 5583 * Sets the color of links in the text. 5584 * 5585 * @see #setLinkTextColor(ColorStateList) 5586 * @see #getLinkTextColors() 5587 * 5588 * @attr ref android.R.styleable#TextView_textColorLink 5589 */ 5590 @android.view.RemotableViewMethod setLinkTextColor(@olorInt int color)5591 public final void setLinkTextColor(@ColorInt int color) { 5592 mLinkTextColor = ColorStateList.valueOf(color); 5593 updateTextColors(); 5594 } 5595 5596 /** 5597 * Sets the color of links in the text. 5598 * 5599 * @see #setLinkTextColor(int) 5600 * @see #getLinkTextColors() 5601 * @see #setTextColor(ColorStateList) 5602 * @see #setHintTextColor(ColorStateList) 5603 * 5604 * @attr ref android.R.styleable#TextView_textColorLink 5605 */ setLinkTextColor(ColorStateList colors)5606 public final void setLinkTextColor(ColorStateList colors) { 5607 mLinkTextColor = colors; 5608 updateTextColors(); 5609 } 5610 5611 /** 5612 * @return the list of colors used to paint the links in the text, for the different states of 5613 * this TextView 5614 * 5615 * @see #setLinkTextColor(ColorStateList) 5616 * @see #setLinkTextColor(int) 5617 * 5618 * @attr ref android.R.styleable#TextView_textColorLink 5619 */ 5620 @InspectableProperty(name = "textColorLink") getLinkTextColors()5621 public final ColorStateList getLinkTextColors() { 5622 return mLinkTextColor; 5623 } 5624 5625 /** 5626 * Sets the horizontal alignment of the text and the 5627 * vertical gravity that will be used when there is extra space 5628 * in the TextView beyond what is required for the text itself. 5629 * 5630 * @see android.view.Gravity 5631 * @attr ref android.R.styleable#TextView_gravity 5632 */ 5633 @android.view.RemotableViewMethod setGravity(int gravity)5634 public void setGravity(int gravity) { 5635 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 5636 gravity |= Gravity.START; 5637 } 5638 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 5639 gravity |= Gravity.TOP; 5640 } 5641 5642 boolean newLayout = false; 5643 5644 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) 5645 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) { 5646 newLayout = true; 5647 } 5648 5649 if (gravity != mGravity) { 5650 invalidate(); 5651 } 5652 5653 mGravity = gravity; 5654 5655 if (mLayout != null && newLayout) { 5656 // XXX this is heavy-handed because no actual content changes. 5657 int want = mLayout.getWidth(); 5658 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 5659 5660 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 5661 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true); 5662 } 5663 } 5664 5665 /** 5666 * Returns the horizontal and vertical alignment of this TextView. 5667 * 5668 * @see android.view.Gravity 5669 * @attr ref android.R.styleable#TextView_gravity 5670 */ 5671 @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY) getGravity()5672 public int getGravity() { 5673 return mGravity; 5674 } 5675 5676 /** 5677 * Gets the flags on the Paint being used to display the text. 5678 * @return The flags on the Paint being used to display the text. 5679 * @see Paint#getFlags 5680 */ getPaintFlags()5681 public int getPaintFlags() { 5682 return mTextPaint.getFlags(); 5683 } 5684 5685 /** 5686 * Sets flags on the Paint being used to display the text and 5687 * reflows the text if they are different from the old flags. 5688 * @see Paint#setFlags 5689 */ 5690 @android.view.RemotableViewMethod setPaintFlags(int flags)5691 public void setPaintFlags(int flags) { 5692 if (mTextPaint.getFlags() != flags) { 5693 mTextPaint.setFlags(flags); 5694 5695 if (mLayout != null) { 5696 nullLayouts(); 5697 requestLayout(); 5698 invalidate(); 5699 } 5700 } 5701 } 5702 5703 /** 5704 * Sets whether the text should be allowed to be wider than the 5705 * View is. If false, it will be wrapped to the width of the View. 5706 * 5707 * @attr ref android.R.styleable#TextView_scrollHorizontally 5708 */ setHorizontallyScrolling(boolean whether)5709 public void setHorizontallyScrolling(boolean whether) { 5710 if (mHorizontallyScrolling != whether) { 5711 mHorizontallyScrolling = whether; 5712 5713 if (mLayout != null) { 5714 nullLayouts(); 5715 requestLayout(); 5716 invalidate(); 5717 } 5718 } 5719 } 5720 5721 /** 5722 * Returns whether the text is allowed to be wider than the View. 5723 * If false, the text will be wrapped to the width of the View. 5724 * 5725 * @attr ref android.R.styleable#TextView_scrollHorizontally 5726 * @see #setHorizontallyScrolling(boolean) 5727 */ 5728 @InspectableProperty(name = "scrollHorizontally") isHorizontallyScrollable()5729 public final boolean isHorizontallyScrollable() { 5730 return mHorizontallyScrolling; 5731 } 5732 5733 /** 5734 * Returns whether the text is allowed to be wider than the View. 5735 * If false, the text will be wrapped to the width of the View. 5736 * 5737 * @attr ref android.R.styleable#TextView_scrollHorizontally 5738 * @hide 5739 */ 5740 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) getHorizontallyScrolling()5741 public boolean getHorizontallyScrolling() { 5742 return mHorizontallyScrolling; 5743 } 5744 5745 /** 5746 * Sets the height of the TextView to be at least {@code minLines} tall. 5747 * <p> 5748 * This value is used for height calculation if LayoutParams does not force TextView to have an 5749 * exact height. Setting this value overrides other previous minimum height configurations such 5750 * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set 5751 * this value to 1. 5752 * 5753 * @param minLines the minimum height of TextView in terms of number of lines 5754 * 5755 * @see #getMinLines() 5756 * @see #setLines(int) 5757 * 5758 * @attr ref android.R.styleable#TextView_minLines 5759 */ 5760 @android.view.RemotableViewMethod setMinLines(int minLines)5761 public void setMinLines(int minLines) { 5762 mMinimum = minLines; 5763 mMinMode = LINES; 5764 5765 requestLayout(); 5766 invalidate(); 5767 } 5768 5769 /** 5770 * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum 5771 * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}. 5772 * 5773 * @return the minimum height of TextView in terms of number of lines or -1 if the minimum 5774 * height is not defined in lines 5775 * 5776 * @see #setMinLines(int) 5777 * @see #setLines(int) 5778 * 5779 * @attr ref android.R.styleable#TextView_minLines 5780 */ 5781 @InspectableProperty getMinLines()5782 public int getMinLines() { 5783 return mMinMode == LINES ? mMinimum : -1; 5784 } 5785 5786 /** 5787 * Sets the height of the TextView to be at least {@code minPixels} tall. 5788 * <p> 5789 * This value is used for height calculation if LayoutParams does not force TextView to have an 5790 * exact height. Setting this value overrides previous minimum height configurations such as 5791 * {@link #setMinLines(int)} or {@link #setLines(int)}. 5792 * <p> 5793 * The value given here is different than {@link #setMinimumHeight(int)}. Between 5794 * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is 5795 * used to decide the final height. 5796 * 5797 * @param minPixels the minimum height of TextView in terms of pixels 5798 * 5799 * @see #getMinHeight() 5800 * @see #setHeight(int) 5801 * 5802 * @attr ref android.R.styleable#TextView_minHeight 5803 */ 5804 @android.view.RemotableViewMethod setMinHeight(int minPixels)5805 public void setMinHeight(int minPixels) { 5806 mMinimum = minPixels; 5807 mMinMode = PIXELS; 5808 5809 requestLayout(); 5810 invalidate(); 5811 } 5812 5813 /** 5814 * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was 5815 * set using {@link #setMinLines(int)} or {@link #setLines(int)}. 5816 * 5817 * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not 5818 * defined in pixels 5819 * 5820 * @see #setMinHeight(int) 5821 * @see #setHeight(int) 5822 * 5823 * @attr ref android.R.styleable#TextView_minHeight 5824 */ getMinHeight()5825 public int getMinHeight() { 5826 return mMinMode == PIXELS ? mMinimum : -1; 5827 } 5828 5829 /** 5830 * Sets the height of the TextView to be at most {@code maxLines} tall. 5831 * <p> 5832 * This value is used for height calculation if LayoutParams does not force TextView to have an 5833 * exact height. Setting this value overrides previous maximum height configurations such as 5834 * {@link #setMaxHeight(int)} or {@link #setLines(int)}. 5835 * 5836 * @param maxLines the maximum height of TextView in terms of number of lines 5837 * 5838 * @see #getMaxLines() 5839 * @see #setLines(int) 5840 * 5841 * @attr ref android.R.styleable#TextView_maxLines 5842 */ 5843 @android.view.RemotableViewMethod setMaxLines(int maxLines)5844 public void setMaxLines(int maxLines) { 5845 mMaximum = maxLines; 5846 mMaxMode = LINES; 5847 5848 requestLayout(); 5849 invalidate(); 5850 } 5851 5852 /** 5853 * Returns the maximum height of TextView in terms of number of lines or -1 if the 5854 * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}. 5855 * 5856 * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height 5857 * is not defined in lines. 5858 * 5859 * @see #setMaxLines(int) 5860 * @see #setLines(int) 5861 * 5862 * @attr ref android.R.styleable#TextView_maxLines 5863 */ 5864 @InspectableProperty getMaxLines()5865 public int getMaxLines() { 5866 return mMaxMode == LINES ? mMaximum : -1; 5867 } 5868 5869 /** 5870 * Sets the height of the TextView to be at most {@code maxPixels} tall. 5871 * <p> 5872 * This value is used for height calculation if LayoutParams does not force TextView to have an 5873 * exact height. Setting this value overrides previous maximum height configurations such as 5874 * {@link #setMaxLines(int)} or {@link #setLines(int)}. 5875 * 5876 * @param maxPixels the maximum height of TextView in terms of pixels 5877 * 5878 * @see #getMaxHeight() 5879 * @see #setHeight(int) 5880 * 5881 * @attr ref android.R.styleable#TextView_maxHeight 5882 */ 5883 @android.view.RemotableViewMethod setMaxHeight(int maxPixels)5884 public void setMaxHeight(int maxPixels) { 5885 mMaximum = maxPixels; 5886 mMaxMode = PIXELS; 5887 5888 requestLayout(); 5889 invalidate(); 5890 } 5891 5892 /** 5893 * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was 5894 * set using {@link #setMaxLines(int)} or {@link #setLines(int)}. 5895 * 5896 * @return the maximum height of TextView in terms of pixels or -1 if the maximum height 5897 * is not defined in pixels 5898 * 5899 * @see #setMaxHeight(int) 5900 * @see #setHeight(int) 5901 * 5902 * @attr ref android.R.styleable#TextView_maxHeight 5903 */ 5904 @InspectableProperty getMaxHeight()5905 public int getMaxHeight() { 5906 return mMaxMode == PIXELS ? mMaximum : -1; 5907 } 5908 5909 /** 5910 * Sets the height of the TextView to be exactly {@code lines} tall. 5911 * <p> 5912 * This value is used for height calculation if LayoutParams does not force TextView to have an 5913 * exact height. Setting this value overrides previous minimum/maximum height configurations 5914 * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will 5915 * set this value to 1. 5916 * 5917 * @param lines the exact height of the TextView in terms of lines 5918 * 5919 * @see #setHeight(int) 5920 * 5921 * @attr ref android.R.styleable#TextView_lines 5922 */ 5923 @android.view.RemotableViewMethod setLines(int lines)5924 public void setLines(int lines) { 5925 mMaximum = mMinimum = lines; 5926 mMaxMode = mMinMode = LINES; 5927 5928 requestLayout(); 5929 invalidate(); 5930 } 5931 5932 /** 5933 * Sets the height of the TextView to be exactly <code>pixels</code> tall. 5934 * <p> 5935 * This value is used for height calculation if LayoutParams does not force TextView to have an 5936 * exact height. Setting this value overrides previous minimum/maximum height configurations 5937 * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}. 5938 * 5939 * @param pixels the exact height of the TextView in terms of pixels 5940 * 5941 * @see #setLines(int) 5942 * 5943 * @attr ref android.R.styleable#TextView_height 5944 */ 5945 @android.view.RemotableViewMethod setHeight(int pixels)5946 public void setHeight(int pixels) { 5947 mMaximum = mMinimum = pixels; 5948 mMaxMode = mMinMode = PIXELS; 5949 5950 requestLayout(); 5951 invalidate(); 5952 } 5953 5954 /** 5955 * Sets the width of the TextView to be at least {@code minEms} wide. 5956 * <p> 5957 * This value is used for width calculation if LayoutParams does not force TextView to have an 5958 * exact width. Setting this value overrides previous minimum width configurations such as 5959 * {@link #setMinWidth(int)} or {@link #setWidth(int)}. 5960 * 5961 * @param minEms the minimum width of TextView in terms of ems 5962 * 5963 * @see #getMinEms() 5964 * @see #setEms(int) 5965 * 5966 * @attr ref android.R.styleable#TextView_minEms 5967 */ 5968 @android.view.RemotableViewMethod setMinEms(int minEms)5969 public void setMinEms(int minEms) { 5970 mMinWidth = minEms; 5971 mMinWidthMode = EMS; 5972 5973 requestLayout(); 5974 invalidate(); 5975 } 5976 5977 /** 5978 * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set 5979 * using {@link #setMinWidth(int)} or {@link #setWidth(int)}. 5980 * 5981 * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not 5982 * defined in ems 5983 * 5984 * @see #setMinEms(int) 5985 * @see #setEms(int) 5986 * 5987 * @attr ref android.R.styleable#TextView_minEms 5988 */ 5989 @InspectableProperty getMinEms()5990 public int getMinEms() { 5991 return mMinWidthMode == EMS ? mMinWidth : -1; 5992 } 5993 5994 /** 5995 * Sets the width of the TextView to be at least {@code minPixels} wide. 5996 * <p> 5997 * This value is used for width calculation if LayoutParams does not force TextView to have an 5998 * exact width. Setting this value overrides previous minimum width configurations such as 5999 * {@link #setMinEms(int)} or {@link #setEms(int)}. 6000 * <p> 6001 * The value given here is different than {@link #setMinimumWidth(int)}. Between 6002 * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used 6003 * to decide the final width. 6004 * 6005 * @param minPixels the minimum width of TextView in terms of pixels 6006 * 6007 * @see #getMinWidth() 6008 * @see #setWidth(int) 6009 * 6010 * @attr ref android.R.styleable#TextView_minWidth 6011 */ 6012 @android.view.RemotableViewMethod setMinWidth(int minPixels)6013 public void setMinWidth(int minPixels) { 6014 mMinWidth = minPixels; 6015 mMinWidthMode = PIXELS; 6016 6017 requestLayout(); 6018 invalidate(); 6019 } 6020 6021 /** 6022 * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set 6023 * using {@link #setMinEms(int)} or {@link #setEms(int)}. 6024 * 6025 * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not 6026 * defined in pixels 6027 * 6028 * @see #setMinWidth(int) 6029 * @see #setWidth(int) 6030 * 6031 * @attr ref android.R.styleable#TextView_minWidth 6032 */ 6033 @InspectableProperty getMinWidth()6034 public int getMinWidth() { 6035 return mMinWidthMode == PIXELS ? mMinWidth : -1; 6036 } 6037 6038 /** 6039 * Sets the width of the TextView to be at most {@code maxEms} wide. 6040 * <p> 6041 * This value is used for width calculation if LayoutParams does not force TextView to have an 6042 * exact width. Setting this value overrides previous maximum width configurations such as 6043 * {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 6044 * 6045 * @param maxEms the maximum width of TextView in terms of ems 6046 * 6047 * @see #getMaxEms() 6048 * @see #setEms(int) 6049 * 6050 * @attr ref android.R.styleable#TextView_maxEms 6051 */ 6052 @android.view.RemotableViewMethod setMaxEms(int maxEms)6053 public void setMaxEms(int maxEms) { 6054 mMaxWidth = maxEms; 6055 mMaxWidthMode = EMS; 6056 6057 requestLayout(); 6058 invalidate(); 6059 } 6060 6061 /** 6062 * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set 6063 * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}. 6064 * 6065 * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not 6066 * defined in ems 6067 * 6068 * @see #setMaxEms(int) 6069 * @see #setEms(int) 6070 * 6071 * @attr ref android.R.styleable#TextView_maxEms 6072 */ 6073 @InspectableProperty getMaxEms()6074 public int getMaxEms() { 6075 return mMaxWidthMode == EMS ? mMaxWidth : -1; 6076 } 6077 6078 /** 6079 * Sets the width of the TextView to be at most {@code maxPixels} wide. 6080 * <p> 6081 * This value is used for width calculation if LayoutParams does not force TextView to have an 6082 * exact width. Setting this value overrides previous maximum width configurations such as 6083 * {@link #setMaxEms(int)} or {@link #setEms(int)}. 6084 * 6085 * @param maxPixels the maximum width of TextView in terms of pixels 6086 * 6087 * @see #getMaxWidth() 6088 * @see #setWidth(int) 6089 * 6090 * @attr ref android.R.styleable#TextView_maxWidth 6091 */ 6092 @android.view.RemotableViewMethod setMaxWidth(int maxPixels)6093 public void setMaxWidth(int maxPixels) { 6094 mMaxWidth = maxPixels; 6095 mMaxWidthMode = PIXELS; 6096 6097 requestLayout(); 6098 invalidate(); 6099 } 6100 6101 /** 6102 * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set 6103 * using {@link #setMaxEms(int)} or {@link #setEms(int)}. 6104 * 6105 * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not 6106 * defined in pixels 6107 * 6108 * @see #setMaxWidth(int) 6109 * @see #setWidth(int) 6110 * 6111 * @attr ref android.R.styleable#TextView_maxWidth 6112 */ 6113 @InspectableProperty getMaxWidth()6114 public int getMaxWidth() { 6115 return mMaxWidthMode == PIXELS ? mMaxWidth : -1; 6116 } 6117 6118 /** 6119 * Sets the width of the TextView to be exactly {@code ems} wide. 6120 * 6121 * This value is used for width calculation if LayoutParams does not force TextView to have an 6122 * exact width. Setting this value overrides previous minimum/maximum configurations such as 6123 * {@link #setMinEms(int)} or {@link #setMaxEms(int)}. 6124 * 6125 * @param ems the exact width of the TextView in terms of ems 6126 * 6127 * @see #setWidth(int) 6128 * 6129 * @attr ref android.R.styleable#TextView_ems 6130 */ 6131 @android.view.RemotableViewMethod setEms(int ems)6132 public void setEms(int ems) { 6133 mMaxWidth = mMinWidth = ems; 6134 mMaxWidthMode = mMinWidthMode = EMS; 6135 6136 requestLayout(); 6137 invalidate(); 6138 } 6139 6140 /** 6141 * Sets the width of the TextView to be exactly {@code pixels} wide. 6142 * <p> 6143 * This value is used for width calculation if LayoutParams does not force TextView to have an 6144 * exact width. Setting this value overrides previous minimum/maximum width configurations 6145 * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}. 6146 * 6147 * @param pixels the exact width of the TextView in terms of pixels 6148 * 6149 * @see #setEms(int) 6150 * 6151 * @attr ref android.R.styleable#TextView_width 6152 */ 6153 @android.view.RemotableViewMethod setWidth(int pixels)6154 public void setWidth(int pixels) { 6155 mMaxWidth = mMinWidth = pixels; 6156 mMaxWidthMode = mMinWidthMode = PIXELS; 6157 6158 requestLayout(); 6159 invalidate(); 6160 } 6161 6162 /** 6163 * Sets line spacing for this TextView. Each line other than the last line will have its height 6164 * multiplied by {@code mult} and have {@code add} added to it. 6165 * 6166 * @param add The value in pixels that should be added to each line other than the last line. 6167 * This will be applied after the multiplier 6168 * @param mult The value by which each line height other than the last line will be multiplied 6169 * by 6170 * 6171 * @attr ref android.R.styleable#TextView_lineSpacingExtra 6172 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 6173 */ setLineSpacing(float add, float mult)6174 public void setLineSpacing(float add, float mult) { 6175 if (mSpacingAdd != add || mSpacingMult != mult) { 6176 mSpacingAdd = add; 6177 mSpacingMult = mult; 6178 6179 if (mLayout != null) { 6180 nullLayouts(); 6181 requestLayout(); 6182 invalidate(); 6183 } 6184 } 6185 } 6186 6187 /** 6188 * Gets the line spacing multiplier 6189 * 6190 * @return the value by which each line's height is multiplied to get its actual height. 6191 * 6192 * @see #setLineSpacing(float, float) 6193 * @see #getLineSpacingExtra() 6194 * 6195 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 6196 */ 6197 @InspectableProperty getLineSpacingMultiplier()6198 public float getLineSpacingMultiplier() { 6199 return mSpacingMult; 6200 } 6201 6202 /** 6203 * Gets the line spacing extra space 6204 * 6205 * @return the extra space that is added to the height of each lines of this TextView. 6206 * 6207 * @see #setLineSpacing(float, float) 6208 * @see #getLineSpacingMultiplier() 6209 * 6210 * @attr ref android.R.styleable#TextView_lineSpacingExtra 6211 */ 6212 @InspectableProperty getLineSpacingExtra()6213 public float getLineSpacingExtra() { 6214 return mSpacingAdd; 6215 } 6216 6217 /** 6218 * Sets an explicit line height for this TextView. This is equivalent to the vertical distance 6219 * between subsequent baselines in the TextView. 6220 * 6221 * @param lineHeight the line height in pixels 6222 * 6223 * @see #setLineSpacing(float, float) 6224 * @see #getLineSpacingExtra() 6225 * 6226 * @attr ref android.R.styleable#TextView_lineHeight 6227 */ 6228 @android.view.RemotableViewMethod setLineHeight(@x @ntRangefrom = 0) int lineHeight)6229 public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) { 6230 setLineHeightPx(lineHeight); 6231 } 6232 setLineHeightPx(@x @loatRangefrom = 0) float lineHeight)6233 private void setLineHeightPx(@Px @FloatRange(from = 0) float lineHeight) { 6234 Preconditions.checkArgumentNonNegative(lineHeight, 6235 "Expecting non-negative lineHeight while the input is " + lineHeight); 6236 6237 final int fontHeight = getPaint().getFontMetricsInt(null); 6238 // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw. 6239 // TODO(b/274974975): should this also check if lineSpacing needs to change? 6240 if (lineHeight != fontHeight) { 6241 // Set lineSpacingExtra by the difference of lineSpacing with lineHeight 6242 setLineSpacing(lineHeight - fontHeight, 1f); 6243 6244 mLineHeightComplexDimen = 6245 TypedValue.createComplexDimension(lineHeight, TypedValue.COMPLEX_UNIT_PX); 6246 } 6247 } 6248 6249 /** 6250 * Sets an explicit line height to a given unit and value for this TextView. This is equivalent 6251 * to the vertical distance between subsequent baselines in the TextView. See {@link 6252 * TypedValue} for the possible dimension units. 6253 * 6254 * @param unit The desired dimension unit. SP units are strongly recommended so that line height 6255 * stays proportional to the text size when fonts are scaled up for accessibility. 6256 * @param lineHeight The desired line height in the given units. 6257 * 6258 * @see #setLineSpacing(float, float) 6259 * @see #getLineSpacingExtra() 6260 * 6261 * @attr ref android.R.styleable#TextView_lineHeight 6262 */ 6263 @android.view.RemotableViewMethod setLineHeight( @ypedValue.ComplexDimensionUnit int unit, @FloatRange(from = 0) float lineHeight )6264 public void setLineHeight( 6265 @TypedValue.ComplexDimensionUnit int unit, 6266 @FloatRange(from = 0) float lineHeight 6267 ) { 6268 var metrics = getDisplayMetricsOrSystem(); 6269 // We can avoid the recalculation if we know non-linear font scaling isn't being used 6270 // (an optimization for the majority case). 6271 // We also don't try to do the recalculation unless both textSize and lineHeight are in SP. 6272 if (!FontScaleConverterFactory.isNonLinearFontScalingActive( 6273 getResources().getConfiguration().fontScale) 6274 || unit != TypedValue.COMPLEX_UNIT_SP 6275 || mTextSizeUnit != TypedValue.COMPLEX_UNIT_SP 6276 ) { 6277 setLineHeightPx(TypedValue.applyDimension(unit, lineHeight, metrics)); 6278 6279 // Do this last so it overwrites what setLineHeightPx() sets it to. 6280 mLineHeightComplexDimen = TypedValue.createComplexDimension(lineHeight, unit); 6281 return; 6282 } 6283 6284 // Recalculate a proportional line height when non-linear font scaling is in effect. 6285 // Otherwise, a desired 2x line height at font scale 1.0 will not be 2x at font scale 2.0, 6286 // due to non-linear font scaling compressing higher SP sizes. See b/273326061 for details. 6287 // We know they are using SP units for both the text size and the line height 6288 // at this point, so determine the ratio between them. This is the *intended* line spacing 6289 // multiplier if font scale == 1.0. We can then determine what the pixel value for the line 6290 // height would be if we preserved proportions. 6291 var textSizePx = getTextSize(); 6292 var textSizeSp = TypedValue.convertPixelsToDimension( 6293 TypedValue.COMPLEX_UNIT_SP, 6294 textSizePx, 6295 metrics 6296 ); 6297 var ratio = lineHeight / textSizeSp; 6298 setLineHeightPx(textSizePx * ratio); 6299 6300 // Do this last so it overwrites what setLineHeightPx() sets it to. 6301 mLineHeightComplexDimen = TypedValue.createComplexDimension(lineHeight, unit); 6302 } 6303 maybeRecalculateLineHeight()6304 private void maybeRecalculateLineHeight() { 6305 if (mLineHeightComplexDimen == 0) { 6306 return; 6307 } 6308 int unit = TypedValue.getUnitFromComplexDimension(mLineHeightComplexDimen); 6309 if (unit != TypedValue.COMPLEX_UNIT_SP) { 6310 // The lineHeight was never supplied in SP, so we didn't do any fancy recalculations 6311 // in setLineHeight(). We don't need to recalculate. 6312 return; 6313 } 6314 6315 setLineHeight(unit, TypedValue.complexToFloat(mLineHeightComplexDimen)); 6316 } 6317 6318 /** 6319 * Set Highlights 6320 * 6321 * @param highlights A highlight object. Call with null for reset. 6322 * 6323 * @see #getHighlights() 6324 * @see Highlights 6325 */ setHighlights(@ullable Highlights highlights)6326 public void setHighlights(@Nullable Highlights highlights) { 6327 mHighlights = highlights; 6328 mHighlightPathsBogus = true; 6329 invalidate(); 6330 } 6331 6332 /** 6333 * Returns highlights 6334 * 6335 * @return a highlight to be drawn. null if no highlight was set. 6336 * 6337 * @see #setHighlights(Highlights) 6338 * @see Highlights 6339 * 6340 */ 6341 @Nullable getHighlights()6342 public Highlights getHighlights() { 6343 return mHighlights; 6344 } 6345 6346 /** 6347 * Sets the search result ranges with flatten range representation. 6348 * 6349 * Ranges are represented of flattened inclusive start and exclusive end integers array. The 6350 * inclusive start offset of the {@code i}-th range is stored in {@code 2 * i}-th of the array. 6351 * The exclusive end offset of the {@code i}-th range is stored in {@code 2* i + 1}-th of the 6352 * array. For example, the two ranges: (1, 2) and (3, 4) are flattened into single int array 6353 * [1, 2, 3, 4]. 6354 * 6355 * TextView will render the search result with the highlights with specified color in the theme. 6356 * If there is a focused search result, it is rendered with focused color. By calling this 6357 * method, the focused search index will be cleared. 6358 * 6359 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6360 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6361 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6362 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6363 * 6364 * @see #getSearchResultHighlights() 6365 * @see #setFocusedSearchResultIndex(int) 6366 * @see #getFocusedSearchResultIndex() 6367 * @see #setSearchResultHighlightColor(int) 6368 * @see #getSearchResultHighlightColor() 6369 * @see #setFocusedSearchResultHighlightColor(int) 6370 * @see #getFocusedSearchResultHighlightColor() 6371 * 6372 * @param ranges the flatten ranges of the search result. null for clear. 6373 */ setSearchResultHighlights(@ullable int... ranges)6374 public void setSearchResultHighlights(@Nullable int... ranges) { 6375 if (ranges == null) { 6376 mSearchResultHighlights = null; 6377 mHighlightPathsBogus = true; 6378 return; 6379 } 6380 if (ranges.length % 2 == 1) { 6381 throw new IllegalArgumentException( 6382 "Flatten ranges must have even numbered elements"); 6383 } 6384 for (int j = 0; j < ranges.length / 2; ++j) { 6385 int start = ranges[j * 2]; 6386 int end = ranges[j * 2 + 1]; 6387 if (start > end) { 6388 throw new IllegalArgumentException( 6389 "Reverse range found in the flatten range: " + start + ", " + end + "" 6390 + " at " + j + "-th range"); 6391 } 6392 } 6393 mHighlightPathsBogus = true; 6394 mSearchResultHighlights = ranges; 6395 mFocusedSearchResultIndex = FOCUSED_SEARCH_RESULT_INDEX_NONE; 6396 invalidate(); 6397 } 6398 6399 /** 6400 * Gets the current search result ranges. 6401 * 6402 * @see #setSearchResultHighlights(int[]) 6403 * @see #setFocusedSearchResultIndex(int) 6404 * @see #getFocusedSearchResultIndex() 6405 * @see #setSearchResultHighlightColor(int) 6406 * @see #getSearchResultHighlightColor() 6407 * @see #setFocusedSearchResultHighlightColor(int) 6408 * @see #getFocusedSearchResultHighlightColor() 6409 * 6410 * @return a flatten search result ranges. null if not available. 6411 */ 6412 @Nullable getSearchResultHighlights()6413 public int[] getSearchResultHighlights() { 6414 return mSearchResultHighlights; 6415 } 6416 6417 /** 6418 * A special index used for {@link #setFocusedSearchResultIndex(int)} and 6419 * {@link #getFocusedSearchResultIndex()} inidicating there is no focused search result. 6420 */ 6421 public static final int FOCUSED_SEARCH_RESULT_INDEX_NONE = -1; 6422 6423 /** 6424 * Sets the focused search result index. 6425 * 6426 * The focused search result is drawn in a focused color. 6427 * Calling {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE} for clearing focused search result. 6428 * 6429 * This method must be called after setting search result ranges by 6430 * {@link #setSearchResultHighlights(int[])}. 6431 * 6432 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6433 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6434 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6435 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6436 * 6437 * @see #setSearchResultHighlights(int[]) 6438 * @see #getSearchResultHighlights() 6439 * @see #setFocusedSearchResultIndex(int) 6440 * @see #getFocusedSearchResultIndex() 6441 * @see #setSearchResultHighlightColor(int) 6442 * @see #getSearchResultHighlightColor() 6443 * @see #setFocusedSearchResultHighlightColor(int) 6444 * @see #getFocusedSearchResultHighlightColor() 6445 * 6446 * @param index a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE} 6447 */ setFocusedSearchResultIndex(int index)6448 public void setFocusedSearchResultIndex(int index) { 6449 if (mSearchResultHighlights == null) { 6450 throw new IllegalArgumentException("Search result range must be set beforehand."); 6451 } 6452 if (index < -1 || index >= mSearchResultHighlights.length / 2) { 6453 throw new IllegalArgumentException("Focused index(" + index + ") must be larger than " 6454 + "-1 and less than range count(" + (mSearchResultHighlights.length / 2) + ")"); 6455 } 6456 mFocusedSearchResultIndex = index; 6457 mHighlightPathsBogus = true; 6458 invalidate(); 6459 } 6460 6461 /** 6462 * Gets the focused search result index. 6463 * 6464 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6465 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6466 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6467 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6468 * 6469 * @see #setSearchResultHighlights(int[]) 6470 * @see #getSearchResultHighlights() 6471 * @see #setFocusedSearchResultIndex(int) 6472 * @see #getFocusedSearchResultIndex() 6473 * @see #setSearchResultHighlightColor(int) 6474 * @see #getSearchResultHighlightColor() 6475 * @see #setFocusedSearchResultHighlightColor(int) 6476 * @see #getFocusedSearchResultHighlightColor() 6477 6478 * @return a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE} 6479 */ getFocusedSearchResultIndex()6480 public int getFocusedSearchResultIndex() { 6481 return mFocusedSearchResultIndex; 6482 } 6483 6484 /** 6485 * Sets the search result highlight color. 6486 * 6487 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6488 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6489 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6490 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6491 * 6492 * @see #setSearchResultHighlights(int[]) 6493 * @see #getSearchResultHighlights() 6494 * @see #setFocusedSearchResultIndex(int) 6495 * @see #getFocusedSearchResultIndex() 6496 * @see #setSearchResultHighlightColor(int) 6497 * @see #getSearchResultHighlightColor() 6498 * @see #setFocusedSearchResultHighlightColor(int) 6499 * @see #getFocusedSearchResultHighlightColor() 6500 6501 * @param color a search result highlight color. 6502 */ setSearchResultHighlightColor(@olorInt int color)6503 public void setSearchResultHighlightColor(@ColorInt int color) { 6504 mSearchResultHighlightColor = color; 6505 } 6506 6507 /** 6508 * Gets the search result highlight color. 6509 * 6510 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6511 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6512 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6513 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6514 * 6515 * @see #setSearchResultHighlights(int[]) 6516 * @see #getSearchResultHighlights() 6517 * @see #setFocusedSearchResultIndex(int) 6518 * @see #getFocusedSearchResultIndex() 6519 * @see #setSearchResultHighlightColor(int) 6520 * @see #getSearchResultHighlightColor() 6521 * @see #setFocusedSearchResultHighlightColor(int) 6522 * @see #getFocusedSearchResultHighlightColor() 6523 6524 * @return a search result highlight color. 6525 */ 6526 @ColorInt getSearchResultHighlightColor()6527 public int getSearchResultHighlightColor() { 6528 return mSearchResultHighlightColor; 6529 } 6530 6531 /** 6532 * Sets focused search result highlight color. 6533 * 6534 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6535 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6536 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6537 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6538 * 6539 * @see #setSearchResultHighlights(int[]) 6540 * @see #getSearchResultHighlights() 6541 * @see #setFocusedSearchResultIndex(int) 6542 * @see #getFocusedSearchResultIndex() 6543 * @see #setSearchResultHighlightColor(int) 6544 * @see #getSearchResultHighlightColor() 6545 * @see #setFocusedSearchResultHighlightColor(int) 6546 * @see #getFocusedSearchResultHighlightColor() 6547 6548 * @param color a focused search result highlight color. 6549 */ setFocusedSearchResultHighlightColor(@olorInt int color)6550 public void setFocusedSearchResultHighlightColor(@ColorInt int color) { 6551 mFocusedSearchResultHighlightColor = color; 6552 } 6553 6554 /** 6555 * Gets focused search result highlight color. 6556 * 6557 * @attr ref android.R.styleable#TextView_searchResultHighlightColor 6558 * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor 6559 * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor 6560 * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor 6561 * 6562 * @see #setSearchResultHighlights(int[]) 6563 * @see #getSearchResultHighlights() 6564 * @see #setFocusedSearchResultIndex(int) 6565 * @see #getFocusedSearchResultIndex() 6566 * @see #setSearchResultHighlightColor(int) 6567 * @see #getSearchResultHighlightColor() 6568 * @see #setFocusedSearchResultHighlightColor(int) 6569 * @see #getFocusedSearchResultHighlightColor() 6570 6571 * @return a focused search result highlight color. 6572 */ 6573 @ColorInt getFocusedSearchResultHighlightColor()6574 public int getFocusedSearchResultHighlightColor() { 6575 return mFocusedSearchResultHighlightColor; 6576 } 6577 6578 /** 6579 * Highlights the text range (from inclusive start offset to exclusive end offset) to show what 6580 * will be selected by the ongoing select handwriting gesture. While the gesture preview 6581 * highlight is shown, the selection or cursor is hidden. If the text or selection is changed, 6582 * the gesture preview highlight will be cleared. 6583 */ setSelectGesturePreviewHighlight(int start, int end)6584 private void setSelectGesturePreviewHighlight(int start, int end) { 6585 // Selection preview highlight color is the same as selection highlight color. 6586 setGesturePreviewHighlight(start, end, mHighlightColor); 6587 } 6588 6589 /** 6590 * Highlights the text range (from inclusive start offset to exclusive end offset) to show what 6591 * will be deleted by the ongoing delete handwriting gesture. While the gesture preview 6592 * highlight is shown, the selection or cursor is hidden. If the text or selection is changed, 6593 * the gesture preview highlight will be cleared. 6594 */ setDeleteGesturePreviewHighlight(int start, int end)6595 private void setDeleteGesturePreviewHighlight(int start, int end) { 6596 // Deletion preview highlight color is 20% opacity of the default text color. 6597 int color = mTextColor.getDefaultColor(); 6598 color = ColorUtils.setAlphaComponent(color, (int) (0.2f * Color.alpha(color))); 6599 setGesturePreviewHighlight(start, end, color); 6600 } 6601 setGesturePreviewHighlight(int start, int end, int color)6602 private void setGesturePreviewHighlight(int start, int end, int color) { 6603 mGesturePreviewHighlightStart = start; 6604 mGesturePreviewHighlightEnd = end; 6605 if (mGesturePreviewHighlightPaint == null) { 6606 mGesturePreviewHighlightPaint = new Paint(); 6607 mGesturePreviewHighlightPaint.setStyle(Paint.Style.FILL); 6608 } 6609 mGesturePreviewHighlightPaint.setColor(color); 6610 6611 if (mEditor != null) { 6612 mEditor.hideCursorAndSpanControllers(); 6613 mEditor.stopTextActionModeWithPreservingSelection(); 6614 } 6615 6616 mHighlightPathsBogus = true; 6617 invalidate(); 6618 } 6619 clearGesturePreviewHighlight()6620 private void clearGesturePreviewHighlight() { 6621 mGesturePreviewHighlightStart = -1; 6622 mGesturePreviewHighlightEnd = -1; 6623 mHighlightPathsBogus = true; 6624 invalidate(); 6625 } 6626 hasGesturePreviewHighlight()6627 boolean hasGesturePreviewHighlight() { 6628 return mGesturePreviewHighlightStart >= 0; 6629 } 6630 6631 /** 6632 * Convenience method to append the specified text to the TextView's 6633 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 6634 * if it was not already editable. 6635 * 6636 * @param text text to be appended to the already displayed text 6637 */ append(CharSequence text)6638 public final void append(CharSequence text) { 6639 append(text, 0, text.length()); 6640 } 6641 6642 /** 6643 * Convenience method to append the specified text slice to the TextView's 6644 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} 6645 * if it was not already editable. 6646 * 6647 * @param text text to be appended to the already displayed text 6648 * @param start the index of the first character in the {@code text} 6649 * @param end the index of the character following the last character in the {@code text} 6650 * 6651 * @see Appendable#append(CharSequence, int, int) 6652 */ append(CharSequence text, int start, int end)6653 public void append(CharSequence text, int start, int end) { 6654 if (!(mText instanceof Editable)) { 6655 setText(mText, BufferType.EDITABLE); 6656 } 6657 6658 ((Editable) mText).append(text, start, end); 6659 6660 if (mAutoLinkMask != 0) { 6661 boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask); 6662 // Do not change the movement method for text that support text selection as it 6663 // would prevent an arbitrary cursor displacement. 6664 if (linksWereAdded && mLinksClickable && !textCanBeSelected()) { 6665 setMovementMethod(LinkMovementMethod.getInstance()); 6666 } 6667 } 6668 } 6669 updateTextColors()6670 private void updateTextColors() { 6671 boolean inval = false; 6672 final int[] drawableState = getDrawableState(); 6673 int color = mTextColor.getColorForState(drawableState, 0); 6674 if (color != mCurTextColor) { 6675 mCurTextColor = color; 6676 inval = true; 6677 } 6678 if (mLinkTextColor != null) { 6679 color = mLinkTextColor.getColorForState(drawableState, 0); 6680 if (color != mTextPaint.linkColor) { 6681 mTextPaint.linkColor = color; 6682 inval = true; 6683 } 6684 } 6685 if (mHintTextColor != null) { 6686 color = mHintTextColor.getColorForState(drawableState, 0); 6687 if (color != mCurHintTextColor) { 6688 mCurHintTextColor = color; 6689 if (mText.length() == 0) { 6690 inval = true; 6691 } 6692 } 6693 } 6694 if (inval) { 6695 // Text needs to be redrawn with the new color 6696 if (mEditor != null) mEditor.invalidateTextDisplayList(); 6697 invalidate(); 6698 } 6699 } 6700 6701 @Override drawableStateChanged()6702 protected void drawableStateChanged() { 6703 super.drawableStateChanged(); 6704 6705 if (mTextColor != null && mTextColor.isStateful() 6706 || (mHintTextColor != null && mHintTextColor.isStateful()) 6707 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 6708 updateTextColors(); 6709 } 6710 6711 if (mDrawables != null) { 6712 final int[] state = getDrawableState(); 6713 for (Drawable dr : mDrawables.mShowing) { 6714 if (dr != null && dr.isStateful() && dr.setState(state)) { 6715 invalidateDrawable(dr); 6716 } 6717 } 6718 } 6719 } 6720 6721 @Override drawableHotspotChanged(float x, float y)6722 public void drawableHotspotChanged(float x, float y) { 6723 super.drawableHotspotChanged(x, y); 6724 6725 if (mDrawables != null) { 6726 for (Drawable dr : mDrawables.mShowing) { 6727 if (dr != null) { 6728 dr.setHotspot(x, y); 6729 } 6730 } 6731 } 6732 } 6733 6734 @Override onSaveInstanceState()6735 public Parcelable onSaveInstanceState() { 6736 Parcelable superState = super.onSaveInstanceState(); 6737 6738 // Save state if we are forced to 6739 final boolean freezesText = getFreezesText(); 6740 boolean hasSelection = false; 6741 int start = -1; 6742 int end = -1; 6743 6744 if (mText != null) { 6745 start = getSelectionStart(); 6746 end = getSelectionEnd(); 6747 if (start >= 0 || end >= 0) { 6748 // Or save state if there is a selection 6749 hasSelection = true; 6750 } 6751 } 6752 6753 if (freezesText || hasSelection) { 6754 SavedState ss = new SavedState(superState); 6755 6756 if (freezesText) { 6757 if (mText instanceof Spanned) { 6758 final Spannable sp = new SpannableStringBuilder(mText); 6759 6760 if (mEditor != null) { 6761 removeMisspelledSpans(sp); 6762 sp.removeSpan(mEditor.mSuggestionRangeSpan); 6763 } 6764 6765 ss.text = sp; 6766 } else { 6767 ss.text = mText.toString(); 6768 } 6769 } 6770 6771 if (hasSelection) { 6772 // XXX Should also save the current scroll position! 6773 ss.selStart = start; 6774 ss.selEnd = end; 6775 } 6776 6777 if (isFocused() && start >= 0 && end >= 0) { 6778 ss.frozenWithFocus = true; 6779 } 6780 6781 ss.error = getError(); 6782 6783 if (mEditor != null) { 6784 ss.editorState = mEditor.saveInstanceState(); 6785 } 6786 return ss; 6787 } 6788 6789 return superState; 6790 } 6791 removeMisspelledSpans(Spannable spannable)6792 void removeMisspelledSpans(Spannable spannable) { 6793 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(), 6794 SuggestionSpan.class); 6795 for (int i = 0; i < suggestionSpans.length; i++) { 6796 int flags = suggestionSpans[i].getFlags(); 6797 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 6798 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { 6799 spannable.removeSpan(suggestionSpans[i]); 6800 } 6801 } 6802 } 6803 6804 @Override onRestoreInstanceState(Parcelable state)6805 public void onRestoreInstanceState(Parcelable state) { 6806 if (!(state instanceof SavedState)) { 6807 super.onRestoreInstanceState(state); 6808 return; 6809 } 6810 6811 SavedState ss = (SavedState) state; 6812 super.onRestoreInstanceState(ss.getSuperState()); 6813 6814 // XXX restore buffer type too, as well as lots of other stuff 6815 if (ss.text != null) { 6816 setText(ss.text); 6817 } 6818 6819 if (ss.selStart >= 0 && ss.selEnd >= 0) { 6820 if (mSpannable != null) { 6821 int len = mText.length(); 6822 6823 if (ss.selStart > len || ss.selEnd > len) { 6824 String restored = ""; 6825 6826 if (ss.text != null) { 6827 restored = "(restored) "; 6828 } 6829 6830 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd 6831 + " out of range for " + restored + "text " + mText); 6832 } else { 6833 Selection.setSelection(mSpannable, ss.selStart, ss.selEnd); 6834 6835 if (ss.frozenWithFocus) { 6836 createEditorIfNeeded(); 6837 mEditor.mFrozenWithFocus = true; 6838 } 6839 } 6840 } 6841 } 6842 6843 if (ss.error != null) { 6844 final CharSequence error = ss.error; 6845 // Display the error later, after the first layout pass 6846 post(new Runnable() { 6847 public void run() { 6848 if (mEditor == null || !mEditor.mErrorWasChanged) { 6849 setError(error); 6850 } 6851 } 6852 }); 6853 } 6854 6855 if (ss.editorState != null) { 6856 createEditorIfNeeded(); 6857 mEditor.restoreInstanceState(ss.editorState); 6858 } 6859 } 6860 6861 /** 6862 * Control whether this text view saves its entire text contents when 6863 * freezing to an icicle, in addition to dynamic state such as cursor 6864 * position. By default this is false, not saving the text. Set to true 6865 * if the text in the text view is not being saved somewhere else in 6866 * persistent storage (such as in a content provider) so that if the 6867 * view is later thawed the user will not lose their data. For 6868 * {@link android.widget.EditText} it is always enabled, regardless of 6869 * the value of the attribute. 6870 * 6871 * @param freezesText Controls whether a frozen icicle should include the 6872 * entire text data: true to include it, false to not. 6873 * 6874 * @attr ref android.R.styleable#TextView_freezesText 6875 */ 6876 @android.view.RemotableViewMethod setFreezesText(boolean freezesText)6877 public void setFreezesText(boolean freezesText) { 6878 mFreezesText = freezesText; 6879 } 6880 6881 /** 6882 * Return whether this text view is including its entire text contents 6883 * in frozen icicles. For {@link android.widget.EditText} it always returns true. 6884 * 6885 * @return Returns true if text is included, false if it isn't. 6886 * 6887 * @see #setFreezesText 6888 */ 6889 @InspectableProperty getFreezesText()6890 public boolean getFreezesText() { 6891 return mFreezesText; 6892 } 6893 6894 /////////////////////////////////////////////////////////////////////////// 6895 6896 /** 6897 * Sets the Factory used to create new {@link Editable Editables}. 6898 * 6899 * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used 6900 * 6901 * @see android.text.Editable.Factory 6902 * @see android.widget.TextView.BufferType#EDITABLE 6903 */ setEditableFactory(Editable.Factory factory)6904 public final void setEditableFactory(Editable.Factory factory) { 6905 mEditableFactory = factory; 6906 setText(mText); 6907 } 6908 6909 /** 6910 * Sets the Factory used to create new {@link Spannable Spannables}. 6911 * 6912 * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used 6913 * 6914 * @see android.text.Spannable.Factory 6915 * @see android.widget.TextView.BufferType#SPANNABLE 6916 */ setSpannableFactory(Spannable.Factory factory)6917 public final void setSpannableFactory(Spannable.Factory factory) { 6918 mSpannableFactory = factory; 6919 setText(mText); 6920 } 6921 6922 /** 6923 * Sets the text to be displayed. TextView <em>does not</em> accept 6924 * HTML-like formatting, which you can do with text strings in XML resource files. 6925 * To style your strings, attach android.text.style.* objects to a 6926 * {@link android.text.SpannableString}, or see the 6927 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources"> 6928 * Available Resource Types</a> documentation for an example of setting 6929 * formatted text in the XML resource file. 6930 * <p/> 6931 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6932 * intermediate {@link Spannable Spannables}. Likewise it will use 6933 * {@link android.text.Editable.Factory} to create final or intermediate 6934 * {@link Editable Editables}. 6935 * 6936 * If the passed text is a {@link PrecomputedText} but the parameters used to create the 6937 * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure 6938 * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this. 6939 * 6940 * @param text text to be displayed 6941 * 6942 * @attr ref android.R.styleable#TextView_text 6943 * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the 6944 * parameters used to create the PrecomputedText mismatches 6945 * with this TextView. 6946 */ 6947 @android.view.RemotableViewMethod(asyncImpl = "setTextAsync") setText(CharSequence text)6948 public final void setText(CharSequence text) { 6949 setText(text, mBufferType); 6950 } 6951 6952 /** 6953 * RemotableViewMethod's asyncImpl of {@link #setText(CharSequence)}. 6954 * This should be called on a background thread, and returns a Runnable which is then must be 6955 * called on the main thread to complete the operation and set text. 6956 * @param text text to be displayed 6957 * @return Runnable that sets text; must be called on the main thread by the caller of this 6958 * method to complete the operation 6959 * @hide 6960 */ 6961 @NonNull setTextAsync(@ullable CharSequence text)6962 public Runnable setTextAsync(@Nullable CharSequence text) { 6963 return () -> setText(text); 6964 } 6965 6966 /** 6967 * Sets the text to be displayed but retains the cursor position. Same as 6968 * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the 6969 * new text. 6970 * <p/> 6971 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6972 * intermediate {@link Spannable Spannables}. Likewise it will use 6973 * {@link android.text.Editable.Factory} to create final or intermediate 6974 * {@link Editable Editables}. 6975 * 6976 * @param text text to be displayed 6977 * 6978 * @see #setText(CharSequence) 6979 */ 6980 @android.view.RemotableViewMethod setTextKeepState(CharSequence text)6981 public final void setTextKeepState(CharSequence text) { 6982 setTextKeepState(text, mBufferType); 6983 } 6984 6985 /** 6986 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}. 6987 * <p/> 6988 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 6989 * intermediate {@link Spannable Spannables}. Likewise it will use 6990 * {@link android.text.Editable.Factory} to create final or intermediate 6991 * {@link Editable Editables}. 6992 * 6993 * Subclasses overriding this method should ensure that the following post condition holds, 6994 * in order to guarantee the safety of the view's measurement and layout operations: 6995 * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed} 6996 * will be different from {@code null}. 6997 * 6998 * @param text text to be displayed 6999 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 7000 * stored as a static text, styleable/spannable text, or editable text 7001 * 7002 * @see #setText(CharSequence) 7003 * @see android.widget.TextView.BufferType 7004 * @see #setSpannableFactory(Spannable.Factory) 7005 * @see #setEditableFactory(Editable.Factory) 7006 * 7007 * @attr ref android.R.styleable#TextView_text 7008 * @attr ref android.R.styleable#TextView_bufferType 7009 */ setText(CharSequence text, BufferType type)7010 public void setText(CharSequence text, BufferType type) { 7011 setText(text, type, true, 0); 7012 7013 // drop any potential mCharWrappper leaks 7014 mCharWrapper = null; 7015 } 7016 7017 @UnsupportedAppUsage setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)7018 private void setText(CharSequence text, BufferType type, 7019 boolean notifyBefore, int oldlen) { 7020 if (mEditor != null) { 7021 mEditor.beforeSetText(); 7022 } 7023 mTextSetFromXmlOrResourceId = false; 7024 if (text == null) { 7025 text = ""; 7026 } 7027 7028 // If suggestions are not enabled, remove the suggestion spans from the text 7029 if (!isSuggestionsEnabled()) { 7030 text = removeSuggestionSpans(text); 7031 } 7032 7033 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); 7034 7035 if (text instanceof Spanned 7036 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 7037 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) { 7038 setHorizontalFadingEdgeEnabled(true); 7039 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 7040 } else { 7041 setHorizontalFadingEdgeEnabled(false); 7042 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 7043 } 7044 setEllipsize(TextUtils.TruncateAt.MARQUEE); 7045 } 7046 7047 int n = mFilters.length; 7048 for (int i = 0; i < n; i++) { 7049 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); 7050 if (out != null) { 7051 text = out; 7052 } 7053 } 7054 7055 if (notifyBefore) { 7056 if (mText != null) { 7057 oldlen = mText.length(); 7058 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 7059 } else { 7060 sendBeforeTextChanged("", 0, 0, text.length()); 7061 } 7062 } 7063 7064 boolean needEditableForNotification = false; 7065 7066 if (mListeners != null && mListeners.size() != 0) { 7067 needEditableForNotification = true; 7068 } 7069 7070 PrecomputedText precomputed = 7071 (text instanceof PrecomputedText) ? (PrecomputedText) text : null; 7072 if (type == BufferType.EDITABLE || getKeyListener() != null 7073 || needEditableForNotification) { 7074 createEditorIfNeeded(); 7075 mEditor.forgetUndoRedo(); 7076 mEditor.scheduleRestartInputForSetText(); 7077 Editable t = mEditableFactory.newEditable(text); 7078 text = t; 7079 setFilters(t, mFilters); 7080 } else if (precomputed != null) { 7081 if (mTextDir == null) { 7082 mTextDir = getTextDirectionHeuristic(); 7083 } 7084 final @PrecomputedText.Params.CheckResultUsableResult int checkResult = 7085 precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, 7086 mHyphenationFrequency, LineBreakConfig.getLineBreakConfig( 7087 mLineBreakStyle, mLineBreakWordStyle)); 7088 switch (checkResult) { 7089 case PrecomputedText.Params.UNUSABLE: 7090 throw new IllegalArgumentException( 7091 "PrecomputedText's Parameters don't match the parameters of this TextView." 7092 + "Consider using setTextMetricsParams(precomputedText.getParams()) " 7093 + "to override the settings of this TextView: " 7094 + "PrecomputedText: " + precomputed.getParams() 7095 + "TextView: " + getTextMetricsParams()); 7096 case PrecomputedText.Params.NEED_RECOMPUTE: 7097 precomputed = PrecomputedText.create(precomputed, getTextMetricsParams()); 7098 break; 7099 case PrecomputedText.Params.USABLE: 7100 // pass through 7101 } 7102 } else if (type == BufferType.SPANNABLE || mMovement != null) { 7103 text = mSpannableFactory.newSpannable(text); 7104 } else if (!(text instanceof CharWrapper)) { 7105 text = TextUtils.stringOrSpannedString(text); 7106 } 7107 7108 @AccessibilityUtils.A11yTextChangeType int a11yTextChangeType = AccessibilityUtils.NONE; 7109 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 7110 a11yTextChangeType = AccessibilityUtils.textOrSpanChanged(text, mText); 7111 } 7112 7113 if (mAutoLinkMask != 0) { 7114 Spannable s2; 7115 7116 if (type == BufferType.EDITABLE || text instanceof Spannable) { 7117 s2 = (Spannable) text; 7118 } else { 7119 s2 = mSpannableFactory.newSpannable(text); 7120 } 7121 7122 if (Linkify.addLinks(s2, mAutoLinkMask)) { 7123 text = s2; 7124 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 7125 7126 /* 7127 * We must go ahead and set the text before changing the 7128 * movement method, because setMovementMethod() may call 7129 * setText() again to try to upgrade the buffer type. 7130 */ 7131 setTextInternal(text); 7132 if (a11yTextChangeType == AccessibilityUtils.NONE) { 7133 a11yTextChangeType = AccessibilityUtils.PARCELABLE_SPAN; 7134 } 7135 7136 // Do not change the movement method for text that support text selection as it 7137 // would prevent an arbitrary cursor displacement. 7138 if (mLinksClickable && !textCanBeSelected()) { 7139 setMovementMethod(LinkMovementMethod.getInstance()); 7140 } 7141 } 7142 } 7143 7144 mBufferType = type; 7145 setTextInternal(text); 7146 7147 if (mTransformation == null) { 7148 mTransformed = text; 7149 } else { 7150 mTransformed = mTransformation.getTransformation(text, this); 7151 } 7152 if (mTransformed == null) { 7153 // Should not happen if the transformation method follows the non-null postcondition. 7154 mTransformed = ""; 7155 } 7156 7157 final int textLength = text.length(); 7158 final boolean isOffsetMapping = mTransformed instanceof OffsetMapping; 7159 7160 if (text instanceof Spannable && (!mAllowTransformationLengthChange || isOffsetMapping)) { 7161 Spannable sp = (Spannable) text; 7162 7163 // Remove any ChangeWatchers that might have come from other TextViews. 7164 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 7165 final int count = watchers.length; 7166 for (int i = 0; i < count; i++) { 7167 sp.removeSpan(watchers[i]); 7168 } 7169 7170 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher(); 7171 7172 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE 7173 | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 7174 7175 if (mEditor != null) mEditor.addSpanWatchers(sp); 7176 7177 if (mTransformation != null) { 7178 final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0; 7179 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE 7180 | (priority << Spanned.SPAN_PRIORITY_SHIFT)); 7181 } 7182 7183 if (mMovement != null) { 7184 mMovement.initialize(this, (Spannable) text); 7185 7186 /* 7187 * Initializing the movement method will have set the 7188 * selection, so reset mSelectionMoved to keep that from 7189 * interfering with the normal on-focus selection-setting. 7190 */ 7191 if (mEditor != null) mEditor.mSelectionMoved = false; 7192 } 7193 } 7194 7195 if (mLayout != null) { 7196 checkForRelayout(); 7197 } 7198 7199 sendOnTextChanged(text, 0, oldlen, textLength); 7200 onTextChanged(text, 0, oldlen, textLength); 7201 7202 mHideHint = false; 7203 7204 if (a11yTextChangeType == AccessibilityUtils.TEXT) { 7205 notifyViewAccessibilityStateChangedIfNeeded( 7206 AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); 7207 } else if (a11yTextChangeType == AccessibilityUtils.PARCELABLE_SPAN) { 7208 notifyViewAccessibilityStateChangedIfNeeded( 7209 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 7210 } 7211 7212 if (needEditableForNotification) { 7213 sendAfterTextChanged((Editable) text); 7214 } else { 7215 notifyListeningManagersAfterTextChanged(); 7216 } 7217 7218 if (mEditor != null) { 7219 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text 7220 mEditor.prepareCursorControllers(); 7221 7222 mEditor.maybeFireScheduledRestartInputForSetText(); 7223 } 7224 } 7225 7226 /** 7227 * Sets the TextView to display the specified slice of the specified 7228 * char array. You must promise that you will not change the contents 7229 * of the array except for right before another call to setText(), 7230 * since the TextView has no way to know that the text 7231 * has changed and that it needs to invalidate and re-layout. 7232 * 7233 * @throws NullPointerException if text is null 7234 * @throws IndexOutOfBoundsException if start or start+len are not in 0 to text.length 7235 * 7236 * @param text char array to be displayed 7237 * @param start start index in the char array 7238 * @param len length of char count after {@code start} 7239 */ setText(@onNull char[] text, int start, int len)7240 public final void setText(@NonNull char[] text, int start, int len) { 7241 int oldlen = 0; 7242 7243 if (start < 0 || len < 0 || start + len > text.length) { 7244 throw new IndexOutOfBoundsException(start + ", " + len); 7245 } 7246 7247 /* 7248 * We must do the before-notification here ourselves because if 7249 * the old text is a CharWrapper we destroy it before calling 7250 * into the normal path. 7251 */ 7252 if (mText != null) { 7253 oldlen = mText.length(); 7254 sendBeforeTextChanged(mText, 0, oldlen, len); 7255 } else { 7256 sendBeforeTextChanged("", 0, 0, len); 7257 } 7258 7259 if (mCharWrapper == null) { 7260 mCharWrapper = new CharWrapper(text, start, len); 7261 } else { 7262 mCharWrapper.set(text, start, len); 7263 } 7264 7265 setText(mCharWrapper, mBufferType, false, oldlen); 7266 } 7267 7268 /** 7269 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains 7270 * the cursor position. Same as 7271 * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor 7272 * position (if any) is retained in the new text. 7273 * <p/> 7274 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 7275 * intermediate {@link Spannable Spannables}. Likewise it will use 7276 * {@link android.text.Editable.Factory} to create final or intermediate 7277 * {@link Editable Editables}. 7278 * 7279 * @param text text to be displayed 7280 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 7281 * stored as a static text, styleable/spannable text, or editable text 7282 * 7283 * @see #setText(CharSequence, android.widget.TextView.BufferType) 7284 */ setTextKeepState(CharSequence text, BufferType type)7285 public final void setTextKeepState(CharSequence text, BufferType type) { 7286 int start = getSelectionStart(); 7287 int end = getSelectionEnd(); 7288 int len = text.length(); 7289 7290 setText(text, type); 7291 7292 if (start >= 0 || end >= 0) { 7293 if (mSpannable != null) { 7294 Selection.setSelection(mSpannable, 7295 Math.max(0, Math.min(start, len)), 7296 Math.max(0, Math.min(end, len))); 7297 } 7298 } 7299 } 7300 7301 /** 7302 * Sets the text to be displayed using a string resource identifier. 7303 * 7304 * @param resid the resource identifier of the string resource to be displayed 7305 * 7306 * @see #setText(CharSequence) 7307 * 7308 * @attr ref android.R.styleable#TextView_text 7309 */ 7310 @android.view.RemotableViewMethod setText(@tringRes int resid)7311 public final void setText(@StringRes int resid) { 7312 setText(getContext().getResources().getText(resid)); 7313 mTextSetFromXmlOrResourceId = true; 7314 mTextId = resid; 7315 } 7316 7317 /** 7318 * Sets the text to be displayed using a string resource identifier and the 7319 * {@link android.widget.TextView.BufferType}. 7320 * <p/> 7321 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or 7322 * intermediate {@link Spannable Spannables}. Likewise it will use 7323 * {@link android.text.Editable.Factory} to create final or intermediate 7324 * {@link Editable Editables}. 7325 * 7326 * @param resid the resource identifier of the string resource to be displayed 7327 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is 7328 * stored as a static text, styleable/spannable text, or editable text 7329 * 7330 * @see #setText(int) 7331 * @see #setText(CharSequence) 7332 * @see android.widget.TextView.BufferType 7333 * @see #setSpannableFactory(Spannable.Factory) 7334 * @see #setEditableFactory(Editable.Factory) 7335 * 7336 * @attr ref android.R.styleable#TextView_text 7337 * @attr ref android.R.styleable#TextView_bufferType 7338 */ setText(@tringRes int resid, BufferType type)7339 public final void setText(@StringRes int resid, BufferType type) { 7340 setText(getContext().getResources().getText(resid), type); 7341 mTextSetFromXmlOrResourceId = true; 7342 mTextId = resid; 7343 } 7344 7345 /** 7346 * Sets the text to be displayed when the text of the TextView is empty. 7347 * Null means to use the normal empty text. The hint does not currently 7348 * participate in determining the size of the view. 7349 * 7350 * @attr ref android.R.styleable#TextView_hint 7351 */ 7352 @android.view.RemotableViewMethod setHint(CharSequence hint)7353 public final void setHint(CharSequence hint) { 7354 setHintInternal(hint); 7355 7356 if (mEditor != null && isInputMethodTarget()) { 7357 mEditor.reportExtractedText(); 7358 } 7359 } 7360 setHintInternal(CharSequence hint)7361 private void setHintInternal(CharSequence hint) { 7362 mHideHint = false; 7363 mHint = TextUtils.stringOrSpannedString(hint); 7364 7365 if (mLayout != null) { 7366 checkForRelayout(); 7367 } 7368 7369 if (mText.length() == 0) { 7370 invalidate(); 7371 } 7372 7373 // Invalidate display list if hint is currently used 7374 if (mEditor != null && mText.length() == 0 && mHint != null) { 7375 mEditor.invalidateTextDisplayList(); 7376 } 7377 } 7378 7379 /** 7380 * Sets the text to be displayed when the text of the TextView is empty, 7381 * from a resource. 7382 * 7383 * @attr ref android.R.styleable#TextView_hint 7384 */ 7385 @android.view.RemotableViewMethod setHint(@tringRes int resid)7386 public final void setHint(@StringRes int resid) { 7387 mHintId = resid; 7388 setHint(getContext().getResources().getText(resid)); 7389 } 7390 7391 /** 7392 * Returns the hint that is displayed when the text of the TextView 7393 * is empty. 7394 * 7395 * @attr ref android.R.styleable#TextView_hint 7396 */ 7397 @InspectableProperty 7398 @ViewDebug.CapturedViewProperty getHint()7399 public CharSequence getHint() { 7400 return mHint; 7401 } 7402 7403 /** 7404 * Temporarily hides the hint text until the text is modified, or the hint text is modified, or 7405 * the view gains or loses focus. 7406 * 7407 * @hide 7408 */ hideHint()7409 public void hideHint() { 7410 if (isShowingHint()) { 7411 mHideHint = true; 7412 invalidate(); 7413 } 7414 } 7415 7416 /** 7417 * Returns if the text is constrained to a single horizontally scrolling line ignoring new 7418 * line characters instead of letting it wrap onto multiple lines. 7419 * 7420 * @attr ref android.R.styleable#TextView_singleLine 7421 */ 7422 @InspectableProperty isSingleLine()7423 public boolean isSingleLine() { 7424 return mSingleLine; 7425 } 7426 isMultilineInputType(int type)7427 private static boolean isMultilineInputType(int type) { 7428 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) 7429 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 7430 } 7431 7432 /** 7433 * Removes the suggestion spans. 7434 */ removeSuggestionSpans(CharSequence text)7435 CharSequence removeSuggestionSpans(CharSequence text) { 7436 if (text instanceof Spanned) { 7437 Spannable spannable; 7438 if (text instanceof Spannable) { 7439 spannable = (Spannable) text; 7440 } else { 7441 spannable = mSpannableFactory.newSpannable(text); 7442 } 7443 7444 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); 7445 if (spans.length == 0) { 7446 return text; 7447 } else { 7448 text = spannable; 7449 } 7450 7451 for (int i = 0; i < spans.length; i++) { 7452 spannable.removeSpan(spans[i]); 7453 } 7454 } 7455 return text; 7456 } 7457 7458 /** 7459 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This 7460 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, 7461 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} 7462 * then a soft keyboard will not be displayed for this text view. 7463 * 7464 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be 7465 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input 7466 * type. 7467 * 7468 * @see #getInputType() 7469 * @see #setRawInputType(int) 7470 * @see android.text.InputType 7471 * @attr ref android.R.styleable#TextView_inputType 7472 */ setInputType(int type)7473 public void setInputType(int type) { 7474 final boolean wasPassword = isPasswordInputType(getInputType()); 7475 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); 7476 setInputType(type, false); 7477 final boolean isPassword = isPasswordInputType(type); 7478 final boolean isVisiblePassword = isVisiblePasswordInputType(type); 7479 boolean forceUpdate = false; 7480 if (isPassword) { 7481 setTransformationMethod(PasswordTransformationMethod.getInstance()); 7482 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 7483 Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED); 7484 } else if (isVisiblePassword) { 7485 if (mTransformation == PasswordTransformationMethod.getInstance()) { 7486 forceUpdate = true; 7487 } 7488 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 7489 Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED); 7490 } else if (wasPassword || wasVisiblePassword) { 7491 // not in password mode, clean up typeface and transformation 7492 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, 7493 DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL, 7494 FontStyle.FONT_WEIGHT_UNSPECIFIED); 7495 if (mTransformation == PasswordTransformationMethod.getInstance()) { 7496 forceUpdate = true; 7497 } 7498 } 7499 7500 boolean singleLine = !isMultilineInputType(type); 7501 7502 // We need to update the single line mode if it has changed or we 7503 // were previously in password mode. 7504 if (mSingleLine != singleLine || forceUpdate) { 7505 // Change single line mode, but only change the transformation if 7506 // we are not in password mode. 7507 applySingleLine(singleLine, !isPassword, true, true); 7508 } 7509 7510 if (!isSuggestionsEnabled()) { 7511 setTextInternal(removeSuggestionSpans(mText)); 7512 } 7513 7514 InputMethodManager imm = getInputMethodManager(); 7515 if (imm != null) imm.restartInput(this); 7516 } 7517 7518 /** 7519 * It would be better to rely on the input type for everything. A password inputType should have 7520 * a password transformation. We should hence use isPasswordInputType instead of this method. 7521 * 7522 * We should: 7523 * - Call setInputType in setKeyListener instead of changing the input type directly (which 7524 * would install the correct transformation). 7525 * - Refuse the installation of a non-password transformation in setTransformation if the input 7526 * type is password. 7527 * 7528 * However, this is like this for legacy reasons and we cannot break existing apps. This method 7529 * is useful since it matches what the user can see (obfuscated text or not). 7530 * 7531 * @return true if the current transformation method is of the password type. 7532 */ hasPasswordTransformationMethod()7533 boolean hasPasswordTransformationMethod() { 7534 return mTransformation instanceof PasswordTransformationMethod; 7535 } 7536 7537 /** 7538 * Returns true if the current inputType is any type of password. 7539 * 7540 * @hide 7541 */ isAnyPasswordInputType()7542 public boolean isAnyPasswordInputType() { 7543 final int inputType = getInputType(); 7544 return isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType); 7545 } 7546 isPasswordInputType(int inputType)7547 static boolean isPasswordInputType(int inputType) { 7548 final int variation = 7549 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 7550 return variation 7551 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) 7552 || variation 7553 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) 7554 || variation 7555 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 7556 } 7557 isVisiblePasswordInputType(int inputType)7558 private static boolean isVisiblePasswordInputType(int inputType) { 7559 final int variation = 7560 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 7561 return variation 7562 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 7563 } 7564 7565 /** 7566 * Directly change the content type integer of the text view, without 7567 * modifying any other state. 7568 * @see #setInputType(int) 7569 * @see android.text.InputType 7570 * @attr ref android.R.styleable#TextView_inputType 7571 */ setRawInputType(int type)7572 public void setRawInputType(int type) { 7573 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value 7574 createEditorIfNeeded(); 7575 mEditor.mInputType = type; 7576 } 7577 7578 @Override getAutofillHints()7579 public String[] getAutofillHints() { 7580 String[] hints = super.getAutofillHints(); 7581 if (isAnyPasswordInputType()) { 7582 if (!ArrayUtils.contains(hints, AUTOFILL_HINT_PASSWORD_AUTO)) { 7583 hints = ArrayUtils.appendElement(String.class, hints, 7584 AUTOFILL_HINT_PASSWORD_AUTO); 7585 } 7586 } 7587 return hints; 7588 } 7589 7590 /** 7591 * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise 7592 * a {@code Locale} object that can be used to customize key various listeners. 7593 * @see DateKeyListener#getInstance(Locale) 7594 * @see DateTimeKeyListener#getInstance(Locale) 7595 * @see DigitsKeyListener#getInstance(Locale) 7596 * @see TimeKeyListener#getInstance(Locale) 7597 */ 7598 @Nullable getCustomLocaleForKeyListenerOrNull()7599 private Locale getCustomLocaleForKeyListenerOrNull() { 7600 if (!mUseInternationalizedInput) { 7601 // If the application does not target O, stick to the previous behavior. 7602 return null; 7603 } 7604 final LocaleList locales = getImeHintLocales(); 7605 if (locales == null) { 7606 // If the application does not explicitly specify IME hint locale, also stick to the 7607 // previous behavior. 7608 return null; 7609 } 7610 return locales.get(0); 7611 } 7612 7613 @UnsupportedAppUsage setInputType(int type, boolean direct)7614 private void setInputType(int type, boolean direct) { 7615 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 7616 KeyListener input; 7617 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 7618 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; 7619 TextKeyListener.Capitalize cap; 7620 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 7621 cap = TextKeyListener.Capitalize.CHARACTERS; 7622 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 7623 cap = TextKeyListener.Capitalize.WORDS; 7624 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 7625 cap = TextKeyListener.Capitalize.SENTENCES; 7626 } else { 7627 cap = TextKeyListener.Capitalize.NONE; 7628 } 7629 input = TextKeyListener.getInstance(autotext, cap); 7630 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 7631 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 7632 input = DigitsKeyListener.getInstance( 7633 locale, 7634 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 7635 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 7636 if (locale != null) { 7637 // Override type, if necessary for i18n. 7638 int newType = input.getInputType(); 7639 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS; 7640 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) { 7641 // The class is different from the original class. So we need to override 7642 // 'type'. But we want to keep the password flag if it's there. 7643 if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) { 7644 newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 7645 } 7646 type = newType; 7647 } 7648 } 7649 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 7650 final Locale locale = getCustomLocaleForKeyListenerOrNull(); 7651 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 7652 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 7653 input = DateKeyListener.getInstance(locale); 7654 break; 7655 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 7656 input = TimeKeyListener.getInstance(locale); 7657 break; 7658 default: 7659 input = DateTimeKeyListener.getInstance(locale); 7660 break; 7661 } 7662 if (mUseInternationalizedInput) { 7663 type = input.getInputType(); // Override type, if necessary for i18n. 7664 } 7665 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 7666 input = DialerKeyListener.getInstance(); 7667 } else { 7668 input = TextKeyListener.getInstance(); 7669 } 7670 setRawInputType(type); 7671 mListenerChanged = false; 7672 if (direct) { 7673 createEditorIfNeeded(); 7674 mEditor.mKeyListener = input; 7675 } else { 7676 setKeyListenerOnly(input); 7677 } 7678 } 7679 7680 /** 7681 * Get the type of the editable content. 7682 * 7683 * @see #setInputType(int) 7684 * @see android.text.InputType 7685 */ 7686 @InspectableProperty(flagMapping = { 7687 @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL), 7688 @FlagEntry( 7689 name = "text", 7690 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7691 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL), 7692 @FlagEntry( 7693 name = "textUri", 7694 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7695 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI), 7696 @FlagEntry( 7697 name = "textEmailAddress", 7698 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7699 target = InputType.TYPE_CLASS_TEXT 7700 | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS), 7701 @FlagEntry( 7702 name = "textEmailSubject", 7703 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7704 target = InputType.TYPE_CLASS_TEXT 7705 | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT), 7706 @FlagEntry( 7707 name = "textShortMessage", 7708 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7709 target = InputType.TYPE_CLASS_TEXT 7710 | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE), 7711 @FlagEntry( 7712 name = "textLongMessage", 7713 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7714 target = InputType.TYPE_CLASS_TEXT 7715 | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE), 7716 @FlagEntry( 7717 name = "textPersonName", 7718 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7719 target = InputType.TYPE_CLASS_TEXT 7720 | InputType.TYPE_TEXT_VARIATION_PERSON_NAME), 7721 @FlagEntry( 7722 name = "textPostalAddress", 7723 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7724 target = InputType.TYPE_CLASS_TEXT 7725 | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS), 7726 @FlagEntry( 7727 name = "textPassword", 7728 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7729 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD), 7730 @FlagEntry( 7731 name = "textVisiblePassword", 7732 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7733 target = InputType.TYPE_CLASS_TEXT 7734 | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD), 7735 @FlagEntry( 7736 name = "textWebEditText", 7737 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7738 target = InputType.TYPE_CLASS_TEXT 7739 | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT), 7740 @FlagEntry( 7741 name = "textFilter", 7742 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7743 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER), 7744 @FlagEntry( 7745 name = "textPhonetic", 7746 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7747 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC), 7748 @FlagEntry( 7749 name = "textWebEmailAddress", 7750 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7751 target = InputType.TYPE_CLASS_TEXT 7752 | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS), 7753 @FlagEntry( 7754 name = "textWebPassword", 7755 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7756 target = InputType.TYPE_CLASS_TEXT 7757 | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD), 7758 @FlagEntry( 7759 name = "number", 7760 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7761 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL), 7762 @FlagEntry( 7763 name = "numberPassword", 7764 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7765 target = InputType.TYPE_CLASS_NUMBER 7766 | InputType.TYPE_NUMBER_VARIATION_PASSWORD), 7767 @FlagEntry( 7768 name = "phone", 7769 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7770 target = InputType.TYPE_CLASS_PHONE), 7771 @FlagEntry( 7772 name = "datetime", 7773 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7774 target = InputType.TYPE_CLASS_DATETIME 7775 | InputType.TYPE_DATETIME_VARIATION_NORMAL), 7776 @FlagEntry( 7777 name = "date", 7778 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7779 target = InputType.TYPE_CLASS_DATETIME 7780 | InputType.TYPE_DATETIME_VARIATION_DATE), 7781 @FlagEntry( 7782 name = "time", 7783 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION, 7784 target = InputType.TYPE_CLASS_DATETIME 7785 | InputType.TYPE_DATETIME_VARIATION_TIME), 7786 @FlagEntry( 7787 name = "textCapCharacters", 7788 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7789 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS), 7790 @FlagEntry( 7791 name = "textCapWords", 7792 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7793 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS), 7794 @FlagEntry( 7795 name = "textCapSentences", 7796 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7797 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES), 7798 @FlagEntry( 7799 name = "textAutoCorrect", 7800 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7801 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT), 7802 @FlagEntry( 7803 name = "textAutoComplete", 7804 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7805 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE), 7806 @FlagEntry( 7807 name = "textMultiLine", 7808 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7809 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE), 7810 @FlagEntry( 7811 name = "textImeMultiLine", 7812 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7813 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE), 7814 @FlagEntry( 7815 name = "textNoSuggestions", 7816 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7817 target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS), 7818 @FlagEntry( 7819 name = "numberSigned", 7820 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7821 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED), 7822 @FlagEntry( 7823 name = "numberDecimal", 7824 mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS, 7825 target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL), 7826 }) getInputType()7827 public int getInputType() { 7828 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; 7829 } 7830 7831 /** 7832 * Change the editor type integer associated with the text view, which 7833 * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions} 7834 * when it has focus. 7835 * @see #getImeOptions 7836 * @see android.view.inputmethod.EditorInfo 7837 * @attr ref android.R.styleable#TextView_imeOptions 7838 */ setImeOptions(int imeOptions)7839 public void setImeOptions(int imeOptions) { 7840 createEditorIfNeeded(); 7841 mEditor.createInputContentTypeIfNeeded(); 7842 mEditor.mInputContentType.imeOptions = imeOptions; 7843 } 7844 7845 /** 7846 * Get the type of the Input Method Editor (IME). 7847 * @return the type of the IME 7848 * @see #setImeOptions(int) 7849 * @see EditorInfo 7850 */ 7851 @InspectableProperty(flagMapping = { 7852 @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL), 7853 @FlagEntry( 7854 name = "actionUnspecified", 7855 mask = EditorInfo.IME_MASK_ACTION, 7856 target = EditorInfo.IME_ACTION_UNSPECIFIED), 7857 @FlagEntry( 7858 name = "actionNone", 7859 mask = EditorInfo.IME_MASK_ACTION, 7860 target = EditorInfo.IME_ACTION_NONE), 7861 @FlagEntry( 7862 name = "actionGo", 7863 mask = EditorInfo.IME_MASK_ACTION, 7864 target = EditorInfo.IME_ACTION_GO), 7865 @FlagEntry( 7866 name = "actionSearch", 7867 mask = EditorInfo.IME_MASK_ACTION, 7868 target = EditorInfo.IME_ACTION_SEARCH), 7869 @FlagEntry( 7870 name = "actionSend", 7871 mask = EditorInfo.IME_MASK_ACTION, 7872 target = EditorInfo.IME_ACTION_SEND), 7873 @FlagEntry( 7874 name = "actionNext", 7875 mask = EditorInfo.IME_MASK_ACTION, 7876 target = EditorInfo.IME_ACTION_NEXT), 7877 @FlagEntry( 7878 name = "actionDone", 7879 mask = EditorInfo.IME_MASK_ACTION, 7880 target = EditorInfo.IME_ACTION_DONE), 7881 @FlagEntry( 7882 name = "actionPrevious", 7883 mask = EditorInfo.IME_MASK_ACTION, 7884 target = EditorInfo.IME_ACTION_PREVIOUS), 7885 @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII), 7886 @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT), 7887 @FlagEntry( 7888 name = "flagNavigatePrevious", 7889 target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS), 7890 @FlagEntry( 7891 name = "flagNoAccessoryAction", 7892 target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION), 7893 @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION), 7894 @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI), 7895 @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN), 7896 @FlagEntry( 7897 name = "flagNoPersonalizedLearning", 7898 target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING), 7899 }) getImeOptions()7900 public int getImeOptions() { 7901 return mEditor != null && mEditor.mInputContentType != null 7902 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; 7903 } 7904 7905 /** 7906 * Change the custom IME action associated with the text view, which 7907 * will be reported to an IME with {@link EditorInfo#actionLabel} 7908 * and {@link EditorInfo#actionId} when it has focus. 7909 * @see #getImeActionLabel 7910 * @see #getImeActionId 7911 * @see android.view.inputmethod.EditorInfo 7912 * @attr ref android.R.styleable#TextView_imeActionLabel 7913 * @attr ref android.R.styleable#TextView_imeActionId 7914 */ setImeActionLabel(CharSequence label, int actionId)7915 public void setImeActionLabel(CharSequence label, int actionId) { 7916 createEditorIfNeeded(); 7917 mEditor.createInputContentTypeIfNeeded(); 7918 mEditor.mInputContentType.imeActionLabel = label; 7919 mEditor.mInputContentType.imeActionId = actionId; 7920 } 7921 7922 /** 7923 * Get the IME action label previous set with {@link #setImeActionLabel}. 7924 * 7925 * @see #setImeActionLabel 7926 * @see android.view.inputmethod.EditorInfo 7927 */ 7928 @InspectableProperty getImeActionLabel()7929 public CharSequence getImeActionLabel() { 7930 return mEditor != null && mEditor.mInputContentType != null 7931 ? mEditor.mInputContentType.imeActionLabel : null; 7932 } 7933 7934 /** 7935 * Get the IME action ID previous set with {@link #setImeActionLabel}. 7936 * 7937 * @see #setImeActionLabel 7938 * @see android.view.inputmethod.EditorInfo 7939 */ 7940 @InspectableProperty getImeActionId()7941 public int getImeActionId() { 7942 return mEditor != null && mEditor.mInputContentType != null 7943 ? mEditor.mInputContentType.imeActionId : 0; 7944 } 7945 7946 /** 7947 * Set a special listener to be called when an action is performed 7948 * on the text view. This will be called when the enter key is pressed, 7949 * or when an action supplied to the IME is selected by the user. Setting 7950 * this means that the normal hard key event will not insert a newline 7951 * into the text view, even if it is multi-line; holding down the ALT 7952 * modifier will, however, allow the user to insert a newline character. 7953 */ setOnEditorActionListener(OnEditorActionListener l)7954 public void setOnEditorActionListener(OnEditorActionListener l) { 7955 createEditorIfNeeded(); 7956 mEditor.createInputContentTypeIfNeeded(); 7957 mEditor.mInputContentType.onEditorActionListener = l; 7958 } 7959 7960 /** 7961 * Called when an attached input method calls 7962 * {@link InputConnection#performEditorAction(int) 7963 * InputConnection.performEditorAction()} 7964 * for this text view. The default implementation will call your action 7965 * listener supplied to {@link #setOnEditorActionListener}, or perform 7966 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 7967 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS 7968 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE 7969 * EditorInfo.IME_ACTION_DONE}. 7970 * 7971 * <p>For backwards compatibility, if no IME options have been set and the 7972 * text view would not normally advance focus on enter, then 7973 * the NEXT and DONE actions received here will be turned into an enter 7974 * key down/up pair to go through the normal key handling. 7975 * 7976 * @param actionCode The code of the action being performed. 7977 * 7978 * @see #setOnEditorActionListener 7979 */ onEditorAction(int actionCode)7980 public void onEditorAction(int actionCode) { 7981 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; 7982 if (ict != null) { 7983 if (ict.onEditorActionListener != null) { 7984 if (ict.onEditorActionListener.onEditorAction(this, 7985 actionCode, null)) { 7986 return; 7987 } 7988 } 7989 7990 // This is the handling for some default action. 7991 // Note that for backwards compatibility we don't do this 7992 // default handling if explicit ime options have not been given, 7993 // instead turning this into the normal enter key codes that an 7994 // app may be expecting. 7995 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 7996 View v = focusSearch(FOCUS_FORWARD); 7997 if (v != null) { 7998 if (!v.requestFocus(FOCUS_FORWARD)) { 7999 throw new IllegalStateException("focus search returned a view " 8000 + "that wasn't able to take focus!"); 8001 } 8002 } 8003 return; 8004 8005 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { 8006 View v = focusSearch(FOCUS_BACKWARD); 8007 if (v != null) { 8008 if (!v.requestFocus(FOCUS_BACKWARD)) { 8009 throw new IllegalStateException("focus search returned a view " 8010 + "that wasn't able to take focus!"); 8011 } 8012 } 8013 return; 8014 8015 } else if (actionCode == EditorInfo.IME_ACTION_DONE) { 8016 InputMethodManager imm = getInputMethodManager(); 8017 if (imm != null && imm.isActive(this)) { 8018 imm.hideSoftInputFromWindow(getWindowToken(), 0); 8019 } 8020 return; 8021 } 8022 } 8023 8024 ViewRootImpl viewRootImpl = getViewRootImpl(); 8025 if (viewRootImpl != null) { 8026 long eventTime = SystemClock.uptimeMillis(); 8027 viewRootImpl.dispatchKeyFromIme( 8028 new KeyEvent(eventTime, eventTime, 8029 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 8030 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 8031 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 8032 | KeyEvent.FLAG_EDITOR_ACTION)); 8033 viewRootImpl.dispatchKeyFromIme( 8034 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 8035 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 8036 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 8037 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 8038 | KeyEvent.FLAG_EDITOR_ACTION)); 8039 } 8040 } 8041 8042 /** 8043 * Set the private content type of the text, which is the 8044 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 8045 * field that will be filled in when creating an input connection. 8046 * 8047 * @see #getPrivateImeOptions() 8048 * @see EditorInfo#privateImeOptions 8049 * @attr ref android.R.styleable#TextView_privateImeOptions 8050 */ setPrivateImeOptions(String type)8051 public void setPrivateImeOptions(String type) { 8052 createEditorIfNeeded(); 8053 mEditor.createInputContentTypeIfNeeded(); 8054 mEditor.mInputContentType.privateImeOptions = type; 8055 } 8056 8057 /** 8058 * Get the private type of the content. 8059 * 8060 * @see #setPrivateImeOptions(String) 8061 * @see EditorInfo#privateImeOptions 8062 */ 8063 @InspectableProperty getPrivateImeOptions()8064 public String getPrivateImeOptions() { 8065 return mEditor != null && mEditor.mInputContentType != null 8066 ? mEditor.mInputContentType.privateImeOptions : null; 8067 } 8068 8069 /** 8070 * Set the extra input data of the text, which is the 8071 * {@link EditorInfo#extras TextBoxAttribute.extras} 8072 * Bundle that will be filled in when creating an input connection. The 8073 * given integer is the resource identifier of an XML resource holding an 8074 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 8075 * 8076 * @see #getInputExtras(boolean) 8077 * @see EditorInfo#extras 8078 * @attr ref android.R.styleable#TextView_editorExtras 8079 */ setInputExtras(@mlRes int xmlResId)8080 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException { 8081 createEditorIfNeeded(); 8082 XmlResourceParser parser = getResources().getXml(xmlResId); 8083 mEditor.createInputContentTypeIfNeeded(); 8084 mEditor.mInputContentType.extras = new Bundle(); 8085 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); 8086 } 8087 8088 /** 8089 * Retrieve the input extras currently associated with the text view, which 8090 * can be viewed as well as modified. 8091 * 8092 * @param create If true, the extras will be created if they don't already 8093 * exist. Otherwise, null will be returned if none have been created. 8094 * @see #setInputExtras(int) 8095 * @see EditorInfo#extras 8096 * @attr ref android.R.styleable#TextView_editorExtras 8097 */ getInputExtras(boolean create)8098 public Bundle getInputExtras(boolean create) { 8099 if (mEditor == null && !create) return null; 8100 createEditorIfNeeded(); 8101 if (mEditor.mInputContentType == null) { 8102 if (!create) return null; 8103 mEditor.createInputContentTypeIfNeeded(); 8104 } 8105 if (mEditor.mInputContentType.extras == null) { 8106 if (!create) return null; 8107 mEditor.mInputContentType.extras = new Bundle(); 8108 } 8109 return mEditor.mInputContentType.extras; 8110 } 8111 8112 /** 8113 * Change "hint" locales associated with the text view, which will be reported to an IME with 8114 * {@link EditorInfo#hintLocales} when it has focus. 8115 * 8116 * Starting with Android O, this also causes internationalized listeners to be created (or 8117 * change locale) based on the first locale in the input locale list. 8118 * 8119 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to 8120 * call {@link InputMethodManager#restartInput(View)}.</p> 8121 * @param hintLocales List of the languages that the user is supposed to switch to no matter 8122 * what input method subtype is currently used. Set {@code null} to clear the current "hint". 8123 * @see #getImeHintLocales() 8124 * @see android.view.inputmethod.EditorInfo#hintLocales 8125 */ setImeHintLocales(@ullable LocaleList hintLocales)8126 public void setImeHintLocales(@Nullable LocaleList hintLocales) { 8127 createEditorIfNeeded(); 8128 mEditor.createInputContentTypeIfNeeded(); 8129 mEditor.mInputContentType.imeHintLocales = hintLocales; 8130 if (mUseInternationalizedInput) { 8131 changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0)); 8132 } 8133 } 8134 8135 /** 8136 * @return The current languages list "hint". {@code null} when no "hint" is available. 8137 * @see #setImeHintLocales(LocaleList) 8138 * @see android.view.inputmethod.EditorInfo#hintLocales 8139 */ 8140 @Nullable getImeHintLocales()8141 public LocaleList getImeHintLocales() { 8142 if (mEditor == null) { 8143 return null; 8144 } 8145 if (mEditor.mInputContentType == null) { 8146 return null; 8147 } 8148 return mEditor.mInputContentType.imeHintLocales; 8149 } 8150 8151 /** 8152 * Returns the error message that was set to be displayed with 8153 * {@link #setError}, or <code>null</code> if no error was set 8154 * or if it the error was cleared by the widget after user input. 8155 */ getError()8156 public CharSequence getError() { 8157 return mEditor == null ? null : mEditor.mError; 8158 } 8159 8160 /** 8161 * Sets the right-hand compound drawable of the TextView to the "error" 8162 * icon and sets an error message that will be displayed in a popup when 8163 * the TextView has focus. The icon and error message will be reset to 8164 * null when any key events cause changes to the TextView's text. If the 8165 * <code>error</code> is <code>null</code>, the error message and icon 8166 * will be cleared. 8167 */ 8168 @android.view.RemotableViewMethod setError(CharSequence error)8169 public void setError(CharSequence error) { 8170 if (error == null) { 8171 setError(null, null); 8172 } else { 8173 Drawable dr = getContext().getDrawable( 8174 com.android.internal.R.drawable.indicator_input_error); 8175 8176 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 8177 setError(error, dr); 8178 } 8179 } 8180 8181 /** 8182 * Sets the right-hand compound drawable of the TextView to the specified 8183 * icon and sets an error message that will be displayed in a popup when 8184 * the TextView has focus. The icon and error message will be reset to 8185 * null when any key events cause changes to the TextView's text. The 8186 * drawable must already have had {@link Drawable#setBounds} set on it. 8187 * If the <code>error</code> is <code>null</code>, the error message will 8188 * be cleared (and you should provide a <code>null</code> icon as well). 8189 */ setError(CharSequence error, Drawable icon)8190 public void setError(CharSequence error, Drawable icon) { 8191 createEditorIfNeeded(); 8192 mEditor.setError(error, icon); 8193 notifyViewAccessibilityStateChangedIfNeeded( 8194 AccessibilityEvent.CONTENT_CHANGE_TYPE_ERROR 8195 | AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_INVALID); 8196 } 8197 8198 @Override setFrame(int l, int t, int r, int b)8199 protected boolean setFrame(int l, int t, int r, int b) { 8200 boolean result = super.setFrame(l, t, r, b); 8201 8202 if (mEditor != null) mEditor.setFrame(); 8203 8204 restartMarqueeIfNeeded(); 8205 8206 return result; 8207 } 8208 restartMarqueeIfNeeded()8209 private void restartMarqueeIfNeeded() { 8210 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 8211 mRestartMarquee = false; 8212 startMarquee(); 8213 } 8214 } 8215 8216 /** 8217 * Sets the list of input filters that will be used if the buffer is 8218 * Editable. Has no effect otherwise. 8219 * 8220 * @attr ref android.R.styleable#TextView_maxLength 8221 */ setFilters(InputFilter[] filters)8222 public void setFilters(InputFilter[] filters) { 8223 if (filters == null) { 8224 throw new IllegalArgumentException(); 8225 } 8226 8227 mFilters = filters; 8228 8229 if (mText instanceof Editable) { 8230 setFilters((Editable) mText, filters); 8231 } 8232 } 8233 8234 /** 8235 * Sets the list of input filters on the specified Editable, 8236 * and includes mInput in the list if it is an InputFilter. 8237 */ setFilters(Editable e, InputFilter[] filters)8238 private void setFilters(Editable e, InputFilter[] filters) { 8239 if (mEditor != null) { 8240 final boolean undoFilter = mEditor.mUndoInputFilter != null; 8241 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; 8242 int num = 0; 8243 if (undoFilter) num++; 8244 if (keyFilter) num++; 8245 if (num > 0) { 8246 InputFilter[] nf = new InputFilter[filters.length + num]; 8247 8248 System.arraycopy(filters, 0, nf, 0, filters.length); 8249 num = 0; 8250 if (undoFilter) { 8251 nf[filters.length] = mEditor.mUndoInputFilter; 8252 num++; 8253 } 8254 if (keyFilter) { 8255 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; 8256 } 8257 8258 e.setFilters(nf); 8259 return; 8260 } 8261 } 8262 e.setFilters(filters); 8263 } 8264 8265 /** 8266 * Returns the current list of input filters. 8267 * 8268 * @attr ref android.R.styleable#TextView_maxLength 8269 */ getFilters()8270 public InputFilter[] getFilters() { 8271 return mFilters; 8272 } 8273 8274 ///////////////////////////////////////////////////////////////////////// 8275 getBoxHeight(Layout l)8276 private int getBoxHeight(Layout l) { 8277 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; 8278 int padding = (l == mHintLayout) 8279 ? getCompoundPaddingTop() + getCompoundPaddingBottom() 8280 : getExtendedPaddingTop() + getExtendedPaddingBottom(); 8281 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; 8282 } 8283 8284 @UnsupportedAppUsage getVerticalOffset(boolean forceNormal)8285 int getVerticalOffset(boolean forceNormal) { 8286 int voffset = 0; 8287 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 8288 8289 Layout l = mLayout; 8290 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 8291 l = mHintLayout; 8292 } 8293 8294 if (gravity != Gravity.TOP) { 8295 int boxht = getBoxHeight(l); 8296 int textht = l.getHeight(); 8297 8298 if (textht < boxht) { 8299 if (gravity == Gravity.BOTTOM) { 8300 voffset = boxht - textht; 8301 } else { // (gravity == Gravity.CENTER_VERTICAL) 8302 voffset = (boxht - textht) >> 1; 8303 } 8304 } 8305 } 8306 return voffset; 8307 } 8308 getBottomVerticalOffset(boolean forceNormal)8309 private int getBottomVerticalOffset(boolean forceNormal) { 8310 int voffset = 0; 8311 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 8312 8313 Layout l = mLayout; 8314 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 8315 l = mHintLayout; 8316 } 8317 8318 if (gravity != Gravity.BOTTOM) { 8319 int boxht = getBoxHeight(l); 8320 int textht = l.getHeight(); 8321 8322 if (textht < boxht) { 8323 if (gravity == Gravity.TOP) { 8324 voffset = boxht - textht; 8325 } else { // (gravity == Gravity.CENTER_VERTICAL) 8326 voffset = (boxht - textht) >> 1; 8327 } 8328 } 8329 } 8330 return voffset; 8331 } 8332 invalidateCursorPath()8333 void invalidateCursorPath() { 8334 if (mHighlightPathBogus) { 8335 invalidateCursor(); 8336 } else { 8337 final int horizontalPadding = getCompoundPaddingLeft(); 8338 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 8339 8340 if (mEditor.mDrawableForCursor == null) { 8341 synchronized (TEMP_RECTF) { 8342 /* 8343 * The reason for this concern about the thickness of the 8344 * cursor and doing the floor/ceil on the coordinates is that 8345 * some EditTexts (notably textfields in the Browser) have 8346 * anti-aliased text where not all the characters are 8347 * necessarily at integer-multiple locations. This should 8348 * make sure the entire cursor gets invalidated instead of 8349 * sometimes missing half a pixel. 8350 */ 8351 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth()); 8352 if (thick < 1.0f) { 8353 thick = 1.0f; 8354 } 8355 8356 thick /= 2.0f; 8357 8358 // mHighlightPath is guaranteed to be non null at that point. 8359 mHighlightPath.computeBounds(TEMP_RECTF, false); 8360 8361 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick), 8362 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick), 8363 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick), 8364 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); 8365 } 8366 } else { 8367 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 8368 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, 8369 bounds.right + horizontalPadding, bounds.bottom + verticalPadding); 8370 } 8371 } 8372 } 8373 invalidateCursor()8374 void invalidateCursor() { 8375 int where = getSelectionEnd(); 8376 8377 invalidateCursor(where, where, where); 8378 } 8379 invalidateCursor(int a, int b, int c)8380 private void invalidateCursor(int a, int b, int c) { 8381 if (a >= 0 || b >= 0 || c >= 0) { 8382 int start = Math.min(Math.min(a, b), c); 8383 int end = Math.max(Math.max(a, b), c); 8384 invalidateRegion(start, end, true /* Also invalidates blinking cursor */); 8385 } 8386 } 8387 8388 /** 8389 * Invalidates the region of text enclosed between the start and end text offsets. 8390 */ invalidateRegion(int start, int end, boolean invalidateCursor)8391 void invalidateRegion(int start, int end, boolean invalidateCursor) { 8392 if (mLayout == null) { 8393 invalidate(); 8394 } else { 8395 start = originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR); 8396 end = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR); 8397 int lineStart = mLayout.getLineForOffset(start); 8398 int top = mLayout.getLineTop(lineStart); 8399 8400 // This is ridiculous, but the descent from the line above 8401 // can hang down into the line we really want to redraw, 8402 // so we have to invalidate part of the line above to make 8403 // sure everything that needs to be redrawn really is. 8404 // (But not the whole line above, because that would cause 8405 // the same problem with the descenders on the line above it!) 8406 if (lineStart > 0) { 8407 top -= mLayout.getLineDescent(lineStart - 1); 8408 } 8409 8410 int lineEnd; 8411 8412 if (start == end) { 8413 lineEnd = lineStart; 8414 } else { 8415 lineEnd = mLayout.getLineForOffset(end); 8416 } 8417 8418 int bottom = mLayout.getLineBottom(lineEnd); 8419 8420 // mEditor can be null in case selection is set programmatically. 8421 if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) { 8422 final Rect bounds = mEditor.mDrawableForCursor.getBounds(); 8423 top = Math.min(top, bounds.top); 8424 bottom = Math.max(bottom, bounds.bottom); 8425 } 8426 8427 final int compoundPaddingLeft = getCompoundPaddingLeft(); 8428 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 8429 8430 int left, right; 8431 if (lineStart == lineEnd && !invalidateCursor) { 8432 left = (int) mLayout.getPrimaryHorizontal(start); 8433 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); 8434 left += compoundPaddingLeft; 8435 right += compoundPaddingLeft; 8436 } else { 8437 // Rectangle bounding box when the region spans several lines 8438 left = compoundPaddingLeft; 8439 right = getWidth() - getCompoundPaddingRight(); 8440 } 8441 8442 invalidate(mScrollX + left, verticalPadding + top, 8443 mScrollX + right, verticalPadding + bottom); 8444 } 8445 } 8446 registerForPreDraw()8447 private void registerForPreDraw() { 8448 if (!mPreDrawRegistered) { 8449 getViewTreeObserver().addOnPreDrawListener(this); 8450 mPreDrawRegistered = true; 8451 } 8452 } 8453 unregisterForPreDraw()8454 private void unregisterForPreDraw() { 8455 getViewTreeObserver().removeOnPreDrawListener(this); 8456 mPreDrawRegistered = false; 8457 mPreDrawListenerDetached = false; 8458 } 8459 8460 /** 8461 * {@inheritDoc} 8462 */ 8463 @Override onPreDraw()8464 public boolean onPreDraw() { 8465 if (mLayout == null) { 8466 assumeLayout(); 8467 } 8468 8469 if (mMovement != null) { 8470 /* This code also provides auto-scrolling when a cursor is moved using a 8471 * CursorController (insertion point or selection limits). 8472 * For selection, ensure start or end is visible depending on controller's state. 8473 */ 8474 int curs = getSelectionEnd(); 8475 // Do not create the controller if it is not already created. 8476 if (mEditor != null && mEditor.mSelectionModifierCursorController != null 8477 && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { 8478 curs = getSelectionStart(); 8479 } 8480 8481 /* 8482 * TODO: This should really only keep the end in view if 8483 * it already was before the text changed. I'm not sure 8484 * of a good way to tell from here if it was. 8485 */ 8486 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 8487 curs = mText.length(); 8488 } 8489 8490 if (curs >= 0) { 8491 bringPointIntoView(curs); 8492 } 8493 } else { 8494 bringTextIntoView(); 8495 } 8496 8497 // This has to be checked here since: 8498 // - onFocusChanged cannot start it when focus is given to a view with selected text (after 8499 // a screen rotation) since layout is not yet initialized at that point. 8500 if (mEditor != null && mEditor.mCreatedWithASelection) { 8501 mEditor.refreshTextActionMode(); 8502 mEditor.mCreatedWithASelection = false; 8503 } 8504 8505 unregisterForPreDraw(); 8506 8507 return true; 8508 } 8509 8510 @Override onAttachedToWindow()8511 protected void onAttachedToWindow() { 8512 super.onAttachedToWindow(); 8513 8514 if (mEditor != null) mEditor.onAttachedToWindow(); 8515 8516 if (mPreDrawListenerDetached) { 8517 getViewTreeObserver().addOnPreDrawListener(this); 8518 mPreDrawListenerDetached = false; 8519 } 8520 } 8521 8522 /** @hide */ 8523 @Override onDetachedFromWindowInternal()8524 protected void onDetachedFromWindowInternal() { 8525 if (mPreDrawRegistered) { 8526 getViewTreeObserver().removeOnPreDrawListener(this); 8527 mPreDrawListenerDetached = true; 8528 } 8529 8530 resetResolvedDrawables(); 8531 8532 if (mEditor != null) mEditor.onDetachedFromWindow(); 8533 8534 super.onDetachedFromWindowInternal(); 8535 } 8536 8537 @Override onScreenStateChanged(int screenState)8538 public void onScreenStateChanged(int screenState) { 8539 super.onScreenStateChanged(screenState); 8540 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 8541 } 8542 8543 @Override isPaddingOffsetRequired()8544 protected boolean isPaddingOffsetRequired() { 8545 return mShadowRadius != 0 || mDrawables != null; 8546 } 8547 8548 @Override getLeftPaddingOffset()8549 protected int getLeftPaddingOffset() { 8550 return getCompoundPaddingLeft() - mPaddingLeft 8551 + (int) Math.min(0, mShadowDx - mShadowRadius); 8552 } 8553 8554 @Override getTopPaddingOffset()8555 protected int getTopPaddingOffset() { 8556 return (int) Math.min(0, mShadowDy - mShadowRadius); 8557 } 8558 8559 @Override getBottomPaddingOffset()8560 protected int getBottomPaddingOffset() { 8561 return (int) Math.max(0, mShadowDy + mShadowRadius); 8562 } 8563 8564 @Override getRightPaddingOffset()8565 protected int getRightPaddingOffset() { 8566 return -(getCompoundPaddingRight() - mPaddingRight) 8567 + (int) Math.max(0, mShadowDx + mShadowRadius); 8568 } 8569 8570 @Override verifyDrawable(@onNull Drawable who)8571 protected boolean verifyDrawable(@NonNull Drawable who) { 8572 final boolean verified = super.verifyDrawable(who); 8573 if (!verified && mDrawables != null) { 8574 for (Drawable dr : mDrawables.mShowing) { 8575 if (who == dr) { 8576 return true; 8577 } 8578 } 8579 } 8580 return verified; 8581 } 8582 8583 @Override jumpDrawablesToCurrentState()8584 public void jumpDrawablesToCurrentState() { 8585 super.jumpDrawablesToCurrentState(); 8586 if (mDrawables != null) { 8587 for (Drawable dr : mDrawables.mShowing) { 8588 if (dr != null) { 8589 dr.jumpToCurrentState(); 8590 } 8591 } 8592 } 8593 } 8594 8595 @Override invalidateDrawable(@onNull Drawable drawable)8596 public void invalidateDrawable(@NonNull Drawable drawable) { 8597 boolean handled = false; 8598 8599 if (verifyDrawable(drawable)) { 8600 final Rect dirty = drawable.getBounds(); 8601 int scrollX = mScrollX; 8602 int scrollY = mScrollY; 8603 8604 // IMPORTANT: The coordinates below are based on the coordinates computed 8605 // for each compound drawable in onDraw(). Make sure to update each section 8606 // accordingly. 8607 final TextView.Drawables drawables = mDrawables; 8608 if (drawables != null) { 8609 if (drawable == drawables.mShowing[Drawables.LEFT]) { 8610 final int compoundPaddingTop = getCompoundPaddingTop(); 8611 final int compoundPaddingBottom = getCompoundPaddingBottom(); 8612 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 8613 8614 scrollX += mPaddingLeft; 8615 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; 8616 handled = true; 8617 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) { 8618 final int compoundPaddingTop = getCompoundPaddingTop(); 8619 final int compoundPaddingBottom = getCompoundPaddingBottom(); 8620 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 8621 8622 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); 8623 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; 8624 handled = true; 8625 } else if (drawable == drawables.mShowing[Drawables.TOP]) { 8626 final int compoundPaddingLeft = getCompoundPaddingLeft(); 8627 final int compoundPaddingRight = getCompoundPaddingRight(); 8628 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 8629 8630 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; 8631 scrollY += mPaddingTop; 8632 handled = true; 8633 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) { 8634 final int compoundPaddingLeft = getCompoundPaddingLeft(); 8635 final int compoundPaddingRight = getCompoundPaddingRight(); 8636 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 8637 8638 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; 8639 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); 8640 handled = true; 8641 } 8642 } 8643 8644 if (handled) { 8645 invalidate(dirty.left + scrollX, dirty.top + scrollY, 8646 dirty.right + scrollX, dirty.bottom + scrollY); 8647 } 8648 } 8649 8650 if (!handled) { 8651 super.invalidateDrawable(drawable); 8652 } 8653 } 8654 8655 @Override hasOverlappingRendering()8656 public boolean hasOverlappingRendering() { 8657 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation 8658 return ((getBackground() != null && getBackground().getCurrent() != null) 8659 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled() 8660 || mShadowColor != 0); 8661 } 8662 8663 /** 8664 * 8665 * Returns the state of the {@code textIsSelectable} flag (See 8666 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag 8667 * to allow users to select and copy text in a non-editable TextView, the content of an 8668 * {@link EditText} can always be selected, independently of the value of this flag. 8669 * <p> 8670 * 8671 * @return True if the text displayed in this TextView can be selected by the user. 8672 * 8673 * @attr ref android.R.styleable#TextView_textIsSelectable 8674 */ 8675 @InspectableProperty(name = "textIsSelectable") isTextSelectable()8676 public boolean isTextSelectable() { 8677 return mEditor == null ? false : mEditor.mTextIsSelectable; 8678 } 8679 8680 /** 8681 * Sets whether the content of this view is selectable by the user. The default is 8682 * {@code false}, meaning that the content is not selectable. 8683 * <p> 8684 * When you use a TextView to display a useful piece of information to the user (such as a 8685 * contact's address), make it selectable, so that the user can select and copy its 8686 * content. You can also use set the XML attribute 8687 * {@link android.R.styleable#TextView_textIsSelectable} to "true". 8688 * <p> 8689 * When you call this method to set the value of {@code textIsSelectable}, it sets 8690 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, 8691 * and {@code longClickable} to the same value. These flags correspond to the attributes 8692 * {@link android.R.styleable#View_focusable android:focusable}, 8693 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, 8694 * {@link android.R.styleable#View_clickable android:clickable}, and 8695 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these 8696 * flags to a state you had set previously, call one or more of the following methods: 8697 * {@link #setFocusable(boolean) setFocusable()}, 8698 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, 8699 * {@link #setClickable(boolean) setClickable()} or 8700 * {@link #setLongClickable(boolean) setLongClickable()}. 8701 * 8702 * @param selectable Whether the content of this TextView should be selectable. 8703 */ setTextIsSelectable(boolean selectable)8704 public void setTextIsSelectable(boolean selectable) { 8705 if (!selectable && mEditor == null) return; // false is default value with no edit data 8706 8707 createEditorIfNeeded(); 8708 if (mEditor.mTextIsSelectable == selectable) return; 8709 8710 mEditor.mTextIsSelectable = selectable; 8711 setFocusableInTouchMode(selectable); 8712 setFocusable(FOCUSABLE_AUTO); 8713 setClickable(selectable); 8714 setLongClickable(selectable); 8715 8716 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null 8717 8718 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); 8719 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); 8720 8721 // Called by setText above, but safer in case of future code changes 8722 mEditor.prepareCursorControllers(); 8723 } 8724 8725 @Override onCreateDrawableState(int extraSpace)8726 protected int[] onCreateDrawableState(int extraSpace) { 8727 final int[] drawableState; 8728 8729 if (mSingleLine) { 8730 drawableState = super.onCreateDrawableState(extraSpace); 8731 } else { 8732 drawableState = super.onCreateDrawableState(extraSpace + 1); 8733 mergeDrawableStates(drawableState, MULTILINE_STATE_SET); 8734 } 8735 8736 if (isTextSelectable()) { 8737 // Disable pressed state, which was introduced when TextView was made clickable. 8738 // Prevents text color change. 8739 // setClickable(false) would have a similar effect, but it also disables focus changes 8740 // and long press actions, which are both needed by text selection. 8741 final int length = drawableState.length; 8742 for (int i = 0; i < length; i++) { 8743 if (drawableState[i] == R.attr.state_pressed) { 8744 final int[] nonPressedState = new int[length - 1]; 8745 System.arraycopy(drawableState, 0, nonPressedState, 0, i); 8746 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); 8747 return nonPressedState; 8748 } 8749 } 8750 } 8751 8752 return drawableState; 8753 } 8754 maybeUpdateHighlightPaths()8755 private void maybeUpdateHighlightPaths() { 8756 if (!mHighlightPathsBogus) { 8757 return; 8758 } 8759 8760 if (mHighlightPaths != null) { 8761 mPathRecyclePool.addAll(mHighlightPaths); 8762 mHighlightPaths.clear(); 8763 mHighlightPaints.clear(); 8764 } else { 8765 mHighlightPaths = new ArrayList<>(); 8766 mHighlightPaints = new ArrayList<>(); 8767 } 8768 8769 if (mHighlights != null) { 8770 for (int i = 0; i < mHighlights.getSize(); ++i) { 8771 final int[] ranges = mHighlights.getRanges(i); 8772 final Paint paint = mHighlights.getPaint(i); 8773 final Path path; 8774 if (mPathRecyclePool.isEmpty()) { 8775 path = new Path(); 8776 } else { 8777 path = mPathRecyclePool.get(mPathRecyclePool.size() - 1); 8778 mPathRecyclePool.remove(mPathRecyclePool.size() - 1); 8779 path.reset(); 8780 } 8781 8782 boolean atLeastOnePathAdded = false; 8783 for (int j = 0; j < ranges.length / 2; ++j) { 8784 final int start = ranges[2 * j]; 8785 final int end = ranges[2 * j + 1]; 8786 if (start < end) { 8787 mLayout.getSelection(start, end, (left, top, right, bottom, layout) -> 8788 path.addRect(left, top, right, bottom, Path.Direction.CW) 8789 ); 8790 atLeastOnePathAdded = true; 8791 } 8792 } 8793 if (atLeastOnePathAdded) { 8794 mHighlightPaths.add(path); 8795 mHighlightPaints.add(paint); 8796 } 8797 } 8798 } 8799 8800 addSearchHighlightPaths(); 8801 8802 if (hasGesturePreviewHighlight()) { 8803 final Path path; 8804 if (mPathRecyclePool.isEmpty()) { 8805 path = new Path(); 8806 } else { 8807 path = mPathRecyclePool.get(mPathRecyclePool.size() - 1); 8808 mPathRecyclePool.remove(mPathRecyclePool.size() - 1); 8809 path.reset(); 8810 } 8811 mLayout.getSelectionPath( 8812 mGesturePreviewHighlightStart, mGesturePreviewHighlightEnd, path); 8813 mHighlightPaths.add(path); 8814 mHighlightPaints.add(mGesturePreviewHighlightPaint); 8815 } 8816 8817 mHighlightPathsBogus = false; 8818 } 8819 addSearchHighlightPaths()8820 private void addSearchHighlightPaths() { 8821 if (mSearchResultHighlights != null) { 8822 final Path searchResultPath; 8823 if (mPathRecyclePool.isEmpty()) { 8824 searchResultPath = new Path(); 8825 } else { 8826 searchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1); 8827 mPathRecyclePool.remove(mPathRecyclePool.size() - 1); 8828 searchResultPath.reset(); 8829 } 8830 final Path focusedSearchResultPath; 8831 if (mFocusedSearchResultIndex == FOCUSED_SEARCH_RESULT_INDEX_NONE) { 8832 focusedSearchResultPath = null; 8833 } else if (mPathRecyclePool.isEmpty()) { 8834 focusedSearchResultPath = new Path(); 8835 } else { 8836 focusedSearchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1); 8837 mPathRecyclePool.remove(mPathRecyclePool.size() - 1); 8838 focusedSearchResultPath.reset(); 8839 } 8840 8841 boolean atLeastOnePathAdded = false; 8842 for (int j = 0; j < mSearchResultHighlights.length / 2; ++j) { 8843 final int start = mSearchResultHighlights[2 * j]; 8844 final int end = mSearchResultHighlights[2 * j + 1]; 8845 if (start < end) { 8846 if (j == mFocusedSearchResultIndex) { 8847 mLayout.getSelection(start, end, (left, top, right, bottom, layout) -> 8848 focusedSearchResultPath.addRect(left, top, right, bottom, 8849 Path.Direction.CW) 8850 ); 8851 } else { 8852 mLayout.getSelection(start, end, (left, top, right, bottom, layout) -> 8853 searchResultPath.addRect(left, top, right, bottom, 8854 Path.Direction.CW) 8855 ); 8856 atLeastOnePathAdded = true; 8857 } 8858 } 8859 } 8860 if (atLeastOnePathAdded) { 8861 if (mSearchResultHighlightPaint == null) { 8862 mSearchResultHighlightPaint = new Paint(); 8863 } 8864 mSearchResultHighlightPaint.setColor(mSearchResultHighlightColor); 8865 mSearchResultHighlightPaint.setStyle(Paint.Style.FILL); 8866 mHighlightPaths.add(searchResultPath); 8867 mHighlightPaints.add(mSearchResultHighlightPaint); 8868 } 8869 if (focusedSearchResultPath != null) { 8870 if (mFocusedSearchResultHighlightPaint == null) { 8871 mFocusedSearchResultHighlightPaint = new Paint(); 8872 } 8873 mFocusedSearchResultHighlightPaint.setColor(mFocusedSearchResultHighlightColor); 8874 mFocusedSearchResultHighlightPaint.setStyle(Paint.Style.FILL); 8875 mHighlightPaths.add(focusedSearchResultPath); 8876 mHighlightPaints.add(mFocusedSearchResultHighlightPaint); 8877 } 8878 } 8879 } 8880 8881 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getUpdatedHighlightPath()8882 private Path getUpdatedHighlightPath() { 8883 Path highlight = null; 8884 Paint highlightPaint = mHighlightPaint; 8885 8886 final int selStart = getSelectionStartTransformed(); 8887 final int selEnd = getSelectionEndTransformed(); 8888 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { 8889 if (selStart == selEnd) { 8890 if (mEditor != null && mEditor.shouldRenderCursor()) { 8891 if (mHighlightPathBogus) { 8892 if (mHighlightPath == null) mHighlightPath = new Path(); 8893 mHighlightPath.reset(); 8894 mLayout.getCursorPath(selStart, mHighlightPath, mText); 8895 mEditor.updateCursorPosition(); 8896 mHighlightPathBogus = false; 8897 } 8898 8899 // XXX should pass to skin instead of drawing directly 8900 highlightPaint.setColor(mCurTextColor); 8901 highlightPaint.setStyle(Paint.Style.STROKE); 8902 highlight = mHighlightPath; 8903 } 8904 } else { 8905 if (mHighlightPathBogus) { 8906 if (mHighlightPath == null) mHighlightPath = new Path(); 8907 mHighlightPath.reset(); 8908 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 8909 mHighlightPathBogus = false; 8910 } 8911 8912 // XXX should pass to skin instead of drawing directly 8913 highlightPaint.setColor(mHighlightColor); 8914 highlightPaint.setStyle(Paint.Style.FILL); 8915 8916 highlight = mHighlightPath; 8917 } 8918 } 8919 return highlight; 8920 } 8921 8922 /** 8923 * @hide 8924 */ getHorizontalOffsetForDrawables()8925 public int getHorizontalOffsetForDrawables() { 8926 return 0; 8927 } 8928 8929 @Override onDraw(Canvas canvas)8930 protected void onDraw(Canvas canvas) { 8931 restartMarqueeIfNeeded(); 8932 8933 // Draw the background for this view 8934 super.onDraw(canvas); 8935 8936 final int compoundPaddingLeft = getCompoundPaddingLeft(); 8937 final int compoundPaddingTop = getCompoundPaddingTop(); 8938 final int compoundPaddingRight = getCompoundPaddingRight(); 8939 final int compoundPaddingBottom = getCompoundPaddingBottom(); 8940 final int scrollX = mScrollX; 8941 final int scrollY = mScrollY; 8942 final int right = mRight; 8943 final int left = mLeft; 8944 final int bottom = mBottom; 8945 final int top = mTop; 8946 final boolean isLayoutRtl = isLayoutRtl(); 8947 final int offset = getHorizontalOffsetForDrawables(); 8948 final int leftOffset = isLayoutRtl ? 0 : offset; 8949 final int rightOffset = isLayoutRtl ? offset : 0; 8950 8951 final Drawables dr = mDrawables; 8952 if (dr != null) { 8953 /* 8954 * Compound, not extended, because the icon is not clipped 8955 * if the text height is smaller. 8956 */ 8957 8958 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 8959 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 8960 8961 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 8962 // Make sure to update invalidateDrawable() when changing this code. 8963 if (dr.mShowing[Drawables.LEFT] != null) { 8964 canvas.save(); 8965 canvas.translate(scrollX + mPaddingLeft + leftOffset, 8966 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2); 8967 dr.mShowing[Drawables.LEFT].draw(canvas); 8968 canvas.restore(); 8969 } 8970 8971 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 8972 // Make sure to update invalidateDrawable() when changing this code. 8973 if (dr.mShowing[Drawables.RIGHT] != null) { 8974 canvas.save(); 8975 canvas.translate(scrollX + right - left - mPaddingRight 8976 - dr.mDrawableSizeRight - rightOffset, 8977 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 8978 dr.mShowing[Drawables.RIGHT].draw(canvas); 8979 canvas.restore(); 8980 } 8981 8982 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 8983 // Make sure to update invalidateDrawable() when changing this code. 8984 if (dr.mShowing[Drawables.TOP] != null) { 8985 canvas.save(); 8986 canvas.translate(scrollX + compoundPaddingLeft 8987 + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 8988 dr.mShowing[Drawables.TOP].draw(canvas); 8989 canvas.restore(); 8990 } 8991 8992 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 8993 // Make sure to update invalidateDrawable() when changing this code. 8994 if (dr.mShowing[Drawables.BOTTOM] != null) { 8995 canvas.save(); 8996 canvas.translate(scrollX + compoundPaddingLeft 8997 + (hspace - dr.mDrawableWidthBottom) / 2, 8998 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 8999 dr.mShowing[Drawables.BOTTOM].draw(canvas); 9000 canvas.restore(); 9001 } 9002 } 9003 9004 int color = mCurTextColor; 9005 9006 if (mLayout == null) { 9007 assumeLayout(); 9008 } 9009 9010 Layout layout = mLayout; 9011 9012 if (mHint != null && !mHideHint && mText.length() == 0) { 9013 if (mHintTextColor != null) { 9014 color = mCurHintTextColor; 9015 } 9016 9017 layout = mHintLayout; 9018 } 9019 9020 mTextPaint.setColor(color); 9021 mTextPaint.drawableState = getDrawableState(); 9022 9023 canvas.save(); 9024 /* Would be faster if we didn't have to do this. Can we chop the 9025 (displayable) text so that we don't need to do this ever? 9026 */ 9027 9028 int extendedPaddingTop = getExtendedPaddingTop(); 9029 int extendedPaddingBottom = getExtendedPaddingBottom(); 9030 9031 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 9032 final int maxScrollY = mLayout.getHeight() - vspace; 9033 9034 float clipLeft = compoundPaddingLeft + scrollX; 9035 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; 9036 float clipRight = right - left - getCompoundPaddingRight() + scrollX; 9037 float clipBottom = bottom - top + scrollY 9038 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); 9039 9040 if (mShadowRadius != 0) { 9041 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 9042 clipRight += Math.max(0, mShadowDx + mShadowRadius); 9043 9044 clipTop += Math.min(0, mShadowDy - mShadowRadius); 9045 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 9046 } 9047 9048 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 9049 9050 int voffsetText = 0; 9051 int voffsetCursor = 0; 9052 9053 // translate in by our padding 9054 /* shortcircuit calling getVerticaOffset() */ 9055 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 9056 voffsetText = getVerticalOffset(false); 9057 voffsetCursor = getVerticalOffset(true); 9058 } 9059 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 9060 9061 final int layoutDirection = getLayoutDirection(); 9062 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 9063 if (isMarqueeFadeEnabled()) { 9064 if (!mSingleLine && getLineCount() == 1 && canMarquee() 9065 && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 9066 final int width = mRight - mLeft; 9067 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); 9068 final float dx = mLayout.getLineRight(0) - (width - padding); 9069 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 9070 } 9071 9072 if (mMarquee != null && mMarquee.isRunning()) { 9073 final float dx = -mMarquee.getScroll(); 9074 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 9075 } 9076 } 9077 9078 final int cursorOffsetVertical = voffsetCursor - voffsetText; 9079 9080 maybeUpdateHighlightPaths(); 9081 // If there is a gesture preview highlight, then the selection or cursor is not drawn. 9082 Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath(); 9083 if (mEditor != null) { 9084 mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight, 9085 mHighlightPaint, cursorOffsetVertical); 9086 } else { 9087 layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint, 9088 cursorOffsetVertical); 9089 } 9090 9091 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 9092 final float dx = mMarquee.getGhostOffset(); 9093 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 9094 layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint, 9095 cursorOffsetVertical); 9096 } 9097 9098 canvas.restore(); 9099 } 9100 9101 @Override getFocusedRect(Rect r)9102 public void getFocusedRect(Rect r) { 9103 if (mLayout == null) { 9104 super.getFocusedRect(r); 9105 return; 9106 } 9107 9108 int selEnd = getSelectionEndTransformed(); 9109 if (selEnd < 0) { 9110 super.getFocusedRect(r); 9111 return; 9112 } 9113 9114 int selStart = getSelectionStartTransformed(); 9115 if (selStart < 0 || selStart >= selEnd) { 9116 int line = mLayout.getLineForOffset(selEnd); 9117 r.top = mLayout.getLineTop(line); 9118 r.bottom = mLayout.getLineBottom(line); 9119 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; 9120 r.right = r.left + 4; 9121 } else { 9122 int lineStart = mLayout.getLineForOffset(selStart); 9123 int lineEnd = mLayout.getLineForOffset(selEnd); 9124 r.top = mLayout.getLineTop(lineStart); 9125 r.bottom = mLayout.getLineBottom(lineEnd); 9126 if (lineStart == lineEnd) { 9127 r.left = (int) mLayout.getPrimaryHorizontal(selStart); 9128 r.right = (int) mLayout.getPrimaryHorizontal(selEnd); 9129 } else { 9130 // Selection extends across multiple lines -- make the focused 9131 // rect cover the entire width. 9132 if (mHighlightPathBogus) { 9133 if (mHighlightPath == null) mHighlightPath = new Path(); 9134 mHighlightPath.reset(); 9135 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 9136 mHighlightPathBogus = false; 9137 } 9138 synchronized (TEMP_RECTF) { 9139 mHighlightPath.computeBounds(TEMP_RECTF, true); 9140 r.left = (int) TEMP_RECTF.left - 1; 9141 r.right = (int) TEMP_RECTF.right + 1; 9142 } 9143 } 9144 } 9145 9146 // Adjust for padding and gravity. 9147 int paddingLeft = getCompoundPaddingLeft(); 9148 int paddingTop = getExtendedPaddingTop(); 9149 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 9150 paddingTop += getVerticalOffset(false); 9151 } 9152 r.offset(paddingLeft, paddingTop); 9153 int paddingBottom = getExtendedPaddingBottom(); 9154 r.bottom += paddingBottom; 9155 } 9156 9157 /** 9158 * Return the number of lines of text, or 0 if the internal Layout has not 9159 * been built. 9160 */ getLineCount()9161 public int getLineCount() { 9162 return mLayout != null ? mLayout.getLineCount() : 0; 9163 } 9164 9165 /** 9166 * Return the baseline for the specified line (0...getLineCount() - 1) 9167 * If bounds is not null, return the top, left, right, bottom extents 9168 * of the specified line in it. If the internal Layout has not been built, 9169 * return 0 and set bounds to (0, 0, 0, 0) 9170 * @param line which line to examine (0..getLineCount() - 1) 9171 * @param bounds Optional. If not null, it returns the extent of the line 9172 * @return the Y-coordinate of the baseline 9173 */ getLineBounds(int line, Rect bounds)9174 public int getLineBounds(int line, Rect bounds) { 9175 if (mLayout == null) { 9176 if (bounds != null) { 9177 bounds.set(0, 0, 0, 0); 9178 } 9179 return 0; 9180 } else { 9181 int baseline = mLayout.getLineBounds(line, bounds); 9182 9183 int voffset = getExtendedPaddingTop(); 9184 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 9185 voffset += getVerticalOffset(true); 9186 } 9187 if (bounds != null) { 9188 bounds.offset(getCompoundPaddingLeft(), voffset); 9189 } 9190 return baseline + voffset; 9191 } 9192 } 9193 9194 @Override getBaseline()9195 public int getBaseline() { 9196 if (mLayout == null) { 9197 return super.getBaseline(); 9198 } 9199 9200 return getBaselineOffset() + mLayout.getLineBaseline(0); 9201 } 9202 getBaselineOffset()9203 int getBaselineOffset() { 9204 int voffset = 0; 9205 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 9206 voffset = getVerticalOffset(true); 9207 } 9208 9209 if (isLayoutModeOptical(mParent)) { 9210 voffset -= getOpticalInsets().top; 9211 } 9212 9213 return getExtendedPaddingTop() + voffset; 9214 } 9215 9216 /** 9217 * @hide 9218 */ 9219 @Override getFadeTop(boolean offsetRequired)9220 protected int getFadeTop(boolean offsetRequired) { 9221 if (mLayout == null) return 0; 9222 9223 int voffset = 0; 9224 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 9225 voffset = getVerticalOffset(true); 9226 } 9227 9228 if (offsetRequired) voffset += getTopPaddingOffset(); 9229 9230 return getExtendedPaddingTop() + voffset; 9231 } 9232 9233 /** 9234 * @hide 9235 */ 9236 @Override getFadeHeight(boolean offsetRequired)9237 protected int getFadeHeight(boolean offsetRequired) { 9238 return mLayout != null ? mLayout.getHeight() : 0; 9239 } 9240 9241 @Override onResolvePointerIcon(MotionEvent event, int pointerIndex)9242 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { 9243 if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { 9244 if (mSpannable != null && mLinksClickable) { 9245 final float x = event.getX(pointerIndex); 9246 final float y = event.getY(pointerIndex); 9247 final int offset = getOffsetForPosition(x, y); 9248 final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset, 9249 ClickableSpan.class); 9250 if (clickables.length > 0) { 9251 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND); 9252 } 9253 } 9254 if (isTextSelectable() || isTextEditable()) { 9255 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT); 9256 } 9257 } 9258 return super.onResolvePointerIcon(event, pointerIndex); 9259 } 9260 9261 @Override onKeyPreIme(int keyCode, KeyEvent event)9262 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 9263 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode, 9264 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call 9265 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event). 9266 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) { 9267 return true; 9268 } 9269 return super.onKeyPreIme(keyCode, event); 9270 } 9271 9272 /** 9273 * @hide 9274 */ handleBackInTextActionModeIfNeeded(KeyEvent event)9275 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) { 9276 // Do nothing unless mEditor is in text action mode. 9277 if (mEditor == null || mEditor.getTextActionMode() == null) { 9278 return false; 9279 } 9280 9281 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 9282 KeyEvent.DispatcherState state = getKeyDispatcherState(); 9283 if (state != null) { 9284 state.startTracking(event, this); 9285 } 9286 return true; 9287 } else if (event.getAction() == KeyEvent.ACTION_UP) { 9288 KeyEvent.DispatcherState state = getKeyDispatcherState(); 9289 if (state != null) { 9290 state.handleUpEvent(event); 9291 } 9292 if (event.isTracking() && !event.isCanceled()) { 9293 stopTextActionMode(); 9294 return true; 9295 } 9296 } 9297 return false; 9298 } 9299 9300 @Override onKeyDown(int keyCode, KeyEvent event)9301 public boolean onKeyDown(int keyCode, KeyEvent event) { 9302 final int which = doKeyDown(keyCode, event, null); 9303 if (which == KEY_EVENT_NOT_HANDLED) { 9304 return super.onKeyDown(keyCode, event); 9305 } 9306 9307 return true; 9308 } 9309 9310 @Override onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)9311 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 9312 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 9313 final int which = doKeyDown(keyCode, down, event); 9314 if (which == KEY_EVENT_NOT_HANDLED) { 9315 // Go through default dispatching. 9316 return super.onKeyMultiple(keyCode, repeatCount, event); 9317 } 9318 if (which == KEY_EVENT_HANDLED) { 9319 // Consumed the whole thing. 9320 return true; 9321 } 9322 9323 repeatCount--; 9324 9325 // We are going to dispatch the remaining events to either the input 9326 // or movement method. To do this, we will just send a repeated stream 9327 // of down and up events until we have done the complete repeatCount. 9328 // It would be nice if those interfaces had an onKeyMultiple() method, 9329 // but adding that is a more complicated change. 9330 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 9331 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) { 9332 // mEditor and mEditor.mInput are not null from doKeyDown 9333 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 9334 while (--repeatCount > 0) { 9335 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down); 9336 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); 9337 } 9338 hideErrorIfUnchanged(); 9339 9340 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) { 9341 // mMovement is not null from doKeyDown 9342 mMovement.onKeyUp(this, mSpannable, keyCode, up); 9343 while (--repeatCount > 0) { 9344 mMovement.onKeyDown(this, mSpannable, keyCode, down); 9345 mMovement.onKeyUp(this, mSpannable, keyCode, up); 9346 } 9347 } 9348 9349 return true; 9350 } 9351 9352 /** 9353 * Returns true if pressing ENTER in this field advances focus instead 9354 * of inserting the character. This is true mostly in single-line fields, 9355 * but also in mail addresses and subjects which will display on multiple 9356 * lines but where it doesn't make sense to insert newlines. 9357 */ shouldAdvanceFocusOnEnter()9358 private boolean shouldAdvanceFocusOnEnter() { 9359 if (getKeyListener() == null) { 9360 return false; 9361 } 9362 9363 if (mSingleLine) { 9364 return true; 9365 } 9366 9367 if (mEditor != null 9368 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 9369 == EditorInfo.TYPE_CLASS_TEXT) { 9370 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 9371 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 9372 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 9373 return true; 9374 } 9375 } 9376 9377 return false; 9378 } 9379 isDirectionalNavigationKey(int keyCode)9380 private boolean isDirectionalNavigationKey(int keyCode) { 9381 switch(keyCode) { 9382 case KeyEvent.KEYCODE_DPAD_UP: 9383 case KeyEvent.KEYCODE_DPAD_DOWN: 9384 case KeyEvent.KEYCODE_DPAD_LEFT: 9385 case KeyEvent.KEYCODE_DPAD_RIGHT: 9386 return true; 9387 } 9388 return false; 9389 } 9390 doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)9391 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 9392 if (!isEnabled()) { 9393 return KEY_EVENT_NOT_HANDLED; 9394 } 9395 9396 // If this is the initial keydown, we don't want to prevent a movement away from this view. 9397 // While this shouldn't be necessary because any time we're preventing default movement we 9398 // should be restricting the focus to remain within this view, thus we'll also receive 9399 // the key up event, occasionally key up events will get dropped and we don't want to 9400 // prevent the user from traversing out of this on the next key down. 9401 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 9402 mPreventDefaultMovement = false; 9403 } 9404 9405 switch (keyCode) { 9406 case KeyEvent.KEYCODE_ENTER: 9407 case KeyEvent.KEYCODE_NUMPAD_ENTER: 9408 if (event.hasNoModifiers()) { 9409 // When mInputContentType is set, we know that we are 9410 // running in a "modern" cupcake environment, so don't need 9411 // to worry about the application trying to capture 9412 // enter key events. 9413 if (mEditor != null && mEditor.mInputContentType != null) { 9414 // If there is an action listener, given them a 9415 // chance to consume the event. 9416 if (mEditor.mInputContentType.onEditorActionListener != null 9417 && mEditor.mInputContentType.onEditorActionListener.onEditorAction( 9418 this, 9419 getActionIdForEnterEvent(), 9420 event)) { 9421 mEditor.mInputContentType.enterDown = true; 9422 // We are consuming the enter key for them. 9423 return KEY_EVENT_HANDLED; 9424 } 9425 } 9426 9427 // If our editor should move focus when enter is pressed, or 9428 // this is a generated event from an IME action button, then 9429 // don't let it be inserted into the text. 9430 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 9431 || shouldAdvanceFocusOnEnter()) { 9432 if (hasOnClickListeners()) { 9433 return KEY_EVENT_NOT_HANDLED; 9434 } 9435 return KEY_EVENT_HANDLED; 9436 } 9437 } 9438 break; 9439 9440 case KeyEvent.KEYCODE_DPAD_CENTER: 9441 if (event.hasNoModifiers()) { 9442 if (shouldAdvanceFocusOnEnter()) { 9443 return KEY_EVENT_NOT_HANDLED; 9444 } 9445 } 9446 break; 9447 9448 case KeyEvent.KEYCODE_TAB: 9449 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 9450 // Tab is used to move focus. 9451 return KEY_EVENT_NOT_HANDLED; 9452 } 9453 break; 9454 9455 // Has to be done on key down (and not on key up) to correctly be intercepted. 9456 case KeyEvent.KEYCODE_BACK: 9457 if (mEditor != null && mEditor.getTextActionMode() != null) { 9458 stopTextActionMode(); 9459 return KEY_EVENT_HANDLED; 9460 } 9461 break; 9462 9463 case KeyEvent.KEYCODE_CUT: 9464 if (event.hasNoModifiers() && canCut()) { 9465 if (onTextContextMenuItem(ID_CUT)) { 9466 return KEY_EVENT_HANDLED; 9467 } 9468 } 9469 break; 9470 9471 case KeyEvent.KEYCODE_COPY: 9472 if (event.hasNoModifiers() && canCopy()) { 9473 if (onTextContextMenuItem(ID_COPY)) { 9474 return KEY_EVENT_HANDLED; 9475 } 9476 } 9477 break; 9478 9479 case KeyEvent.KEYCODE_PASTE: 9480 if (event.hasNoModifiers() && canPaste()) { 9481 if (onTextContextMenuItem(ID_PASTE)) { 9482 return KEY_EVENT_HANDLED; 9483 } 9484 } 9485 break; 9486 9487 case KeyEvent.KEYCODE_FORWARD_DEL: 9488 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) { 9489 if (onTextContextMenuItem(ID_CUT)) { 9490 return KEY_EVENT_HANDLED; 9491 } 9492 } 9493 break; 9494 9495 case KeyEvent.KEYCODE_INSERT: 9496 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) { 9497 if (onTextContextMenuItem(ID_COPY)) { 9498 return KEY_EVENT_HANDLED; 9499 } 9500 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) { 9501 if (onTextContextMenuItem(ID_PASTE)) { 9502 return KEY_EVENT_HANDLED; 9503 } 9504 } 9505 break; 9506 } 9507 9508 if (mEditor != null && mEditor.mKeyListener != null) { 9509 boolean doDown = true; 9510 if (otherEvent != null) { 9511 try { 9512 beginBatchEdit(); 9513 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, 9514 otherEvent); 9515 hideErrorIfUnchanged(); 9516 doDown = false; 9517 if (handled) { 9518 return KEY_EVENT_HANDLED; 9519 } 9520 } catch (AbstractMethodError e) { 9521 // onKeyOther was added after 1.0, so if it isn't 9522 // implemented we need to try to dispatch as a regular down. 9523 } finally { 9524 endBatchEdit(); 9525 } 9526 } 9527 9528 if (doDown) { 9529 beginBatchEdit(); 9530 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, 9531 keyCode, event); 9532 endBatchEdit(); 9533 hideErrorIfUnchanged(); 9534 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER; 9535 } 9536 } 9537 9538 // bug 650865: sometimes we get a key event before a layout. 9539 // don't try to move around if we don't know the layout. 9540 9541 if (mMovement != null && mLayout != null) { 9542 boolean doDown = true; 9543 if (otherEvent != null) { 9544 try { 9545 boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent); 9546 doDown = false; 9547 if (handled) { 9548 return KEY_EVENT_HANDLED; 9549 } 9550 } catch (AbstractMethodError e) { 9551 // onKeyOther was added after 1.0, so if it isn't 9552 // implemented we need to try to dispatch as a regular down. 9553 } 9554 } 9555 if (doDown) { 9556 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) { 9557 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 9558 mPreventDefaultMovement = true; 9559 } 9560 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD; 9561 } 9562 } 9563 // Consume arrows from keyboard devices to prevent focus leaving the editor. 9564 // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those 9565 // to move focus with arrows. 9566 if (event.getSource() == InputDevice.SOURCE_KEYBOARD 9567 && isDirectionalNavigationKey(keyCode)) { 9568 return KEY_EVENT_HANDLED; 9569 } 9570 } 9571 9572 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) 9573 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED; 9574 } 9575 9576 /** 9577 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} 9578 * can be recorded. 9579 * @hide 9580 */ resetErrorChangedFlag()9581 public void resetErrorChangedFlag() { 9582 /* 9583 * Keep track of what the error was before doing the input 9584 * so that if an input filter changed the error, we leave 9585 * that error showing. Otherwise, we take down whatever 9586 * error was showing when the user types something. 9587 */ 9588 if (mEditor != null) mEditor.mErrorWasChanged = false; 9589 } 9590 9591 /** 9592 * @hide 9593 */ hideErrorIfUnchanged()9594 public void hideErrorIfUnchanged() { 9595 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 9596 setError(null, null); 9597 } 9598 } 9599 9600 @Override onKeyUp(int keyCode, KeyEvent event)9601 public boolean onKeyUp(int keyCode, KeyEvent event) { 9602 if (!isEnabled()) { 9603 return super.onKeyUp(keyCode, event); 9604 } 9605 9606 if (!KeyEvent.isModifierKey(keyCode)) { 9607 mPreventDefaultMovement = false; 9608 } 9609 9610 switch (keyCode) { 9611 case KeyEvent.KEYCODE_DPAD_CENTER: 9612 if (event.hasNoModifiers()) { 9613 /* 9614 * If there is a click listener, just call through to 9615 * super, which will invoke it. 9616 * 9617 * If there isn't a click listener, try to show the soft 9618 * input method. (It will also 9619 * call performClick(), but that won't do anything in 9620 * this case.) 9621 */ 9622 if (!hasOnClickListeners()) { 9623 if (mMovement != null && mText instanceof Editable 9624 && mLayout != null && onCheckIsTextEditor()) { 9625 InputMethodManager imm = getInputMethodManager(); 9626 viewClicked(imm); 9627 if (imm != null && getShowSoftInputOnFocus()) { 9628 imm.showSoftInput(this, 0); 9629 } 9630 } 9631 } 9632 } 9633 return super.onKeyUp(keyCode, event); 9634 9635 case KeyEvent.KEYCODE_ENTER: 9636 case KeyEvent.KEYCODE_NUMPAD_ENTER: 9637 if (event.hasNoModifiers()) { 9638 if (mEditor != null && mEditor.mInputContentType != null 9639 && mEditor.mInputContentType.onEditorActionListener != null 9640 && mEditor.mInputContentType.enterDown) { 9641 mEditor.mInputContentType.enterDown = false; 9642 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( 9643 this, getActionIdForEnterEvent(), event)) { 9644 return true; 9645 } 9646 } 9647 9648 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 9649 || shouldAdvanceFocusOnEnter()) { 9650 /* 9651 * If there is a click listener, just call through to 9652 * super, which will invoke it. 9653 * 9654 * If there isn't a click listener, try to advance focus, 9655 * but still call through to super, which will reset the 9656 * pressed state and longpress state. (It will also 9657 * call performClick(), but that won't do anything in 9658 * this case.) 9659 */ 9660 if (!hasOnClickListeners()) { 9661 View v = focusSearch(FOCUS_DOWN); 9662 9663 if (v != null) { 9664 if (!v.requestFocus(FOCUS_DOWN)) { 9665 throw new IllegalStateException("focus search returned a view " 9666 + "that wasn't able to take focus!"); 9667 } 9668 9669 /* 9670 * Return true because we handled the key; super 9671 * will return false because there was no click 9672 * listener. 9673 */ 9674 super.onKeyUp(keyCode, event); 9675 return true; 9676 } else if ((event.getFlags() 9677 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 9678 // No target for next focus, but make sure the IME 9679 // if this came from it. 9680 InputMethodManager imm = getInputMethodManager(); 9681 if (imm != null && imm.isActive(this)) { 9682 imm.hideSoftInputFromWindow(getWindowToken(), 0); 9683 } 9684 } 9685 } 9686 } 9687 return super.onKeyUp(keyCode, event); 9688 } 9689 break; 9690 } 9691 9692 if (mEditor != null && mEditor.mKeyListener != null) { 9693 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) { 9694 return true; 9695 } 9696 } 9697 9698 if (mMovement != null && mLayout != null) { 9699 if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) { 9700 return true; 9701 } 9702 } 9703 9704 return super.onKeyUp(keyCode, event); 9705 } 9706 getActionIdForEnterEvent()9707 private int getActionIdForEnterEvent() { 9708 // If it's not single line, no action 9709 if (!isSingleLine()) { 9710 return EditorInfo.IME_NULL; 9711 } 9712 // Return the action that was specified for Enter 9713 return getImeOptions() & EditorInfo.IME_MASK_ACTION; 9714 } 9715 9716 @Override onCheckIsTextEditor()9717 public boolean onCheckIsTextEditor() { 9718 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 9719 } 9720 hasEditorInFocusSearchDirection(@ocusRealDirection int direction)9721 private boolean hasEditorInFocusSearchDirection(@FocusRealDirection int direction) { 9722 final View nextView = focusSearch(direction); 9723 return nextView != null && nextView.onCheckIsTextEditor(); 9724 } 9725 9726 @Override onCreateInputConnection(EditorInfo outAttrs)9727 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 9728 if (onCheckIsTextEditor() && isEnabled()) { 9729 mEditor.createInputMethodStateIfNeeded(); 9730 mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = 0; 9731 mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = 0; 9732 9733 outAttrs.inputType = getInputType(); 9734 if (mEditor.mInputContentType != null) { 9735 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; 9736 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; 9737 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; 9738 outAttrs.actionId = mEditor.mInputContentType.imeActionId; 9739 outAttrs.extras = mEditor.mInputContentType.extras; 9740 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales; 9741 } else { 9742 outAttrs.imeOptions = EditorInfo.IME_NULL; 9743 outAttrs.hintLocales = null; 9744 } 9745 if (hasEditorInFocusSearchDirection(FOCUS_DOWN)) { 9746 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 9747 } 9748 if (hasEditorInFocusSearchDirection(FOCUS_UP)) { 9749 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 9750 } 9751 if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION) 9752 == EditorInfo.IME_ACTION_UNSPECIFIED) { 9753 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { 9754 // An action has not been set, but the enter key will move to 9755 // the next focus, so set the action to that. 9756 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 9757 } else { 9758 // An action has not been set, and there is no focus to move 9759 // to, so let's just supply a "done" action. 9760 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 9761 } 9762 if (!shouldAdvanceFocusOnEnter()) { 9763 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 9764 } 9765 } 9766 if (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) { 9767 outAttrs.internalImeOptions |= EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT; 9768 } 9769 if (isMultilineInputType(outAttrs.inputType)) { 9770 // Multi-line text editors should always show an enter key. 9771 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 9772 } 9773 outAttrs.hintText = mHint; 9774 outAttrs.targetInputMethodUser = mTextOperationUser; 9775 if (mText instanceof Editable) { 9776 InputConnection ic = new EditableInputConnection(this); 9777 outAttrs.initialSelStart = getSelectionStart(); 9778 outAttrs.initialSelEnd = getSelectionEnd(); 9779 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); 9780 outAttrs.setInitialSurroundingText(mText); 9781 outAttrs.contentMimeTypes = getReceiveContentMimeTypes(); 9782 9783 ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>(); 9784 gestures.add(SelectGesture.class); 9785 gestures.add(SelectRangeGesture.class); 9786 gestures.add(DeleteGesture.class); 9787 gestures.add(DeleteRangeGesture.class); 9788 gestures.add(InsertGesture.class); 9789 gestures.add(RemoveSpaceGesture.class); 9790 gestures.add(JoinOrSplitGesture.class); 9791 gestures.add(InsertModeGesture.class); 9792 outAttrs.setSupportedHandwritingGestures(gestures); 9793 9794 Set<Class<? extends PreviewableHandwritingGesture>> previews = new ArraySet<>(); 9795 previews.add(SelectGesture.class); 9796 previews.add(SelectRangeGesture.class); 9797 previews.add(DeleteGesture.class); 9798 previews.add(DeleteRangeGesture.class); 9799 outAttrs.setSupportedHandwritingGesturePreviews(previews); 9800 9801 return ic; 9802 } 9803 } 9804 return null; 9805 } 9806 9807 /** 9808 * Called back by the system to handle {@link InputConnection#requestCursorUpdates(int, int)}. 9809 * 9810 * @param cursorUpdateMode modes defined in {@link InputConnection.CursorUpdateMode}. 9811 * @param cursorUpdateFilter modes defined in {@link InputConnection.CursorUpdateFilter}. 9812 * 9813 * @hide 9814 */ onRequestCursorUpdatesInternal( @nputConnection.CursorUpdateMode int cursorUpdateMode, @InputConnection.CursorUpdateFilter int cursorUpdateFilter)9815 public void onRequestCursorUpdatesInternal( 9816 @InputConnection.CursorUpdateMode int cursorUpdateMode, 9817 @InputConnection.CursorUpdateFilter int cursorUpdateFilter) { 9818 mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = cursorUpdateMode; 9819 mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = cursorUpdateFilter; 9820 if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) == 0) { 9821 return; 9822 } 9823 if (isInLayout()) { 9824 // In this case, the view hierarchy is currently undergoing a layout pass. 9825 // IMM#updateCursorAnchorInfo is supposed to be called soon after the layout 9826 // pass is finished. 9827 } else { 9828 // This will schedule a layout pass of the view tree, and the layout event 9829 // eventually triggers IMM#updateCursorAnchorInfo. 9830 requestLayout(); 9831 } 9832 } 9833 9834 /** 9835 * If this TextView contains editable content, extract a portion of it 9836 * based on the information in <var>request</var> in to <var>outText</var>. 9837 * @return Returns true if the text was successfully extracted, else false. 9838 */ extractText(ExtractedTextRequest request, ExtractedText outText)9839 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 9840 createEditorIfNeeded(); 9841 return mEditor.extractText(request, outText); 9842 } 9843 9844 /** 9845 * This is used to remove all style-impacting spans from text before new 9846 * extracted text is being replaced into it, so that we don't have any 9847 * lingering spans applied during the replace. 9848 */ removeParcelableSpans(Spannable spannable, int start, int end)9849 static void removeParcelableSpans(Spannable spannable, int start, int end) { 9850 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 9851 int i = spans.length; 9852 while (i > 0) { 9853 i--; 9854 spannable.removeSpan(spans[i]); 9855 } 9856 } 9857 9858 /** 9859 * Apply to this text view the given extracted text, as previously 9860 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 9861 */ setExtractedText(ExtractedText text)9862 public void setExtractedText(ExtractedText text) { 9863 Editable content = getEditableText(); 9864 if (text.text != null) { 9865 if (content == null) { 9866 setText(text.text, TextView.BufferType.EDITABLE); 9867 } else { 9868 int start = 0; 9869 int end = content.length(); 9870 9871 if (text.partialStartOffset >= 0) { 9872 final int N = content.length(); 9873 start = text.partialStartOffset; 9874 if (start > N) start = N; 9875 end = text.partialEndOffset; 9876 if (end > N) end = N; 9877 } 9878 9879 removeParcelableSpans(content, start, end); 9880 if (TextUtils.equals(content.subSequence(start, end), text.text)) { 9881 if (text.text instanceof Spanned) { 9882 // OK to copy spans only. 9883 TextUtils.copySpansFrom((Spanned) text.text, 0, end - start, 9884 Object.class, content, start); 9885 } 9886 } else { 9887 content.replace(start, end, text.text); 9888 } 9889 } 9890 } 9891 9892 // Now set the selection position... make sure it is in range, to 9893 // avoid crashes. If this is a partial update, it is possible that 9894 // the underlying text may have changed, causing us problems here. 9895 // Also we just don't want to trust clients to do the right thing. 9896 Spannable sp = (Spannable) getText(); 9897 final int N = sp.length(); 9898 int start = text.selectionStart; 9899 if (start < 0) { 9900 start = 0; 9901 } else if (start > N) { 9902 start = N; 9903 } 9904 int end = text.selectionEnd; 9905 if (end < 0) { 9906 end = 0; 9907 } else if (end > N) { 9908 end = N; 9909 } 9910 Selection.setSelection(sp, start, end); 9911 9912 // Finally, update the selection mode. 9913 if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) { 9914 MetaKeyKeyListener.startSelecting(this, sp); 9915 } else { 9916 MetaKeyKeyListener.stopSelecting(this, sp); 9917 } 9918 9919 setHintInternal(text.hint); 9920 } 9921 9922 /** 9923 * @hide 9924 */ setExtracting(ExtractedTextRequest req)9925 public void setExtracting(ExtractedTextRequest req) { 9926 if (mEditor.mInputMethodState != null) { 9927 mEditor.mInputMethodState.mExtractedTextRequest = req; 9928 } 9929 // This would stop a possible selection mode, but no such mode is started in case 9930 // extracted mode will start. Some text is selected though, and will trigger an action mode 9931 // in the extracted view. 9932 mEditor.hideCursorAndSpanControllers(); 9933 stopTextActionMode(); 9934 if (mEditor.mSelectionModifierCursorController != null) { 9935 mEditor.mSelectionModifierCursorController.resetTouchOffsets(); 9936 } 9937 } 9938 9939 /** 9940 * Called by the framework in response to a text completion from 9941 * the current input method, provided by it calling 9942 * {@link InputConnection#commitCompletion 9943 * InputConnection.commitCompletion()}. The default implementation does 9944 * nothing; text views that are supporting auto-completion should override 9945 * this to do their desired behavior. 9946 * 9947 * @param text The auto complete text the user has selected. 9948 */ onCommitCompletion(CompletionInfo text)9949 public void onCommitCompletion(CompletionInfo text) { 9950 // intentionally empty 9951 } 9952 9953 /** 9954 * Called by the framework in response to a text auto-correction (such as fixing a typo using a 9955 * dictionary) from the current input method, provided by it calling 9956 * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}. 9957 * The default implementation flashes the background of the corrected word to provide 9958 * feedback to the user. 9959 * 9960 * @param info The auto correct info about the text that was corrected. 9961 */ onCommitCorrection(CorrectionInfo info)9962 public void onCommitCorrection(CorrectionInfo info) { 9963 if (mEditor != null) mEditor.onCommitCorrection(info); 9964 } 9965 beginBatchEdit()9966 public void beginBatchEdit() { 9967 if (mEditor != null) mEditor.beginBatchEdit(); 9968 } 9969 endBatchEdit()9970 public void endBatchEdit() { 9971 if (mEditor != null) mEditor.endBatchEdit(); 9972 } 9973 9974 /** 9975 * Called by the framework in response to a request to begin a batch 9976 * of edit operations through a call to link {@link #beginBatchEdit()}. 9977 */ onBeginBatchEdit()9978 public void onBeginBatchEdit() { 9979 // intentionally empty 9980 } 9981 9982 /** 9983 * Called by the framework in response to a request to end a batch 9984 * of edit operations through a call to link {@link #endBatchEdit}. 9985 */ onEndBatchEdit()9986 public void onEndBatchEdit() { 9987 // intentionally empty 9988 } 9989 9990 /** @hide */ onPerformSpellCheck()9991 public void onPerformSpellCheck() { 9992 if (mEditor != null && mEditor.mSpellChecker != null) { 9993 mEditor.mSpellChecker.onPerformSpellCheck(); 9994 } 9995 } 9996 9997 /** 9998 * Called by the framework in response to a private command from the 9999 * current method, provided by it calling 10000 * {@link InputConnection#performPrivateCommand 10001 * InputConnection.performPrivateCommand()}. 10002 * 10003 * @param action The action name of the command. 10004 * @param data Any additional data for the command. This may be null. 10005 * @return Return true if you handled the command, else false. 10006 */ onPrivateIMECommand(String action, Bundle data)10007 public boolean onPrivateIMECommand(String action, Bundle data) { 10008 return false; 10009 } 10010 10011 /** 10012 * Return whether the text is transformed and has {@link OffsetMapping}. 10013 * @hide 10014 */ isOffsetMappingAvailable()10015 public boolean isOffsetMappingAvailable() { 10016 return mTransformation != null && mTransformed instanceof OffsetMapping; 10017 } 10018 10019 /** @hide */ previewHandwritingGesture( @onNull PreviewableHandwritingGesture gesture, @Nullable CancellationSignal cancellationSignal)10020 public boolean previewHandwritingGesture( 10021 @NonNull PreviewableHandwritingGesture gesture, 10022 @Nullable CancellationSignal cancellationSignal) { 10023 if (gesture instanceof SelectGesture) { 10024 performHandwritingSelectGesture((SelectGesture) gesture, /* isPreview= */ true); 10025 } else if (gesture instanceof SelectRangeGesture) { 10026 performHandwritingSelectRangeGesture( 10027 (SelectRangeGesture) gesture, /* isPreview= */ true); 10028 } else if (gesture instanceof DeleteGesture) { 10029 performHandwritingDeleteGesture((DeleteGesture) gesture, /* isPreview= */ true); 10030 } else if (gesture instanceof DeleteRangeGesture) { 10031 performHandwritingDeleteRangeGesture( 10032 (DeleteRangeGesture) gesture, /* isPreview= */ true); 10033 } else { 10034 return false; 10035 } 10036 if (cancellationSignal != null) { 10037 cancellationSignal.setOnCancelListener(this::clearGesturePreviewHighlight); 10038 } 10039 return true; 10040 } 10041 10042 /** @hide */ performHandwritingSelectGesture(@onNull SelectGesture gesture)10043 public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) { 10044 return performHandwritingSelectGesture(gesture, /* isPreview= */ false); 10045 } 10046 performHandwritingSelectGesture(@onNull SelectGesture gesture, boolean isPreview)10047 private int performHandwritingSelectGesture(@NonNull SelectGesture gesture, boolean isPreview) { 10048 if (isOffsetMappingAvailable()) { 10049 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10050 } 10051 int[] range = getRangeForRect( 10052 convertFromScreenToContentCoordinates(gesture.getSelectionArea()), 10053 gesture.getGranularity()); 10054 if (range == null) { 10055 return handleGestureFailure(gesture, isPreview); 10056 } 10057 return performHandwritingSelectGesture(range, isPreview); 10058 } 10059 performHandwritingSelectGesture(int[] range, boolean isPreview)10060 private int performHandwritingSelectGesture(int[] range, boolean isPreview) { 10061 if (isPreview) { 10062 setSelectGesturePreviewHighlight(range[0], range[1]); 10063 } else { 10064 Selection.setSelection(getEditableText(), range[0], range[1]); 10065 mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false); 10066 } 10067 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10068 } 10069 10070 /** @hide */ performHandwritingSelectRangeGesture(@onNull SelectRangeGesture gesture)10071 public int performHandwritingSelectRangeGesture(@NonNull SelectRangeGesture gesture) { 10072 return performHandwritingSelectRangeGesture(gesture, /* isPreview= */ false); 10073 } 10074 performHandwritingSelectRangeGesture( @onNull SelectRangeGesture gesture, boolean isPreview)10075 private int performHandwritingSelectRangeGesture( 10076 @NonNull SelectRangeGesture gesture, boolean isPreview) { 10077 if (isOffsetMappingAvailable()) { 10078 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10079 } 10080 int[] startRange = getRangeForRect( 10081 convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()), 10082 gesture.getGranularity()); 10083 if (startRange == null) { 10084 return handleGestureFailure(gesture, isPreview); 10085 } 10086 int[] endRange = getRangeForRect( 10087 convertFromScreenToContentCoordinates(gesture.getSelectionEndArea()), 10088 gesture.getGranularity()); 10089 if (endRange == null) { 10090 return handleGestureFailure(gesture, isPreview); 10091 } 10092 int[] range = new int[] { 10093 Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1]) 10094 }; 10095 return performHandwritingSelectGesture(range, isPreview); 10096 } 10097 10098 /** @hide */ performHandwritingDeleteGesture(@onNull DeleteGesture gesture)10099 public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) { 10100 return performHandwritingDeleteGesture(gesture, /* isPreview= */ false); 10101 } 10102 performHandwritingDeleteGesture(@onNull DeleteGesture gesture, boolean isPreview)10103 private int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture, boolean isPreview) { 10104 if (isOffsetMappingAvailable()) { 10105 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10106 } 10107 int[] range = getRangeForRect( 10108 convertFromScreenToContentCoordinates(gesture.getDeletionArea()), 10109 gesture.getGranularity()); 10110 if (range == null) { 10111 return handleGestureFailure(gesture, isPreview); 10112 } 10113 return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview); 10114 } 10115 performHandwritingDeleteGesture(int[] range, int granularity, boolean isPreview)10116 private int performHandwritingDeleteGesture(int[] range, int granularity, boolean isPreview) { 10117 if (isPreview) { 10118 setDeleteGesturePreviewHighlight(range[0], range[1]); 10119 } else { 10120 if (granularity == HandwritingGesture.GRANULARITY_WORD) { 10121 range = adjustHandwritingDeleteGestureRange(range); 10122 } 10123 10124 Selection.setSelection(getEditableText(), range[0]); 10125 getEditableText().delete(range[0], range[1]); 10126 } 10127 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10128 } 10129 10130 /** @hide */ performHandwritingDeleteRangeGesture(@onNull DeleteRangeGesture gesture)10131 public int performHandwritingDeleteRangeGesture(@NonNull DeleteRangeGesture gesture) { 10132 return performHandwritingDeleteRangeGesture(gesture, /* isPreview= */ false); 10133 } 10134 performHandwritingDeleteRangeGesture( @onNull DeleteRangeGesture gesture, boolean isPreview)10135 private int performHandwritingDeleteRangeGesture( 10136 @NonNull DeleteRangeGesture gesture, boolean isPreview) { 10137 if (isOffsetMappingAvailable()) { 10138 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10139 } 10140 int[] startRange = getRangeForRect( 10141 convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()), 10142 gesture.getGranularity()); 10143 if (startRange == null) { 10144 return handleGestureFailure(gesture, isPreview); 10145 } 10146 int[] endRange = getRangeForRect( 10147 convertFromScreenToContentCoordinates(gesture.getDeletionEndArea()), 10148 gesture.getGranularity()); 10149 if (endRange == null) { 10150 return handleGestureFailure(gesture, isPreview); 10151 } 10152 int[] range = new int[] { 10153 Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1]) 10154 }; 10155 return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview); 10156 } 10157 adjustHandwritingDeleteGestureRange(int[] range)10158 private int[] adjustHandwritingDeleteGestureRange(int[] range) { 10159 // For handwriting delete gestures with word granularity, adjust the start and end offsets 10160 // to remove extra whitespace around the deleted text. 10161 10162 int start = range[0]; 10163 int end = range[1]; 10164 10165 // If the deleted text is at the start of the text, the behavior is the same as the case 10166 // where the deleted text follows a new line character. 10167 int codePointBeforeStart = start > 0 10168 ? Character.codePointBefore(mText, start) : TextUtils.LINE_FEED_CODE_POINT; 10169 // If the deleted text is at the end of the text, the behavior is the same as the case where 10170 // the deleted text precedes a new line character. 10171 int codePointAtEnd = end < mText.length() 10172 ? Character.codePointAt(mText, end) : TextUtils.LINE_FEED_CODE_POINT; 10173 10174 if (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart) 10175 && (TextUtils.isWhitespace(codePointAtEnd) 10176 || TextUtils.isPunctuation(codePointAtEnd))) { 10177 // Remove whitespace (except new lines) before the deleted text, in these cases: 10178 // - There is whitespace following the deleted text 10179 // e.g. "one [deleted] three" -> "one | three" -> "one| three" 10180 // - There is punctuation following the deleted text 10181 // e.g. "one [deleted]!" -> "one |!" -> "one|!" 10182 // - There is a new line following the deleted text 10183 // e.g. "one [deleted]\n" -> "one |\n" -> "one|\n" 10184 // - The deleted text is at the end of the text 10185 // e.g. "one [deleted]" -> "one |" -> "one|" 10186 // (The pipe | indicates the cursor position.) 10187 do { 10188 start -= Character.charCount(codePointBeforeStart); 10189 if (start == 0) break; 10190 codePointBeforeStart = Character.codePointBefore(mText, start); 10191 } while (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)); 10192 return new int[] {start, end}; 10193 } 10194 10195 if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd) 10196 && (TextUtils.isWhitespace(codePointBeforeStart) 10197 || TextUtils.isPunctuation(codePointBeforeStart))) { 10198 // Remove whitespace (except new lines) after the deleted text, in these cases: 10199 // - There is punctuation preceding the deleted text 10200 // e.g. "([deleted] two)" -> "(| two)" -> "(|two)" 10201 // - There is a new line preceding the deleted text 10202 // e.g. "\n[deleted] two" -> "\n| two" -> "\n|two" 10203 // - The deleted text is at the start of the text 10204 // e.g. "[deleted] two" -> "| two" -> "|two" 10205 // (The pipe | indicates the cursor position.) 10206 do { 10207 end += Character.charCount(codePointAtEnd); 10208 if (end == mText.length()) break; 10209 codePointAtEnd = Character.codePointAt(mText, end); 10210 } while (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)); 10211 return new int[] {start, end}; 10212 } 10213 10214 // Return the original range. 10215 return range; 10216 } 10217 10218 /** @hide */ 10219 public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) { 10220 if (isOffsetMappingAvailable()) { 10221 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10222 } 10223 PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint()); 10224 int line = getLineForHandwritingGesture(point); 10225 if (line == -1) { 10226 return handleGestureFailure(gesture); 10227 } 10228 int offset = mLayout.getOffsetForHorizontal(line, point.x); 10229 String textToInsert = gesture.getTextToInsert(); 10230 return tryInsertTextForHandwritingGesture(offset, textToInsert, gesture); 10231 // TODO(b/243980426): Insert extra spaces if necessary. 10232 } 10233 10234 /** @hide */ 10235 public int performHandwritingRemoveSpaceGesture(@NonNull RemoveSpaceGesture gesture) { 10236 if (isOffsetMappingAvailable()) { 10237 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10238 } 10239 PointF startPoint = convertFromScreenToContentCoordinates(gesture.getStartPoint()); 10240 PointF endPoint = convertFromScreenToContentCoordinates(gesture.getEndPoint()); 10241 10242 // The operation should be applied to the first line of text containing one of the points. 10243 int startPointLine = getLineForHandwritingGesture(startPoint); 10244 int endPointLine = getLineForHandwritingGesture(endPoint); 10245 int line; 10246 if (startPointLine == -1) { 10247 if (endPointLine == -1) { 10248 return handleGestureFailure(gesture); 10249 } 10250 line = endPointLine; 10251 } else { 10252 line = (endPointLine == -1) ? startPointLine : Math.min(startPointLine, endPointLine); 10253 } 10254 10255 // The operation should be applied to all characters touched by the line joining the points. 10256 float lineVerticalCenter = (mLayout.getLineTop(line) 10257 + mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) / 2f; 10258 // Create a rectangle which is +/-0.1f around the line's vertical center, so that the 10259 // rectangle doesn't touch the line above or below. (The line height is at least 1f.) 10260 RectF area = new RectF( 10261 Math.min(startPoint.x, endPoint.x), 10262 lineVerticalCenter + 0.1f, 10263 Math.max(startPoint.x, endPoint.x), 10264 lineVerticalCenter - 0.1f); 10265 int[] range = mLayout.getRangeForRect( 10266 area, new GraphemeClusterSegmentFinder(mText, mTextPaint), 10267 Layout.INCLUSION_STRATEGY_ANY_OVERLAP); 10268 if (range == null) { 10269 return handleGestureFailure(gesture); 10270 } 10271 int startOffset = range[0]; 10272 int endOffset = range[1]; 10273 // TODO(b/247557062): This doesn't handle bidirectional text correctly. 10274 10275 Pattern whitespacePattern = getWhitespacePattern(); 10276 Matcher matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset)); 10277 int lastRemoveOffset = -1; 10278 while (matcher.find()) { 10279 lastRemoveOffset = startOffset + matcher.start(); 10280 getEditableText().delete(lastRemoveOffset, startOffset + matcher.end()); 10281 startOffset = lastRemoveOffset; 10282 endOffset -= matcher.end() - matcher.start(); 10283 if (startOffset == endOffset) { 10284 break; 10285 } 10286 matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset)); 10287 } 10288 if (lastRemoveOffset == -1) { 10289 return handleGestureFailure(gesture); 10290 } 10291 Selection.setSelection(getEditableText(), lastRemoveOffset); 10292 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10293 } 10294 10295 /** @hide */ 10296 public int performHandwritingJoinOrSplitGesture(@NonNull JoinOrSplitGesture gesture) { 10297 if (isOffsetMappingAvailable()) { 10298 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10299 } 10300 PointF point = convertFromScreenToContentCoordinates(gesture.getJoinOrSplitPoint()); 10301 10302 int line = getLineForHandwritingGesture(point); 10303 if (line == -1) { 10304 return handleGestureFailure(gesture); 10305 } 10306 10307 int startOffset = mLayout.getOffsetForHorizontal(line, point.x); 10308 if (mLayout.isLevelBoundary(startOffset)) { 10309 // TODO(b/247551937): Support gesture at level boundaries. 10310 return handleGestureFailure(gesture); 10311 } 10312 10313 int endOffset = startOffset; 10314 while (startOffset > 0) { 10315 int codePointBeforeStart = Character.codePointBefore(mText, startOffset); 10316 if (!TextUtils.isWhitespace(codePointBeforeStart)) { 10317 break; 10318 } 10319 startOffset -= Character.charCount(codePointBeforeStart); 10320 } 10321 while (endOffset < mText.length()) { 10322 int codePointAtEnd = Character.codePointAt(mText, endOffset); 10323 if (!TextUtils.isWhitespace(codePointAtEnd)) { 10324 break; 10325 } 10326 endOffset += Character.charCount(codePointAtEnd); 10327 } 10328 if (startOffset < endOffset) { 10329 Selection.setSelection(getEditableText(), startOffset); 10330 getEditableText().delete(startOffset, endOffset); 10331 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10332 } else { 10333 // No whitespace found, so insert a space. 10334 return tryInsertTextForHandwritingGesture(startOffset, " ", gesture); 10335 } 10336 } 10337 10338 /** @hide */ 10339 public int performHandwritingInsertModeGesture(@NonNull InsertModeGesture gesture) { 10340 final PointF insertPoint = 10341 convertFromScreenToContentCoordinates(gesture.getInsertionPoint()); 10342 final int line = getLineForHandwritingGesture(insertPoint); 10343 final CancellationSignal cancellationSignal = gesture.getCancellationSignal(); 10344 10345 // If no cancellationSignal is provided, don't enter the insert mode. 10346 if (line == -1 || cancellationSignal == null) { 10347 return handleGestureFailure(gesture); 10348 } 10349 10350 final int offset = mLayout.getOffsetForHorizontal(line, insertPoint.x); 10351 10352 if (!mEditor.enterInsertMode(offset)) { 10353 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10354 } 10355 cancellationSignal.setOnCancelListener(() -> mEditor.exitInsertMode()); 10356 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10357 } 10358 10359 private int handleGestureFailure(HandwritingGesture gesture) { 10360 return handleGestureFailure(gesture, /* isPreview= */ false); 10361 } 10362 10363 private int handleGestureFailure(HandwritingGesture gesture, boolean isPreview) { 10364 clearGesturePreviewHighlight(); 10365 if (!isPreview && !TextUtils.isEmpty(gesture.getFallbackText())) { 10366 getEditableText() 10367 .replace(getSelectionStart(), getSelectionEnd(), gesture.getFallbackText()); 10368 return InputConnection.HANDWRITING_GESTURE_RESULT_FALLBACK; 10369 } 10370 return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED; 10371 } 10372 10373 /** 10374 * Returns the closest line such that the point is either inside the line bounds or within 10375 * {@link ViewConfiguration#getScaledHandwritingGestureLineMargin} of the line bounds. Returns 10376 * -1 if the point is not within the margin of any line bounds. 10377 */ 10378 private int getLineForHandwritingGesture(PointF point) { 10379 int line = mLayout.getLineForVertical((int) point.y); 10380 int lineMargin = ViewConfiguration.get(mContext).getScaledHandwritingGestureLineMargin(); 10381 if (line < mLayout.getLineCount() - 1 10382 && point.y > mLayout.getLineBottom(line) - lineMargin 10383 && point.y 10384 > (mLayout.getLineBottom(line, false) + mLayout.getLineBottom(line)) / 2f) { 10385 // If a point is in the space between line i and line (i + 1), Layout#getLineForVertical 10386 // returns i. If the point is within lineMargin of line (i + 1), and closer to line 10387 // (i + 1) than line i, then the gesture operation should be applied to line (i + 1). 10388 line++; 10389 } else if (point.y < mLayout.getLineTop(line) - lineMargin 10390 || point.y 10391 > mLayout.getLineBottom(line, /* includeLineSpacing= */ false) 10392 + lineMargin) { 10393 // The point is not within lineMargin of a line. 10394 return -1; 10395 } 10396 if (point.x < -lineMargin || point.x > mLayout.getWidth() + lineMargin) { 10397 // The point is not within lineMargin of a line. 10398 return -1; 10399 } 10400 return line; 10401 } 10402 10403 @Nullable 10404 private int[] getRangeForRect(@NonNull RectF area, int granularity) { 10405 SegmentFinder segmentFinder; 10406 if (granularity == HandwritingGesture.GRANULARITY_WORD) { 10407 WordIterator wordIterator = getWordIterator(); 10408 wordIterator.setCharSequence(mText, 0, mText.length()); 10409 segmentFinder = new WordSegmentFinder(mText, wordIterator); 10410 } else { 10411 segmentFinder = new GraphemeClusterSegmentFinder(mText, mTextPaint); 10412 } 10413 10414 return mLayout.getRangeForRect( 10415 area, segmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER); 10416 } 10417 10418 private int tryInsertTextForHandwritingGesture( 10419 int offset, String textToInsert, HandwritingGesture gesture) { 10420 // A temporary cursor span is placed at the insertion offset. The span will be pushed 10421 // forward when text is inserted, then the real cursor can be placed after the inserted 10422 // text. A temporary cursor span is used in order to avoid modifying the real selection span 10423 // in the case that the text is filtered out. 10424 Editable editableText = getEditableText(); 10425 if (mTempCursor == null) { 10426 mTempCursor = new NoCopySpan.Concrete(); 10427 } 10428 editableText.setSpan(mTempCursor, offset, offset, Spanned.SPAN_POINT_POINT); 10429 10430 editableText.insert(offset, textToInsert); 10431 10432 int newOffset = editableText.getSpanStart(mTempCursor); 10433 editableText.removeSpan(mTempCursor); 10434 if (newOffset == offset) { 10435 // The inserted text was filtered out. 10436 return handleGestureFailure(gesture); 10437 } else { 10438 // Place the cursor after the inserted text. 10439 Selection.setSelection(editableText, newOffset); 10440 return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; 10441 } 10442 } 10443 10444 private Pattern getWhitespacePattern() { 10445 if (mWhitespacePattern == null) { 10446 mWhitespacePattern = Pattern.compile("\\s+"); 10447 } 10448 return mWhitespacePattern; 10449 } 10450 10451 /** @hide */ 10452 @VisibleForTesting 10453 @UnsupportedAppUsage 10454 public void nullLayouts() { 10455 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 10456 mSavedLayout = (BoringLayout) mLayout; 10457 } 10458 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 10459 mSavedHintLayout = (BoringLayout) mHintLayout; 10460 } 10461 10462 mSavedMarqueeModeLayout = mLayout = mHintLayout = null; 10463 10464 mBoring = mHintBoring = null; 10465 10466 // Since it depends on the value of mLayout 10467 if (mEditor != null) mEditor.prepareCursorControllers(); 10468 } 10469 10470 /** 10471 * Make a new Layout based on the already-measured size of the view, 10472 * on the assumption that it was measured correctly at some point. 10473 */ 10474 @UnsupportedAppUsage 10475 private void assumeLayout() { 10476 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10477 10478 if (width < 1) { 10479 width = 0; 10480 } 10481 10482 int physicalWidth = width; 10483 10484 if (mHorizontallyScrolling) { 10485 width = VERY_WIDE; 10486 } 10487 10488 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 10489 physicalWidth, false); 10490 } 10491 10492 @UnsupportedAppUsage 10493 private Layout.Alignment getLayoutAlignment() { 10494 Layout.Alignment alignment; 10495 switch (getTextAlignment()) { 10496 case TEXT_ALIGNMENT_GRAVITY: 10497 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { 10498 case Gravity.START: 10499 alignment = Layout.Alignment.ALIGN_NORMAL; 10500 break; 10501 case Gravity.END: 10502 alignment = Layout.Alignment.ALIGN_OPPOSITE; 10503 break; 10504 case Gravity.LEFT: 10505 alignment = Layout.Alignment.ALIGN_LEFT; 10506 break; 10507 case Gravity.RIGHT: 10508 alignment = Layout.Alignment.ALIGN_RIGHT; 10509 break; 10510 case Gravity.CENTER_HORIZONTAL: 10511 alignment = Layout.Alignment.ALIGN_CENTER; 10512 break; 10513 default: 10514 alignment = Layout.Alignment.ALIGN_NORMAL; 10515 break; 10516 } 10517 break; 10518 case TEXT_ALIGNMENT_TEXT_START: 10519 alignment = Layout.Alignment.ALIGN_NORMAL; 10520 break; 10521 case TEXT_ALIGNMENT_TEXT_END: 10522 alignment = Layout.Alignment.ALIGN_OPPOSITE; 10523 break; 10524 case TEXT_ALIGNMENT_CENTER: 10525 alignment = Layout.Alignment.ALIGN_CENTER; 10526 break; 10527 case TEXT_ALIGNMENT_VIEW_START: 10528 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 10529 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 10530 break; 10531 case TEXT_ALIGNMENT_VIEW_END: 10532 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) 10533 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 10534 break; 10535 case TEXT_ALIGNMENT_INHERIT: 10536 // This should never happen as we have already resolved the text alignment 10537 // but better safe than sorry so we just fall through 10538 default: 10539 alignment = Layout.Alignment.ALIGN_NORMAL; 10540 break; 10541 } 10542 return alignment; 10543 } 10544 10545 /** 10546 * The width passed in is now the desired layout width, 10547 * not the full view width with padding. 10548 * {@hide} 10549 */ 10550 @VisibleForTesting 10551 @UnsupportedAppUsage 10552 public void makeNewLayout(int wantWidth, int hintWidth, 10553 BoringLayout.Metrics boring, 10554 BoringLayout.Metrics hintBoring, 10555 int ellipsisWidth, boolean bringIntoView) { 10556 stopMarquee(); 10557 10558 // Update "old" cached values 10559 mOldMaximum = mMaximum; 10560 mOldMaxMode = mMaxMode; 10561 10562 mHighlightPathBogus = true; 10563 mHighlightPathsBogus = true; 10564 10565 if (wantWidth < 0) { 10566 wantWidth = 0; 10567 } 10568 if (hintWidth < 0) { 10569 hintWidth = 0; 10570 } 10571 10572 Layout.Alignment alignment = getLayoutAlignment(); 10573 final boolean testDirChange = mSingleLine && mLayout != null 10574 && (alignment == Layout.Alignment.ALIGN_NORMAL 10575 || alignment == Layout.Alignment.ALIGN_OPPOSITE); 10576 int oldDir = 0; 10577 if (testDirChange) oldDir = mLayout.getParagraphDirection(0); 10578 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; 10579 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE 10580 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL; 10581 TruncateAt effectiveEllipsize = mEllipsize; 10582 if (mEllipsize == TruncateAt.MARQUEE 10583 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 10584 effectiveEllipsize = TruncateAt.END_SMALL; 10585 } 10586 10587 if (mTextDir == null) { 10588 mTextDir = getTextDirectionHeuristic(); 10589 } 10590 10591 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, 10592 effectiveEllipsize, effectiveEllipsize == mEllipsize); 10593 if (switchEllipsize) { 10594 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE 10595 ? TruncateAt.END : TruncateAt.MARQUEE; 10596 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, 10597 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); 10598 } 10599 10600 shouldEllipsize = mEllipsize != null; 10601 mHintLayout = null; 10602 10603 if (mHint != null) { 10604 if (shouldEllipsize) hintWidth = wantWidth; 10605 10606 if (hintBoring == UNKNOWN_BORING) { 10607 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 10608 isFallbackLineSpacingForBoringLayout(), mHintBoring); 10609 if (hintBoring != null) { 10610 mHintBoring = hintBoring; 10611 } 10612 } 10613 10614 if (hintBoring != null) { 10615 if (hintBoring.width <= hintWidth 10616 && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { 10617 if (mSavedHintLayout != null) { 10618 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 10619 hintWidth, alignment, mSpacingMult, mSpacingAdd, 10620 hintBoring, mIncludePad); 10621 } else { 10622 mHintLayout = BoringLayout.make(mHint, mTextPaint, 10623 hintWidth, alignment, mSpacingMult, mSpacingAdd, 10624 hintBoring, mIncludePad); 10625 } 10626 10627 mSavedHintLayout = (BoringLayout) mHintLayout; 10628 } else if (shouldEllipsize && hintBoring.width <= hintWidth) { 10629 if (mSavedHintLayout != null) { 10630 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, 10631 hintWidth, alignment, mSpacingMult, mSpacingAdd, 10632 hintBoring, mIncludePad, mEllipsize, 10633 ellipsisWidth); 10634 } else { 10635 mHintLayout = BoringLayout.make(mHint, mTextPaint, 10636 hintWidth, alignment, mSpacingMult, mSpacingAdd, 10637 hintBoring, mIncludePad, mEllipsize, 10638 ellipsisWidth); 10639 } 10640 } 10641 } 10642 // TODO: code duplication with makeSingleLayout() 10643 if (mHintLayout == null) { 10644 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, 10645 mHint.length(), mTextPaint, hintWidth) 10646 .setAlignment(alignment) 10647 .setTextDirection(mTextDir) 10648 .setLineSpacing(mSpacingAdd, mSpacingMult) 10649 .setIncludePad(mIncludePad) 10650 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) 10651 .setBreakStrategy(mBreakStrategy) 10652 .setHyphenationFrequency(mHyphenationFrequency) 10653 .setJustificationMode(mJustificationMode) 10654 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 10655 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( 10656 mLineBreakStyle, mLineBreakWordStyle)); 10657 if (shouldEllipsize) { 10658 builder.setEllipsize(mEllipsize) 10659 .setEllipsizedWidth(ellipsisWidth); 10660 } 10661 mHintLayout = builder.build(); 10662 } 10663 } 10664 10665 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) { 10666 registerForPreDraw(); 10667 } 10668 10669 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 10670 if (!compressText(ellipsisWidth)) { 10671 final int height = mLayoutParams.height; 10672 // If the size of the view does not depend on the size of the text, try to 10673 // start the marquee immediately 10674 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { 10675 startMarquee(); 10676 } else { 10677 // Defer the start of the marquee until we know our width (see setFrame()) 10678 mRestartMarquee = true; 10679 } 10680 } 10681 } 10682 10683 // CursorControllers need a non-null mLayout 10684 if (mEditor != null) mEditor.prepareCursorControllers(); 10685 } 10686 10687 /** 10688 * Returns true if DynamicLayout is required 10689 * 10690 * @hide 10691 */ 10692 @VisibleForTesting 10693 public boolean useDynamicLayout() { 10694 return isTextSelectable() || (mSpannable != null && mPrecomputed == null); 10695 } 10696 10697 /** 10698 * @hide 10699 */ 10700 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 10701 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, 10702 boolean useSaved) { 10703 Layout result = null; 10704 if (useDynamicLayout()) { 10705 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint, 10706 wantWidth) 10707 .setDisplayText(mTransformed) 10708 .setAlignment(alignment) 10709 .setTextDirection(mTextDir) 10710 .setLineSpacing(mSpacingAdd, mSpacingMult) 10711 .setIncludePad(mIncludePad) 10712 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) 10713 .setBreakStrategy(mBreakStrategy) 10714 .setHyphenationFrequency(mHyphenationFrequency) 10715 .setJustificationMode(mJustificationMode) 10716 .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null) 10717 .setEllipsizedWidth(ellipsisWidth); 10718 result = builder.build(); 10719 } else { 10720 if (boring == UNKNOWN_BORING) { 10721 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, 10722 isFallbackLineSpacingForBoringLayout(), mBoring); 10723 if (boring != null) { 10724 mBoring = boring; 10725 } 10726 } 10727 10728 if (boring != null) { 10729 if (boring.width <= wantWidth 10730 && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { 10731 if (useSaved && mSavedLayout != null) { 10732 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 10733 wantWidth, alignment, mSpacingMult, mSpacingAdd, 10734 boring, mIncludePad); 10735 } else { 10736 result = BoringLayout.make(mTransformed, mTextPaint, 10737 wantWidth, alignment, mSpacingMult, mSpacingAdd, 10738 boring, mIncludePad); 10739 } 10740 10741 if (useSaved) { 10742 mSavedLayout = (BoringLayout) result; 10743 } 10744 } else if (shouldEllipsize && boring.width <= wantWidth) { 10745 if (useSaved && mSavedLayout != null) { 10746 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 10747 wantWidth, alignment, mSpacingMult, mSpacingAdd, 10748 boring, mIncludePad, effectiveEllipsize, 10749 ellipsisWidth); 10750 } else { 10751 result = BoringLayout.make(mTransformed, mTextPaint, 10752 wantWidth, alignment, mSpacingMult, mSpacingAdd, 10753 boring, mIncludePad, effectiveEllipsize, 10754 ellipsisWidth); 10755 } 10756 } 10757 } 10758 } 10759 if (result == null) { 10760 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 10761 0, mTransformed.length(), mTextPaint, wantWidth) 10762 .setAlignment(alignment) 10763 .setTextDirection(mTextDir) 10764 .setLineSpacing(mSpacingAdd, mSpacingMult) 10765 .setIncludePad(mIncludePad) 10766 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) 10767 .setBreakStrategy(mBreakStrategy) 10768 .setHyphenationFrequency(mHyphenationFrequency) 10769 .setJustificationMode(mJustificationMode) 10770 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 10771 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( 10772 mLineBreakStyle, mLineBreakWordStyle)); 10773 if (shouldEllipsize) { 10774 builder.setEllipsize(effectiveEllipsize) 10775 .setEllipsizedWidth(ellipsisWidth); 10776 } 10777 result = builder.build(); 10778 } 10779 return result; 10780 } 10781 10782 @UnsupportedAppUsage 10783 private boolean compressText(float width) { 10784 if (isHardwareAccelerated()) return false; 10785 10786 // Only compress the text if it hasn't been compressed by the previous pass 10787 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX 10788 && mTextPaint.getTextScaleX() == 1.0f) { 10789 final float textWidth = mLayout.getLineWidth(0); 10790 final float overflow = (textWidth + 1.0f - width) / width; 10791 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 10792 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 10793 post(new Runnable() { 10794 public void run() { 10795 requestLayout(); 10796 } 10797 }); 10798 return true; 10799 } 10800 } 10801 10802 return false; 10803 } 10804 10805 private static int desired(Layout layout) { 10806 int n = layout.getLineCount(); 10807 CharSequence text = layout.getText(); 10808 float max = 0; 10809 10810 // if any line was wrapped, we can't use it. 10811 // but it's ok for the last line not to have a newline 10812 10813 for (int i = 0; i < n - 1; i++) { 10814 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') { 10815 return -1; 10816 } 10817 } 10818 10819 for (int i = 0; i < n; i++) { 10820 max = Math.max(max, layout.getLineMax(i)); 10821 } 10822 10823 return (int) Math.ceil(max); 10824 } 10825 10826 /** 10827 * Set whether the TextView includes extra top and bottom padding to make 10828 * room for accents that go above the normal ascent and descent. 10829 * The default is true. 10830 * 10831 * @see #getIncludeFontPadding() 10832 * 10833 * @attr ref android.R.styleable#TextView_includeFontPadding 10834 */ 10835 public void setIncludeFontPadding(boolean includepad) { 10836 if (mIncludePad != includepad) { 10837 mIncludePad = includepad; 10838 10839 if (mLayout != null) { 10840 nullLayouts(); 10841 requestLayout(); 10842 invalidate(); 10843 } 10844 } 10845 } 10846 10847 /** 10848 * Gets whether the TextView includes extra top and bottom padding to make 10849 * room for accents that go above the normal ascent and descent. 10850 * 10851 * @see #setIncludeFontPadding(boolean) 10852 * 10853 * @attr ref android.R.styleable#TextView_includeFontPadding 10854 */ 10855 @InspectableProperty 10856 public boolean getIncludeFontPadding() { 10857 return mIncludePad; 10858 } 10859 10860 /** @hide */ 10861 @VisibleForTesting 10862 public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 10863 10864 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)10865 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 10866 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 10867 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 10868 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 10869 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 10870 10871 int width; 10872 int height; 10873 10874 BoringLayout.Metrics boring = UNKNOWN_BORING; 10875 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 10876 10877 if (mTextDir == null) { 10878 mTextDir = getTextDirectionHeuristic(); 10879 } 10880 10881 int des = -1; 10882 boolean fromexisting = false; 10883 final float widthLimit = (widthMode == MeasureSpec.AT_MOST) 10884 ? (float) widthSize : Float.MAX_VALUE; 10885 10886 if (widthMode == MeasureSpec.EXACTLY) { 10887 // Parent has told us how big to be. So be it. 10888 width = widthSize; 10889 } else { 10890 if (mLayout != null && mEllipsize == null) { 10891 des = desired(mLayout); 10892 } 10893 10894 if (des < 0) { 10895 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, 10896 isFallbackLineSpacingForBoringLayout(), mBoring); 10897 if (boring != null) { 10898 mBoring = boring; 10899 } 10900 } else { 10901 fromexisting = true; 10902 } 10903 10904 if (boring == null || boring == UNKNOWN_BORING) { 10905 if (des < 0) { 10906 des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0, 10907 mTransformed.length(), mTextPaint, mTextDir, widthLimit)); 10908 } 10909 width = des; 10910 } else { 10911 width = boring.width; 10912 } 10913 10914 final Drawables dr = mDrawables; 10915 if (dr != null) { 10916 width = Math.max(width, dr.mDrawableWidthTop); 10917 width = Math.max(width, dr.mDrawableWidthBottom); 10918 } 10919 10920 if (mHint != null) { 10921 int hintDes = -1; 10922 int hintWidth; 10923 10924 if (mHintLayout != null && mEllipsize == null) { 10925 hintDes = desired(mHintLayout); 10926 } 10927 10928 if (hintDes < 0) { 10929 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 10930 isFallbackLineSpacingForBoringLayout(), mHintBoring); 10931 if (hintBoring != null) { 10932 mHintBoring = hintBoring; 10933 } 10934 } 10935 10936 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 10937 if (hintDes < 0) { 10938 hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0, 10939 mHint.length(), mTextPaint, mTextDir, widthLimit)); 10940 } 10941 hintWidth = hintDes; 10942 } else { 10943 hintWidth = hintBoring.width; 10944 } 10945 10946 if (hintWidth > width) { 10947 width = hintWidth; 10948 } 10949 } 10950 10951 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 10952 10953 if (mMaxWidthMode == EMS) { 10954 width = Math.min(width, mMaxWidth * getLineHeight()); 10955 } else { 10956 width = Math.min(width, mMaxWidth); 10957 } 10958 10959 if (mMinWidthMode == EMS) { 10960 width = Math.max(width, mMinWidth * getLineHeight()); 10961 } else { 10962 width = Math.max(width, mMinWidth); 10963 } 10964 10965 // Check against our minimum width 10966 width = Math.max(width, getSuggestedMinimumWidth()); 10967 10968 if (widthMode == MeasureSpec.AT_MOST) { 10969 width = Math.min(widthSize, width); 10970 } 10971 } 10972 10973 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 10974 int unpaddedWidth = want; 10975 10976 if (mHorizontallyScrolling) want = VERY_WIDE; 10977 10978 int hintWant = want; 10979 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); 10980 10981 if (mLayout == null) { 10982 makeNewLayout(want, hintWant, boring, hintBoring, 10983 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 10984 } else { 10985 final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant) 10986 || (mLayout.getEllipsizedWidth() 10987 != width - getCompoundPaddingLeft() - getCompoundPaddingRight()); 10988 10989 final boolean widthChanged = (mHint == null) && (mEllipsize == null) 10990 && (want > mLayout.getWidth()) 10991 && (mLayout instanceof BoringLayout 10992 || (fromexisting && des >= 0 && des <= want)); 10993 10994 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); 10995 10996 if (layoutChanged || maximumChanged) { 10997 if (!maximumChanged && widthChanged) { 10998 mLayout.increaseWidthTo(want); 10999 } else { 11000 makeNewLayout(want, hintWant, boring, hintBoring, 11001 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 11002 } 11003 } else { 11004 // Nothing has changed 11005 } 11006 } 11007 11008 if (heightMode == MeasureSpec.EXACTLY) { 11009 // Parent has told us how big to be. So be it. 11010 height = heightSize; 11011 mDesiredHeightAtMeasure = -1; 11012 } else { 11013 int desired = getDesiredHeight(); 11014 11015 height = desired; 11016 mDesiredHeightAtMeasure = desired; 11017 11018 if (heightMode == MeasureSpec.AT_MOST) { 11019 height = Math.min(desired, heightSize); 11020 } 11021 } 11022 11023 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); 11024 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 11025 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); 11026 } 11027 11028 /* 11029 * We didn't let makeNewLayout() register to bring the cursor into view, 11030 * so do it here if there is any possibility that it is needed. 11031 */ 11032 if (mMovement != null 11033 || mLayout.getWidth() > unpaddedWidth 11034 || mLayout.getHeight() > unpaddedHeight) { 11035 registerForPreDraw(); 11036 } else { 11037 scrollTo(0, 0); 11038 } 11039 11040 setMeasuredDimension(width, height); 11041 } 11042 11043 /** 11044 * Automatically computes and sets the text size. 11045 */ autoSizeText()11046 private void autoSizeText() { 11047 if (!isAutoSizeEnabled()) { 11048 return; 11049 } 11050 11051 if (mNeedsAutoSizeText) { 11052 if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) { 11053 return; 11054 } 11055 11056 final int availableWidth = mHorizontallyScrolling 11057 ? VERY_WIDE 11058 : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight(); 11059 final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom() 11060 - getExtendedPaddingTop(); 11061 11062 if (availableWidth <= 0 || availableHeight <= 0) { 11063 return; 11064 } 11065 11066 synchronized (TEMP_RECTF) { 11067 TEMP_RECTF.setEmpty(); 11068 TEMP_RECTF.right = availableWidth; 11069 TEMP_RECTF.bottom = availableHeight; 11070 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF); 11071 11072 if (optimalTextSize != getTextSize()) { 11073 setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize, 11074 false /* shouldRequestLayout */); 11075 11076 makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING, 11077 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 11078 false /* bringIntoView */); 11079 } 11080 } 11081 } 11082 // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing 11083 // after the next layout pass should set this to false. 11084 mNeedsAutoSizeText = true; 11085 } 11086 11087 /** 11088 * Performs a binary search to find the largest text size that will still fit within the size 11089 * available to this view. 11090 */ findLargestTextSizeWhichFits(RectF availableSpace)11091 private int findLargestTextSizeWhichFits(RectF availableSpace) { 11092 final int sizesCount = mAutoSizeTextSizesInPx.length; 11093 if (sizesCount == 0) { 11094 throw new IllegalStateException("No available text sizes to choose from."); 11095 } 11096 11097 int bestSizeIndex = 0; 11098 int lowIndex = bestSizeIndex + 1; 11099 int highIndex = sizesCount - 1; 11100 int sizeToTryIndex; 11101 while (lowIndex <= highIndex) { 11102 sizeToTryIndex = (lowIndex + highIndex) / 2; 11103 if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) { 11104 bestSizeIndex = lowIndex; 11105 lowIndex = sizeToTryIndex + 1; 11106 } else { 11107 highIndex = sizeToTryIndex - 1; 11108 bestSizeIndex = highIndex; 11109 } 11110 } 11111 11112 return mAutoSizeTextSizesInPx[bestSizeIndex]; 11113 } 11114 suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace)11115 private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) { 11116 final CharSequence text = mTransformed != null 11117 ? mTransformed 11118 : getText(); 11119 final int maxLines = getMaxLines(); 11120 if (mTempTextPaint == null) { 11121 mTempTextPaint = new TextPaint(); 11122 } else { 11123 mTempTextPaint.reset(); 11124 } 11125 mTempTextPaint.set(getPaint()); 11126 mTempTextPaint.setTextSize(suggestedSizeInPx); 11127 11128 final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain( 11129 text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right)); 11130 layoutBuilder.setAlignment(getLayoutAlignment()) 11131 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) 11132 .setIncludePad(getIncludeFontPadding()) 11133 .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) 11134 .setBreakStrategy(getBreakStrategy()) 11135 .setHyphenationFrequency(getHyphenationFrequency()) 11136 .setJustificationMode(getJustificationMode()) 11137 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) 11138 .setTextDirection(getTextDirectionHeuristic()) 11139 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( 11140 mLineBreakStyle, mLineBreakWordStyle)); 11141 11142 final StaticLayout layout = layoutBuilder.build(); 11143 11144 // Lines overflow. 11145 if (maxLines != -1 && layout.getLineCount() > maxLines) { 11146 return false; 11147 } 11148 11149 // Height overflow. 11150 if (layout.getHeight() > availableSpace.bottom) { 11151 return false; 11152 } 11153 11154 return true; 11155 } 11156 getDesiredHeight()11157 private int getDesiredHeight() { 11158 return Math.max( 11159 getDesiredHeight(mLayout, true), 11160 getDesiredHeight(mHintLayout, mEllipsize != null)); 11161 } 11162 getDesiredHeight(Layout layout, boolean cap)11163 private int getDesiredHeight(Layout layout, boolean cap) { 11164 if (layout == null) { 11165 return 0; 11166 } 11167 11168 /* 11169 * Don't cap the hint to a certain number of lines. 11170 * (Do cap it, though, if we have a maximum pixel height.) 11171 */ 11172 int desired = layout.getHeight(cap); 11173 11174 final Drawables dr = mDrawables; 11175 if (dr != null) { 11176 desired = Math.max(desired, dr.mDrawableHeightLeft); 11177 desired = Math.max(desired, dr.mDrawableHeightRight); 11178 } 11179 11180 int linecount = layout.getLineCount(); 11181 final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom(); 11182 desired += padding; 11183 11184 if (mMaxMode != LINES) { 11185 desired = Math.min(desired, mMaximum); 11186 } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout 11187 || layout instanceof BoringLayout)) { 11188 desired = layout.getLineTop(mMaximum); 11189 11190 if (dr != null) { 11191 desired = Math.max(desired, dr.mDrawableHeightLeft); 11192 desired = Math.max(desired, dr.mDrawableHeightRight); 11193 } 11194 11195 desired += padding; 11196 linecount = mMaximum; 11197 } 11198 11199 if (mMinMode == LINES) { 11200 if (linecount < mMinimum) { 11201 desired += getLineHeight() * (mMinimum - linecount); 11202 } 11203 } else { 11204 desired = Math.max(desired, mMinimum); 11205 } 11206 11207 // Check against our minimum height 11208 desired = Math.max(desired, getSuggestedMinimumHeight()); 11209 11210 return desired; 11211 } 11212 11213 /** 11214 * Check whether a change to the existing text layout requires a 11215 * new view layout. 11216 */ checkForResize()11217 private void checkForResize() { 11218 boolean sizeChanged = false; 11219 11220 if (mLayout != null) { 11221 // Check if our width changed 11222 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 11223 sizeChanged = true; 11224 invalidate(); 11225 } 11226 11227 // Check if our height changed 11228 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 11229 int desiredHeight = getDesiredHeight(); 11230 11231 if (desiredHeight != this.getHeight()) { 11232 sizeChanged = true; 11233 } 11234 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { 11235 if (mDesiredHeightAtMeasure >= 0) { 11236 int desiredHeight = getDesiredHeight(); 11237 11238 if (desiredHeight != mDesiredHeightAtMeasure) { 11239 sizeChanged = true; 11240 } 11241 } 11242 } 11243 } 11244 11245 if (sizeChanged) { 11246 requestLayout(); 11247 // caller will have already invalidated 11248 } 11249 } 11250 11251 /** 11252 * Check whether entirely new text requires a new view layout 11253 * or merely a new text layout. 11254 */ 11255 @UnsupportedAppUsage checkForRelayout()11256 private void checkForRelayout() { 11257 // If we have a fixed width, we can just swap in a new text layout 11258 // if the text height stays the same or if the view height is fixed. 11259 11260 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT 11261 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) 11262 && (mHint == null || mHintLayout != null) 11263 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 11264 // Static width, so try making a new text layout. 11265 11266 int oldht = mLayout.getHeight(); 11267 int want = mLayout.getWidth(); 11268 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 11269 11270 /* 11271 * No need to bring the text into view, since the size is not 11272 * changing (unless we do the requestLayout(), in which case it 11273 * will happen at measure). 11274 */ 11275 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 11276 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 11277 false); 11278 11279 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { 11280 // In a fixed-height view, so use our new text layout. 11281 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT 11282 && mLayoutParams.height != LayoutParams.MATCH_PARENT) { 11283 autoSizeText(); 11284 invalidate(); 11285 return; 11286 } 11287 11288 // Dynamic height, but height has stayed the same, 11289 // so use our new text layout. 11290 if (mLayout.getHeight() == oldht 11291 && (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 11292 autoSizeText(); 11293 invalidate(); 11294 return; 11295 } 11296 } 11297 11298 // We lose: the height has changed and we have a dynamic height. 11299 // Request a new view layout using our new text layout. 11300 requestLayout(); 11301 invalidate(); 11302 } else { 11303 // Dynamic width, so we have no choice but to request a new 11304 // view layout with a new text layout. 11305 nullLayouts(); 11306 requestLayout(); 11307 invalidate(); 11308 } 11309 } 11310 11311 @Override onLayout(boolean changed, int left, int top, int right, int bottom)11312 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 11313 super.onLayout(changed, left, top, right, bottom); 11314 if (mDeferScroll >= 0) { 11315 int curs = mDeferScroll; 11316 mDeferScroll = -1; 11317 bringPointIntoView(Math.min(curs, mText.length())); 11318 } 11319 // Call auto-size after the width and height have been calculated. 11320 autoSizeText(); 11321 } 11322 isShowingHint()11323 private boolean isShowingHint() { 11324 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint) && !mHideHint; 11325 } 11326 11327 /** 11328 * Returns true if anything changed. 11329 */ 11330 @UnsupportedAppUsage bringTextIntoView()11331 private boolean bringTextIntoView() { 11332 Layout layout = isShowingHint() ? mHintLayout : mLayout; 11333 int line = 0; 11334 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 11335 line = layout.getLineCount() - 1; 11336 } 11337 11338 Layout.Alignment a = layout.getParagraphAlignment(line); 11339 int dir = layout.getParagraphDirection(line); 11340 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 11341 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 11342 int ht = layout.getHeight(); 11343 11344 int scrollx, scrolly; 11345 11346 // Convert to left, center, or right alignment. 11347 if (a == Layout.Alignment.ALIGN_NORMAL) { 11348 a = dir == Layout.DIR_LEFT_TO_RIGHT 11349 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 11350 } else if (a == Layout.Alignment.ALIGN_OPPOSITE) { 11351 a = dir == Layout.DIR_LEFT_TO_RIGHT 11352 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 11353 } 11354 11355 if (a == Layout.Alignment.ALIGN_CENTER) { 11356 /* 11357 * Keep centered if possible, or, if it is too wide to fit, 11358 * keep leading edge in view. 11359 */ 11360 11361 int left = (int) Math.floor(layout.getLineLeft(line)); 11362 int right = (int) Math.ceil(layout.getLineRight(line)); 11363 11364 if (right - left < hspace) { 11365 scrollx = (right + left) / 2 - hspace / 2; 11366 } else { 11367 if (dir < 0) { 11368 scrollx = right - hspace; 11369 } else { 11370 scrollx = left; 11371 } 11372 } 11373 } else if (a == Layout.Alignment.ALIGN_RIGHT) { 11374 int right = (int) Math.ceil(layout.getLineRight(line)); 11375 scrollx = right - hspace; 11376 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) 11377 scrollx = (int) Math.floor(layout.getLineLeft(line)); 11378 } 11379 11380 if (ht < vspace) { 11381 scrolly = 0; 11382 } else { 11383 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 11384 scrolly = ht - vspace; 11385 } else { 11386 scrolly = 0; 11387 } 11388 } 11389 11390 if (scrollx != mScrollX || scrolly != mScrollY) { 11391 scrollTo(scrollx, scrolly); 11392 return true; 11393 } else { 11394 return false; 11395 } 11396 } 11397 11398 /** 11399 * Move the point, specified by the offset, into the view if it is needed. 11400 * This has to be called after layout. Returns true if anything changed. 11401 */ bringPointIntoView(int offset)11402 public boolean bringPointIntoView(int offset) { 11403 return bringPointIntoView(offset, false); 11404 } 11405 11406 /** 11407 * Move the insertion position of the given offset into visible area of the View. 11408 * 11409 * If the View is focused or {@code requestRectWithoutFocus} is set to true, this API may call 11410 * {@link View#requestRectangleOnScreen(Rect)} to bring the point to the visible area if 11411 * necessary. 11412 * 11413 * @param offset an offset of the character. 11414 * @param requestRectWithoutFocus True for calling {@link View#requestRectangleOnScreen(Rect)} 11415 * in the unfocused state. False for calling it only the View has 11416 * the focus. 11417 * @return true if anything changed, otherwise false. 11418 * 11419 * @see #bringPointIntoView(int) 11420 */ bringPointIntoView(@ntRangefrom = 0) int offset, boolean requestRectWithoutFocus)11421 public boolean bringPointIntoView(@IntRange(from = 0) int offset, 11422 boolean requestRectWithoutFocus) { 11423 if (isLayoutRequested()) { 11424 mDeferScroll = offset; 11425 return false; 11426 } 11427 final int offsetTransformed = 11428 originalToTransformed(offset, OffsetMapping.MAP_STRATEGY_CURSOR); 11429 boolean changed = false; 11430 11431 Layout layout = isShowingHint() ? mHintLayout : mLayout; 11432 11433 if (layout == null) return changed; 11434 11435 int line = layout.getLineForOffset(offsetTransformed); 11436 11437 int grav; 11438 11439 switch (layout.getParagraphAlignment(line)) { 11440 case ALIGN_LEFT: 11441 grav = 1; 11442 break; 11443 case ALIGN_RIGHT: 11444 grav = -1; 11445 break; 11446 case ALIGN_NORMAL: 11447 grav = layout.getParagraphDirection(line); 11448 break; 11449 case ALIGN_OPPOSITE: 11450 grav = -layout.getParagraphDirection(line); 11451 break; 11452 case ALIGN_CENTER: 11453 default: 11454 grav = 0; 11455 break; 11456 } 11457 11458 // We only want to clamp the cursor to fit within the layout width 11459 // in left-to-right modes, because in a right to left alignment, 11460 // we want to scroll to keep the line-right on the screen, as other 11461 // lines are likely to have text flush with the right margin, which 11462 // we want to keep visible. 11463 // A better long-term solution would probably be to measure both 11464 // the full line and a blank-trimmed version, and, for example, use 11465 // the latter measurement for centering and right alignment, but for 11466 // the time being we only implement the cursor clamping in left to 11467 // right where it is most likely to be annoying. 11468 final boolean clamped = grav > 0; 11469 // FIXME: Is it okay to truncate this, or should we round? 11470 final int x = (int) layout.getPrimaryHorizontal(offsetTransformed, clamped); 11471 final int top = layout.getLineTop(line); 11472 final int bottom = layout.getLineTop(line + 1); 11473 11474 int left = (int) Math.floor(layout.getLineLeft(line)); 11475 int right = (int) Math.ceil(layout.getLineRight(line)); 11476 int ht = layout.getHeight(); 11477 11478 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 11479 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 11480 if (!mHorizontallyScrolling && right - left > hspace && right > x) { 11481 // If cursor has been clamped, make sure we don't scroll. 11482 right = Math.max(x, left + hspace); 11483 } 11484 11485 int hslack = (bottom - top) / 2; 11486 int vslack = hslack; 11487 11488 if (vslack > vspace / 4) { 11489 vslack = vspace / 4; 11490 } 11491 if (hslack > hspace / 4) { 11492 hslack = hspace / 4; 11493 } 11494 11495 int hs = mScrollX; 11496 int vs = mScrollY; 11497 11498 if (top - vs < vslack) { 11499 vs = top - vslack; 11500 } 11501 if (bottom - vs > vspace - vslack) { 11502 vs = bottom - (vspace - vslack); 11503 } 11504 if (ht - vs < vspace) { 11505 vs = ht - vspace; 11506 } 11507 if (0 - vs > 0) { 11508 vs = 0; 11509 } 11510 11511 if (grav != 0) { 11512 if (x - hs < hslack) { 11513 hs = x - hslack; 11514 } 11515 if (x - hs > hspace - hslack) { 11516 hs = x - (hspace - hslack); 11517 } 11518 } 11519 11520 if (grav < 0) { 11521 if (left - hs > 0) { 11522 hs = left; 11523 } 11524 if (right - hs < hspace) { 11525 hs = right - hspace; 11526 } 11527 } else if (grav > 0) { 11528 if (right - hs < hspace) { 11529 hs = right - hspace; 11530 } 11531 if (left - hs > 0) { 11532 hs = left; 11533 } 11534 } else /* grav == 0 */ { 11535 if (right - left <= hspace) { 11536 /* 11537 * If the entire text fits, center it exactly. 11538 */ 11539 hs = left - (hspace - (right - left)) / 2; 11540 } else if (x > right - hslack) { 11541 /* 11542 * If we are near the right edge, keep the right edge 11543 * at the edge of the view. 11544 */ 11545 hs = right - hspace; 11546 } else if (x < left + hslack) { 11547 /* 11548 * If we are near the left edge, keep the left edge 11549 * at the edge of the view. 11550 */ 11551 hs = left; 11552 } else if (left > hs) { 11553 /* 11554 * Is there whitespace visible at the left? Fix it if so. 11555 */ 11556 hs = left; 11557 } else if (right < hs + hspace) { 11558 /* 11559 * Is there whitespace visible at the right? Fix it if so. 11560 */ 11561 hs = right - hspace; 11562 } else { 11563 /* 11564 * Otherwise, float as needed. 11565 */ 11566 if (x - hs < hslack) { 11567 hs = x - hslack; 11568 } 11569 if (x - hs > hspace - hslack) { 11570 hs = x - (hspace - hslack); 11571 } 11572 } 11573 } 11574 11575 if (hs != mScrollX || vs != mScrollY) { 11576 if (mScroller == null) { 11577 scrollTo(hs, vs); 11578 } else { 11579 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 11580 int dx = hs - mScrollX; 11581 int dy = vs - mScrollY; 11582 11583 if (duration > ANIMATED_SCROLL_GAP) { 11584 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 11585 awakenScrollBars(mScroller.getDuration()); 11586 invalidate(); 11587 } else { 11588 if (!mScroller.isFinished()) { 11589 mScroller.abortAnimation(); 11590 } 11591 11592 scrollBy(dx, dy); 11593 } 11594 11595 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 11596 } 11597 11598 changed = true; 11599 } 11600 11601 if (requestRectWithoutFocus || isFocused()) { 11602 // This offsets because getInterestingRect() is in terms of viewport coordinates, but 11603 // requestRectangleOnScreen() is in terms of content coordinates. 11604 11605 // The offsets here are to ensure the rectangle we are using is 11606 // within our view bounds, in case the cursor is on the far left 11607 // or right. If it isn't withing the bounds, then this request 11608 // will be ignored. 11609 if (mTempRect == null) mTempRect = new Rect(); 11610 mTempRect.set(x - 2, top, x + 2, bottom); 11611 getInterestingRect(mTempRect, line); 11612 mTempRect.offset(mScrollX, mScrollY); 11613 11614 if (requestRectangleOnScreen(mTempRect)) { 11615 changed = true; 11616 } 11617 } 11618 11619 return changed; 11620 } 11621 11622 /** 11623 * Move the cursor, if needed, so that it is at an offset that is visible 11624 * to the user. This will not move the cursor if it represents more than 11625 * one character (a selection range). This will only work if the 11626 * TextView contains spannable text; otherwise it will do nothing. 11627 * 11628 * @return True if the cursor was actually moved, false otherwise. 11629 */ moveCursorToVisibleOffset()11630 public boolean moveCursorToVisibleOffset() { 11631 if (!(mText instanceof Spannable)) { 11632 return false; 11633 } 11634 int start = getSelectionStartTransformed(); 11635 int end = getSelectionEndTransformed(); 11636 if (start != end) { 11637 return false; 11638 } 11639 11640 // First: make sure the line is visible on screen: 11641 11642 int line = mLayout.getLineForOffset(start); 11643 11644 final int top = mLayout.getLineTop(line); 11645 final int bottom = mLayout.getLineTop(line + 1); 11646 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 11647 int vslack = (bottom - top) / 2; 11648 if (vslack > vspace / 4) { 11649 vslack = vspace / 4; 11650 } 11651 final int vs = mScrollY; 11652 11653 if (top < (vs + vslack)) { 11654 line = mLayout.getLineForVertical(vs + vslack + (bottom - top)); 11655 } else if (bottom > (vspace + vs - vslack)) { 11656 line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top)); 11657 } 11658 11659 // Next: make sure the character is visible on screen: 11660 11661 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 11662 final int hs = mScrollX; 11663 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 11664 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs); 11665 11666 // line might contain bidirectional text 11667 final int lowChar = leftChar < rightChar ? leftChar : rightChar; 11668 final int highChar = leftChar > rightChar ? leftChar : rightChar; 11669 11670 int newStart = start; 11671 if (newStart < lowChar) { 11672 newStart = lowChar; 11673 } else if (newStart > highChar) { 11674 newStart = highChar; 11675 } 11676 11677 if (newStart != start) { 11678 Selection.setSelection(mSpannable, 11679 transformedToOriginal(newStart, OffsetMapping.MAP_STRATEGY_CURSOR)); 11680 return true; 11681 } 11682 11683 return false; 11684 } 11685 11686 @Override computeScroll()11687 public void computeScroll() { 11688 if (mScroller != null) { 11689 if (mScroller.computeScrollOffset()) { 11690 mScrollX = mScroller.getCurrX(); 11691 mScrollY = mScroller.getCurrY(); 11692 invalidateParentCaches(); 11693 postInvalidate(); // So we draw again 11694 } 11695 } 11696 } 11697 getInterestingRect(Rect r, int line)11698 private void getInterestingRect(Rect r, int line) { 11699 convertFromViewportToContentCoordinates(r); 11700 11701 // Rectangle can can be expanded on first and last line to take 11702 // padding into account. 11703 // TODO Take left/right padding into account too? 11704 if (line == 0) r.top -= getExtendedPaddingTop(); 11705 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); 11706 } 11707 convertFromViewportToContentCoordinates(Rect r)11708 private void convertFromViewportToContentCoordinates(Rect r) { 11709 final int horizontalOffset = viewportToContentHorizontalOffset(); 11710 r.left += horizontalOffset; 11711 r.right += horizontalOffset; 11712 11713 final int verticalOffset = viewportToContentVerticalOffset(); 11714 r.top += verticalOffset; 11715 r.bottom += verticalOffset; 11716 } 11717 convertFromScreenToContentCoordinates(PointF point)11718 private PointF convertFromScreenToContentCoordinates(PointF point) { 11719 int[] screenToViewport = getLocationOnScreen(); 11720 PointF copy = new PointF(point); 11721 copy.offset( 11722 -(screenToViewport[0] + viewportToContentHorizontalOffset()), 11723 -(screenToViewport[1] + viewportToContentVerticalOffset())); 11724 return copy; 11725 } 11726 convertFromScreenToContentCoordinates(RectF rect)11727 private RectF convertFromScreenToContentCoordinates(RectF rect) { 11728 int[] screenToViewport = getLocationOnScreen(); 11729 RectF copy = new RectF(rect); 11730 copy.offset( 11731 -(screenToViewport[0] + viewportToContentHorizontalOffset()), 11732 -(screenToViewport[1] + viewportToContentVerticalOffset())); 11733 return copy; 11734 } 11735 viewportToContentHorizontalOffset()11736 int viewportToContentHorizontalOffset() { 11737 return getCompoundPaddingLeft() - mScrollX; 11738 } 11739 11740 @UnsupportedAppUsage viewportToContentVerticalOffset()11741 int viewportToContentVerticalOffset() { 11742 int offset = getExtendedPaddingTop() - mScrollY; 11743 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 11744 offset += getVerticalOffset(false); 11745 } 11746 return offset; 11747 } 11748 11749 @Override debug(int depth)11750 public void debug(int depth) { 11751 super.debug(depth); 11752 11753 String output = debugIndent(depth); 11754 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 11755 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 11756 + "} "; 11757 11758 if (mText != null) { 11759 11760 output += "mText=\"" + mText + "\" "; 11761 if (mLayout != null) { 11762 output += "mLayout width=" + mLayout.getWidth() 11763 + " height=" + mLayout.getHeight(); 11764 } 11765 } else { 11766 output += "mText=NULL"; 11767 } 11768 Log.d(VIEW_LOG_TAG, output); 11769 } 11770 11771 /** 11772 * Convenience for {@link Selection#getSelectionStart}. 11773 */ 11774 @ViewDebug.ExportedProperty(category = "text") getSelectionStart()11775 public int getSelectionStart() { 11776 return Selection.getSelectionStart(getText()); 11777 } 11778 11779 /** 11780 * Convenience for {@link Selection#getSelectionEnd}. 11781 */ 11782 @ViewDebug.ExportedProperty(category = "text") getSelectionEnd()11783 public int getSelectionEnd() { 11784 return Selection.getSelectionEnd(getText()); 11785 } 11786 11787 /** 11788 * Calculates the rectangles which should be highlighted to indicate a selection between start 11789 * and end and feeds them into the given {@link Layout.SelectionRectangleConsumer}. 11790 * 11791 * @param start the starting index of the selection 11792 * @param end the ending index of the selection 11793 * @param consumer the {@link Layout.SelectionRectangleConsumer} which will receive the 11794 * generated rectangles. It will be called every time a rectangle is generated. 11795 * @hide 11796 */ getSelection(int start, int end, final Layout.SelectionRectangleConsumer consumer)11797 public void getSelection(int start, int end, final Layout.SelectionRectangleConsumer consumer) { 11798 final int transformedStart = 11799 originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR); 11800 final int transformedEnd = originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR); 11801 mLayout.getSelection(transformedStart, transformedEnd, consumer); 11802 } 11803 getSelectionStartTransformed()11804 int getSelectionStartTransformed() { 11805 final int start = getSelectionStart(); 11806 if (start < 0) return start; 11807 return originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CURSOR); 11808 } 11809 getSelectionEndTransformed()11810 int getSelectionEndTransformed() { 11811 final int end = getSelectionEnd(); 11812 if (end < 0) return end; 11813 return originalToTransformed(end, OffsetMapping.MAP_STRATEGY_CURSOR); 11814 } 11815 11816 /** 11817 * Return true iff there is a selection of nonzero length inside this text view. 11818 */ hasSelection()11819 public boolean hasSelection() { 11820 final int selectionStart = getSelectionStart(); 11821 final int selectionEnd = getSelectionEnd(); 11822 final int selectionMin; 11823 final int selectionMax; 11824 if (selectionStart < selectionEnd) { 11825 selectionMin = selectionStart; 11826 selectionMax = selectionEnd; 11827 } else { 11828 selectionMin = selectionEnd; 11829 selectionMax = selectionStart; 11830 } 11831 11832 return selectionMin >= 0 && selectionMax > 0 && selectionMin != selectionMax; 11833 } 11834 getSelectedText()11835 String getSelectedText() { 11836 if (!hasSelection()) { 11837 return null; 11838 } 11839 11840 final int start = getSelectionStart(); 11841 final int end = getSelectionEnd(); 11842 return String.valueOf( 11843 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end)); 11844 } 11845 11846 /** 11847 * Sets the properties of this field (lines, horizontally scrolling, 11848 * transformation method) to be for a single-line input. 11849 * 11850 * @attr ref android.R.styleable#TextView_singleLine 11851 */ setSingleLine()11852 public void setSingleLine() { 11853 setSingleLine(true); 11854 } 11855 11856 /** 11857 * Sets the properties of this field to transform input to ALL CAPS 11858 * display. This may use a "small caps" formatting if available. 11859 * This setting will be ignored if this field is editable or selectable. 11860 * 11861 * This call replaces the current transformation method. Disabling this 11862 * will not necessarily restore the previous behavior from before this 11863 * was enabled. 11864 * 11865 * @see #setTransformationMethod(TransformationMethod) 11866 * @attr ref android.R.styleable#TextView_textAllCaps 11867 */ 11868 @android.view.RemotableViewMethod setAllCaps(boolean allCaps)11869 public void setAllCaps(boolean allCaps) { 11870 if (allCaps) { 11871 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 11872 } else { 11873 setTransformationMethod(null); 11874 } 11875 } 11876 11877 /** 11878 * 11879 * Checks whether the transformation method applied to this TextView is set to ALL CAPS. 11880 * @return Whether the current transformation method is for ALL CAPS. 11881 * 11882 * @see #setAllCaps(boolean) 11883 * @see #setTransformationMethod(TransformationMethod) 11884 */ 11885 @InspectableProperty(name = "textAllCaps") isAllCaps()11886 public boolean isAllCaps() { 11887 final TransformationMethod method = getTransformationMethod(); 11888 return method != null && method instanceof AllCapsTransformationMethod; 11889 } 11890 11891 /** 11892 * If true, sets the properties of this field (number of lines, horizontally scrolling, 11893 * transformation method) to be for a single-line input; if false, restores these to the default 11894 * conditions. 11895 * 11896 * Note that the default conditions are not necessarily those that were in effect prior this 11897 * method, and you may want to reset these properties to your custom values. 11898 * 11899 * Note that due to performance reasons, by setting single line for the EditText, the maximum 11900 * text length is set to 5000 if no other character limitation are applied. 11901 * 11902 * @attr ref android.R.styleable#TextView_singleLine 11903 */ 11904 @android.view.RemotableViewMethod setSingleLine(boolean singleLine)11905 public void setSingleLine(boolean singleLine) { 11906 // Could be used, but may break backward compatibility. 11907 // if (mSingleLine == singleLine) return; 11908 setInputTypeSingleLine(singleLine); 11909 applySingleLine(singleLine, true, true, true); 11910 } 11911 11912 /** 11913 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. 11914 * @param singleLine 11915 */ setInputTypeSingleLine(boolean singleLine)11916 private void setInputTypeSingleLine(boolean singleLine) { 11917 if (mEditor != null 11918 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 11919 == EditorInfo.TYPE_CLASS_TEXT) { 11920 if (singleLine) { 11921 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 11922 } else { 11923 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 11924 } 11925 } 11926 } 11927 applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines, boolean changeMaxLength)11928 private void applySingleLine(boolean singleLine, boolean applyTransformation, 11929 boolean changeMaxLines, boolean changeMaxLength) { 11930 mSingleLine = singleLine; 11931 11932 if (singleLine) { 11933 setLines(1); 11934 setHorizontallyScrolling(true); 11935 if (applyTransformation) { 11936 setTransformationMethod(SingleLineTransformationMethod.getInstance()); 11937 } 11938 11939 if (!changeMaxLength) return; 11940 11941 // Single line length filter is only applicable editable text. 11942 if (mBufferType != BufferType.EDITABLE) return; 11943 11944 final InputFilter[] prevFilters = getFilters(); 11945 for (InputFilter filter: getFilters()) { 11946 // We don't add LengthFilter if already there. 11947 if (filter instanceof InputFilter.LengthFilter) return; 11948 } 11949 11950 if (mSingleLineLengthFilter == null) { 11951 mSingleLineLengthFilter = new InputFilter.LengthFilter( 11952 MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT); 11953 } 11954 11955 final InputFilter[] newFilters = new InputFilter[prevFilters.length + 1]; 11956 System.arraycopy(prevFilters, 0, newFilters, 0, prevFilters.length); 11957 newFilters[prevFilters.length] = mSingleLineLengthFilter; 11958 11959 setFilters(newFilters); 11960 11961 // Since filter doesn't apply to existing text, trigger filter by setting text. 11962 setText(getText()); 11963 } else { 11964 if (changeMaxLines) { 11965 setMaxLines(Integer.MAX_VALUE); 11966 } 11967 setHorizontallyScrolling(false); 11968 if (applyTransformation) { 11969 setTransformationMethod(null); 11970 } 11971 11972 if (!changeMaxLength) return; 11973 11974 // Single line length filter is only applicable editable text. 11975 if (mBufferType != BufferType.EDITABLE) return; 11976 11977 final InputFilter[] prevFilters = getFilters(); 11978 if (prevFilters.length == 0) return; 11979 11980 // Short Circuit: if mSingleLineLengthFilter is not allocated, nobody sets automated 11981 // single line char limit filter. 11982 if (mSingleLineLengthFilter == null) return; 11983 11984 // If we need to remove mSingleLineLengthFilter, we need to allocate another array. 11985 // Since filter list is expected to be small and want to avoid unnecessary array 11986 // allocation, check if there is mSingleLengthFilter first. 11987 int targetIndex = -1; 11988 for (int i = 0; i < prevFilters.length; ++i) { 11989 if (prevFilters[i] == mSingleLineLengthFilter) { 11990 targetIndex = i; 11991 break; 11992 } 11993 } 11994 if (targetIndex == -1) return; // not found. Do nothing. 11995 11996 if (prevFilters.length == 1) { 11997 setFilters(NO_FILTERS); 11998 return; 11999 } 12000 12001 // Create new array which doesn't include mSingleLengthFilter. 12002 final InputFilter[] newFilters = new InputFilter[prevFilters.length - 1]; 12003 System.arraycopy(prevFilters, 0, newFilters, 0, targetIndex); 12004 System.arraycopy( 12005 prevFilters, 12006 targetIndex + 1, 12007 newFilters, 12008 targetIndex, 12009 prevFilters.length - targetIndex - 1); 12010 setFilters(newFilters); 12011 mSingleLineLengthFilter = null; 12012 } 12013 } 12014 12015 /** 12016 * Causes words in the text that are longer than the view's width 12017 * to be ellipsized instead of broken in the middle. You may also 12018 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 12019 * to constrain the text to a single line. Use <code>null</code> 12020 * to turn off ellipsizing. 12021 * 12022 * If {@link #setMaxLines} has been used to set two or more lines, 12023 * only {@link android.text.TextUtils.TruncateAt#END} and 12024 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported 12025 * (other ellipsizing types will not do anything). 12026 * 12027 * @attr ref android.R.styleable#TextView_ellipsize 12028 */ setEllipsize(TextUtils.TruncateAt where)12029 public void setEllipsize(TextUtils.TruncateAt where) { 12030 // TruncateAt is an enum. != comparison is ok between these singleton objects. 12031 if (mEllipsize != where) { 12032 mEllipsize = where; 12033 12034 if (mLayout != null) { 12035 nullLayouts(); 12036 requestLayout(); 12037 invalidate(); 12038 } 12039 } 12040 } 12041 12042 /** 12043 * Sets how many times to repeat the marquee animation. Only applied if the 12044 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 12045 * 12046 * @see #getMarqueeRepeatLimit() 12047 * 12048 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 12049 */ setMarqueeRepeatLimit(int marqueeLimit)12050 public void setMarqueeRepeatLimit(int marqueeLimit) { 12051 mMarqueeRepeatLimit = marqueeLimit; 12052 } 12053 12054 /** 12055 * Gets the number of times the marquee animation is repeated. Only meaningful if the 12056 * TextView has marquee enabled. 12057 * 12058 * @return the number of times the marquee animation is repeated. -1 if the animation 12059 * repeats indefinitely 12060 * 12061 * @see #setMarqueeRepeatLimit(int) 12062 * 12063 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 12064 */ 12065 @InspectableProperty getMarqueeRepeatLimit()12066 public int getMarqueeRepeatLimit() { 12067 return mMarqueeRepeatLimit; 12068 } 12069 12070 /** 12071 * Returns where, if anywhere, words that are longer than the view 12072 * is wide should be ellipsized. 12073 */ 12074 @InspectableProperty 12075 @ViewDebug.ExportedProperty getEllipsize()12076 public TextUtils.TruncateAt getEllipsize() { 12077 return mEllipsize; 12078 } 12079 12080 /** 12081 * Set the TextView so that when it takes focus, all the text is 12082 * selected. 12083 * 12084 * @attr ref android.R.styleable#TextView_selectAllOnFocus 12085 */ 12086 @android.view.RemotableViewMethod setSelectAllOnFocus(boolean selectAllOnFocus)12087 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 12088 createEditorIfNeeded(); 12089 mEditor.mSelectAllOnFocus = selectAllOnFocus; 12090 12091 if (selectAllOnFocus && !(mText instanceof Spannable)) { 12092 setText(mText, BufferType.SPANNABLE); 12093 } 12094 } 12095 12096 /** 12097 * Set whether the cursor is visible. The default is true. Note that this property only 12098 * makes sense for editable TextView. If IME is consuming the input, the cursor will always be 12099 * invisible, visibility will be updated as the last state when IME does not consume 12100 * the input anymore. 12101 * 12102 * @see #isCursorVisible() 12103 * 12104 * @attr ref android.R.styleable#TextView_cursorVisible 12105 */ 12106 @android.view.RemotableViewMethod setCursorVisible(boolean visible)12107 public void setCursorVisible(boolean visible) { 12108 mCursorVisibleFromAttr = visible; 12109 updateCursorVisibleInternal(); 12110 } 12111 12112 /** 12113 * Sets the IME is consuming the input and make the cursor invisible if {@code imeConsumesInput} 12114 * is {@code true}. Otherwise, make the cursor visible. 12115 * 12116 * @param imeConsumesInput {@code true} if IME is consuming the input 12117 * 12118 * @hide 12119 */ setImeConsumesInput(boolean imeConsumesInput)12120 public void setImeConsumesInput(boolean imeConsumesInput) { 12121 mImeIsConsumingInput = imeConsumesInput; 12122 updateCursorVisibleInternal(); 12123 } 12124 updateCursorVisibleInternal()12125 private void updateCursorVisibleInternal() { 12126 boolean visible = mCursorVisibleFromAttr && !mImeIsConsumingInput; 12127 if (visible && mEditor == null) return; // visible is the default value with no edit data 12128 createEditorIfNeeded(); 12129 if (mEditor.mCursorVisible != visible) { 12130 mEditor.mCursorVisible = visible; 12131 invalidate(); 12132 12133 mEditor.makeBlink(); 12134 12135 // InsertionPointCursorController depends on mCursorVisible 12136 mEditor.prepareCursorControllers(); 12137 } 12138 } 12139 12140 /** 12141 * @return whether or not the cursor is visible (assuming this TextView is editable). This 12142 * method may return {@code false} when the IME is consuming the input even if the 12143 * {@code mEditor.mCursorVisible} attribute is {@code true} or {@code #setCursorVisible(true)} 12144 * is called. 12145 * 12146 * @see #setCursorVisible(boolean) 12147 * 12148 * @attr ref android.R.styleable#TextView_cursorVisible 12149 */ 12150 @InspectableProperty isCursorVisible()12151 public boolean isCursorVisible() { 12152 // true is the default value 12153 return mEditor == null ? true : mEditor.mCursorVisible; 12154 } 12155 12156 /** 12157 * @return whether cursor is visible without regard to {@code mImeIsConsumingInput}. 12158 * {@code true} is the default value. 12159 * 12160 * @see #setCursorVisible(boolean) 12161 * @hide 12162 */ isCursorVisibleFromAttr()12163 public boolean isCursorVisibleFromAttr() { 12164 return mCursorVisibleFromAttr; 12165 } 12166 canMarquee()12167 private boolean canMarquee() { 12168 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 12169 return width > 0 && (mLayout.getLineWidth(0) > width 12170 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null 12171 && mSavedMarqueeModeLayout.getLineWidth(0) > width)); 12172 } 12173 12174 /** 12175 * @hide 12176 */ 12177 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startMarquee()12178 protected void startMarquee() { 12179 // Do not ellipsize EditText 12180 if (getKeyListener() != null) return; 12181 12182 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 12183 return; 12184 } 12185 12186 if ((mMarquee == null || mMarquee.isStopped()) && isAggregatedVisible() 12187 && (isFocused() || isSelected()) && getLineCount() == 1 && canMarquee()) { 12188 12189 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 12190 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; 12191 final Layout tmp = mLayout; 12192 mLayout = mSavedMarqueeModeLayout; 12193 mSavedMarqueeModeLayout = tmp; 12194 setHorizontalFadingEdgeEnabled(true); 12195 requestLayout(); 12196 invalidate(); 12197 } 12198 12199 if (mMarquee == null) mMarquee = new Marquee(this); 12200 mMarquee.start(mMarqueeRepeatLimit); 12201 } 12202 } 12203 12204 /** 12205 * @hide 12206 */ stopMarquee()12207 protected void stopMarquee() { 12208 if (mMarquee != null && !mMarquee.isStopped()) { 12209 mMarquee.stop(); 12210 } 12211 12212 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { 12213 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 12214 final Layout tmp = mSavedMarqueeModeLayout; 12215 mSavedMarqueeModeLayout = mLayout; 12216 mLayout = tmp; 12217 setHorizontalFadingEdgeEnabled(false); 12218 requestLayout(); 12219 invalidate(); 12220 } 12221 } 12222 12223 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) startStopMarquee(boolean start)12224 private void startStopMarquee(boolean start) { 12225 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 12226 if (start) { 12227 startMarquee(); 12228 } else { 12229 stopMarquee(); 12230 } 12231 } 12232 } 12233 12234 /** 12235 * This method is called when the text is changed, in case any subclasses 12236 * would like to know. 12237 * 12238 * Within <code>text</code>, the <code>lengthAfter</code> characters 12239 * beginning at <code>start</code> have just replaced old text that had 12240 * length <code>lengthBefore</code>. It is an error to attempt to make 12241 * changes to <code>text</code> from this callback. 12242 * 12243 * @param text The text the TextView is displaying 12244 * @param start The offset of the start of the range of the text that was 12245 * modified 12246 * @param lengthBefore The length of the former text that has been replaced 12247 * @param lengthAfter The length of the replacement modified text 12248 */ onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)12249 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 12250 // intentionally empty, template pattern method can be overridden by subclasses 12251 } 12252 12253 /** 12254 * This method is called when the selection has changed, in case any 12255 * subclasses would like to know. 12256 * </p> 12257 * <p class="note"><strong>Note:</strong> Always call the super implementation, which informs 12258 * the accessibility subsystem about the selection change. 12259 * </p> 12260 * 12261 * @param selStart The new selection start location. 12262 * @param selEnd The new selection end location. 12263 */ 12264 @CallSuper onSelectionChanged(int selStart, int selEnd)12265 protected void onSelectionChanged(int selStart, int selEnd) { 12266 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 12267 } 12268 12269 /** 12270 * Adds a TextWatcher to the list of those whose methods are called 12271 * whenever this TextView's text changes. 12272 * <p> 12273 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 12274 * not called after {@link #setText} calls. Now, doing {@link #setText} 12275 * if there are any text changed listeners forces the buffer type to 12276 * Editable if it would not otherwise be and does call this method. 12277 */ addTextChangedListener(TextWatcher watcher)12278 public void addTextChangedListener(TextWatcher watcher) { 12279 if (mListeners == null) { 12280 mListeners = new ArrayList<TextWatcher>(); 12281 } 12282 12283 mListeners.add(watcher); 12284 } 12285 12286 /** 12287 * Removes the specified TextWatcher from the list of those whose 12288 * methods are called 12289 * whenever this TextView's text changes. 12290 */ removeTextChangedListener(TextWatcher watcher)12291 public void removeTextChangedListener(TextWatcher watcher) { 12292 if (mListeners != null) { 12293 int i = mListeners.indexOf(watcher); 12294 12295 if (i >= 0) { 12296 mListeners.remove(i); 12297 } 12298 } 12299 } 12300 sendBeforeTextChanged(CharSequence text, int start, int before, int after)12301 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { 12302 if (mListeners != null) { 12303 final ArrayList<TextWatcher> list = mListeners; 12304 final int count = list.size(); 12305 for (int i = 0; i < count; i++) { 12306 list.get(i).beforeTextChanged(text, start, before, after); 12307 } 12308 } 12309 12310 // The spans that are inside or intersect the modified region no longer make sense 12311 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); 12312 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); 12313 } 12314 12315 // Removes all spans that are inside or actually overlap the start..end range removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)12316 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { 12317 if (!(mText instanceof Editable)) return; 12318 Editable text = (Editable) mText; 12319 12320 T[] spans = text.getSpans(start, end, type); 12321 ArrayList<T> spansToRemove = new ArrayList<>(); 12322 for (T span : spans) { 12323 final int spanStart = text.getSpanStart(span); 12324 final int spanEnd = text.getSpanEnd(span); 12325 if (spanEnd == start || spanStart == end) continue; 12326 spansToRemove.add(span); 12327 } 12328 for (T span : spansToRemove) { 12329 text.removeSpan(span); 12330 } 12331 } 12332 removeAdjacentSuggestionSpans(final int pos)12333 void removeAdjacentSuggestionSpans(final int pos) { 12334 if (!(mText instanceof Editable)) return; 12335 final Editable text = (Editable) mText; 12336 12337 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); 12338 final int length = spans.length; 12339 for (int i = 0; i < length; i++) { 12340 final int spanStart = text.getSpanStart(spans[i]); 12341 final int spanEnd = text.getSpanEnd(spans[i]); 12342 if (spanEnd == pos || spanStart == pos) { 12343 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { 12344 text.removeSpan(spans[i]); 12345 } 12346 } 12347 } 12348 } 12349 12350 /** 12351 * Not private so it can be called from an inner class without going 12352 * through a thunk. 12353 */ sendOnTextChanged(CharSequence text, int start, int before, int after)12354 void sendOnTextChanged(CharSequence text, int start, int before, int after) { 12355 if (mListeners != null) { 12356 final ArrayList<TextWatcher> list = mListeners; 12357 final int count = list.size(); 12358 for (int i = 0; i < count; i++) { 12359 list.get(i).onTextChanged(text, start, before, after); 12360 } 12361 } 12362 12363 if (mEditor != null) mEditor.sendOnTextChanged(start, before, after); 12364 } 12365 12366 /** 12367 * Not private so it can be called from an inner class without going 12368 * through a thunk. 12369 */ sendAfterTextChanged(Editable text)12370 void sendAfterTextChanged(Editable text) { 12371 if (mListeners != null) { 12372 final ArrayList<TextWatcher> list = mListeners; 12373 final int count = list.size(); 12374 for (int i = 0; i < count; i++) { 12375 list.get(i).afterTextChanged(text); 12376 } 12377 } 12378 12379 notifyListeningManagersAfterTextChanged(); 12380 12381 hideErrorIfUnchanged(); 12382 } 12383 12384 /** 12385 * Notify managers (such as {@link AutofillManager} and {@link ContentCaptureManager}) that are 12386 * interested on text changes. 12387 */ notifyListeningManagersAfterTextChanged()12388 private void notifyListeningManagersAfterTextChanged() { 12389 12390 // Autofill 12391 if (isAutofillable()) { 12392 // It is important to not check whether the view is important for autofill 12393 // since the user can trigger autofill manually on not important views. 12394 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 12395 if (afm != null) { 12396 if (android.view.autofill.Helper.sVerbose) { 12397 Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged"); 12398 } 12399 afm.notifyValueChanged(TextView.this); 12400 } 12401 } 12402 12403 notifyContentCaptureTextChanged(); 12404 } 12405 12406 /** 12407 * Notifies the ContentCapture service that the text of the view has changed (only if 12408 * ContentCapture has been notified of this view's existence already). 12409 * 12410 * @hide 12411 */ notifyContentCaptureTextChanged()12412 public void notifyContentCaptureTextChanged() { 12413 // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead 12414 // of using isLaidout(), so it's not called in cases where it's laid out but a 12415 // notifyAppeared was not sent. 12416 if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) { 12417 final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class); 12418 if (cm != null && cm.isContentCaptureEnabled()) { 12419 final ContentCaptureSession session = getContentCaptureSession(); 12420 if (session != null) { 12421 // TODO(b/111276913): pass flags when edited by user / add CTS test 12422 session.notifyViewTextChanged(getAutofillId(), getText()); 12423 } 12424 } 12425 } 12426 } 12427 isAutofillable()12428 private boolean isAutofillable() { 12429 // It is important to not check whether the view is important for autofill 12430 // since the user can trigger autofill manually on not important views. 12431 return getAutofillType() != AUTOFILL_TYPE_NONE; 12432 } 12433 updateAfterEdit()12434 void updateAfterEdit() { 12435 invalidate(); 12436 int curs = getSelectionStart(); 12437 12438 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 12439 registerForPreDraw(); 12440 } 12441 12442 checkForResize(); 12443 12444 if (curs >= 0) { 12445 mHighlightPathBogus = true; 12446 if (mEditor != null) mEditor.makeBlink(); 12447 bringPointIntoView(curs); 12448 } 12449 } 12450 12451 /** 12452 * Not private so it can be called from an inner class without going 12453 * through a thunk. 12454 */ handleTextChanged(CharSequence buffer, int start, int before, int after)12455 void handleTextChanged(CharSequence buffer, int start, int before, int after) { 12456 sLastCutCopyOrTextChangedTime = 0; 12457 12458 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 12459 if (ims == null || ims.mBatchEditNesting == 0) { 12460 updateAfterEdit(); 12461 } 12462 if (ims != null) { 12463 ims.mContentChanged = true; 12464 if (ims.mChangedStart < 0) { 12465 ims.mChangedStart = start; 12466 ims.mChangedEnd = start + before; 12467 } else { 12468 ims.mChangedStart = Math.min(ims.mChangedStart, start); 12469 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); 12470 } 12471 ims.mChangedDelta += after - before; 12472 } 12473 resetErrorChangedFlag(); 12474 sendOnTextChanged(buffer, start, before, after); 12475 onTextChanged(buffer, start, before, after); 12476 12477 mHideHint = false; 12478 clearGesturePreviewHighlight(); 12479 } 12480 12481 /** 12482 * Not private so it can be called from an inner class without going 12483 * through a thunk. 12484 */ spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)12485 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { 12486 // XXX Make the start and end move together if this ends up 12487 // spending too much time invalidating. 12488 12489 boolean selChanged = false; 12490 int newSelStart = -1, newSelEnd = -1; 12491 12492 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 12493 12494 if (what == Selection.SELECTION_END) { 12495 selChanged = true; 12496 newSelEnd = newStart; 12497 12498 if (oldStart >= 0 || newStart >= 0) { 12499 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 12500 checkForResize(); 12501 registerForPreDraw(); 12502 if (mEditor != null) mEditor.makeBlink(); 12503 } 12504 } 12505 12506 if (what == Selection.SELECTION_START) { 12507 selChanged = true; 12508 newSelStart = newStart; 12509 12510 if (oldStart >= 0 || newStart >= 0) { 12511 int end = Selection.getSelectionEnd(buf); 12512 invalidateCursor(end, oldStart, newStart); 12513 } 12514 } 12515 12516 if (selChanged) { 12517 clearGesturePreviewHighlight(); 12518 mHighlightPathBogus = true; 12519 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; 12520 12521 if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) { 12522 if (newSelStart < 0) { 12523 newSelStart = Selection.getSelectionStart(buf); 12524 } 12525 if (newSelEnd < 0) { 12526 newSelEnd = Selection.getSelectionEnd(buf); 12527 } 12528 12529 if (mEditor != null) { 12530 mEditor.refreshTextActionMode(); 12531 if (!hasSelection() 12532 && mEditor.getTextActionMode() == null && hasTransientState()) { 12533 // User generated selection has been removed. 12534 setHasTransientState(false); 12535 } 12536 } 12537 onSelectionChanged(newSelStart, newSelEnd); 12538 } 12539 } 12540 12541 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle 12542 || what instanceof CharacterStyle) { 12543 if (ims == null || ims.mBatchEditNesting == 0) { 12544 invalidate(); 12545 mHighlightPathBogus = true; 12546 checkForResize(); 12547 } else { 12548 ims.mContentChanged = true; 12549 } 12550 if (mEditor != null) { 12551 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); 12552 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); 12553 mEditor.invalidateHandlesAndActionMode(); 12554 } 12555 } 12556 12557 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 12558 mHighlightPathBogus = true; 12559 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 12560 ims.mSelectionModeChanged = true; 12561 } 12562 12563 if (Selection.getSelectionStart(buf) >= 0) { 12564 if (ims == null || ims.mBatchEditNesting == 0) { 12565 invalidateCursor(); 12566 } else { 12567 ims.mCursorChanged = true; 12568 } 12569 } 12570 } 12571 12572 if (what instanceof ParcelableSpan) { 12573 // If this is a span that can be sent to a remote process, 12574 // the current extract editor would be interested in it. 12575 if (ims != null && ims.mExtractedTextRequest != null) { 12576 if (ims.mBatchEditNesting != 0) { 12577 if (oldStart >= 0) { 12578 if (ims.mChangedStart > oldStart) { 12579 ims.mChangedStart = oldStart; 12580 } 12581 if (ims.mChangedStart > oldEnd) { 12582 ims.mChangedStart = oldEnd; 12583 } 12584 } 12585 if (newStart >= 0) { 12586 if (ims.mChangedStart > newStart) { 12587 ims.mChangedStart = newStart; 12588 } 12589 if (ims.mChangedStart > newEnd) { 12590 ims.mChangedStart = newEnd; 12591 } 12592 } 12593 } else { 12594 if (DEBUG_EXTRACT) { 12595 Log.v(LOG_TAG, "Span change outside of batch: " 12596 + oldStart + "-" + oldEnd + "," 12597 + newStart + "-" + newEnd + " " + what); 12598 } 12599 ims.mContentChanged = true; 12600 } 12601 } 12602 } 12603 12604 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 12605 && what instanceof SpellCheckSpan) { 12606 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); 12607 } 12608 } 12609 12610 @Override onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)12611 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 12612 if (isTemporarilyDetached()) { 12613 // If we are temporarily in the detach state, then do nothing. 12614 super.onFocusChanged(focused, direction, previouslyFocusedRect); 12615 return; 12616 } 12617 12618 mHideHint = false; 12619 12620 if (mEditor != null) mEditor.onFocusChanged(focused, direction); 12621 12622 if (focused) { 12623 if (mSpannable != null) { 12624 MetaKeyKeyListener.resetMetaState(mSpannable); 12625 } 12626 } 12627 12628 startStopMarquee(focused); 12629 12630 if (mTransformation != null) { 12631 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); 12632 } 12633 12634 super.onFocusChanged(focused, direction, previouslyFocusedRect); 12635 } 12636 12637 @Override onWindowFocusChanged(boolean hasWindowFocus)12638 public void onWindowFocusChanged(boolean hasWindowFocus) { 12639 super.onWindowFocusChanged(hasWindowFocus); 12640 12641 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); 12642 12643 startStopMarquee(hasWindowFocus); 12644 } 12645 12646 @Override onVisibilityChanged(View changedView, int visibility)12647 protected void onVisibilityChanged(View changedView, int visibility) { 12648 super.onVisibilityChanged(changedView, visibility); 12649 if (mEditor != null && visibility != VISIBLE) { 12650 mEditor.hideCursorAndSpanControllers(); 12651 stopTextActionMode(); 12652 } 12653 } 12654 12655 @Override onVisibilityAggregated(boolean isVisible)12656 public void onVisibilityAggregated(boolean isVisible) { 12657 super.onVisibilityAggregated(isVisible); 12658 startStopMarquee(isVisible); 12659 } 12660 12661 /** 12662 * Use {@link BaseInputConnection#removeComposingSpans 12663 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 12664 * state from this text view. 12665 */ clearComposingText()12666 public void clearComposingText() { 12667 if (mText instanceof Spannable) { 12668 BaseInputConnection.removeComposingSpans(mSpannable); 12669 } 12670 } 12671 12672 @Override setSelected(boolean selected)12673 public void setSelected(boolean selected) { 12674 boolean wasSelected = isSelected(); 12675 12676 super.setSelected(selected); 12677 12678 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 12679 if (selected) { 12680 startMarquee(); 12681 } else { 12682 stopMarquee(); 12683 } 12684 } 12685 } 12686 12687 /** 12688 * Called from onTouchEvent() to prevent the touches by secondary fingers. 12689 * Dragging on handles can revise cursor/selection, so can dragging on the text view. 12690 * This method is a lock to avoid processing multiple fingers on both text view and handles. 12691 * Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work. 12692 * 12693 * @param event The motion event that is being handled and carries the pointer info. 12694 * @param fromHandleView true if the event is delivered to selection handle or insertion 12695 * handle; false if this event is delivered to TextView. 12696 * @return Returns true to indicate that onTouchEvent() can continue processing the motion 12697 * event, otherwise false. 12698 * - Always returns true for the first finger. 12699 * - For secondary fingers, if the first or current finger is from TextView, returns false. 12700 * This is to make touch mutually exclusive between the TextView and the handles, but 12701 * not among the handles. 12702 */ isFromPrimePointer(MotionEvent event, boolean fromHandleView)12703 boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) { 12704 boolean res = true; 12705 if (mPrimePointerId == NO_POINTER_ID) { 12706 mPrimePointerId = event.getPointerId(0); 12707 mIsPrimePointerFromHandleView = fromHandleView; 12708 } else if (mPrimePointerId != event.getPointerId(0)) { 12709 res = mIsPrimePointerFromHandleView && fromHandleView; 12710 } 12711 if (event.getActionMasked() == MotionEvent.ACTION_UP 12712 || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { 12713 mPrimePointerId = -1; 12714 } 12715 return res; 12716 } 12717 12718 @Override onTouchEvent(MotionEvent event)12719 public boolean onTouchEvent(MotionEvent event) { 12720 if (DEBUG_CURSOR) { 12721 logCursor("onTouchEvent", "%d: %s (%f,%f)", 12722 event.getSequenceNumber(), 12723 MotionEvent.actionToString(event.getActionMasked()), 12724 event.getX(), event.getY()); 12725 } 12726 mLastInputSource = event.getSource(); 12727 final int action = event.getActionMasked(); 12728 if (mEditor != null) { 12729 if (!isFromPrimePointer(event, false)) { 12730 return true; 12731 } 12732 12733 mEditor.onTouchEvent(event); 12734 12735 if (mEditor.mInsertionPointCursorController != null 12736 && mEditor.mInsertionPointCursorController.isCursorBeingModified()) { 12737 return true; 12738 } 12739 if (mEditor.mSelectionModifierCursorController != null 12740 && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) { 12741 return true; 12742 } 12743 } 12744 12745 final boolean superResult = super.onTouchEvent(event); 12746 if (DEBUG_CURSOR) { 12747 logCursor("onTouchEvent", "superResult=%s", superResult); 12748 } 12749 12750 /* 12751 * Don't handle the release after a long press, because it will move the selection away from 12752 * whatever the menu action was trying to affect. If the long press should have triggered an 12753 * insertion action mode, we can now actually show it. 12754 */ 12755 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { 12756 mEditor.mDiscardNextActionUp = false; 12757 if (DEBUG_CURSOR) { 12758 logCursor("onTouchEvent", "release after long press detected"); 12759 } 12760 if (mEditor.mIsInsertionActionModeStartPending) { 12761 mEditor.startInsertionActionMode(); 12762 mEditor.mIsInsertionActionModeStartPending = false; 12763 } 12764 return superResult; 12765 } 12766 12767 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) 12768 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); 12769 12770 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 12771 && mText instanceof Spannable && mLayout != null) { 12772 boolean handled = false; 12773 12774 if (mMovement != null) { 12775 handled |= mMovement.onTouchEvent(this, mSpannable, event); 12776 } 12777 12778 final boolean textIsSelectable = isTextSelectable(); 12779 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { 12780 // The LinkMovementMethod which should handle taps on links has not been installed 12781 // on non editable text that support text selection. 12782 // We reproduce its behavior here to open links for these. 12783 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(), 12784 getSelectionEnd(), ClickableSpan.class); 12785 12786 if (links.length > 0) { 12787 links[0].onClick(this); 12788 handled = true; 12789 } 12790 } 12791 12792 if (touchIsFinished && (isTextEditable() || textIsSelectable)) { 12793 // Show the IME, except when selecting in read-only text. 12794 final InputMethodManager imm = getInputMethodManager(); 12795 viewClicked(imm); 12796 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null 12797 && !showAutofillDialog()) { 12798 imm.showSoftInput(this, 0); 12799 } 12800 12801 // The above condition ensures that the mEditor is not null 12802 mEditor.onTouchUpEvent(event); 12803 12804 handled = true; 12805 } 12806 12807 if (handled) { 12808 return true; 12809 } 12810 } 12811 12812 return superResult; 12813 } 12814 12815 /** 12816 * Returns true when need to show UIs, e.g. floating toolbar, etc, for finger based interaction. 12817 * 12818 * @return true if UIs need to show for finger interaciton. false if UIs are not necessary. 12819 * @hide 12820 */ showUIForTouchScreen()12821 public final boolean showUIForTouchScreen() { 12822 return (mLastInputSource & InputDevice.SOURCE_TOUCHSCREEN) 12823 == InputDevice.SOURCE_TOUCHSCREEN; 12824 } 12825 12826 /** 12827 * The fill dialog UI is a more conspicuous and efficient interface than dropdown UI. 12828 * If autofill suggestions are available when the user clicks on a field that supports filling 12829 * the dialog UI, Autofill will pop up a fill dialog. The dialog will take up a larger area 12830 * to display the datasets, so it is easy for users to pay attention to the datasets and 12831 * selecting a dataset. The autofill dialog is shown as the bottom sheet, the better 12832 * experience is not to show the IME if there is a fill dialog. 12833 */ showAutofillDialog()12834 private boolean showAutofillDialog() { 12835 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 12836 if (afm != null) { 12837 return afm.showAutofillDialog(this); 12838 } 12839 return false; 12840 } 12841 12842 @Override onGenericMotionEvent(MotionEvent event)12843 public boolean onGenericMotionEvent(MotionEvent event) { 12844 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 12845 try { 12846 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) { 12847 return true; 12848 } 12849 } catch (AbstractMethodError ex) { 12850 // onGenericMotionEvent was added to the MovementMethod interface in API 12. 12851 // Ignore its absence in case third party applications implemented the 12852 // interface directly. 12853 } 12854 } 12855 return super.onGenericMotionEvent(event); 12856 } 12857 12858 @Override onCreateContextMenu(ContextMenu menu)12859 protected void onCreateContextMenu(ContextMenu menu) { 12860 if (mEditor != null) { 12861 mEditor.onCreateContextMenu(menu); 12862 } 12863 } 12864 12865 @Override showContextMenu()12866 public boolean showContextMenu() { 12867 if (mEditor != null) { 12868 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN); 12869 } 12870 return super.showContextMenu(); 12871 } 12872 12873 @Override showContextMenu(float x, float y)12874 public boolean showContextMenu(float x, float y) { 12875 if (mEditor != null) { 12876 mEditor.setContextMenuAnchor(x, y); 12877 } 12878 return super.showContextMenu(x, y); 12879 } 12880 12881 /** 12882 * @return True iff this TextView contains a text that can be edited, or if this is 12883 * a selectable TextView. 12884 */ 12885 @UnsupportedAppUsage isTextEditable()12886 boolean isTextEditable() { 12887 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); 12888 } 12889 12890 /** 12891 * @return true if this TextView could be filled by an Autofill service. Note that disabled 12892 * fields can still be filled. 12893 */ 12894 @UnsupportedAppUsage isTextAutofillable()12895 boolean isTextAutofillable() { 12896 return mText instanceof Editable && onCheckIsTextEditor(); 12897 } 12898 12899 /** 12900 * Returns true, only while processing a touch gesture, if the initial 12901 * touch down event caused focus to move to the text view and as a result 12902 * its selection changed. Only valid while processing the touch gesture 12903 * of interest, in an editable text view. 12904 */ didTouchFocusSelect()12905 public boolean didTouchFocusSelect() { 12906 return mEditor != null && mEditor.mTouchFocusSelected; 12907 } 12908 12909 @Override cancelLongPress()12910 public void cancelLongPress() { 12911 super.cancelLongPress(); 12912 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 12913 } 12914 12915 @Override onTrackballEvent(MotionEvent event)12916 public boolean onTrackballEvent(MotionEvent event) { 12917 if (mMovement != null && mSpannable != null && mLayout != null) { 12918 if (mMovement.onTrackballEvent(this, mSpannable, event)) { 12919 return true; 12920 } 12921 } 12922 12923 return super.onTrackballEvent(event); 12924 } 12925 12926 /** 12927 * Sets the Scroller used for producing a scrolling animation 12928 * 12929 * @param s A Scroller instance 12930 */ setScroller(Scroller s)12931 public void setScroller(Scroller s) { 12932 mScroller = s; 12933 } 12934 12935 @Override getLeftFadingEdgeStrength()12936 protected float getLeftFadingEdgeStrength() { 12937 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 12938 final Marquee marquee = mMarquee; 12939 if (marquee.shouldDrawLeftFade()) { 12940 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f); 12941 } else { 12942 return 0.0f; 12943 } 12944 } else if (getLineCount() == 1) { 12945 final float lineLeft = getLayout().getLineLeft(0); 12946 if (lineLeft > mScrollX) return 0.0f; 12947 return getHorizontalFadingEdgeStrength(mScrollX, lineLeft); 12948 } 12949 return super.getLeftFadingEdgeStrength(); 12950 } 12951 12952 @Override getRightFadingEdgeStrength()12953 protected float getRightFadingEdgeStrength() { 12954 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { 12955 final Marquee marquee = mMarquee; 12956 return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll()); 12957 } else if (getLineCount() == 1) { 12958 final float rightEdge = mScrollX + 12959 (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight()); 12960 final float lineRight = getLayout().getLineRight(0); 12961 if (lineRight < rightEdge) return 0.0f; 12962 return getHorizontalFadingEdgeStrength(rightEdge, lineRight); 12963 } 12964 return super.getRightFadingEdgeStrength(); 12965 } 12966 12967 /** 12968 * Calculates the fading edge strength as the ratio of the distance between two 12969 * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute 12970 * value for the distance calculation. 12971 * 12972 * @param position1 A horizontal position. 12973 * @param position2 A horizontal position. 12974 * @return Fading edge strength between [0.0f, 1.0f]. 12975 */ 12976 @FloatRange(from = 0.0, to = 1.0) getHorizontalFadingEdgeStrength(float position1, float position2)12977 private float getHorizontalFadingEdgeStrength(float position1, float position2) { 12978 final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength(); 12979 if (horizontalFadingEdgeLength == 0) return 0.0f; 12980 final float diff = Math.abs(position1 - position2); 12981 if (diff > horizontalFadingEdgeLength) return 1.0f; 12982 return diff / horizontalFadingEdgeLength; 12983 } 12984 isMarqueeFadeEnabled()12985 private boolean isMarqueeFadeEnabled() { 12986 return mEllipsize == TextUtils.TruncateAt.MARQUEE 12987 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 12988 } 12989 12990 @Override computeHorizontalScrollRange()12991 protected int computeHorizontalScrollRange() { 12992 if (mLayout != null) { 12993 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT 12994 ? (int) mLayout.getLineWidth(0) : mLayout.getWidth(); 12995 } 12996 12997 return super.computeHorizontalScrollRange(); 12998 } 12999 13000 @Override computeVerticalScrollRange()13001 protected int computeVerticalScrollRange() { 13002 if (mLayout != null) { 13003 return mLayout.getHeight(); 13004 } 13005 return super.computeVerticalScrollRange(); 13006 } 13007 13008 @Override computeVerticalScrollExtent()13009 protected int computeVerticalScrollExtent() { 13010 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 13011 } 13012 13013 @Override findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)13014 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { 13015 super.findViewsWithText(outViews, searched, flags); 13016 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 13017 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { 13018 String searchedLowerCase = searched.toString().toLowerCase(); 13019 String textLowerCase = mText.toString().toLowerCase(); 13020 if (textLowerCase.contains(searchedLowerCase)) { 13021 outViews.add(this); 13022 } 13023 } 13024 } 13025 13026 /** 13027 * Type of the text buffer that defines the characteristics of the text such as static, 13028 * styleable, or editable. 13029 */ 13030 public enum BufferType { 13031 NORMAL, SPANNABLE, EDITABLE 13032 } 13033 13034 /** 13035 * Returns the TextView_textColor attribute from the TypedArray, if set, or 13036 * the TextAppearance_textColor from the TextView_textAppearance attribute, 13037 * if TextView_textColor was not set directly. 13038 * 13039 * @removed 13040 */ getTextColors(Context context, TypedArray attrs)13041 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 13042 if (attrs == null) { 13043 // Preserve behavior prior to removal of this API. 13044 throw new NullPointerException(); 13045 } 13046 13047 // It's not safe to use this method from apps. The parameter 'attrs' 13048 // must have been obtained using the TextView filter array which is not 13049 // available to the SDK. As such, we grab a default TypedArray with the 13050 // right filter instead here. 13051 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView); 13052 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor); 13053 if (colors == null) { 13054 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0); 13055 if (ap != 0) { 13056 final TypedArray appearance = context.obtainStyledAttributes( 13057 ap, R.styleable.TextAppearance); 13058 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor); 13059 appearance.recycle(); 13060 } 13061 } 13062 a.recycle(); 13063 13064 return colors; 13065 } 13066 13067 /** 13068 * Returns the default color from the TextView_textColor attribute from the 13069 * AttributeSet, if set, or the default color from the 13070 * TextAppearance_textColor from the TextView_textAppearance attribute, if 13071 * TextView_textColor was not set directly. 13072 * 13073 * @removed 13074 */ getTextColor(Context context, TypedArray attrs, int def)13075 public static int getTextColor(Context context, TypedArray attrs, int def) { 13076 final ColorStateList colors = getTextColors(context, attrs); 13077 if (colors == null) { 13078 return def; 13079 } else { 13080 return colors.getDefaultColor(); 13081 } 13082 } 13083 13084 @Override onKeyShortcut(int keyCode, KeyEvent event)13085 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 13086 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { 13087 // Handle Ctrl-only shortcuts. 13088 switch (keyCode) { 13089 case KeyEvent.KEYCODE_A: 13090 if (canSelectText()) { 13091 return onTextContextMenuItem(ID_SELECT_ALL); 13092 } 13093 break; 13094 case KeyEvent.KEYCODE_Z: 13095 if (canUndo()) { 13096 return onTextContextMenuItem(ID_UNDO); 13097 } 13098 break; 13099 case KeyEvent.KEYCODE_X: 13100 if (canCut()) { 13101 return onTextContextMenuItem(ID_CUT); 13102 } 13103 break; 13104 case KeyEvent.KEYCODE_C: 13105 if (canCopy()) { 13106 return onTextContextMenuItem(ID_COPY); 13107 } 13108 break; 13109 case KeyEvent.KEYCODE_V: 13110 if (canPaste()) { 13111 return onTextContextMenuItem(ID_PASTE); 13112 } 13113 break; 13114 case KeyEvent.KEYCODE_Y: 13115 if (canRedo()) { 13116 return onTextContextMenuItem(ID_REDO); 13117 } 13118 break; 13119 } 13120 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { 13121 // Handle Ctrl-Shift shortcuts. 13122 switch (keyCode) { 13123 case KeyEvent.KEYCODE_Z: 13124 if (canRedo()) { 13125 return onTextContextMenuItem(ID_REDO); 13126 } 13127 break; 13128 case KeyEvent.KEYCODE_V: 13129 if (canPaste()) { 13130 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT); 13131 } 13132 } 13133 } 13134 return super.onKeyShortcut(keyCode, event); 13135 } 13136 13137 /** 13138 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the 13139 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have 13140 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not 13141 * sufficient. 13142 */ canSelectText()13143 boolean canSelectText() { 13144 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); 13145 } 13146 13147 /** 13148 * Test based on the <i>intrinsic</i> charateristics of the TextView. 13149 * The text must be spannable and the movement method must allow for arbitary selection. 13150 * 13151 * See also {@link #canSelectText()}. 13152 */ textCanBeSelected()13153 boolean textCanBeSelected() { 13154 // prepareCursorController() relies on this method. 13155 // If you change this condition, make sure prepareCursorController is called anywhere 13156 // the value of this condition might be changed. 13157 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; 13158 return isTextEditable() 13159 || (isTextSelectable() && mText instanceof Spannable && isEnabled()); 13160 } 13161 13162 @UnsupportedAppUsage getTextServicesLocale(boolean allowNullLocale)13163 private Locale getTextServicesLocale(boolean allowNullLocale) { 13164 // Start fetching the text services locale asynchronously. 13165 updateTextServicesLocaleAsync(); 13166 // If !allowNullLocale and there is no cached text services locale, just return the default 13167 // locale. 13168 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() 13169 : mCurrentSpellCheckerLocaleCache; 13170 } 13171 13172 /** 13173 * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in 13174 * this {@link TextView}. 13175 * 13176 * <p>Most of applications should not worry about this. Some privileged apps that host UI for 13177 * other apps may need to set this so that the system can user right user's resources and 13178 * services such as input methods and spell checkers.</p> 13179 * 13180 * @param user {@link UserHandle} who is considered to be the owner of the text shown in this 13181 * {@link TextView}. {@code null} to reset {@link #mTextOperationUser}. 13182 * @hide 13183 */ 13184 @RequiresPermission(INTERACT_ACROSS_USERS_FULL) setTextOperationUser(@ullable UserHandle user)13185 public final void setTextOperationUser(@Nullable UserHandle user) { 13186 if (Objects.equals(mTextOperationUser, user)) { 13187 return; 13188 } 13189 if (user != null && !Process.myUserHandle().equals(user)) { 13190 // Just for preventing people from accidentally using this hidden API without 13191 // the required permission. The same permission is also checked in the system server. 13192 if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL) 13193 != PackageManager.PERMISSION_GRANTED) { 13194 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required." 13195 + " userId=" + user.getIdentifier() 13196 + " callingUserId" + UserHandle.myUserId()); 13197 } 13198 } 13199 mTextOperationUser = user; 13200 // Invalidate some resources 13201 mCurrentSpellCheckerLocaleCache = null; 13202 if (mEditor != null) { 13203 mEditor.onTextOperationUserChanged(); 13204 } 13205 } 13206 13207 @Override isAutoHandwritingEnabled()13208 public boolean isAutoHandwritingEnabled() { 13209 return super.isAutoHandwritingEnabled() && !isAnyPasswordInputType(); 13210 } 13211 13212 /** @hide */ 13213 @Override isStylusHandwritingAvailable()13214 public boolean isStylusHandwritingAvailable() { 13215 if (mTextOperationUser == null) { 13216 return super.isStylusHandwritingAvailable(); 13217 } 13218 final int userId = mTextOperationUser.getIdentifier(); 13219 final InputMethodManager imm = getInputMethodManager(); 13220 return imm.isStylusHandwritingAvailableAsUser(userId); 13221 } 13222 13223 @Nullable getTextServicesManagerForUser()13224 final TextServicesManager getTextServicesManagerForUser() { 13225 return getServiceManagerForUser("android", TextServicesManager.class); 13226 } 13227 13228 @Nullable getClipboardManagerForUser()13229 final ClipboardManager getClipboardManagerForUser() { 13230 return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class); 13231 } 13232 13233 @Nullable getTextClassificationManagerForUser()13234 final TextClassificationManager getTextClassificationManagerForUser() { 13235 return getServiceManagerForUser( 13236 getContext().getPackageName(), TextClassificationManager.class); 13237 } 13238 13239 @Nullable getServiceManagerForUser(String packageName, Class<T> managerClazz)13240 final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) { 13241 if (mTextOperationUser == null) { 13242 return getContext().getSystemService(managerClazz); 13243 } 13244 try { 13245 Context context = getContext().createPackageContextAsUser( 13246 packageName, 0 /* flags */, mTextOperationUser); 13247 return context.getSystemService(managerClazz); 13248 } catch (PackageManager.NameNotFoundException e) { 13249 return null; 13250 } 13251 } 13252 13253 /** 13254 * Starts {@link Activity} as a text-operation user if it is specified with 13255 * {@link #setTextOperationUser(UserHandle)}. 13256 * 13257 * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p> 13258 * 13259 * @param intent The description of the activity to start. 13260 */ startActivityAsTextOperationUserIfNecessary(@onNull Intent intent)13261 void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) { 13262 if (mTextOperationUser != null) { 13263 getContext().startActivityAsUser(intent, mTextOperationUser); 13264 } else { 13265 getContext().startActivity(intent); 13266 } 13267 } 13268 13269 /** 13270 * This is a temporary method. Future versions may support multi-locale text. 13271 * Caveat: This method may not return the latest text services locale, but this should be 13272 * acceptable and it's more important to make this method asynchronous. 13273 * 13274 * @return The locale that should be used for a word iterator 13275 * in this TextView, based on the current spell checker settings, 13276 * the current IME's locale, or the system default locale. 13277 * Please note that a word iterator in this TextView is different from another word iterator 13278 * used by SpellChecker.java of TextView. This method should be used for the former. 13279 * @hide 13280 */ 13281 // TODO: Support multi-locale 13282 // TODO: Update the text services locale immediately after the keyboard locale is switched 13283 // by catching intent of keyboard switch event getTextServicesLocale()13284 public Locale getTextServicesLocale() { 13285 return getTextServicesLocale(false /* allowNullLocale */); 13286 } 13287 13288 /** 13289 * @return {@code true} if this TextView is specialized for showing and interacting with the 13290 * extracted text in a full-screen input method. 13291 * @hide 13292 */ isInExtractedMode()13293 public boolean isInExtractedMode() { 13294 return false; 13295 } 13296 13297 /** 13298 * @return {@code true} if this widget supports auto-sizing text and has been configured to 13299 * auto-size. 13300 */ isAutoSizeEnabled()13301 private boolean isAutoSizeEnabled() { 13302 return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE; 13303 } 13304 13305 /** 13306 * @return {@code true} if this TextView supports auto-sizing text to fit within its container. 13307 * @hide 13308 */ supportsAutoSizeText()13309 protected boolean supportsAutoSizeText() { 13310 return true; 13311 } 13312 13313 /** 13314 * This is a temporary method. Future versions may support multi-locale text. 13315 * Caveat: This method may not return the latest spell checker locale, but this should be 13316 * acceptable and it's more important to make this method asynchronous. 13317 * 13318 * @return The locale that should be used for a spell checker in this TextView, 13319 * based on the current spell checker settings, the current IME's locale, or the system default 13320 * locale. 13321 * @hide 13322 */ getSpellCheckerLocale()13323 public Locale getSpellCheckerLocale() { 13324 return getTextServicesLocale(true /* allowNullLocale */); 13325 } 13326 updateTextServicesLocaleAsync()13327 private void updateTextServicesLocaleAsync() { 13328 // AsyncTask.execute() uses a serial executor which means we don't have 13329 // to lock around updateTextServicesLocaleLocked() to prevent it from 13330 // being executed n times in parallel. 13331 AsyncTask.execute(new Runnable() { 13332 @Override 13333 public void run() { 13334 updateTextServicesLocaleLocked(); 13335 } 13336 }); 13337 } 13338 13339 @UnsupportedAppUsage updateTextServicesLocaleLocked()13340 private void updateTextServicesLocaleLocked() { 13341 final TextServicesManager textServicesManager = getTextServicesManagerForUser(); 13342 if (textServicesManager == null) { 13343 return; 13344 } 13345 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); 13346 final Locale locale; 13347 if (subtype != null) { 13348 locale = subtype.getLocaleObject(); 13349 } else { 13350 locale = null; 13351 } 13352 mCurrentSpellCheckerLocaleCache = locale; 13353 } 13354 onLocaleChanged()13355 void onLocaleChanged() { 13356 mEditor.onLocaleChanged(); 13357 } 13358 13359 /** 13360 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. 13361 * Made available to achieve a consistent behavior. 13362 * @hide 13363 */ getWordIterator()13364 public WordIterator getWordIterator() { 13365 if (mEditor != null) { 13366 return mEditor.getWordIterator(); 13367 } else { 13368 return null; 13369 } 13370 } 13371 13372 /** @hide */ 13373 @Override onPopulateAccessibilityEventInternal(AccessibilityEvent event)13374 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 13375 super.onPopulateAccessibilityEventInternal(event); 13376 13377 if (this.isAccessibilityDataSensitive() && !event.isAccessibilityDataSensitive()) { 13378 // This view's accessibility data is sensitive, but another view that generated this 13379 // event is not, so don't append this view's text to the event in order to prevent 13380 // sharing this view's contents with non-accessibility-tool services. 13381 return; 13382 } 13383 13384 final CharSequence text = getTextForAccessibility(); 13385 if (!TextUtils.isEmpty(text)) { 13386 event.getText().add(text); 13387 } 13388 } 13389 13390 @Override getAccessibilityClassName()13391 public CharSequence getAccessibilityClassName() { 13392 return TextView.class.getName(); 13393 } 13394 13395 /** @hide */ 13396 @Override onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)13397 protected void onProvideStructure(@NonNull ViewStructure structure, 13398 @ViewStructureType int viewFor, int flags) { 13399 super.onProvideStructure(structure, viewFor, flags); 13400 13401 final boolean isPassword = hasPasswordTransformationMethod() 13402 || isPasswordInputType(getInputType()); 13403 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 13404 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 13405 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 13406 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId); 13407 } 13408 if (mTextId != Resources.ID_NULL) { 13409 try { 13410 structure.setTextIdEntry(getResources().getResourceEntryName(mTextId)); 13411 } catch (Resources.NotFoundException e) { 13412 if (android.view.autofill.Helper.sVerbose) { 13413 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id " 13414 + mTextId + ": " + e.getMessage()); 13415 } 13416 } 13417 } 13418 String[] mimeTypes = getReceiveContentMimeTypes(); 13419 if (mimeTypes == null && mEditor != null) { 13420 // If the app hasn't set a listener for receiving content on this view (ie, 13421 // getReceiveContentMimeTypes() returns null), check if it implements the 13422 // keyboard image API and, if possible, use those MIME types as fallback. 13423 // This fallback is only in place for autofill, not other mechanisms for 13424 // inserting content. See AUTOFILL_NON_TEXT_REQUIRES_ON_RECEIVE_CONTENT_LISTENER 13425 // in TextViewOnReceiveContentListener for more info. 13426 mimeTypes = mEditor.getDefaultOnReceiveContentListener() 13427 .getFallbackMimeTypesForAutofill(this); 13428 } 13429 structure.setReceiveContentMimeTypes(mimeTypes); 13430 } 13431 13432 if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 13433 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 13434 if (mLayout == null) { 13435 if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 13436 Log.w(LOG_TAG, "onProvideContentCaptureStructure(): calling assumeLayout()"); 13437 } 13438 assumeLayout(); 13439 } 13440 Layout layout = mLayout; 13441 final int lineCount = layout.getLineCount(); 13442 if (lineCount <= 1) { 13443 // Simple case: this is a single line. 13444 final CharSequence text = getText(); 13445 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 13446 structure.setText(text); 13447 } else { 13448 structure.setText(text, getSelectionStart(), getSelectionEnd()); 13449 } 13450 } else { 13451 // Complex case: multi-line, could be scrolled or within a scroll container 13452 // so some lines are not visible. 13453 final int[] tmpCords = new int[2]; 13454 getLocationInWindow(tmpCords); 13455 final int topWindowLocation = tmpCords[1]; 13456 View root = this; 13457 ViewParent viewParent = getParent(); 13458 while (viewParent instanceof View) { 13459 root = (View) viewParent; 13460 viewParent = root.getParent(); 13461 } 13462 final int windowHeight = root.getHeight(); 13463 final int topLine; 13464 final int bottomLine; 13465 if (topWindowLocation >= 0) { 13466 // The top of the view is fully within its window; start text at line 0. 13467 topLine = getLineAtCoordinateUnclamped(0); 13468 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1); 13469 } else { 13470 // The top of hte window has scrolled off the top of the window; figure out 13471 // the starting line for this. 13472 topLine = getLineAtCoordinateUnclamped(-topWindowLocation); 13473 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation); 13474 } 13475 // We want to return some contextual lines above/below the lines that are 13476 // actually visible. 13477 int expandedTopLine = topLine - (bottomLine - topLine) / 2; 13478 if (expandedTopLine < 0) { 13479 expandedTopLine = 0; 13480 } 13481 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2; 13482 if (expandedBottomLine >= lineCount) { 13483 expandedBottomLine = lineCount - 1; 13484 } 13485 13486 // Convert lines into character offsets. 13487 int expandedTopChar = transformedToOriginal( 13488 layout.getLineStart(expandedTopLine), 13489 OffsetMapping.MAP_STRATEGY_CHARACTER); 13490 int expandedBottomChar = transformedToOriginal( 13491 layout.getLineEnd(expandedBottomLine), 13492 OffsetMapping.MAP_STRATEGY_CHARACTER); 13493 13494 // Take into account selection -- if there is a selection, we need to expand 13495 // the text we are returning to include that selection. 13496 final int selStart = getSelectionStart(); 13497 final int selEnd = getSelectionEnd(); 13498 if (selStart < selEnd) { 13499 if (selStart < expandedTopChar) { 13500 expandedTopChar = selStart; 13501 } 13502 if (selEnd > expandedBottomChar) { 13503 expandedBottomChar = selEnd; 13504 } 13505 } 13506 13507 // Get the text and trim it to the range we are reporting. 13508 CharSequence text = getText(); 13509 13510 if (text != null) { 13511 if (expandedTopChar > 0 || expandedBottomChar < text.length()) { 13512 // Cap the offsets to avoid an OOB exception. That can happen if the 13513 // displayed/layout text, on which these offsets are calculated, is longer 13514 // than the original text (such as when the view is translated by the 13515 // platform intelligence). 13516 // TODO(b/196433694): Figure out how to better handle the offset 13517 // calculations for this case (so we don't unnecessarily cutoff the original 13518 // text, for example). 13519 expandedTopChar = Math.min(expandedTopChar, text.length()); 13520 expandedBottomChar = Math.min(expandedBottomChar, text.length()); 13521 text = text.subSequence(expandedTopChar, expandedBottomChar); 13522 } 13523 13524 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 13525 structure.setText(text); 13526 } else { 13527 structure.setText(text, 13528 selStart - expandedTopChar, 13529 selEnd - expandedTopChar); 13530 13531 final int[] lineOffsets = new int[bottomLine - topLine + 1]; 13532 final int[] lineBaselines = new int[bottomLine - topLine + 1]; 13533 final int baselineOffset = getBaselineOffset(); 13534 for (int i = topLine; i <= bottomLine; i++) { 13535 lineOffsets[i - topLine] = transformedToOriginal(layout.getLineStart(i), 13536 OffsetMapping.MAP_STRATEGY_CHARACTER); 13537 lineBaselines[i - topLine] = 13538 layout.getLineBaseline(i) + baselineOffset; 13539 } 13540 structure.setTextLines(lineOffsets, lineBaselines); 13541 } 13542 } 13543 } 13544 13545 if (viewFor == VIEW_STRUCTURE_FOR_ASSIST 13546 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 13547 // Extract style information that applies to the TextView as a whole. 13548 int style = 0; 13549 int typefaceStyle = getTypefaceStyle(); 13550 if ((typefaceStyle & Typeface.BOLD) != 0) { 13551 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 13552 } 13553 if ((typefaceStyle & Typeface.ITALIC) != 0) { 13554 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC; 13555 } 13556 13557 // Global styles can also be set via TextView.setPaintFlags(). 13558 int paintFlags = mTextPaint.getFlags(); 13559 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) { 13560 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; 13561 } 13562 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) { 13563 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE; 13564 } 13565 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { 13566 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU; 13567 } 13568 13569 // TextView does not have its own text background color. A background is either part 13570 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text. 13571 structure.setTextStyle(getTextSize(), getCurrentTextColor(), 13572 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style); 13573 } 13574 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL 13575 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { 13576 structure.setMinTextEms(getMinEms()); 13577 structure.setMaxTextEms(getMaxEms()); 13578 int maxLength = -1; 13579 for (InputFilter filter: getFilters()) { 13580 if (filter instanceof InputFilter.LengthFilter) { 13581 maxLength = ((InputFilter.LengthFilter) filter).getMax(); 13582 break; 13583 } 13584 } 13585 structure.setMaxTextLength(maxLength); 13586 } 13587 } 13588 if (mHintId != Resources.ID_NULL) { 13589 try { 13590 structure.setHintIdEntry(getResources().getResourceEntryName(mHintId)); 13591 } catch (Resources.NotFoundException e) { 13592 if (android.view.autofill.Helper.sVerbose) { 13593 Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for hint id " 13594 + mHintId + ": " + e.getMessage()); 13595 } 13596 } 13597 } 13598 structure.setHint(getHint()); 13599 structure.setInputType(getInputType()); 13600 } 13601 canRequestAutofill()13602 boolean canRequestAutofill() { 13603 if (!isAutofillable()) { 13604 return false; 13605 } 13606 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 13607 if (afm != null) { 13608 return afm.isEnabled(); 13609 } 13610 return false; 13611 } 13612 requestAutofill()13613 private void requestAutofill() { 13614 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 13615 if (afm != null) { 13616 afm.requestAutofill(this); 13617 } 13618 } 13619 13620 @Override autofill(AutofillValue value)13621 public void autofill(AutofillValue value) { 13622 if (!isTextAutofillable()) { 13623 Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this); 13624 return; 13625 } 13626 if (!value.isText()) { 13627 Log.w(LOG_TAG, "value of type " + value.describeContents() 13628 + " cannot be autofilled into " + this); 13629 return; 13630 } 13631 final ClipData clip = ClipData.newPlainText("", value.getTextValue()); 13632 final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build(); 13633 performReceiveContent(payload); 13634 } 13635 13636 @Override getAutofillType()13637 public @AutofillType int getAutofillType() { 13638 return isTextAutofillable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE; 13639 } 13640 13641 /** 13642 * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K 13643 * {@code char}s if longer. 13644 * 13645 * @return current text, {@code null} if the text is not editable 13646 * 13647 * @see View#getAutofillValue() 13648 */ 13649 @Override 13650 @Nullable getAutofillValue()13651 public AutofillValue getAutofillValue() { 13652 if (isTextAutofillable()) { 13653 final CharSequence text = TextUtils.trimToParcelableSize(getText()); 13654 return AutofillValue.forText(text); 13655 } 13656 return null; 13657 } 13658 13659 /** @hide */ 13660 @Override onInitializeAccessibilityEventInternal(AccessibilityEvent event)13661 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 13662 super.onInitializeAccessibilityEventInternal(event); 13663 13664 final boolean isPassword = hasPasswordTransformationMethod(); 13665 event.setPassword(isPassword); 13666 13667 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { 13668 event.setFromIndex(Selection.getSelectionStart(mText)); 13669 event.setToIndex(Selection.getSelectionEnd(mText)); 13670 event.setItemCount(mText.length()); 13671 } 13672 } 13673 13674 /** @hide */ 13675 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)13676 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 13677 super.onInitializeAccessibilityNodeInfoInternal(info); 13678 13679 final boolean isPassword = hasPasswordTransformationMethod(); 13680 info.setPassword(isPassword); 13681 info.setText(getTextForAccessibility()); 13682 info.setHintText(mHint); 13683 info.setShowingHintText(isShowingHint()); 13684 13685 if (mBufferType == BufferType.EDITABLE) { 13686 info.setEditable(true); 13687 if (isEnabled()) { 13688 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT); 13689 } 13690 } 13691 13692 if (mEditor != null) { 13693 info.setInputType(mEditor.mInputType); 13694 13695 if (mEditor.mError != null) { 13696 info.setContentInvalid(true); 13697 info.setError(mEditor.mError); 13698 } 13699 // TextView will expose this action if it is editable and has focus. 13700 if (isTextEditable() && isFocused()) { 13701 CharSequence imeActionLabel = mContext.getResources().getString( 13702 com.android.internal.R.string.keyboardview_keycode_enter); 13703 if (getImeActionLabel() != null) { 13704 imeActionLabel = getImeActionLabel(); 13705 } 13706 AccessibilityNodeInfo.AccessibilityAction action = 13707 new AccessibilityNodeInfo.AccessibilityAction( 13708 R.id.accessibilityActionImeEnter, imeActionLabel); 13709 info.addAction(action); 13710 } 13711 } 13712 13713 if (!TextUtils.isEmpty(mText)) { 13714 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); 13715 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); 13716 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER 13717 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD 13718 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE 13719 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH 13720 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); 13721 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); 13722 info.setAvailableExtraData(Arrays.asList( 13723 EXTRA_DATA_RENDERING_INFO_KEY, 13724 EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY 13725 )); 13726 info.setTextSelectable(isTextSelectable() || isTextEditable()); 13727 } else { 13728 info.setAvailableExtraData(Arrays.asList( 13729 EXTRA_DATA_RENDERING_INFO_KEY 13730 )); 13731 } 13732 13733 if (isFocused()) { 13734 if (canCopy()) { 13735 info.addAction(AccessibilityNodeInfo.ACTION_COPY); 13736 } 13737 if (canPaste()) { 13738 info.addAction(AccessibilityNodeInfo.ACTION_PASTE); 13739 } 13740 if (canCut()) { 13741 info.addAction(AccessibilityNodeInfo.ACTION_CUT); 13742 } 13743 if (canReplace()) { 13744 info.addAction( 13745 AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TEXT_SUGGESTIONS); 13746 } 13747 if (canShare()) { 13748 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 13749 ACCESSIBILITY_ACTION_SHARE, 13750 getResources().getString(com.android.internal.R.string.share))); 13751 } 13752 if (canProcessText()) { // also implies mEditor is not null. 13753 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info); 13754 mEditor.onInitializeSmartActionsAccessibilityNodeInfo(info); 13755 } 13756 } 13757 13758 // Check for known input filter types. 13759 final int numFilters = mFilters.length; 13760 for (int i = 0; i < numFilters; i++) { 13761 final InputFilter filter = mFilters[i]; 13762 if (filter instanceof InputFilter.LengthFilter) { 13763 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax()); 13764 } 13765 } 13766 13767 if (!isSingleLine()) { 13768 info.setMultiLine(true); 13769 } 13770 13771 // A view should not be exposed as clickable/long-clickable to a service because of a 13772 // LinkMovementMethod or because it has selectable and non-editable text. 13773 if ((info.isClickable() || info.isLongClickable()) 13774 && (mMovement instanceof LinkMovementMethod 13775 || (isTextSelectable() && !isTextEditable()))) { 13776 if (!hasOnClickListeners()) { 13777 info.setClickable(false); 13778 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); 13779 } 13780 if (!hasOnLongClickListeners()) { 13781 info.setLongClickable(false); 13782 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); 13783 } 13784 } 13785 } 13786 13787 @Override addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments)13788 public void addExtraDataToAccessibilityNodeInfo( 13789 AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) { 13790 if (arguments != null && extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) { 13791 int positionInfoStartIndex = arguments.getInt( 13792 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1); 13793 int positionInfoLength = arguments.getInt( 13794 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1); 13795 if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0) 13796 || (positionInfoStartIndex >= mText.length())) { 13797 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations"); 13798 return; 13799 } 13800 RectF[] boundingRects = new RectF[positionInfoLength]; 13801 final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); 13802 populateCharacterBounds(builder, positionInfoStartIndex, 13803 Math.min(positionInfoStartIndex + positionInfoLength, length()), 13804 viewportToContentHorizontalOffset(), viewportToContentVerticalOffset()); 13805 CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build(); 13806 for (int i = 0; i < positionInfoLength; i++) { 13807 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i); 13808 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) { 13809 RectF bounds = cursorAnchorInfo 13810 .getCharacterBounds(positionInfoStartIndex + i); 13811 if (bounds != null) { 13812 mapRectFromViewToScreenCoords(bounds, true); 13813 boundingRects[i] = bounds; 13814 } 13815 } 13816 } 13817 info.getExtras().putParcelableArray(extraDataKey, boundingRects); 13818 return; 13819 } 13820 if (extraDataKey.equals(AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY)) { 13821 final AccessibilityNodeInfo.ExtraRenderingInfo extraRenderingInfo = 13822 AccessibilityNodeInfo.ExtraRenderingInfo.obtain(); 13823 extraRenderingInfo.setLayoutSize(getLayoutParams().width, getLayoutParams().height); 13824 extraRenderingInfo.setTextSizeInPx(getTextSize()); 13825 extraRenderingInfo.setTextSizeUnit(getTextSizeUnit()); 13826 info.setExtraRenderingInfo(extraRenderingInfo); 13827 } 13828 } 13829 13830 /** 13831 * Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates. 13832 * This method obtains the view's visible rectangle whereas the method 13833 * {@link #getContentVisibleRect} returns the text layout's visible rectangle. 13834 * 13835 * @return true if at least part of the text content is visible; false if the text content is 13836 * completely clipped or translated out of the visible area. 13837 */ getViewVisibleRect(Rect rect)13838 private boolean getViewVisibleRect(Rect rect) { 13839 if (!getLocalVisibleRect(rect)) { 13840 return false; 13841 } 13842 // getLocalVisibleRect returns a rect relative to the unscrolled left top corner of the 13843 // view. In other words, the returned rectangle's origin point is (-scrollX, -scrollY) in 13844 // view's coordinates. So we need to offset it with the negative scrolled amount to convert 13845 // it to view's coordinate. 13846 rect.offset(-getScrollX(), -getScrollY()); 13847 return true; 13848 } 13849 13850 /** 13851 * Helper method to set {@code rect} to the text content's non-clipped area in the view's 13852 * coordinates. 13853 * 13854 * @return true if at least part of the text content is visible; false if the text content is 13855 * completely clipped or translated out of the visible area. 13856 */ getContentVisibleRect(Rect rect)13857 private boolean getContentVisibleRect(Rect rect) { 13858 if (!getViewVisibleRect(rect)) { 13859 return false; 13860 } 13861 // Clip the view's visible rect with the text layout's visible rect. 13862 return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(), 13863 getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom()); 13864 } 13865 13866 /** 13867 * Populate requested character bounds in a {@link CursorAnchorInfo.Builder} 13868 * 13869 * @param builder The builder to populate 13870 * @param startIndex The starting character index to populate 13871 * @param endIndex The ending character index to populate 13872 * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the 13873 * content 13874 * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content 13875 * @hide 13876 */ populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset)13877 public void populateCharacterBounds(CursorAnchorInfo.Builder builder, 13878 int startIndex, int endIndex, float viewportToContentHorizontalOffset, 13879 float viewportToContentVerticalOffset) { 13880 if (isOffsetMappingAvailable()) { 13881 // The text is transformed, and has different length, we don't support 13882 // character bounds in this case yet. 13883 return; 13884 } 13885 final Rect rect = new Rect(); 13886 getContentVisibleRect(rect); 13887 final RectF visibleRect = new RectF(rect); 13888 13889 final float[] characterBounds = getCharacterBounds(startIndex, endIndex, 13890 viewportToContentHorizontalOffset, viewportToContentVerticalOffset); 13891 final int limit = endIndex - startIndex; 13892 for (int offset = 0; offset < limit; ++offset) { 13893 final float left = characterBounds[offset * 4]; 13894 final float top = characterBounds[offset * 4 + 1]; 13895 final float right = characterBounds[offset * 4 + 2]; 13896 final float bottom = characterBounds[offset * 4 + 3]; 13897 13898 final boolean hasVisibleRegion = visibleRect.intersects(left, top, right, bottom); 13899 final boolean hasInVisibleRegion = !visibleRect.contains(left, top, right, bottom); 13900 int characterBoundsFlags = 0; 13901 if (hasVisibleRegion) { 13902 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; 13903 } 13904 if (hasInVisibleRegion) { 13905 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; 13906 } 13907 13908 if (mLayout.isRtlCharAt(offset)) { 13909 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; 13910 } 13911 builder.addCharacterBounds(offset + startIndex, left, top, right, bottom, 13912 characterBoundsFlags); 13913 } 13914 } 13915 13916 /** 13917 * Return the bounds of the characters in the given range, in TextView's coordinates. 13918 * 13919 * @param start the start index of the interested text range, inclusive. 13920 * @param end the end index of the interested text range, exclusive. 13921 * @param layoutLeft the left of the given {@code layout} in the editor view's coordinates. 13922 * @param layoutTop the top of the given {@code layout} in the editor view's coordinates. 13923 * @return the character bounds stored in a flattened array, in the editor view's coordinates. 13924 */ getCharacterBounds(int start, int end, float layoutLeft, float layoutTop)13925 private float[] getCharacterBounds(int start, int end, float layoutLeft, float layoutTop) { 13926 final float[] characterBounds = new float[4 * (end - start)]; 13927 mLayout.fillCharacterBounds(start, end, characterBounds, 0); 13928 for (int offset = 0; offset < end - start; ++offset) { 13929 characterBounds[4 * offset] += layoutLeft; 13930 characterBounds[4 * offset + 1] += layoutTop; 13931 characterBounds[4 * offset + 2] += layoutLeft; 13932 characterBounds[4 * offset + 3] += layoutTop; 13933 } 13934 return characterBounds; 13935 } 13936 13937 /** 13938 * Compute {@link CursorAnchorInfo} from this {@link TextView}. 13939 * 13940 * @param filter the {@link CursorAnchorInfo} update filter which specified the needed 13941 * information from IME. 13942 * @param cursorAnchorInfoBuilder a cached {@link CursorAnchorInfo.Builder} object used to build 13943 * the result {@link CursorAnchorInfo}. 13944 * @param viewToScreenMatrix a cached {@link Matrix} object used to compute the view to screen 13945 * matrix. 13946 * @return the result {@link CursorAnchorInfo} to be passed to IME. 13947 * @hide 13948 */ 13949 @VisibleForTesting 13950 @Nullable getCursorAnchorInfo(@nputConnection.CursorUpdateFilter int filter, @NonNull CursorAnchorInfo.Builder cursorAnchorInfoBuilder, @NonNull Matrix viewToScreenMatrix)13951 public CursorAnchorInfo getCursorAnchorInfo(@InputConnection.CursorUpdateFilter int filter, 13952 @NonNull CursorAnchorInfo.Builder cursorAnchorInfoBuilder, 13953 @NonNull Matrix viewToScreenMatrix) { 13954 Layout layout = getLayout(); 13955 if (layout == null) { 13956 return null; 13957 } 13958 boolean includeEditorBounds = 13959 (filter & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0; 13960 boolean includeCharacterBounds = 13961 (filter & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0; 13962 boolean includeInsertionMarker = 13963 (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0; 13964 boolean includeVisibleLineBounds = 13965 (filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0; 13966 boolean includeTextAppearance = 13967 (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0; 13968 boolean includeAll = 13969 (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker 13970 && !includeVisibleLineBounds && !includeTextAppearance); 13971 13972 includeEditorBounds |= includeAll; 13973 includeCharacterBounds |= includeAll; 13974 includeInsertionMarker |= includeAll; 13975 includeVisibleLineBounds |= includeAll; 13976 includeTextAppearance |= includeAll; 13977 13978 final CursorAnchorInfo.Builder builder = cursorAnchorInfoBuilder; 13979 builder.reset(); 13980 13981 final int selectionStart = getSelectionStart(); 13982 builder.setSelectionRange(selectionStart, getSelectionEnd()); 13983 13984 // Construct transformation matrix from view local coordinates to screen coordinates. 13985 viewToScreenMatrix.reset(); 13986 transformMatrixToGlobal(viewToScreenMatrix); 13987 builder.setMatrix(viewToScreenMatrix); 13988 13989 if (includeEditorBounds) { 13990 if (mTempRect == null) { 13991 mTempRect = new Rect(); 13992 } 13993 final Rect bounds = mTempRect; 13994 final RectF editorBounds; 13995 final RectF handwritingBounds; 13996 if (getViewVisibleRect(bounds)) { 13997 editorBounds = new RectF(bounds); 13998 handwritingBounds = new RectF(editorBounds); 13999 handwritingBounds.top -= getHandwritingBoundsOffsetTop(); 14000 handwritingBounds.left -= getHandwritingBoundsOffsetLeft(); 14001 handwritingBounds.bottom += getHandwritingBoundsOffsetBottom(); 14002 handwritingBounds.right += getHandwritingBoundsOffsetRight(); 14003 } else { 14004 // The editor is not visible at all, return empty rectangles. We still need to 14005 // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo. 14006 editorBounds = new RectF(); 14007 handwritingBounds = new RectF(); 14008 } 14009 EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder(); 14010 EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds) 14011 .setHandwritingBounds(handwritingBounds).build(); 14012 builder.setEditorBoundsInfo(editorBoundsInfo); 14013 } 14014 14015 if (includeCharacterBounds || includeInsertionMarker || includeVisibleLineBounds) { 14016 final float viewportToContentHorizontalOffset = 14017 viewportToContentHorizontalOffset(); 14018 final float viewportToContentVerticalOffset = 14019 viewportToContentVerticalOffset(); 14020 final boolean isTextTransformed = (getTransformationMethod() != null 14021 && getTransformed() instanceof OffsetMapping); 14022 if (includeCharacterBounds && !isTextTransformed) { 14023 final CharSequence text = getText(); 14024 if (text instanceof Spannable) { 14025 final Spannable sp = (Spannable) text; 14026 int composingTextStart = EditableInputConnection.getComposingSpanStart(sp); 14027 int composingTextEnd = EditableInputConnection.getComposingSpanEnd(sp); 14028 if (composingTextEnd < composingTextStart) { 14029 final int temp = composingTextEnd; 14030 composingTextEnd = composingTextStart; 14031 composingTextStart = temp; 14032 } 14033 final boolean hasComposingText = 14034 (0 <= composingTextStart) && (composingTextStart 14035 < composingTextEnd); 14036 if (hasComposingText) { 14037 final CharSequence composingText = text.subSequence(composingTextStart, 14038 composingTextEnd); 14039 builder.setComposingText(composingTextStart, composingText); 14040 populateCharacterBounds(builder, composingTextStart, 14041 composingTextEnd, viewportToContentHorizontalOffset, 14042 viewportToContentVerticalOffset); 14043 } 14044 } 14045 } 14046 14047 if (includeInsertionMarker) { 14048 // Treat selectionStart as the insertion point. 14049 if (0 <= selectionStart) { 14050 final int offsetTransformed = originalToTransformed( 14051 selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR); 14052 final int line = layout.getLineForOffset(offsetTransformed); 14053 final float insertionMarkerX = 14054 layout.getPrimaryHorizontal( 14055 offsetTransformed, layout.shouldClampCursor(line)) 14056 + viewportToContentHorizontalOffset; 14057 final float insertionMarkerTop = layout.getLineTop(line) 14058 + viewportToContentVerticalOffset; 14059 final float insertionMarkerBaseline = layout.getLineBaseline(line) 14060 + viewportToContentVerticalOffset; 14061 final float insertionMarkerBottom = 14062 layout.getLineBottom(line, /* includeLineSpacing= */ false) 14063 + viewportToContentVerticalOffset; 14064 final boolean isTopVisible = 14065 isPositionVisible(insertionMarkerX, insertionMarkerTop); 14066 final boolean isBottomVisible = 14067 isPositionVisible(insertionMarkerX, insertionMarkerBottom); 14068 int insertionMarkerFlags = 0; 14069 if (isTopVisible || isBottomVisible) { 14070 insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; 14071 } 14072 if (!isTopVisible || !isBottomVisible) { 14073 insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; 14074 } 14075 if (layout.isRtlCharAt(offsetTransformed)) { 14076 insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL; 14077 } 14078 builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop, 14079 insertionMarkerBaseline, insertionMarkerBottom, 14080 insertionMarkerFlags); 14081 } 14082 } 14083 14084 if (includeVisibleLineBounds) { 14085 final Rect visibleRect = new Rect(); 14086 if (getContentVisibleRect(visibleRect)) { 14087 // Subtract the viewportToContentVerticalOffset to convert the view 14088 // coordinates to layout coordinates. 14089 final float visibleTop = 14090 visibleRect.top - viewportToContentVerticalOffset; 14091 final float visibleBottom = 14092 visibleRect.bottom - viewportToContentVerticalOffset; 14093 final int firstLine = 14094 layout.getLineForVertical((int) Math.floor(visibleTop)); 14095 final int lastLine = 14096 layout.getLineForVertical((int) Math.ceil(visibleBottom)); 14097 14098 for (int line = firstLine; line <= lastLine; ++line) { 14099 final float left = layout.getLineLeft(line) 14100 + viewportToContentHorizontalOffset; 14101 final float top = layout.getLineTop(line) 14102 + viewportToContentVerticalOffset; 14103 final float right = layout.getLineRight(line) 14104 + viewportToContentHorizontalOffset; 14105 final float bottom = layout.getLineBottom(line, false) 14106 + viewportToContentVerticalOffset; 14107 builder.addVisibleLineBounds(left, top, right, bottom); 14108 } 14109 } 14110 } 14111 } 14112 14113 if (includeTextAppearance) { 14114 builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(this)); 14115 } 14116 return builder.build(); 14117 } 14118 14119 /** 14120 * Creates the {@link TextBoundsInfo} for the text lines that intersects with the {@code rectF}. 14121 * @hide 14122 */ getTextBoundsInfo(@onNull RectF bounds)14123 public TextBoundsInfo getTextBoundsInfo(@NonNull RectF bounds) { 14124 final Layout layout = getLayout(); 14125 if (layout == null) { 14126 // No valid text layout, return null. 14127 return null; 14128 } 14129 final CharSequence text = layout.getText(); 14130 if (text == null || isOffsetMappingAvailable()) { 14131 // The text is Null or the text has been transformed. Can't provide TextBoundsInfo. 14132 return null; 14133 } 14134 14135 final Matrix localToGlobalMatrix = new Matrix(); 14136 transformMatrixToGlobal(localToGlobalMatrix); 14137 final Matrix globalToLocalMatrix = new Matrix(); 14138 if (!localToGlobalMatrix.invert(globalToLocalMatrix)) { 14139 // Can't map global rectF to local coordinates, this is almost impossible in practice. 14140 return null; 14141 } 14142 14143 final float layoutLeft = viewportToContentHorizontalOffset(); 14144 final float layoutTop = viewportToContentVerticalOffset(); 14145 14146 final RectF localBounds = new RectF(bounds); 14147 globalToLocalMatrix.mapRect(localBounds); 14148 localBounds.offset(-layoutLeft, -layoutTop); 14149 14150 // Text length is 0. There is no character bounds, return empty TextBoundsInfo. 14151 // rectF doesn't intersect with the layout, return empty TextBoundsInfo. 14152 if (!localBounds.intersects(0f, 0f, layout.getWidth(), layout.getHeight()) 14153 || text.length() == 0) { 14154 final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder(0, 0); 14155 final SegmentFinder emptySegmentFinder = 14156 new SegmentFinder.PrescribedSegmentFinder(new int[0]); 14157 builder.setMatrix(localToGlobalMatrix) 14158 .setCharacterBounds(new float[0]) 14159 .setCharacterBidiLevel(new int[0]) 14160 .setCharacterFlags(new int[0]) 14161 .setGraphemeSegmentFinder(emptySegmentFinder) 14162 .setLineSegmentFinder(emptySegmentFinder) 14163 .setWordSegmentFinder(emptySegmentFinder); 14164 return builder.build(); 14165 } 14166 14167 final int startLine = layout.getLineForVertical((int) Math.floor(localBounds.top)); 14168 final int endLine = layout.getLineForVertical((int) Math.floor(localBounds.bottom)); 14169 final int start = layout.getLineStart(startLine); 14170 final int end = layout.getLineEnd(endLine); 14171 14172 // Compute character bounds. 14173 final float[] characterBounds = getCharacterBounds(start, end, layoutLeft, layoutTop); 14174 14175 // Compute character flags and BiDi levels. 14176 final int[] characterFlags = new int[end - start]; 14177 final int[] characterBidiLevels = new int[end - start]; 14178 for (int line = startLine; line <= endLine; ++line) { 14179 final int lineStart = layout.getLineStart(line); 14180 final int lineEnd = layout.getLineEnd(line); 14181 final Layout.Directions directions = layout.getLineDirections(line); 14182 for (int i = 0; i < directions.getRunCount(); ++i) { 14183 final int runStart = directions.getRunStart(i) + lineStart; 14184 final int runEnd = Math.min(runStart + directions.getRunLength(i), lineEnd); 14185 final int runLevel = directions.getRunLevel(i); 14186 Arrays.fill(characterBidiLevels, runStart - start, runEnd - start, runLevel); 14187 } 14188 14189 final boolean lineIsRtl = 14190 layout.getParagraphDirection(line) == Layout.DIR_RIGHT_TO_LEFT; 14191 for (int index = lineStart; index < lineEnd; ++index) { 14192 int flags = 0; 14193 if (TextUtils.isWhitespace(text.charAt(index))) { 14194 flags |= TextBoundsInfo.FLAG_CHARACTER_WHITESPACE; 14195 } 14196 if (TextUtils.isPunctuation(Character.codePointAt(text, index))) { 14197 flags |= TextBoundsInfo.FLAG_CHARACTER_PUNCTUATION; 14198 } 14199 if (TextUtils.isNewline(Character.codePointAt(text, index))) { 14200 flags |= TextBoundsInfo.FLAG_CHARACTER_LINEFEED; 14201 } 14202 if (lineIsRtl) { 14203 flags |= TextBoundsInfo.FLAG_LINE_IS_RTL; 14204 } 14205 characterFlags[index - start] = flags; 14206 } 14207 } 14208 14209 // Create grapheme SegmentFinder. 14210 final SegmentFinder graphemeSegmentFinder = 14211 new GraphemeClusterSegmentFinder(text, layout.getPaint()); 14212 14213 // Create word SegmentFinder. 14214 final WordIterator wordIterator = getWordIterator(); 14215 wordIterator.setCharSequence(text, 0, text.length()); 14216 final SegmentFinder wordSegmentFinder = new WordSegmentFinder(text, wordIterator); 14217 14218 // Create line SegmentFinder. 14219 final int lineCount = endLine - startLine + 1; 14220 final int[] lineRanges = new int[2 * lineCount]; 14221 for (int line = startLine; line <= endLine; ++line) { 14222 final int offset = line - startLine; 14223 lineRanges[2 * offset] = layout.getLineStart(line); 14224 lineRanges[2 * offset + 1] = layout.getLineEnd(line); 14225 } 14226 final SegmentFinder lineSegmentFinder = 14227 new SegmentFinder.PrescribedSegmentFinder(lineRanges); 14228 14229 return new TextBoundsInfo.Builder(start, end) 14230 .setMatrix(localToGlobalMatrix) 14231 .setCharacterBounds(characterBounds) 14232 .setCharacterBidiLevel(characterBidiLevels) 14233 .setCharacterFlags(characterFlags) 14234 .setGraphemeSegmentFinder(graphemeSegmentFinder) 14235 .setLineSegmentFinder(lineSegmentFinder) 14236 .setWordSegmentFinder(wordSegmentFinder) 14237 .build(); 14238 } 14239 14240 /** 14241 * @hide 14242 */ isPositionVisible(final float positionX, final float positionY)14243 public boolean isPositionVisible(final float positionX, final float positionY) { 14244 synchronized (TEMP_POSITION) { 14245 final float[] position = TEMP_POSITION; 14246 position[0] = positionX; 14247 position[1] = positionY; 14248 View view = this; 14249 14250 while (view != null) { 14251 if (view != this) { 14252 // Local scroll is already taken into account in positionX/Y 14253 position[0] -= view.getScrollX(); 14254 position[1] -= view.getScrollY(); 14255 } 14256 14257 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth() 14258 || position[1] > view.getHeight()) { 14259 return false; 14260 } 14261 14262 if (!view.getMatrix().isIdentity()) { 14263 view.getMatrix().mapPoints(position); 14264 } 14265 14266 position[0] += view.getLeft(); 14267 position[1] += view.getTop(); 14268 14269 final ViewParent parent = view.getParent(); 14270 if (parent instanceof View) { 14271 view = (View) parent; 14272 } else { 14273 // We've reached the ViewRoot, stop iterating 14274 view = null; 14275 } 14276 } 14277 } 14278 14279 // We've been able to walk up the view hierarchy and the position was never clipped 14280 return true; 14281 } 14282 14283 /** 14284 * Performs an accessibility action after it has been offered to the 14285 * delegate. 14286 * 14287 * @hide 14288 */ 14289 @Override performAccessibilityActionInternal(int action, Bundle arguments)14290 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 14291 if (mEditor != null) { 14292 if (mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action) 14293 || mEditor.performSmartActionsAccessibilityAction(action)) { 14294 return true; 14295 } 14296 } 14297 switch (action) { 14298 case AccessibilityNodeInfo.ACTION_CLICK: { 14299 return performAccessibilityActionClick(arguments); 14300 } 14301 case AccessibilityNodeInfo.ACTION_COPY: { 14302 if (isFocused() && canCopy()) { 14303 if (onTextContextMenuItem(ID_COPY)) { 14304 return true; 14305 } 14306 } 14307 } return false; 14308 case AccessibilityNodeInfo.ACTION_PASTE: { 14309 if (isFocused() && canPaste()) { 14310 if (onTextContextMenuItem(ID_PASTE)) { 14311 return true; 14312 } 14313 } 14314 } return false; 14315 case AccessibilityNodeInfo.ACTION_CUT: { 14316 if (isFocused() && canCut()) { 14317 if (onTextContextMenuItem(ID_CUT)) { 14318 return true; 14319 } 14320 } 14321 } return false; 14322 case AccessibilityNodeInfo.ACTION_SET_SELECTION: { 14323 ensureIterableTextForAccessibilitySelectable(); 14324 CharSequence text = getIterableTextForAccessibility(); 14325 if (text == null) { 14326 return false; 14327 } 14328 final int start = (arguments != null) ? arguments.getInt( 14329 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1; 14330 final int end = (arguments != null) ? arguments.getInt( 14331 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1; 14332 if ((getSelectionStart() != start || getSelectionEnd() != end)) { 14333 // No arguments clears the selection. 14334 if (start == end && end == -1) { 14335 Selection.removeSelection((Spannable) text); 14336 return true; 14337 } 14338 if (start >= 0 && start <= end && end <= text.length()) { 14339 requestFocusOnNonEditableSelectableText(); 14340 Selection.setSelection((Spannable) text, start, end); 14341 // Make sure selection mode is engaged. 14342 if (mEditor != null) { 14343 mEditor.startSelectionActionModeAsync(false); 14344 } 14345 return true; 14346 } 14347 } 14348 } return false; 14349 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 14350 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { 14351 ensureIterableTextForAccessibilitySelectable(); 14352 return super.performAccessibilityActionInternal(action, arguments); 14353 } 14354 case ACCESSIBILITY_ACTION_SHARE: { 14355 if (isFocused() && canShare()) { 14356 if (onTextContextMenuItem(ID_SHARE)) { 14357 return true; 14358 } 14359 } 14360 } return false; 14361 case AccessibilityNodeInfo.ACTION_SET_TEXT: { 14362 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) { 14363 return false; 14364 } 14365 CharSequence text = (arguments != null) ? arguments.getCharSequence( 14366 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null; 14367 setText(text); 14368 if (mText != null) { 14369 int updatedTextLength = mText.length(); 14370 if (updatedTextLength > 0) { 14371 Selection.setSelection(mSpannable, updatedTextLength); 14372 } 14373 } 14374 } return true; 14375 case R.id.accessibilityActionImeEnter: { 14376 if (isFocused() && isTextEditable()) { 14377 onEditorAction(getImeActionId()); 14378 } 14379 } return true; 14380 case AccessibilityNodeInfo.ACTION_LONG_CLICK: { 14381 if (isLongClickable()) { 14382 boolean handled; 14383 if (isEnabled() && (mBufferType == BufferType.EDITABLE)) { 14384 mEditor.mIsBeingLongClickedByAccessibility = true; 14385 try { 14386 handled = performLongClick(); 14387 } finally { 14388 mEditor.mIsBeingLongClickedByAccessibility = false; 14389 } 14390 } else { 14391 handled = performLongClick(); 14392 } 14393 return handled; 14394 } 14395 } 14396 return false; 14397 default: { 14398 // New ids have static blocks to assign values, so they can't be used in a case 14399 // block. 14400 if (action == R.id.accessibilityActionShowTextSuggestions) { 14401 return isFocused() && canReplace() && onTextContextMenuItem(ID_REPLACE); 14402 } 14403 return super.performAccessibilityActionInternal(action, arguments); 14404 } 14405 } 14406 } 14407 performAccessibilityActionClick(Bundle arguments)14408 private boolean performAccessibilityActionClick(Bundle arguments) { 14409 boolean handled = false; 14410 14411 if (!isEnabled()) { 14412 return false; 14413 } 14414 14415 if (isClickable() || isLongClickable()) { 14416 // Simulate View.onTouchEvent for an ACTION_UP event 14417 if (isFocusable() && !isFocused()) { 14418 requestFocus(); 14419 } 14420 14421 performClick(); 14422 handled = true; 14423 } 14424 14425 // Show the IME, except when selecting in read-only text. 14426 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null 14427 && (isTextEditable() || isTextSelectable()) && isFocused()) { 14428 final InputMethodManager imm = getInputMethodManager(); 14429 viewClicked(imm); 14430 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) { 14431 handled |= imm.showSoftInput(this, 0); 14432 } 14433 } 14434 14435 return handled; 14436 } 14437 requestFocusOnNonEditableSelectableText()14438 private void requestFocusOnNonEditableSelectableText() { 14439 if (!isTextEditable() && isTextSelectable()) { 14440 if (!isEnabled()) { 14441 return; 14442 } 14443 14444 if (isFocusable() && !isFocused()) { 14445 requestFocus(); 14446 } 14447 } 14448 } 14449 hasSpannableText()14450 private boolean hasSpannableText() { 14451 return mText != null && mText instanceof Spannable; 14452 } 14453 14454 /** @hide */ 14455 @Override sendAccessibilityEventInternal(int eventType)14456 public void sendAccessibilityEventInternal(int eventType) { 14457 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) { 14458 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions(); 14459 } 14460 14461 super.sendAccessibilityEventInternal(eventType); 14462 } 14463 14464 @Override sendAccessibilityEventUnchecked(AccessibilityEvent event)14465 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { 14466 // Do not send scroll events since first they are not interesting for 14467 // accessibility and second such events a generated too frequently. 14468 // For details see the implementation of bringTextIntoView(). 14469 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 14470 return; 14471 } 14472 super.sendAccessibilityEventUnchecked(event); 14473 } 14474 14475 /** 14476 * Returns the text that should be exposed to accessibility services. 14477 * <p> 14478 * This approximates what is displayed visually. 14479 * 14480 * @return the text that should be exposed to accessibility services, may 14481 * be {@code null} if no text is set 14482 */ 14483 @Nullable 14484 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getTextForAccessibility()14485 private CharSequence getTextForAccessibility() { 14486 // If the text is empty, we must be showing the hint text. 14487 if (TextUtils.isEmpty(mText)) { 14488 return mHint; 14489 } 14490 14491 // Otherwise, return whatever text is being displayed. 14492 return TextUtils.trimToParcelableSize(mTransformed); 14493 } 14494 isVisibleToAccessibility()14495 boolean isVisibleToAccessibility() { 14496 return AccessibilityManager.getInstance(mContext).isEnabled() 14497 && (isFocused() || (isSelected() && isShown())); 14498 } 14499 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)14500 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 14501 int fromIndex, int removedCount, int addedCount) { 14502 AccessibilityEvent event = 14503 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 14504 event.setFromIndex(fromIndex); 14505 event.setRemovedCount(removedCount); 14506 event.setAddedCount(addedCount); 14507 event.setBeforeText(beforeText); 14508 sendAccessibilityEventUnchecked(event); 14509 } 14510 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int toIndex)14511 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 14512 int fromIndex, int toIndex) { 14513 AccessibilityEvent event = 14514 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 14515 event.setFromIndex(fromIndex); 14516 event.setToIndex(toIndex); 14517 event.setBeforeText(beforeText); 14518 sendAccessibilityEventUnchecked(event); 14519 } 14520 getInputMethodManager()14521 private InputMethodManager getInputMethodManager() { 14522 return getContext().getSystemService(InputMethodManager.class); 14523 } 14524 14525 /** 14526 * Returns whether this text view is a current input method target. The 14527 * default implementation just checks with {@link InputMethodManager}. 14528 * @return True if the TextView is a current input method target; false otherwise. 14529 */ isInputMethodTarget()14530 public boolean isInputMethodTarget() { 14531 InputMethodManager imm = getInputMethodManager(); 14532 return imm != null && imm.isActive(this); 14533 } 14534 14535 static final int ID_SELECT_ALL = android.R.id.selectAll; 14536 static final int ID_UNDO = android.R.id.undo; 14537 static final int ID_REDO = android.R.id.redo; 14538 static final int ID_CUT = android.R.id.cut; 14539 static final int ID_COPY = android.R.id.copy; 14540 static final int ID_PASTE = android.R.id.paste; 14541 static final int ID_SHARE = android.R.id.shareText; 14542 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; 14543 static final int ID_REPLACE = android.R.id.replaceText; 14544 static final int ID_ASSIST = android.R.id.textAssist; 14545 static final int ID_AUTOFILL = android.R.id.autofill; 14546 14547 /** 14548 * Called when a context menu option for the text view is selected. Currently 14549 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, 14550 * {@link android.R.id#copy}, {@link android.R.id#paste}, 14551 * {@link android.R.id#pasteAsPlainText} (starting at API level 23) or 14552 * {@link android.R.id#shareText}. 14553 * 14554 * @return true if the context menu item action was performed. 14555 */ onTextContextMenuItem(int id)14556 public boolean onTextContextMenuItem(int id) { 14557 int min = 0; 14558 int max = mText.length(); 14559 14560 if (isFocused()) { 14561 final int selStart = getSelectionStart(); 14562 final int selEnd = getSelectionEnd(); 14563 14564 min = Math.max(0, Math.min(selStart, selEnd)); 14565 max = Math.max(0, Math.max(selStart, selEnd)); 14566 } 14567 14568 switch (id) { 14569 case ID_SELECT_ALL: 14570 final boolean hadSelection = hasSelection(); 14571 selectAllText(); 14572 if (mEditor != null && hadSelection) { 14573 mEditor.invalidateActionModeAsync(); 14574 } 14575 return true; 14576 14577 case ID_UNDO: 14578 if (mEditor != null) { 14579 mEditor.undo(); 14580 } 14581 return true; // Returns true even if nothing was undone. 14582 14583 case ID_REDO: 14584 if (mEditor != null) { 14585 mEditor.redo(); 14586 } 14587 return true; // Returns true even if nothing was undone. 14588 14589 case ID_PASTE: 14590 paste(true /* withFormatting */); 14591 return true; 14592 14593 case ID_PASTE_AS_PLAIN_TEXT: 14594 paste(false /* withFormatting */); 14595 return true; 14596 14597 case ID_CUT: 14598 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max)); 14599 if (setPrimaryClip(cutData)) { 14600 deleteText_internal(min, max); 14601 } else { 14602 Toast.makeText(getContext(), 14603 com.android.internal.R.string.failed_to_copy_to_clipboard, 14604 Toast.LENGTH_SHORT).show(); 14605 } 14606 return true; 14607 14608 case ID_COPY: 14609 // For link action mode in a non-selectable/non-focusable TextView, 14610 // make sure that we set the appropriate min/max. 14611 final int selStart = getSelectionStart(); 14612 final int selEnd = getSelectionEnd(); 14613 min = Math.max(0, Math.min(selStart, selEnd)); 14614 max = Math.max(0, Math.max(selStart, selEnd)); 14615 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max)); 14616 if (setPrimaryClip(copyData)) { 14617 stopTextActionMode(); 14618 } else { 14619 Toast.makeText(getContext(), 14620 com.android.internal.R.string.failed_to_copy_to_clipboard, 14621 Toast.LENGTH_SHORT).show(); 14622 } 14623 return true; 14624 14625 case ID_REPLACE: 14626 if (mEditor != null) { 14627 mEditor.replace(); 14628 } 14629 return true; 14630 14631 case ID_SHARE: 14632 shareSelectedText(); 14633 return true; 14634 14635 case ID_AUTOFILL: 14636 requestAutofill(); 14637 stopTextActionMode(); 14638 return true; 14639 } 14640 return false; 14641 } 14642 14643 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getTransformedText(int start, int end)14644 CharSequence getTransformedText(int start, int end) { 14645 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 14646 } 14647 14648 @Override performLongClick()14649 public boolean performLongClick() { 14650 if (DEBUG_CURSOR) { 14651 logCursor("performLongClick", null); 14652 } 14653 14654 boolean handled = false; 14655 boolean performedHapticFeedback = false; 14656 14657 if (mEditor != null) { 14658 mEditor.mIsBeingLongClicked = true; 14659 } 14660 14661 if (super.performLongClick()) { 14662 handled = true; 14663 performedHapticFeedback = true; 14664 } 14665 14666 if (mEditor != null) { 14667 handled |= mEditor.performLongClick(handled); 14668 mEditor.mIsBeingLongClicked = false; 14669 } 14670 14671 if (handled) { 14672 if (!performedHapticFeedback) { 14673 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 14674 } 14675 if (mEditor != null) mEditor.mDiscardNextActionUp = true; 14676 } else { 14677 MetricsLogger.action( 14678 mContext, 14679 MetricsEvent.TEXT_LONGPRESS, 14680 TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER); 14681 } 14682 14683 return handled; 14684 } 14685 14686 @Override onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)14687 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { 14688 super.onScrollChanged(horiz, vert, oldHoriz, oldVert); 14689 if (mEditor != null) { 14690 mEditor.onScrollChanged(); 14691 } 14692 } 14693 14694 /** 14695 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated 14696 * by the IME or by the spell checker as the user types. This is done by adding 14697 * {@link SuggestionSpan}s to the text. 14698 * 14699 * When suggestions are enabled (default), this list of suggestions will be displayed when the 14700 * user asks for them on these parts of the text. This value depends on the inputType of this 14701 * TextView. 14702 * 14703 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. 14704 * 14705 * In addition, the type variation must be one of 14706 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, 14707 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, 14708 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, 14709 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or 14710 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. 14711 * 14712 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. 14713 * 14714 * @return true if the suggestions popup window is enabled, based on the inputType. 14715 */ isSuggestionsEnabled()14716 public boolean isSuggestionsEnabled() { 14717 if (mEditor == null) return false; 14718 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { 14719 return false; 14720 } 14721 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; 14722 14723 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 14724 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL 14725 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT 14726 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE 14727 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE 14728 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); 14729 } 14730 14731 /** 14732 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 14733 * selection is initiated in this View. 14734 * 14735 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy, 14736 * Paste, Replace and Share actions, depending on what this View supports. 14737 * 14738 * <p>A custom implementation can add new entries in the default menu in its 14739 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)} 14740 * method. The default actions can also be removed from the menu using 14741 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 14742 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, 14743 * {@link android.R.id#pasteAsPlainText} (starting at API level 23), 14744 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. 14745 * 14746 * <p>Returning false from 14747 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)} 14748 * will prevent the action mode from being started. 14749 * 14750 * <p>Action click events should be handled by the custom implementation of 14751 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, 14752 * android.view.MenuItem)}. 14753 * 14754 * <p>Note that text selection mode is not started when a TextView receives focus and the 14755 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in 14756 * that case, to allow for quick replacement. 14757 */ setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)14758 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { 14759 createEditorIfNeeded(); 14760 mEditor.mCustomSelectionActionModeCallback = actionModeCallback; 14761 } 14762 14763 /** 14764 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 14765 * 14766 * @return The current custom selection callback. 14767 */ getCustomSelectionActionModeCallback()14768 public ActionMode.Callback getCustomSelectionActionModeCallback() { 14769 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 14770 } 14771 14772 /** 14773 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 14774 * insertion is initiated in this View. 14775 * The standard implementation populates the menu with a subset of Select All, 14776 * Paste and Replace actions, depending on what this View supports. 14777 * 14778 * <p>A custom implementation can add new entries in the default menu in its 14779 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, 14780 * android.view.Menu)} method. The default actions can also be removed from the menu using 14781 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, 14782 * {@link android.R.id#paste}, {@link android.R.id#pasteAsPlainText} (starting at API 14783 * level 23) or {@link android.R.id#replaceText} ids as parameters.</p> 14784 * 14785 * <p>Returning false from 14786 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, 14787 * android.view.Menu)} will prevent the action mode from being started.</p> 14788 * 14789 * <p>Action click events should be handled by the custom implementation of 14790 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode, 14791 * android.view.MenuItem)}.</p> 14792 * 14793 * <p>Note that text insertion mode is not started when a TextView receives focus and the 14794 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p> 14795 */ setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)14796 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) { 14797 createEditorIfNeeded(); 14798 mEditor.mCustomInsertionActionModeCallback = actionModeCallback; 14799 } 14800 14801 /** 14802 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null. 14803 * 14804 * @return The current custom insertion callback. 14805 */ getCustomInsertionActionModeCallback()14806 public ActionMode.Callback getCustomInsertionActionModeCallback() { 14807 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; 14808 } 14809 14810 /** 14811 * Sets the {@link TextClassifier} for this TextView. 14812 */ setTextClassifier(@ullable TextClassifier textClassifier)14813 public void setTextClassifier(@Nullable TextClassifier textClassifier) { 14814 mTextClassifier = textClassifier; 14815 } 14816 14817 /** 14818 * Returns the {@link TextClassifier} used by this TextView. 14819 * If no TextClassifier has been set, this TextView uses the default set by the 14820 * {@link TextClassificationManager}. 14821 */ 14822 @NonNull getTextClassifier()14823 public TextClassifier getTextClassifier() { 14824 if (mTextClassifier == null) { 14825 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 14826 if (tcm != null) { 14827 return tcm.getTextClassifier(); 14828 } 14829 return TextClassifier.NO_OP; 14830 } 14831 return mTextClassifier; 14832 } 14833 14834 /** 14835 * Returns a session-aware text classifier. 14836 * This method creates one if none already exists or the current one is destroyed. 14837 */ 14838 @NonNull getTextClassificationSession()14839 TextClassifier getTextClassificationSession() { 14840 if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) { 14841 final TextClassificationManager tcm = getTextClassificationManagerForUser(); 14842 if (tcm != null) { 14843 final String widgetType; 14844 if (isTextEditable()) { 14845 widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT; 14846 } else if (isTextSelectable()) { 14847 widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW; 14848 } else { 14849 widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW; 14850 } 14851 mTextClassificationContext = new TextClassificationContext.Builder( 14852 mContext.getPackageName(), widgetType) 14853 .build(); 14854 if (mTextClassifier != null) { 14855 mTextClassificationSession = tcm.createTextClassificationSession( 14856 mTextClassificationContext, mTextClassifier); 14857 } else { 14858 mTextClassificationSession = tcm.createTextClassificationSession( 14859 mTextClassificationContext); 14860 } 14861 } else { 14862 mTextClassificationSession = TextClassifier.NO_OP; 14863 } 14864 } 14865 return mTextClassificationSession; 14866 } 14867 14868 /** 14869 * Returns the {@link TextClassificationContext} for the current TextClassifier session. 14870 * @see #getTextClassificationSession() 14871 */ 14872 @Nullable getTextClassificationContext()14873 TextClassificationContext getTextClassificationContext() { 14874 return mTextClassificationContext; 14875 } 14876 14877 /** 14878 * Returns true if this TextView uses a no-op TextClassifier. 14879 */ usesNoOpTextClassifier()14880 boolean usesNoOpTextClassifier() { 14881 return getTextClassifier() == TextClassifier.NO_OP; 14882 } 14883 14884 /** 14885 * Starts an ActionMode for the specified TextLinkSpan. 14886 * 14887 * @return Whether or not we're attempting to start the action mode. 14888 * @hide 14889 */ requestActionMode(@onNull TextLinks.TextLinkSpan clickedSpan)14890 public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) { 14891 Preconditions.checkNotNull(clickedSpan); 14892 14893 if (!(mText instanceof Spanned)) { 14894 return false; 14895 } 14896 14897 final int start = ((Spanned) mText).getSpanStart(clickedSpan); 14898 final int end = ((Spanned) mText).getSpanEnd(clickedSpan); 14899 14900 if (start < 0 || end > mText.length() || start >= end) { 14901 return false; 14902 } 14903 14904 createEditorIfNeeded(); 14905 mEditor.startLinkActionModeAsync(start, end); 14906 return true; 14907 } 14908 14909 /** 14910 * Handles a click on the specified TextLinkSpan. 14911 * 14912 * @return Whether or not the click is being handled. 14913 * @hide 14914 */ handleClick(@onNull TextLinks.TextLinkSpan clickedSpan)14915 public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) { 14916 Preconditions.checkNotNull(clickedSpan); 14917 if (mText instanceof Spanned) { 14918 final Spanned spanned = (Spanned) mText; 14919 final int start = spanned.getSpanStart(clickedSpan); 14920 final int end = spanned.getSpanEnd(clickedSpan); 14921 if (start >= 0 && end <= mText.length() && start < end) { 14922 final TextClassification.Request request = new TextClassification.Request.Builder( 14923 mText, start, end) 14924 .setDefaultLocales(getTextLocales()) 14925 .build(); 14926 final Supplier<TextClassification> supplier = () -> 14927 getTextClassificationSession().classifyText(request); 14928 final Consumer<TextClassification> consumer = classification -> { 14929 if (classification != null) { 14930 if (!classification.getActions().isEmpty()) { 14931 try { 14932 classification.getActions().get(0).getActionIntent().send(); 14933 } catch (PendingIntent.CanceledException e) { 14934 Log.e(LOG_TAG, "Error sending PendingIntent", e); 14935 } 14936 } else { 14937 Log.d(LOG_TAG, "No link action to perform"); 14938 } 14939 } else { 14940 // classification == null 14941 Log.d(LOG_TAG, "Timeout while classifying text"); 14942 } 14943 }; 14944 CompletableFuture.supplyAsync(supplier) 14945 .completeOnTimeout(null, 1, TimeUnit.SECONDS) 14946 .thenAccept(consumer); 14947 return true; 14948 } 14949 } 14950 return false; 14951 } 14952 14953 /** 14954 * @hide 14955 */ 14956 @UnsupportedAppUsage stopTextActionMode()14957 protected void stopTextActionMode() { 14958 if (mEditor != null) { 14959 mEditor.stopTextActionMode(); 14960 } 14961 } 14962 14963 /** @hide */ hideFloatingToolbar(int durationMs)14964 public void hideFloatingToolbar(int durationMs) { 14965 if (mEditor != null) { 14966 mEditor.hideFloatingToolbar(durationMs); 14967 } 14968 } 14969 canUndo()14970 boolean canUndo() { 14971 return mEditor != null && mEditor.canUndo(); 14972 } 14973 canRedo()14974 boolean canRedo() { 14975 return mEditor != null && mEditor.canRedo(); 14976 } 14977 canCut()14978 boolean canCut() { 14979 if (hasPasswordTransformationMethod()) { 14980 return false; 14981 } 14982 14983 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null 14984 && mEditor.mKeyListener != null) { 14985 return true; 14986 } 14987 14988 return false; 14989 } 14990 canCopy()14991 boolean canCopy() { 14992 if (hasPasswordTransformationMethod()) { 14993 return false; 14994 } 14995 14996 if (mText.length() > 0 && hasSelection() && mEditor != null) { 14997 return true; 14998 } 14999 15000 return false; 15001 } 15002 canReplace()15003 boolean canReplace() { 15004 if (hasPasswordTransformationMethod()) { 15005 return false; 15006 } 15007 15008 return (mText.length() > 0) && (mText instanceof Editable) && (mEditor != null) 15009 && isSuggestionsEnabled() && mEditor.shouldOfferToShowSuggestions(); 15010 } 15011 canShare()15012 boolean canShare() { 15013 if (!getContext().canStartActivityForResult() || !isDeviceProvisioned() 15014 || !getContext().getResources().getBoolean( 15015 com.android.internal.R.bool.config_textShareSupported)) { 15016 return false; 15017 } 15018 return canCopy(); 15019 } 15020 isDeviceProvisioned()15021 boolean isDeviceProvisioned() { 15022 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) { 15023 mDeviceProvisionedState = Settings.Global.getInt( 15024 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0 15025 ? DEVICE_PROVISIONED_YES 15026 : DEVICE_PROVISIONED_NO; 15027 } 15028 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES; 15029 } 15030 15031 @UnsupportedAppUsage canPaste()15032 boolean canPaste() { 15033 return (mText instanceof Editable 15034 && mEditor != null && mEditor.mKeyListener != null 15035 && getSelectionStart() >= 0 15036 && getSelectionEnd() >= 0 15037 && getClipboardManagerForUser().hasPrimaryClip()); 15038 } 15039 canPasteAsPlainText()15040 boolean canPasteAsPlainText() { 15041 if (!canPaste()) { 15042 return false; 15043 } 15044 15045 final ClipDescription description = 15046 getClipboardManagerForUser().getPrimaryClipDescription(); 15047 final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); 15048 return (isPlainType && description.isStyledText()) 15049 || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); 15050 } 15051 canProcessText()15052 boolean canProcessText() { 15053 if (getId() == View.NO_ID) { 15054 return false; 15055 } 15056 return canShare(); 15057 } 15058 canSelectAllText()15059 boolean canSelectAllText() { 15060 return canSelectText() && !hasPasswordTransformationMethod() 15061 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); 15062 } 15063 selectAllText()15064 boolean selectAllText() { 15065 if (mEditor != null) { 15066 // Hide the toolbar before changing the selection to avoid flickering. 15067 hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY); 15068 } 15069 final int length = mText.length(); 15070 Selection.setSelection(mSpannable, 0, length); 15071 return length > 0; 15072 } 15073 paste(boolean withFormatting)15074 private void paste(boolean withFormatting) { 15075 ClipboardManager clipboard = getClipboardManagerForUser(); 15076 ClipData clip = clipboard.getPrimaryClip(); 15077 if (clip == null) { 15078 return; 15079 } 15080 final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_CLIPBOARD) 15081 .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT) 15082 .build(); 15083 performReceiveContent(payload); 15084 sLastCutCopyOrTextChangedTime = 0; 15085 } 15086 shareSelectedText()15087 private void shareSelectedText() { 15088 String selectedText = getSelectedText(); 15089 if (selectedText != null && !selectedText.isEmpty()) { 15090 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); 15091 sharingIntent.setType("text/plain"); 15092 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT); 15093 selectedText = TextUtils.trimToParcelableSize(selectedText); 15094 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText); 15095 getContext().startActivity(Intent.createChooser(sharingIntent, null)); 15096 Selection.setSelection(mSpannable, getSelectionEnd()); 15097 } 15098 } 15099 15100 @CheckResult setPrimaryClip(ClipData clip)15101 private boolean setPrimaryClip(ClipData clip) { 15102 ClipboardManager clipboard = getClipboardManagerForUser(); 15103 try { 15104 clipboard.setPrimaryClip(clip); 15105 } catch (Throwable t) { 15106 return false; 15107 } 15108 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis(); 15109 return true; 15110 } 15111 15112 /** 15113 * Get the character offset closest to the specified absolute position. A typical use case is to 15114 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. 15115 * 15116 * @param x The horizontal absolute position of a point on screen 15117 * @param y The vertical absolute position of a point on screen 15118 * @return the character offset for the character whose position is closest to the specified 15119 * position. Returns -1 if there is no layout. 15120 */ getOffsetForPosition(float x, float y)15121 public int getOffsetForPosition(float x, float y) { 15122 if (getLayout() == null) return -1; 15123 final int line = getLineAtCoordinate(y); 15124 final int offset = getOffsetAtCoordinate(line, x); 15125 return offset; 15126 } 15127 convertToLocalHorizontalCoordinate(float x)15128 float convertToLocalHorizontalCoordinate(float x) { 15129 x -= getTotalPaddingLeft(); 15130 // Clamp the position to inside of the view. 15131 x = Math.max(0.0f, x); 15132 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 15133 x += getScrollX(); 15134 return x; 15135 } 15136 15137 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getLineAtCoordinate(float y)15138 int getLineAtCoordinate(float y) { 15139 y -= getTotalPaddingTop(); 15140 // Clamp the position to inside of the view. 15141 y = Math.max(0.0f, y); 15142 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 15143 y += getScrollY(); 15144 return getLayout().getLineForVertical((int) y); 15145 } 15146 getLineAtCoordinateUnclamped(float y)15147 int getLineAtCoordinateUnclamped(float y) { 15148 y -= getTotalPaddingTop(); 15149 y += getScrollY(); 15150 return getLayout().getLineForVertical((int) y); 15151 } 15152 getOffsetAtCoordinate(int line, float x)15153 int getOffsetAtCoordinate(int line, float x) { 15154 x = convertToLocalHorizontalCoordinate(x); 15155 final int offset = getLayout().getOffsetForHorizontal(line, x); 15156 return transformedToOriginal(offset, OffsetMapping.MAP_STRATEGY_CURSOR); 15157 } 15158 15159 /** 15160 * Convenient method to convert an offset on the transformed text to the original text. 15161 * @hide 15162 */ transformedToOriginal(int offset, @OffsetMapping.MapStrategy int strategy)15163 public int transformedToOriginal(int offset, @OffsetMapping.MapStrategy int strategy) { 15164 if (getTransformationMethod() == null) { 15165 return offset; 15166 } 15167 if (mTransformed instanceof OffsetMapping) { 15168 final OffsetMapping transformedText = (OffsetMapping) mTransformed; 15169 return transformedText.transformedToOriginal(offset, strategy); 15170 } 15171 return offset; 15172 } 15173 15174 /** 15175 * Convenient method to convert an offset on the original text to the transformed text. 15176 * @hide 15177 */ originalToTransformed(int offset, @OffsetMapping.MapStrategy int strategy)15178 public int originalToTransformed(int offset, @OffsetMapping.MapStrategy int strategy) { 15179 if (getTransformationMethod() == null) { 15180 return offset; 15181 } 15182 if (mTransformed instanceof OffsetMapping) { 15183 final OffsetMapping transformedText = (OffsetMapping) mTransformed; 15184 return transformedText.originalToTransformed(offset, strategy); 15185 } 15186 return offset; 15187 } 15188 /** 15189 * Handles drag events sent by the system following a call to 15190 * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int) 15191 * startDragAndDrop()}. 15192 * 15193 * <p>If this text view is not editable, delegates to the default {@link View#onDragEvent} 15194 * implementation. 15195 * 15196 * <p>If this text view is editable, accepts all drag actions (returns true for an 15197 * {@link android.view.DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event and all 15198 * subsequent drag events). While the drag is in progress, updates the cursor position 15199 * to follow the touch location. Once a drop event is received, handles content insertion 15200 * via {@link #performReceiveContent}. 15201 * 15202 * @param event The {@link android.view.DragEvent} sent by the system. 15203 * The {@link android.view.DragEvent#getAction()} method returns an action type constant 15204 * defined in DragEvent, indicating the type of drag event represented by this object. 15205 * @return Returns true if this text view is editable and delegates to super otherwise. 15206 * See {@link View#onDragEvent}. 15207 */ 15208 @Override onDragEvent(DragEvent event)15209 public boolean onDragEvent(DragEvent event) { 15210 if (mEditor == null || !mEditor.hasInsertionController()) { 15211 // If this TextView is not editable, defer to the default View implementation. This 15212 // will check for the presence of an OnReceiveContentListener and accept/reject 15213 // drag events depending on whether the listener is/isn't set. 15214 return super.onDragEvent(event); 15215 } 15216 switch (event.getAction()) { 15217 case DragEvent.ACTION_DRAG_STARTED: 15218 return true; 15219 15220 case DragEvent.ACTION_DRAG_ENTERED: 15221 TextView.this.requestFocus(); 15222 return true; 15223 15224 case DragEvent.ACTION_DRAG_LOCATION: 15225 if (mText instanceof Spannable) { 15226 final int offset = getOffsetForPosition(event.getX(), event.getY()); 15227 Selection.setSelection(mSpannable, offset); 15228 } 15229 return true; 15230 15231 case DragEvent.ACTION_DROP: 15232 if (mEditor != null) mEditor.onDrop(event); 15233 return true; 15234 15235 case DragEvent.ACTION_DRAG_ENDED: 15236 case DragEvent.ACTION_DRAG_EXITED: 15237 default: 15238 return true; 15239 } 15240 } 15241 isInBatchEditMode()15242 boolean isInBatchEditMode() { 15243 if (mEditor == null) return false; 15244 final Editor.InputMethodState ims = mEditor.mInputMethodState; 15245 if (ims != null) { 15246 return ims.mBatchEditNesting > 0; 15247 } 15248 return mEditor.mInBatchEditControllers; 15249 } 15250 15251 @Override onRtlPropertiesChanged(int layoutDirection)15252 public void onRtlPropertiesChanged(int layoutDirection) { 15253 super.onRtlPropertiesChanged(layoutDirection); 15254 15255 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic(); 15256 if (mTextDir != newTextDir) { 15257 mTextDir = newTextDir; 15258 if (mLayout != null) { 15259 checkForRelayout(); 15260 } 15261 } 15262 } 15263 15264 /** 15265 * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout. 15266 * The {@link TextDirectionHeuristic} that is used by TextView is only available after 15267 * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the 15268 * return value may not be the same as the one TextView uses if the View's layout direction is 15269 * not resolved or detached from parent root view. 15270 */ getTextDirectionHeuristic()15271 public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() { 15272 if (hasPasswordTransformationMethod()) { 15273 // passwords fields should be LTR 15274 return TextDirectionHeuristics.LTR; 15275 } 15276 15277 if (mEditor != null 15278 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) 15279 == EditorInfo.TYPE_CLASS_PHONE) { 15280 // Phone numbers must be in the direction of the locale's digits. Most locales have LTR 15281 // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have 15282 // RTL digits. 15283 final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale()); 15284 final String zero = symbols.getDigitStrings()[0]; 15285 // In case the zero digit is multi-codepoint, just use the first codepoint to determine 15286 // direction. 15287 final int firstCodepoint = zero.codePointAt(0); 15288 final byte digitDirection = Character.getDirectionality(firstCodepoint); 15289 if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT 15290 || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) { 15291 return TextDirectionHeuristics.RTL; 15292 } else { 15293 return TextDirectionHeuristics.LTR; 15294 } 15295 } 15296 15297 // Always need to resolve layout direction first 15298 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); 15299 15300 // Now, we can select the heuristic 15301 switch (getTextDirection()) { 15302 default: 15303 case TEXT_DIRECTION_FIRST_STRONG: 15304 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : 15305 TextDirectionHeuristics.FIRSTSTRONG_LTR); 15306 case TEXT_DIRECTION_ANY_RTL: 15307 return TextDirectionHeuristics.ANYRTL_LTR; 15308 case TEXT_DIRECTION_LTR: 15309 return TextDirectionHeuristics.LTR; 15310 case TEXT_DIRECTION_RTL: 15311 return TextDirectionHeuristics.RTL; 15312 case TEXT_DIRECTION_LOCALE: 15313 return TextDirectionHeuristics.LOCALE; 15314 case TEXT_DIRECTION_FIRST_STRONG_LTR: 15315 return TextDirectionHeuristics.FIRSTSTRONG_LTR; 15316 case TEXT_DIRECTION_FIRST_STRONG_RTL: 15317 return TextDirectionHeuristics.FIRSTSTRONG_RTL; 15318 } 15319 } 15320 15321 /** 15322 * @hide 15323 */ 15324 @Override onResolveDrawables(int layoutDirection)15325 public void onResolveDrawables(int layoutDirection) { 15326 // No need to resolve twice 15327 if (mLastLayoutDirection == layoutDirection) { 15328 return; 15329 } 15330 mLastLayoutDirection = layoutDirection; 15331 15332 // Resolve drawables 15333 if (mDrawables != null) { 15334 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) { 15335 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]); 15336 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]); 15337 applyCompoundDrawableTint(); 15338 } 15339 } 15340 } 15341 15342 /** 15343 * Prepares a drawable for display by propagating layout direction and 15344 * drawable state. 15345 * 15346 * @param dr the drawable to prepare 15347 */ prepareDrawableForDisplay(@ullable Drawable dr)15348 private void prepareDrawableForDisplay(@Nullable Drawable dr) { 15349 if (dr == null) { 15350 return; 15351 } 15352 15353 dr.setLayoutDirection(getLayoutDirection()); 15354 15355 if (dr.isStateful()) { 15356 dr.setState(getDrawableState()); 15357 dr.jumpToCurrentState(); 15358 } 15359 } 15360 15361 /** 15362 * @hide 15363 */ resetResolvedDrawables()15364 protected void resetResolvedDrawables() { 15365 super.resetResolvedDrawables(); 15366 mLastLayoutDirection = -1; 15367 } 15368 15369 /** 15370 * @hide 15371 */ viewClicked(InputMethodManager imm)15372 protected void viewClicked(InputMethodManager imm) { 15373 if (imm != null) { 15374 imm.viewClicked(this); 15375 } 15376 } 15377 15378 /** 15379 * Deletes the range of text [start, end[. 15380 * @hide 15381 */ 15382 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) deleteText_internal(int start, int end)15383 protected void deleteText_internal(int start, int end) { 15384 ((Editable) mText).delete(start, end); 15385 } 15386 15387 /** 15388 * Replaces the range of text [start, end[ by replacement text 15389 * @hide 15390 */ replaceText_internal(int start, int end, CharSequence text)15391 protected void replaceText_internal(int start, int end, CharSequence text) { 15392 ((Editable) mText).replace(start, end, text); 15393 } 15394 15395 /** 15396 * Sets a span on the specified range of text 15397 * @hide 15398 */ setSpan_internal(Object span, int start, int end, int flags)15399 protected void setSpan_internal(Object span, int start, int end, int flags) { 15400 ((Editable) mText).setSpan(span, start, end, flags); 15401 } 15402 15403 /** 15404 * Moves the cursor to the specified offset position in text 15405 * @hide 15406 */ setCursorPosition_internal(int start, int end)15407 protected void setCursorPosition_internal(int start, int end) { 15408 Selection.setSelection(((Editable) mText), start, end); 15409 } 15410 15411 /** 15412 * An Editor should be created as soon as any of the editable-specific fields (grouped 15413 * inside the Editor object) is assigned to a non-default value. 15414 * This method will create the Editor if needed. 15415 * 15416 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will 15417 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an 15418 * Editor for backward compatibility, as soon as one of these fields is assigned. 15419 * 15420 * Also note that for performance reasons, the mEditor is created when needed, but not 15421 * reset when no more edit-specific fields are needed. 15422 */ 15423 @UnsupportedAppUsage createEditorIfNeeded()15424 private void createEditorIfNeeded() { 15425 if (mEditor == null) { 15426 mEditor = new Editor(this); 15427 } 15428 } 15429 15430 /** 15431 * @hide 15432 */ 15433 @Override 15434 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getIterableTextForAccessibility()15435 public CharSequence getIterableTextForAccessibility() { 15436 return mText; 15437 } 15438 ensureIterableTextForAccessibilitySelectable()15439 private void ensureIterableTextForAccessibilitySelectable() { 15440 if (!(mText instanceof Spannable)) { 15441 setText(mText, BufferType.SPANNABLE); 15442 } 15443 } 15444 15445 /** 15446 * @hide 15447 */ 15448 @Override getIteratorForGranularity(int granularity)15449 public TextSegmentIterator getIteratorForGranularity(int granularity) { 15450 switch (granularity) { 15451 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { 15452 Spannable text = (Spannable) getIterableTextForAccessibility(); 15453 if (!TextUtils.isEmpty(text) && getLayout() != null) { 15454 AccessibilityIterators.LineTextSegmentIterator iterator = 15455 AccessibilityIterators.LineTextSegmentIterator.getInstance(); 15456 iterator.initialize(text, getLayout()); 15457 return iterator; 15458 } 15459 } break; 15460 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { 15461 Spannable text = (Spannable) getIterableTextForAccessibility(); 15462 if (!TextUtils.isEmpty(text) && getLayout() != null) { 15463 AccessibilityIterators.PageTextSegmentIterator iterator = 15464 AccessibilityIterators.PageTextSegmentIterator.getInstance(); 15465 iterator.initialize(this); 15466 return iterator; 15467 } 15468 } break; 15469 } 15470 return super.getIteratorForGranularity(granularity); 15471 } 15472 15473 /** 15474 * @hide 15475 */ 15476 @Override getAccessibilitySelectionStart()15477 public int getAccessibilitySelectionStart() { 15478 return getSelectionStart(); 15479 } 15480 15481 /** 15482 * @hide 15483 */ isAccessibilitySelectionExtendable()15484 public boolean isAccessibilitySelectionExtendable() { 15485 return true; 15486 } 15487 15488 /** 15489 * @hide 15490 */ prepareForExtendedAccessibilitySelection()15491 public void prepareForExtendedAccessibilitySelection() { 15492 requestFocusOnNonEditableSelectableText(); 15493 } 15494 15495 /** 15496 * @hide 15497 */ 15498 @Override getAccessibilitySelectionEnd()15499 public int getAccessibilitySelectionEnd() { 15500 return getSelectionEnd(); 15501 } 15502 15503 /** 15504 * @hide 15505 */ 15506 @Override setAccessibilitySelection(int start, int end)15507 public void setAccessibilitySelection(int start, int end) { 15508 if (getAccessibilitySelectionStart() == start 15509 && getAccessibilitySelectionEnd() == end) { 15510 return; 15511 } 15512 CharSequence text = getIterableTextForAccessibility(); 15513 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) { 15514 Selection.setSelection((Spannable) text, start, end); 15515 } else { 15516 Selection.removeSelection((Spannable) text); 15517 } 15518 // Hide all selection controllers used for adjusting selection 15519 // since we are doing so explicitlty by other means and these 15520 // controllers interact with how selection behaves. 15521 if (mEditor != null) { 15522 mEditor.hideCursorAndSpanControllers(); 15523 mEditor.stopTextActionMode(); 15524 } 15525 } 15526 15527 /** @hide */ 15528 @Override encodeProperties(@onNull ViewHierarchyEncoder stream)15529 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 15530 super.encodeProperties(stream); 15531 15532 TruncateAt ellipsize = getEllipsize(); 15533 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name()); 15534 stream.addProperty("text:textSize", getTextSize()); 15535 stream.addProperty("text:scaledTextSize", getScaledTextSize()); 15536 stream.addProperty("text:typefaceStyle", getTypefaceStyle()); 15537 stream.addProperty("text:selectionStart", getSelectionStart()); 15538 stream.addProperty("text:selectionEnd", getSelectionEnd()); 15539 stream.addProperty("text:curTextColor", mCurTextColor); 15540 stream.addUserProperty("text:text", mText == null ? null : mText.toString()); 15541 stream.addProperty("text:gravity", mGravity); 15542 } 15543 15544 /** 15545 * User interface state that is stored by TextView for implementing 15546 * {@link View#onSaveInstanceState}. 15547 */ 15548 public static class SavedState extends BaseSavedState { 15549 int selStart = -1; 15550 int selEnd = -1; 15551 @UnsupportedAppUsage 15552 CharSequence text; 15553 boolean frozenWithFocus; 15554 CharSequence error; 15555 ParcelableParcel editorState; // Optional state from Editor. 15556 SavedState(Parcelable superState)15557 SavedState(Parcelable superState) { 15558 super(superState); 15559 } 15560 15561 @Override writeToParcel(Parcel out, int flags)15562 public void writeToParcel(Parcel out, int flags) { 15563 super.writeToParcel(out, flags); 15564 out.writeInt(selStart); 15565 out.writeInt(selEnd); 15566 out.writeInt(frozenWithFocus ? 1 : 0); 15567 TextUtils.writeToParcel(text, out, flags); 15568 15569 if (error == null) { 15570 out.writeInt(0); 15571 } else { 15572 out.writeInt(1); 15573 TextUtils.writeToParcel(error, out, flags); 15574 } 15575 15576 if (editorState == null) { 15577 out.writeInt(0); 15578 } else { 15579 out.writeInt(1); 15580 editorState.writeToParcel(out, flags); 15581 } 15582 } 15583 15584 @Override toString()15585 public String toString() { 15586 String str = "TextView.SavedState{" 15587 + Integer.toHexString(System.identityHashCode(this)) 15588 + " start=" + selStart + " end=" + selEnd; 15589 if (text != null) { 15590 str += " text=" + text; 15591 } 15592 return str + "}"; 15593 } 15594 15595 @SuppressWarnings("hiding") 15596 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR = 15597 new Parcelable.Creator<SavedState>() { 15598 public SavedState createFromParcel(Parcel in) { 15599 return new SavedState(in); 15600 } 15601 15602 public SavedState[] newArray(int size) { 15603 return new SavedState[size]; 15604 } 15605 }; 15606 SavedState(Parcel in)15607 private SavedState(Parcel in) { 15608 super(in); 15609 selStart = in.readInt(); 15610 selEnd = in.readInt(); 15611 frozenWithFocus = (in.readInt() != 0); 15612 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 15613 15614 if (in.readInt() != 0) { 15615 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 15616 } 15617 15618 if (in.readInt() != 0) { 15619 editorState = ParcelableParcel.CREATOR.createFromParcel(in); 15620 } 15621 } 15622 } 15623 15624 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { 15625 @NonNull 15626 private char[] mChars; 15627 private int mStart, mLength; 15628 CharWrapper(@onNull char[] chars, int start, int len)15629 CharWrapper(@NonNull char[] chars, int start, int len) { 15630 mChars = chars; 15631 mStart = start; 15632 mLength = len; 15633 } 15634 set(@onNull char[] chars, int start, int len)15635 /* package */ void set(@NonNull char[] chars, int start, int len) { 15636 mChars = chars; 15637 mStart = start; 15638 mLength = len; 15639 } 15640 length()15641 public int length() { 15642 return mLength; 15643 } 15644 charAt(int off)15645 public char charAt(int off) { 15646 return mChars[off + mStart]; 15647 } 15648 15649 @Override toString()15650 public String toString() { 15651 return new String(mChars, mStart, mLength); 15652 } 15653 subSequence(int start, int end)15654 public CharSequence subSequence(int start, int end) { 15655 if (start < 0 || end < 0 || start > mLength || end > mLength) { 15656 throw new IndexOutOfBoundsException(start + ", " + end); 15657 } 15658 15659 return new String(mChars, start + mStart, end - start); 15660 } 15661 getChars(int start, int end, char[] buf, int off)15662 public void getChars(int start, int end, char[] buf, int off) { 15663 if (start < 0 || end < 0 || start > mLength || end > mLength) { 15664 throw new IndexOutOfBoundsException(start + ", " + end); 15665 } 15666 15667 System.arraycopy(mChars, start + mStart, buf, off, end - start); 15668 } 15669 15670 @Override drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)15671 public void drawText(BaseCanvas c, int start, int end, 15672 float x, float y, Paint p) { 15673 c.drawText(mChars, start + mStart, end - start, x, y, p); 15674 } 15675 15676 @Override drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)15677 public void drawTextRun(BaseCanvas c, int start, int end, 15678 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { 15679 int count = end - start; 15680 int contextCount = contextEnd - contextStart; 15681 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, 15682 contextCount, x, y, isRtl, p); 15683 } 15684 measureText(int start, int end, Paint p)15685 public float measureText(int start, int end, Paint p) { 15686 return p.measureText(mChars, start + mStart, end - start); 15687 } 15688 getTextWidths(int start, int end, float[] widths, Paint p)15689 public int getTextWidths(int start, int end, float[] widths, Paint p) { 15690 return p.getTextWidths(mChars, start + mStart, end - start, widths); 15691 } 15692 getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)15693 public float getTextRunAdvances(int start, int end, int contextStart, 15694 int contextEnd, boolean isRtl, float[] advances, int advancesIndex, 15695 Paint p) { 15696 int count = end - start; 15697 int contextCount = contextEnd - contextStart; 15698 return p.getTextRunAdvances(mChars, start + mStart, count, 15699 contextStart + mStart, contextCount, isRtl, advances, 15700 advancesIndex); 15701 } 15702 getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, int offset, int cursorOpt, Paint p)15703 public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl, 15704 int offset, int cursorOpt, Paint p) { 15705 int contextCount = contextEnd - contextStart; 15706 return p.getTextRunCursor(mChars, contextStart + mStart, 15707 contextCount, isRtl, offset + mStart, cursorOpt); 15708 } 15709 } 15710 15711 private static final class Marquee { 15712 // TODO: Add an option to configure this 15713 private static final float MARQUEE_DELTA_MAX = 0.07f; 15714 private static final int MARQUEE_DELAY = 1200; 15715 private static final int MARQUEE_DP_PER_SECOND = 30; 15716 15717 private static final byte MARQUEE_STOPPED = 0x0; 15718 private static final byte MARQUEE_STARTING = 0x1; 15719 private static final byte MARQUEE_RUNNING = 0x2; 15720 15721 private final WeakReference<TextView> mView; 15722 private final Choreographer mChoreographer; 15723 15724 private byte mStatus = MARQUEE_STOPPED; 15725 private final float mPixelsPerMs; 15726 private float mMaxScroll; 15727 private float mMaxFadeScroll; 15728 private float mGhostStart; 15729 private float mGhostOffset; 15730 private float mFadeStop; 15731 private int mRepeatLimit; 15732 15733 private float mScroll; 15734 private long mLastAnimationMs; 15735 Marquee(TextView v)15736 Marquee(TextView v) { 15737 final float density = v.getContext().getResources().getDisplayMetrics().density; 15738 mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f; 15739 mView = new WeakReference<TextView>(v); 15740 mChoreographer = Choreographer.getInstance(); 15741 } 15742 15743 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { 15744 @Override 15745 public void doFrame(long frameTimeNanos) { 15746 tick(); 15747 } 15748 }; 15749 15750 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { 15751 @Override 15752 public void doFrame(long frameTimeNanos) { 15753 mStatus = MARQUEE_RUNNING; 15754 mLastAnimationMs = mChoreographer.getFrameTime(); 15755 tick(); 15756 } 15757 }; 15758 15759 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { 15760 @Override 15761 public void doFrame(long frameTimeNanos) { 15762 if (mStatus == MARQUEE_RUNNING) { 15763 if (mRepeatLimit >= 0) { 15764 mRepeatLimit--; 15765 } 15766 start(mRepeatLimit); 15767 } 15768 } 15769 }; 15770 tick()15771 void tick() { 15772 if (mStatus != MARQUEE_RUNNING) { 15773 return; 15774 } 15775 15776 mChoreographer.removeFrameCallback(mTickCallback); 15777 15778 final TextView textView = mView.get(); 15779 if (textView != null && textView.isAggregatedVisible() 15780 && (textView.isFocused() || textView.isSelected())) { 15781 long currentMs = mChoreographer.getFrameTime(); 15782 long deltaMs = currentMs - mLastAnimationMs; 15783 mLastAnimationMs = currentMs; 15784 float deltaPx = deltaMs * mPixelsPerMs; 15785 mScroll += deltaPx; 15786 if (mScroll > mMaxScroll) { 15787 mScroll = mMaxScroll; 15788 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); 15789 } else { 15790 mChoreographer.postFrameCallback(mTickCallback); 15791 } 15792 textView.invalidate(); 15793 } 15794 } 15795 stop()15796 void stop() { 15797 mStatus = MARQUEE_STOPPED; 15798 mChoreographer.removeFrameCallback(mStartCallback); 15799 mChoreographer.removeFrameCallback(mRestartCallback); 15800 mChoreographer.removeFrameCallback(mTickCallback); 15801 resetScroll(); 15802 } 15803 resetScroll()15804 private void resetScroll() { 15805 mScroll = 0.0f; 15806 final TextView textView = mView.get(); 15807 if (textView != null) textView.invalidate(); 15808 } 15809 start(int repeatLimit)15810 void start(int repeatLimit) { 15811 if (repeatLimit == 0) { 15812 stop(); 15813 return; 15814 } 15815 mRepeatLimit = repeatLimit; 15816 final TextView textView = mView.get(); 15817 if (textView != null && textView.mLayout != null) { 15818 mStatus = MARQUEE_STARTING; 15819 mScroll = 0.0f; 15820 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() 15821 - textView.getCompoundPaddingRight(); 15822 final float lineWidth = textView.mLayout.getLineWidth(0); 15823 final float gap = textWidth / 3.0f; 15824 mGhostStart = lineWidth - textWidth + gap; 15825 mMaxScroll = mGhostStart + textWidth; 15826 mGhostOffset = lineWidth + gap; 15827 mFadeStop = lineWidth + textWidth / 6.0f; 15828 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 15829 15830 textView.invalidate(); 15831 mChoreographer.postFrameCallback(mStartCallback); 15832 } 15833 } 15834 getGhostOffset()15835 float getGhostOffset() { 15836 return mGhostOffset; 15837 } 15838 getScroll()15839 float getScroll() { 15840 return mScroll; 15841 } 15842 getMaxFadeScroll()15843 float getMaxFadeScroll() { 15844 return mMaxFadeScroll; 15845 } 15846 shouldDrawLeftFade()15847 boolean shouldDrawLeftFade() { 15848 return mScroll <= mFadeStop; 15849 } 15850 shouldDrawGhost()15851 boolean shouldDrawGhost() { 15852 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 15853 } 15854 isRunning()15855 boolean isRunning() { 15856 return mStatus == MARQUEE_RUNNING; 15857 } 15858 isStopped()15859 boolean isStopped() { 15860 return mStatus == MARQUEE_STOPPED; 15861 } 15862 } 15863 15864 private class ChangeWatcher implements TextWatcher, SpanWatcher { 15865 15866 private CharSequence mBeforeText; 15867 beforeTextChanged(CharSequence buffer, int start, int before, int after)15868 public void beforeTextChanged(CharSequence buffer, int start, 15869 int before, int after) { 15870 if (DEBUG_EXTRACT) { 15871 Log.v(LOG_TAG, "beforeTextChanged start=" + start 15872 + " before=" + before + " after=" + after + ": " + buffer); 15873 } 15874 15875 if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) { 15876 mBeforeText = mTransformed.toString(); 15877 } 15878 15879 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 15880 } 15881 onTextChanged(CharSequence buffer, int start, int before, int after)15882 public void onTextChanged(CharSequence buffer, int start, int before, int after) { 15883 if (DEBUG_EXTRACT) { 15884 Log.v(LOG_TAG, "onTextChanged start=" + start 15885 + " before=" + before + " after=" + after + ": " + buffer); 15886 } 15887 TextView.this.handleTextChanged(buffer, start, before, after); 15888 15889 if (isVisibleToAccessibility()) { 15890 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 15891 mBeforeText = null; 15892 } 15893 } 15894 afterTextChanged(Editable buffer)15895 public void afterTextChanged(Editable buffer) { 15896 if (DEBUG_EXTRACT) { 15897 Log.v(LOG_TAG, "afterTextChanged: " + buffer); 15898 } 15899 TextView.this.sendAfterTextChanged(buffer); 15900 15901 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 15902 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 15903 } 15904 } 15905 onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)15906 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { 15907 if (DEBUG_EXTRACT) { 15908 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e 15909 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 15910 } 15911 TextView.this.spanChange(buf, what, s, st, e, en); 15912 } 15913 onSpanAdded(Spannable buf, Object what, int s, int e)15914 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 15915 if (DEBUG_EXTRACT) { 15916 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf); 15917 } 15918 TextView.this.spanChange(buf, what, -1, s, -1, e); 15919 } 15920 onSpanRemoved(Spannable buf, Object what, int s, int e)15921 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 15922 if (DEBUG_EXTRACT) { 15923 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf); 15924 } 15925 TextView.this.spanChange(buf, what, s, -1, e, -1); 15926 } 15927 } 15928 15929 /** @hide */ 15930 @Override onInputConnectionOpenedInternal(@onNull InputConnection ic, @NonNull EditorInfo editorInfo, @Nullable Handler handler)15931 public void onInputConnectionOpenedInternal(@NonNull InputConnection ic, 15932 @NonNull EditorInfo editorInfo, @Nullable Handler handler) { 15933 if (mEditor != null) { 15934 mEditor.getDefaultOnReceiveContentListener().setInputConnectionInfo(this, ic, 15935 editorInfo); 15936 } 15937 } 15938 15939 /** @hide */ 15940 @Override onInputConnectionClosedInternal()15941 public void onInputConnectionClosedInternal() { 15942 if (mEditor != null) { 15943 mEditor.getDefaultOnReceiveContentListener().clearInputConnectionInfo(); 15944 } 15945 } 15946 15947 /** 15948 * Default {@link TextView} implementation for receiving content. Apps wishing to provide 15949 * custom behavior should configure a listener via {@link #setOnReceiveContentListener}. 15950 * 15951 * <p>For non-editable TextViews the default behavior is a no-op (returns the passed-in 15952 * content without acting on it). 15953 * 15954 * <p>For editable TextViews the default behavior is to insert text into the view, coercing 15955 * non-text content to text as needed. The MIME types "text/plain" and "text/html" have 15956 * well-defined behavior for this, while other MIME types have reasonable fallback behavior 15957 * (see {@link ClipData.Item#coerceToStyledText}). 15958 * 15959 * @param payload The content to insert and related metadata. 15960 * 15961 * @return The portion of the passed-in content that was not handled (may be all, some, or none 15962 * of the passed-in content). 15963 */ 15964 @Nullable 15965 @Override onReceiveContent(@onNull ContentInfo payload)15966 public ContentInfo onReceiveContent(@NonNull ContentInfo payload) { 15967 if (mEditor != null) { 15968 return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, payload); 15969 } 15970 return payload; 15971 } 15972 logCursor(String location, @Nullable String msgFormat, Object ... msgArgs)15973 private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) { 15974 if (msgFormat == null) { 15975 Log.d(LOG_TAG, location); 15976 } else { 15977 Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs)); 15978 } 15979 } 15980 15981 /** 15982 * Collects a {@link ViewTranslationRequest} which represents the content to be translated in 15983 * the view. 15984 * 15985 * <p>NOTE: When overriding the method, it should not collect a request to translate this 15986 * TextView if it is displaying a password. 15987 * 15988 * @param supportedFormats the supported translation format. The value could be {@link 15989 * android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}. 15990 * @param requestsCollector {@link Consumer} to receiver the {@link ViewTranslationRequest} 15991 * which contains the information to be translated. 15992 */ 15993 @Override onCreateViewTranslationRequest(@onNull int[] supportedFormats, @NonNull Consumer<ViewTranslationRequest> requestsCollector)15994 public void onCreateViewTranslationRequest(@NonNull int[] supportedFormats, 15995 @NonNull Consumer<ViewTranslationRequest> requestsCollector) { 15996 if (supportedFormats == null || supportedFormats.length == 0) { 15997 if (UiTranslationController.DEBUG) { 15998 Log.w(LOG_TAG, "Do not provide the support translation formats."); 15999 } 16000 return; 16001 } 16002 ViewTranslationRequest.Builder requestBuilder = 16003 new ViewTranslationRequest.Builder(getAutofillId()); 16004 // Support Text translation 16005 if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) { 16006 if (mText == null || mText.length() == 0) { 16007 if (UiTranslationController.DEBUG) { 16008 Log.w(LOG_TAG, "Cannot create translation request for the empty text."); 16009 } 16010 return; 16011 } 16012 boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod(); 16013 if (isTextEditable() || isPassword) { 16014 Log.w(LOG_TAG, "Cannot create translation request. editable = " 16015 + isTextEditable() + ", isPassword = " + isPassword); 16016 return; 16017 } 16018 // TODO(b/176488462): apply the view's important for translation 16019 requestBuilder.setValue(ViewTranslationRequest.ID_TEXT, 16020 TranslationRequestValue.forText(mText)); 16021 if (!TextUtils.isEmpty(getContentDescription())) { 16022 requestBuilder.setValue(ViewTranslationRequest.ID_CONTENT_DESCRIPTION, 16023 TranslationRequestValue.forText(getContentDescription())); 16024 } 16025 } 16026 requestsCollector.accept(requestBuilder.build()); 16027 } 16028 } 16029